Xử lý lỗi gọi System call hoặc hàm thư viện

Trong các bài học trước, chúng ta đã biết các lời gọi sysem call hoặc các hàm thư viện sử dụng system call sẽ chọc xuống Linux kernel để yêu cầu tài nguyên hoặc một dịch vụ của kernel. Điều này đồng nghĩa với việc bạn đã giao phó nhiệm vụ cho 1 hàm và chờ kết quả từ kernel.

Ví dụ về system call open(), mở một file tên là “hello.txt” như sau:

int ret = -1;
ret = open (“hello.txt”, O_RDONLY);

Hoặc một ví dụ khác khi bạn gọi hàm thư viện fopen() để mở một file “hello.txt”, hàm này sẽ gọi đến system call open(). Chi tiết như sau:

FILE *fp = NULL;
fp = fopen(“hello.txt”, “rw”);

Việc mở file “hello.txt” hoàn toàn có thể bị lỗi khi file hello.txt đã bị xóa lúc nào không hay, hoặc có một tín hiệu signal xảy ra (chúng ta sẽ tìm hiểu về signal ở chương sau) khi hàm open() đang thực hiện.

Như nói ở trên, open() system call hoàn toàn có thể trả về giá trị lỗi, khi này con trỏ fp sẽ có giá trị NULL. Nếu bạn vẫn cố truy cập vào con trỏ fp, chương trình của bạn sẽ bị crash.

Vì vậy, khi gọi hàm thư viện hoặc system call, bạn cần phải chú ý đến việc xử lý khi system call trả về lỗi. Có thể là những hành động ứng phó nào đó (tạo file hello.txt mới chẳng hạn), hoặc thoát ra khỏi hàm, hoặc đơn giản là in ra dòng log cảnh báo người dùng.

Xử lý lỗi System call

Trên trang manual của system call đều liệt kê các giá trị trả về có thể của một system call (phần ERROR), chỉ ra giá trị nào tương ứng với lỗi gì. Ví dụ về open() system call: (http://man7.org/linux/man-pages/man2/open.2.html)

RETURN VALUE         top

     open(), openat(), and creat() return the new file descriptor, or -1
      if an error occurred (in which case, errno is set appropriately).

Thông thường, một system call sẽ trả về giá trị -1 khi bị lỗi. Vì vậy, ta có thể kiểm tra lỗi như sau:

fd = open(pathname, flags, mode);

if (fd == -1)
{
   /* Xử lý lỗi system call() open fail*/
}

….

if (close(fd) == -1)
{
   /*Xử lý lỗi system call close() fail*/
}

Khi một system call lỗi, nó sẽ lưu một giá trị số nguyên tương ứng với mã lỗi đó vào một biến toàn cục là errno. Bạn có thể truy xuất giá trị errno (include thư viện <errno.h>) để biết được nguyên nhân system call bị lỗi. Ví dụ như sau, giả sử bạn gọi system call open() để mở file hello.txt không tồn tại:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main(void)
{
	int ret = 0;

	ret = open("hello.txt", O_RDONLY);
	if (-1 == ret)
	{
		printf("open failed, err no:%dn",errno);
		return -1;
	}
	return 0;
}

Bạn hãy compile chương trình trên và chạy nhé, kết quả in ra như sau:

minhlv@vimentor:~/work/code$ gcc -o open_err open_error.c

minhlv@vimentor:~/work/code$ ./open_err

open failed, err no:2

Như vậy system call open() báo bị lỗi, trả về giá trị 2. Các giá trị lỗi của biến errno được định nghĩa trong thư viện </usr/include/asm/errno.h>

#define EPERM            1 /* Operation not permitted */
#define ENOENT           2 /* No such file or directory */
#define ESRCH            3 /* No such process */
#define EINTR            4 /* Interrupted system call */
#define EIO              5 /* I/O error */
#define ENXIO            6 /* No such device or address */
#define E2BIG            7 /* Arg list too long */
#define ENOEXEC          8 /* Exec format error */

Diễn giải các giá trị lỗi này có thể tìm kiếm dễ dàng trên google. Hoặc nếu không muốn mất công tìm kiếm, bạn có thể in thẳng đoạn diễn giải lỗi ra bằng cách dùng hàm char *strerror(errno) như sau:

printf("open failed, error: %sn", strerror(errno));

Bạn thử chèn đoạn code này vào rồi compile và chạy thử xem kết quả như thế nào nhé.

Xử lý lỗi gọi hàm thư viện

Các hàm thư viện khác nhau có thể trả về các kiểu giá trị lỗi khác nhau để biểu thị lỗi. Ta có thể chia các hàm thư viện này thành 3 nhóm như sau:

  • Hàm thư viện trả về giá trị lỗi giống hệt cách trả về của system call: -1 trả về lỗi, với biến toàn cục errno lưu giá trị lỗi. Ví dụ các hàm này như remove(), sử dụng unlink() system call. Có thể debug những hàm này giống như cách làm với system call.

  • Hàm thư viện trả về kiểu giá trị lỗi khác system call, nhưng vẫn lưu giá trị lỗi vào biến errno. Ví dụ hàm fopen() để mở một file, hàm này gọi sysem call open() và trả về con trỏ NULL khi bị lỗi (thay vì -1 của system call open()). Để biết được nguyên nhân lỗi của những hàm này, bạn có thể dùng biến errno hoặc hàm strerror(errno) để in ra nguyên nhân lỗi.

  • Hàm thư viện không sử dụng biến errno. Những hàm này thực hiện và trả về theo cách riêng và không lưu giá trị lỗi vào biến errno. Nếu bạn đọc giá trị errno với các hàm này có thể sẽ bị hiểu sai nguyên nhân lỗi, vì errno lưu kết quả lỗi của system call được gọi gần nhất, chứ không phải hàm này.

Kết luận

Một system call hoặc hàm thư viện có thể thành công hoặc thất bại. Khi lập trình một chương trình Linux, bạn phải chú ý thêm code kiểm tra và xử lý lỗi khi gọi system call hoặc hàm thư viện (gọi lại hàm đó, return thoát khỏi hàm hoặc hiển thị log thông báo). Có nhiều trường hợp debug rất vất vả và mất thời gian chỉ vì không xử lý lỗi ở những hàm thư viện hoặc sysem call này.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *