Hàm fork() trong hệ điều hành Linux hoạt động như thế nào?

Trên lớp thầy em giảng về hệ điều hành mà em chẳng hiểu gì. .
Có anh nào giúp em giải thích về mấy hàm fork và tóm tắt or tài liệu học về hệ điều hành với ạ !

Đề bài như sau:
hello.exe la file minh build ra từ file hello.c

file hello chỉ print ra câu "xin chao "thôi

Hãy sửa đoạn code sau để child process chạy hello.exe.

int main(void) 
{    
    pid_t child_pid;    
    int status;
    pid_t wait_result;
    child_pid = fork();
    
    if (child_pid == 0) 
    { 
        /* this code is only executed in the child process */ 
        printf ("I am a child and my pid = %d\n", getpid());
        execl("/bin/ls", "ls", "-l", NULL);
        /* if execl succeeds, this code is never used */
        printf ("Could not execl file /bin/ls\n");
        /* this exit stops only the child process */        
        exit(1);
    }
    else if (child_pid > 0) 
    {
        /* this code is only executed in the parent process */
        printf ("I am the parent and my pid = %d\n", getpid());
        printf ("My child has pid = %d\n", child_pid);
        printf ("I am a parent and I am going to wait for my child\n");
        do 
        {
            /* parent waits for the SIGCHLD signal sent
            to the parent of a (child) process when 
            the child process terminates */
            wait_result = wait(&status);
        } while (wait_result != child_pid);
        printf ("I am a parent and I am quitting.\n");
    }
    else 
    {
        printf ("The fork system call failed to create a new process\n");
        exit(1);
    }
    return 0;
}
4 Likes

Có nhiều bạn thắc mắc về hàm fork quá, thôi để Đạt cố gắng viết một câu trả lời thật dễ hiểu về hàm này.

Nói tới hàm Fork, fork trong tiếng anh là cái nỉa, mình tưởng tượng nó giống cái nỉa như hình dưới :slight_smile:

Một số thuật ngữ được dùng

  • Chương trình = program
  • Tiến trình = process
  • Tiến trình cha = parent process
  • Tiến trình con = child process
  • pid = process id = định danh tiến trình

Ta tưởng tượng một chương trình giống một cái nỉa vậy. Khi bắt đầu, chương trình chạy từ đuôi nỉa cho tới khi gặp sự kiện fork, tức là khi hàm fork được gọi.

Chuyện gì xảy ra?

Trước khi có sự kiện fork, chương trình == tiến trình. Và chỉ có một tiến trình tại thời điểm hiện tại.

Sau đó chương trình sẽ tách phân thân ra làm hai tiến trình giống nhau hoàn toàn, trừ một điểm* là pid khác nhau. Tức là định danh, hay tên hay số CMND, của tiến trình khác nhau.

Những điểm cần lưu ý

  • Tiến trình ban đầu gọi là tiến trình cha, những tiến trình được “phân thân” ra gọi là tiến trình con.
  • Các tiến trình này có vùng nhớ riêng, hay cả cha lẫn con đều có nhà riêng, không sống chung, không ị chung bồn cầu :smiling_imp: Cho nên việc thay đổi các giá trị(biến, vùng nhớ, …) ở tiến trình cha không ảnh hưởng tới tiến trình con và ngược lại.
  • fork có thể được gọi nhiều lần tức tiến trình cha có thể phân thân nhiều lần.
  • Việc phân thân có thể thất bại, có thể do hết mana :smiling_imp:
  • Điểm khác nhau về pid không phải là duy nhất, nhưng để đơn giản vấn đề, Đạt chỉ nói tới điểm này, muốn tìm hiểu thêm thì các bạn đọc ở đây

Cha con giống nhau thì làm sao biết ai là cha, ai là con?

Ta dựa vào giá trị trả về của fork. Code đang chạy ở tiến trình cha, fork sẽ trả về pid của tiến trình con, tức là một số nguyên dương bất kỳ và khác 0. Cha đẻ ra con thì phải biết tên con là gì chớ :smiling_imp:

  • Tiến trình cha có kết quả trả về của hàm fork là pid của con, pid luôn > 0
  • Tiến trình con nhận về số 0, số 0 này chỉ để thông báo rằng, à, code đang chạy ở tiến trình con. Chứ số 0 này không phải là pid nhé các bạn.

Ủa có khi nào giá trị này trả về bé hơn 0 không? Có, nếu trả về = -1 thì ta có thể hiểu là việc phân thân thất bại rồi :smiling_imp:

Xem hình dưới để dễ tưởng tượng

Mẹo không nhỏ: Dựa vào con số trả về này, ta sẽ phân tách công việc cần phải làm ở các tiến trình cha và con khác nhau.

Ví dụ

child_pid = fork(); // sự kiện fork đây rồi
// kiểm tra child_pid
if (child_pid == 0) {
    // à, child_pid == 0 rồi, số 0 này không phải là pid nhé.
    // số 0 này là để thông báo ta đang ở trong tiến trình con
} else if (child_pid > 0) {
    // à, child_pid > 0, vậy đây tức là định danh của tiến trình con thật rồi
    // Mà nhận được giá trị này,tức đây là tiến trình cha.
} else {
    // ơ, tèo rồi à :cry: 
}

PID là cái gì, xem ở đâu, sao mơ hồ quá?

Mở Task Manager lên, Chọn Chrome hoặc cốc cốc, click chuột phải, chọn Go to details

Sắp xếp các process theo tên, các bạn sẽ thấy có nhiều tiến trình Chome hoặc cốc cốc có khác PID. Mỗi tiến trình sử dụng memory khác nhau.

14 Likes

Nếu pid trả về là 0, nghĩa là đoạn code trong if sẽ thực hiện trên tiến trình con.
Nếu thế thì sau đoạn if, đoạn code tiếp theo sau đó cũng sẽ chạy trên tiến trình con à a Đạt :dizzy_face:
Làm sao để quản lý được đoạn code nào sẽ chạy trên tiến trình nào :dizzy_face:

2 Likes

Câu hỏi hay :slight_smile:

Trả lời ngắn

Để quản lý tiến trình ở với fork ta cho tiến trình con làm một công việc gì đó rồi thoát ngay. Sau đó ta không cần quan tâm đến tiến trình con nữa.

Trả lời đầy đủ

Ta có thể thấy ngay rắc rối là thằng phân thân của mình nó sẽ giống dai, sống dài, sống nguy hiểm, rồi có một ngày nó đâm sau lưng thằng chính :scream:

Thế nên lệnh fork hay đi kèm với lệnh exec ta gọi là cặp fork-exec, tức là khi chạy lệnh này thì tiến trình con hi sinh bản thân mình để gọi một chương trình khác và thực thi chương trình đó. Khi đó tiến trình con sẽ chết.

Lấy code ví dụ ở trên, đọc comment tiếng Việt

if (child_pid == 0) 
{ 
    /* this code is only executed in the child process */ 
    printf ("I am a child and my pid = %d\n", getpid());
    execl("/bin/ls", "ls", "-l", NULL); 

    // Sau khi chạy lệnh execl thì chương trình con sẽ chết.
    // Tiến trình con này sẽ gọi chương trình `ls` của Linux 
    // và list ra danh sách các file ở thư mục hiện hành

    // Tuy nhiên, để chắc chắn trong trường hợp `excel` không chạy được
    // Ta gọi hàm exit để kết liễu tiến trình con này luôn

    /* if execl succeeds, this code is never used */
    printf ("Could not execl file /bin/ls\n");
    /* this exit stops only the child process */        
    exit(1);
}

Một ví dụ cách fork hoạt động theo phong cách Naruto phân thân

Naruto vừa xem phim vừa giặt đồ

11 Likes

Em đã hiểu rồi, cảm ơn anh nhé !
tuy nhiên việc sử dụng hàm exec hơi lạ…
khi em search thông tin về fork-exec
int execl ( const char *path, const char *arg, … );
int execlp( const char *file, const char *arg, … );
int execle( const char *path, const char *arg, …, char *const envp[] );
int execv ( const char *path, char *const argv[] );
int execvp( const char *file, char *const argv[] );
int execve( const char *file, char *const argv[], char *const envp[] );
Trường hợp của em là
execl("/bin/ls", “ls”, “-l”, NULL);
Anh giải thích giúp em với ạ
1 .tại sao Path lại là bin/ls trong khi em khai báo thư mục chứa file của em là e:/fork lại báo lỗi ạ
2. “ls” là lệnh search trong terminal và “-l” chắc cũng vậy nhỷ
3. em muốn run hello.c thì em gõ code execl("/bin/ls", “./hello.exe”, NULL); đúng k ạ
4.khác biệt các hàm trên là như nào ạ

Cảm ơn anh nhiều

Anh lấy ví dụ về Naruto thật sự rất hay nha :smiley:

1 Like

@Nguyen_Dinh_Nam trên linux thì lấy đâu ra ổ e với .exe hả bạn

tại sao tiến trình cha lại nhận pid của con ạ

mỗi tiến trình có 1 pid khác nhau mà nhỉ ,dùng lệnh getpid() để lấy pid phải ko ạ

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?