Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 41.128 phút đọc

File descriptor và open, read, write

Sau bài này bạn sẽ làm được

  • Giải thích file descriptor là gì
  • Dùng open với đúng cờ và quyền
  • Viết vòng lặp đọc và ghi đầy đủ xử lý partial
  • Kiểm tra giá trị trả về của close

Trong Unix, mọi thứ là tệp: tệp thường, thư mục, ống, socket, thiết bị, terminal. Và mọi tệp được thao tác qua bốn lời gọi tầng thấp: open, read, write, close. Chương này chạy được trên bản MinGW, nên các bản ghi là kết quả thật.

#File descriptor

File descriptor
Một số nguyên nhỏ, chỉ mục vào bảng tệp mở của tiến trình. Bạn open một tệp và nhận về một số; mọi thao tác sau đó dùng số đó. Ba số đầu tiên có sẵn khi chương trình khởi động.
#define STDIN_FILENO   0        /* dau vao chuan */
#define STDOUT_FILENO  1        /* dau ra chuan */
#define STDERR_FILENO  2        /* dau ra loi chuan */

/* Khi chuong trinh khoi dong, ba fd nay da mo san, thuong deu
   tro toi terminal. open() tra ve fd nho nhat con TRONG, nen
   file dau tien ban mo thuong la fd 3. */

#open

open và các cờ
#include <fcntl.h>
int open(const char *path, int flags, ... /* mode_t mode */);

int fd = open("data.txt", O_RDONLY);                       /* chi doc */
int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);  /* ghi, tao, xoa cu */
int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644); /* ghi noi cuoi */

if (fd < 0) { perror("open"); return 1; }
CờÝ nghĩa
O_RDONLY / O_WRONLY / O_RDWRChế độ truy cập, chọn đúng một
O_CREATTạo nếu chưa có, cần tham số mode
O_TRUNCXoá sạch nội dung cũ
O_APPENDLuôn ghi vào cuối, nguyên tử khi nhiều tiến trình ghi
O_EXCLVới O_CREAT: lỗi nếu tệp đã có, chống đua tạo
O_NONBLOCKKhông chặn
O_CLOEXECTự đóng fd khi exec, chống rò rỉ sang tiến trình con

#read và write

Chữ ký và giá trị trả về
#include <unistd.h>

ssize_t read (int fd, void *buf, size_t n);
/* tra ve so byte DOC DUOC, 0 = het tep (EOF), -1 = loi */

ssize_t write(int fd, const void *buf, size_t n);
/* tra ve so byte GHI DUOC */

#Vòng lặp đầy đủ

Hai mẫu phải thuộc
#include <unistd.h>
#include <errno.h>

/* Ghi DAY DU n byte */
ssize_t ghi_day_du(int fd, const void *buf, size_t n) {
    const char *p = buf;
    size_t con_lai = n;
    while (con_lai > 0) {
        ssize_t w = write(fd, p, con_lai);
        if (w < 0) {
            if (errno == EINTR) continue;   /* bi tin hieu ngat -> thu lai */
            return -1;                      /* loi that */
        }
        p += w;
        con_lai -= (size_t)w;
    }
    return (ssize_t)n;
}

/* Doc DAY DU n byte (hoac toi EOF) */
ssize_t doc_day_du(int fd, void *buf, size_t n) {
    char *p = buf;
    size_t con_lai = n;
    while (con_lai > 0) {
        ssize_t r = read(fd, p, con_lai);
        if (r < 0) { if (errno == EINTR) continue; return -1; }
        if (r == 0) break;                  /* EOF */
        p += r;
        con_lai -= (size_t)r;
    }
    return (ssize_t)(n - con_lai);
}
mycat.c, tự viết cat bằng open/read/write
int main(int argc, char **argv) {
    if (argc < 2) { fprintf(stderr, "dung: mycat <tep>\n"); return 1; }

    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) { perror("open"); return 1; }

    char buf[4096];
    ssize_t n;
    while ((n = read(fd, buf, sizeof buf)) > 0)
        if (ghi_day_du(STDOUT_FILENO, buf, (size_t)n) < 0) {
            perror("write");
            return 1;
        }
    if (n < 0) perror("read");

    close(fd);
    return 0;
}
terminal
gcc -O2 -Wall -Wextra mycat.c -o mycat.exe
printf 'dong mot\ndong hai\ndong ba\n' > thu.txt && ./mycat.exe thu.txt
dong mot
dong hai
dong ba

#close

Kiểm tra close, và rò rỉ mô tả tệp
if (close(fd) < 0)
    perror("close");        /* loi ghi dia co the lo ra o day, khong phai o write */

/* Vi sao kiem tra close: voi ghi co dem, du lieu co the con nam
   trong bo dem cua nhan luc write tra ve. Loi ghi dia (het cho,
   dia hong) co the chi lo ra khi nhan XA du lieu do, tuc luc close.
   Nen bo qua ket qua close la co the mat du lieu ma khong biet. */

Tự làm thử

  1. Tự viết cat bằng open, read, write và chạy nó trên một tệp.
  2. Tự viết cp sao chép tệp, xử lý đầy đủ đọc và ghi từng phần.
  3. Bỏ vòng lặp ghi_day_du và thay bằng một lời gọi write, rồi copy một tệp lớn qua một ống chậm để thấy nó thiếu byte.
  4. Mở một tệp với O_CREAT mà không đưa tham số mode và xem quyền tệp tạo ra.
  5. Mở một trăm tệp mà không đóng và quan sát open thất bại với EMFILE.
  6. Ghi vào một tệp trên phân vùng đầy và kiểm tra lỗi lộ ra ở write hay close.

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

  • File descriptor là số nguyên nhỏ chỉ vào bảng tệp mở của nhân; 0, 1, 2 là stdin, stdout, stderr.
  • open nhận cờ chọn chế độ và hành vi; tham số mode chỉ dùng khi O_CREAT tạo tệp mới.
  • read và write có thể xử lý ít hơn số byte yêu cầu; luôn dùng vòng lặp đầy đủ.
  • EINTR nghĩa là bị tín hiệu ngắt, không phải lỗi; gọi lại.
  • Luôn kiểm tra close (lỗi ghi có thể lộ ở đó) và đóng fd ở mọi nhánh để không rò rỉ.