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

fork và Copy-on-Write

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

  • Giải thích fork trả về gì ở cha và ở con
  • Giải thích Copy-on-Write làm fork nhanh thế nào
  • Tránh bẫy bộ đệm stdio bị nhân đôi
  • Không bao giờ viết fork bomb

fork là lời gọi hệ thống kỳ lạ nhất trong C: bạn gọi nó một lần, và nó trả về hai lần, ở hai tiến trình khác nhau. Hiểu điều đó là chìa khoá của mọi lập trình đa tiến trình.

#Một lời gọi, ba giá trị

fork.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    printf("Truoc fork: PID=%d\n", getpid());

    pid_t pid = fork();          /* MOT loi goi, HAI tien trinh tra ve */

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        /* TIEN TRINH CON: fork tra ve 0 */
        printf("CON : PID=%d, cha=%d\n", getpid(), getppid());
        _exit(0);                /* dung _exit trong con, xem Bai 40.1 */
    } else {
        /* TIEN TRINH CHA: fork tra ve PID cua con */
        printf("CHA : PID=%d, con=%d\n", getpid(), pid);
        int status;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status))
            printf("Con thoat voi ma %d\n", WEXITSTATUS(status));
    }
    return 0;
}
terminal
gcc fork.c -o fork && ./fork
Truoc fork: PID=25104
CHA : PID=25104, con=25105
CON : PID=25105, cha=25104
Con thoat voi ma 0
fork trả vềỞ đâuNghĩa
< 0Tiến trình gọiLỗi: hết bộ nhớ hoặc quá giới hạn tiến trình
== 0Tiến trình CONĐây là con
> 0Tiến trình CHAGiá trị là PID của con

#Copy-on-Write

Copy-on-Write (COW)
Kỹ thuật làm fork nhanh. Nhân không sao chép bộ nhớ ngay. Nó đánh dấu mọi trang là chỉ đọc và cho hai tiến trình dùng chung. Chỉ khi một bên ghi vào một trang, nhân mới nhân bản đúng trang đó.
/* Neu fork phai chep TOAN BO bo nho cua tien trinh (co the hang
   GB), no se rat cham. Copy-on-Write tranh viec do:

   Ngay sau fork:
     Cha va con CHIA SE moi trang bo nho, danh dau CHI DOC.
     fork gan nhu tuc thoi, chi chep bang trang.

   Khi mot ben GHI vao mot trang:
     Nhan phat hien ghi vao trang chi doc, NHAN BAN dung trang do,
     va cho ben ghi ban rieng. Chi trang bi ghi moi duoc chep.

   He qua:
     - fork nhanh, gan nhu khong ton bo nho luc dau
     - neu con exec ngay (thay chuong trinh), gan nhu khong trang
       nao bi chep -> cuc ky hieu qua
   Do la ly do mau fork + exec (Bai 40.3) re, du fork nghe co ve
   phai chep ca tien trinh. */
terminal
# Do thoi gian fork 1000 lan (con exit ngay)
./do_fork
1000 lan fork + exit: 42 ms
trung binh 42 micro giay moi fork
(COW: khong chep bo nho, chi bang trang)

#Bẫy bộ đệm

#Fork bomb

Không kiểm tra fork
for (int i = 0; i < n; ++i) { if (fork() == 0) { lam_viec(i); /* QUEN _exit! con chay tiep vong for, fork THEM */ } } /* Con khong thoat sau khi lam viec -> no cung fork tiep, sinh so tien trinh bung no. */
Luôn kiểm tra và có điều kiện dừng
for (int i = 0; i < n; ++i) { pid_t pid = fork(); if (pid < 0) { perror("fork"); break; } /* kiem tra loi */ if (pid == 0) { lam_viec(i); _exit(0); /* con THOAT, khong fork tiep */ } /* chi CHA chay tiep vong for */ } for (int i = 0; i < n; ++i) wait(NULL); /* cho moi con */

Tự làm thử

  1. Chạy fork.c nhiều lần và quan sát thứ tự in của cha và con thay đổi.
  2. Sửa một biến trong con và chứng minh cha không thấy thay đổi.
  3. Đo thời gian fork một nghìn lần và tính trung bình.
  4. Tái hiện bẫy bộ đệm bằng printf không xuống dòng rồi chuyển hướng ra tệp.
  5. Tạo cây tiến trình ba tầng: ông, cha, con, mỗi tầng in PID và PPID.
  6. Đặt ulimit -u 50 trong một shell riêng và thử một vòng fork có kiểm soát tới giới hạn đó.

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

  • fork gọi một lần, trả về hai lần: 0 ở con, PID của con ở cha, âm khi lỗi.
  • Sau fork cả hai tiến trình chạy tiếp từ cùng một dòng, chỉ khác giá trị trả về; thứ tự chạy không đoán trước được.
  • Copy-on-Write làm fork nhanh: chia sẻ trang chỉ đọc, chỉ nhân bản trang khi có ghi.
  • Bẫy bộ đệm: printf không \n trước fork bị in hai lần; xả bộ đệm hoặc dùng \n, và dùng _exit trong con.
  • Không bao giờ viết while(1) fork(); luôn kiểm tra giá trị trả về và có điều kiện dừng.