Bài 42.126 phút đọc
Ranh giới user và kernel, errno
Sau bài này bạn sẽ làm được
- Vẽ đường đi của một lời gọi từ user xuống kernel
- Giải thích chi phí một syscall
- Áp dụng ba quy tắc về errno
- Xử lý các mã lỗi thường gặp
System call là cổng giao tiếp duy nhất giữa chương trình của bạn và nhân. Mọi việc chạm tới phần cứng, tệp, mạng, tiến trình đều đi qua đó. Bài này vẽ đường đi, đo cái giá của nó, và dạy cách đọc lỗi qua errno.
#Ranh giới user và kernel
USER SPACE (che do nguoi dung, ring 3)
+----------------------------------------+
| Chuong trinh cua ban |
| printf("hi") |
| | |
| libc: fwrite -> write() |
+-------|--------------------------------+
| lenh CPU: syscall (hoac int 0x80)
| chuyen sang che do KERNEL (ring 0)
+-------v--------------------------------+
| KERNEL SPACE |
| sys_write() -> VFS -> driver -> phan cung
+----------------------------------------+System call
Một yêu cầu từ chương trình gửi tới nhân để làm một việc mà chương trình không được phép tự làm: đọc đĩa, gửi gói mạng, tạo tiến trình. CPU chuyển từ chế độ người dùng sang chế độ nhân, nhân làm việc, rồi trả về.
#Chi phí một syscall
terminal
# Linux: xem MOI syscall chuong trinh goi
strace ./hello
execve("./hello", ["./hello"], ...) = 0
brk(NULL) = 0x55...
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 3
...
write(1, "hi\n", 3) = 3
exit_group(0) = ?#errno
errno_thu.c
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main(void) {
errno = 0; /* dat lai TRUOC khi goi */
int fd = open("khong_ton_tai_12345.txt", O_RDONLY);
if (fd < 0) {
printf("open that bai\n");
perror(" perror"); /* in thong diep he thong */
printf(" errno=%d: %s\n", errno, strerror(errno));
if (errno == ENOENT)
printf(" -> ENOENT: tep khong ton tai\n");
}
return 0;
}terminal
gcc -Wall -Wextra errno_thu.c -o errno_thu.exe && ./errno_thu.exe
open that bai perror: No such file or directory errno=2: No such file or directory -> ENOENT: tep khong ton tai
#Ba quy tắc
Đọc errno khi hàm thành công
open(path, O_RDONLY);
if (errno != 0) { /* SAI: errno co the la rac cu */
perror("open");
}
/* open co the thanh cong nhung errno van mang gia tri tu mot loi
truoc do, nen nhanh nay chay nham. */
Kiểm tra giá trị trả về trước
int fd = open(path, O_RDONLY);
if (fd < 0) { /* kiem tra GIA TRI TRA VE */
perror("open"); /* gio errno moi co nghia */
}
#Mã lỗi thường gặp
| Mã | Nghĩa |
|---|---|
| ENOENT | Tệp hoặc thư mục không tồn tại |
| EACCES | Không có quyền |
| ENOMEM | Hết bộ nhớ |
| EINVAL | Tham số không hợp lệ |
| EAGAIN / EWOULDBLOCK | Thử lại, dùng với không chặn |
| EINTR | Bị tín hiệu ngắt, thường phải thử lại |
| EPIPE | Ghi vào ống không còn ai đọc |
| EEXIST | Tệp đã tồn tại |
| EMFILE / ENFILE | Quá nhiều tệp mở |
Tự làm thử
- Vẽ đường đi của
printftừ user xuốngwritetrong nhân. - Trên Linux, chạy
stracetrên một chương trình Hello World và đếm số syscall. - Viết chương trình gây mười lỗi khác nhau và in tên mã lỗi cho từng cái.
- Chứng minh quy tắc hai: đọc
errnosau mộtprintfxen vào và thấy nó bị đổi. - Xử lý
EINTRđúng cách trong một vòng lặpread. - Phân biệt
man 2 writevàman 3 printfvà tóm tắt khác biệt.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- System call là cổng duy nhất từ chương trình (ring 3) tới nhân (ring 0); nhân kiểm tra quyền trước khi làm.
- Mỗi syscall tốn 100 tới 1000 chu kỳ, nên stdio phải đệm và đọc từng byte chậm hơn nghìn lần.
errnochỉ có nghĩa sau khi hàm báo lỗi; đọc ngay, lưu ngay, và nó là thread-local.- Kiểm tra giá trị trả về của hàm trước, rồi mới đọc
errno. EINTR,EAGAIN,EPIPElà ranh giới giữa lỗi thật và tình huống bình thường phải xử lý.