Mục đích của fork () là gì?


87

Trong nhiều chương trình và trang người dùng của Linux, tôi đã thấy mã sử dụng fork(). Tại sao chúng ta cần sử dụng fork()và mục đích của nó là gì?


150
Để tất cả những triết gia ăn uống đó không chết đói.
kenj0418

Câu trả lời:


106

fork()là cách bạn tạo các quy trình mới trong Unix. Khi bạn gọi fork, bạn đang tạo một bản sao quy trình của riêng mình có không gian địa chỉ riêng . Điều này cho phép nhiều tác vụ chạy độc lập với nhau như thể chúng có toàn bộ bộ nhớ của máy cho riêng mình.

Dưới đây là một số cách sử dụng ví dụ về fork:

  1. Trình bao của bạn sử dụng forkđể chạy các chương trình bạn gọi từ dòng lệnh.
  2. Các máy chủ web như apache sử dụng forkđể tạo nhiều quy trình máy chủ, mỗi quy trình xử lý các yêu cầu trong không gian địa chỉ riêng của nó. Nếu một bộ nhớ bị chết hoặc rò rỉ bộ nhớ, những bộ nhớ khác không bị ảnh hưởng, vì vậy nó hoạt động như một cơ chế để chịu lỗi.
  3. Google Chrome sử dụng forkđể xử lý từng trang trong một quy trình riêng biệt. Điều này sẽ ngăn không cho mã phía máy khách trên một trang đưa toàn bộ trình duyệt của bạn xuống.
  4. forkđược sử dụng để tạo ra các quy trình trong một số chương trình song song (như những chương trình được viết bằng MPI ). Lưu ý rằng điều này khác với việc sử dụng các luồng không có không gian địa chỉ riêng và tồn tại trong một quy trình.
  5. Ngôn ngữ kịch bản sử dụng forkgián tiếp để bắt đầu các quy trình con. Ví dụ: mỗi khi bạn sử dụng một lệnh như subprocess.Popentrong Python, bạn sẽ forkxử lý và đọc đầu ra của nó. Điều này cho phép các chương trình hoạt động cùng nhau.

Cách sử dụng điển hình của forktrong một trình bao có thể trông giống như sau:

int child_process_id = fork();
if (child_process_id) {
    // Fork returns a valid pid in the parent process.  Parent executes this.

    // wait for the child process to complete
    waitpid(child_process_id, ...);  // omitted extra args for brevity

    // child process finished!
} else {
    // Fork returns 0 in the child process.  Child executes this.

    // new argv array for the child process
    const char *argv[] = {"arg1", "arg2", "arg3", NULL};

    // now start executing some other program
    exec("/path/to/a/program", argv);
}

Shell tạo ra một tiến trình con bằng cách sử dụng execvà đợi nó hoàn thành, sau đó tiếp tục với quá trình thực thi của chính nó. Lưu ý rằng bạn không cần phải sử dụng fork theo cách này. Bạn luôn có thể sinh ra nhiều quy trình con, như một chương trình song song có thể làm và mỗi quy trình có thể chạy một chương trình đồng thời. Về cơ bản, bất cứ khi nào bạn tạo quy trình mới trong hệ thống Unix, bạn đều đang sử dụng fork(). Đối với Windows tương đương, hãy xem CreateProcess.

Nếu bạn muốn có thêm ví dụ và giải thích dài hơn, Wikipedia có một bản tóm tắt phù hợp. Và đây là một số trang trình bày về cách các quy trình, luồng và đồng thời hoạt động trong các hệ điều hành hiện đại.


Bullet 5: 'thường xuyên'? Chỉ 'thường xuyên'? Những cái nào không sử dụng nó, hoặc trong những trường hợp không được sử dụng fork () - nghĩa là trên các hệ thống hỗ trợ fork ().
Jonathan Leffler

19
Lạ lùng thay, nó được gọi là CreateProcess () - những kẻ điên của Windows :-)
paxdiablo

2
không bao giờ nhận ra cho đến bây giờ rằng "shell sử dụng fork để chạy các chương trình bạn gọi ra từ dòng lệnh"!
Lazer

1
Liên kết các slide của bị phá vỡ
piertoni

1
Tất cả các câu trả lời nói rằng fork()là cách để tạo ra một quy trình mới trong UNIX, nhưng phải pedantic, có ít nhất một khác: posix_spawn().
Davislor

15

fork () là cách Unix tạo các quy trình mới. Tại thời điểm bạn gọi là fork (), quy trình của bạn được nhân bản và hai quy trình khác nhau tiếp tục thực thi từ đó. Một trong số chúng, con, sẽ có fork () trả về 0. Cái còn lại, cha, sẽ có fork () trả về PID (process ID) của con.

Ví dụ: nếu bạn nhập dòng sau vào shell, chương trình shell sẽ gọi fork () và sau đó thực hiện lệnh bạn đã truyền (trong trường hợp này là telnetd), trong khi lệnh cha cũng sẽ hiển thị lại lời nhắc. như một thông báo cho biết PID của quá trình nền.

$ telnetd &

Về lý do bạn tạo quy trình mới, đó là cách hệ điều hành của bạn có thể làm nhiều việc cùng một lúc. Đó là lý do tại sao bạn có thể chạy một chương trình và trong khi nó đang chạy, hãy chuyển sang một cửa sổ khác và làm việc khác.


@varDumper Bắt tốt!
Daniel C. Sobral

9

fork () được sử dụng để tạo tiến trình con. Khi một hàm fork () được gọi, một tiến trình mới sẽ được tạo ra và lệnh gọi hàm fork () sẽ trả về một giá trị khác cho con và mẹ.

Nếu giá trị trả về là 0, bạn biết bạn là quy trình con và nếu giá trị trả về là một số (xảy ra là id quy trình con), bạn biết bạn là cha mẹ. (và nếu đó là một số âm, thì lần phân tách đã không thành công và không có quy trình con nào được tạo)

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html


1
Trừ khi giá trị trả về là -1, trong trường hợp này thì fork () không thành công.
Jonathan Leffler

8

fork () về cơ bản được sử dụng để tạo một tiến trình con cho tiến trình mà bạn đang gọi hàm này. Bất cứ khi nào bạn gọi một fork (), nó sẽ trả về số 0 cho id con.

pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process

bằng cách này, bạn có thể cung cấp các hành động khác nhau cho phụ huynh và trẻ em và sử dụng tính năng đa luồng.


6

fork () sẽ tạo một tiến trình con mới giống hệt với tiến trình cha. Vì vậy, mọi thứ bạn chạy trong mã sau đó sẽ được chạy bởi cả hai quy trình - rất hữu ích nếu bạn có một máy chủ chẳng hạn và bạn muốn xử lý nhiều yêu cầu.


tại sao bạn tạo ra một đứa trẻ giống hệt với cha mẹ, công dụng là gì?

1
Nó giống như việc xây dựng quân đội với một người lính đơn lẻ. Bạn fork để chương trình của bạn có thể xử lý nhiều yêu cầu hơn cùng một lúc, thay vì từng yêu cầu một.
cloudhead

fork () trả về 0 trên con và pid của con trên cha. Sau đó, đứa trẻ có thể sử dụng một lệnh gọi như execute () để thay thế trạng thái của nó bằng một chương trình mới. Đây là cách các chương trình được khởi chạy.
Todd Gamblin

Các quy trình rất gần giống hệt nhau, nhưng có rất nhiều khác biệt nhỏ. Sự khác biệt rõ ràng là PID hiện tại và PID mẹ. Có các vấn đề liên quan đến khóa được tổ chức và tổ chức bán nguyệt san. Trang hướng dẫn fork () dành cho POSIX liệt kê 25 điểm khác biệt giữa cha và con.
Jonathan Leffler

2
@kar: Khi bạn có hai quy trình, chúng có thể tiếp tục riêng biệt từ đó và một trong số chúng có thể thay thế hoàn toàn chính nó (exex ()) bằng một chương trình khác.
Vatine

4

Bạn có thể không cần sử dụng fork trong lập trình hàng ngày nếu bạn đang viết ứng dụng.

Ngay cả khi bạn muốn chương trình của mình khởi động một chương trình khác để thực hiện một số tác vụ, vẫn có những giao diện đơn giản khác sử dụng fork phía sau, chẳng hạn như "hệ thống" trong C và perl.

Ví dụ: nếu bạn muốn ứng dụng của mình khởi chạy một chương trình khác chẳng hạn như bc để thực hiện một số phép tính cho bạn, bạn có thể sử dụng 'hệ thống' để chạy nó. Hệ thống thực hiện một 'ngã ba' để tạo một quy trình mới, sau đó 'thực thi' để biến quy trình đó thành bc. Khi bc hoàn tất, hệ thống trả lại quyền điều khiển cho chương trình của bạn.

Bạn cũng có thể chạy các chương trình khác không đồng bộ, nhưng tôi không thể nhớ cách làm.

Nếu bạn đang viết máy chủ, trình bao, vi rút hoặc hệ điều hành, bạn có nhiều khả năng muốn sử dụng fork.


Cảm ơn bạn đã system(). Tôi đã đọc về fork()vì tôi muốn mã C của mình chạy một tập lệnh python.
Bean Taxi

4

Fork tạo ra các quy trình mới. Nếu không có fork, bạn sẽ có một hệ thống unix chỉ có thể chạy init.


4

Hệ thống gọi fork () được sử dụng để tạo các quy trình. Nó không cần đối số và trả về một ID quy trình. Mục đích của fork () là tạo ra một tiến trình mới, tiến trình này trở thành tiến trình con của người gọi. Sau khi một tiến trình con mới được tạo, cả hai tiến trình sẽ thực hiện lệnh tiếp theo sau lệnh gọi hệ thống fork (). Vì vậy, chúng ta phải phân biệt cha mẹ với con cái. Điều này có thể được thực hiện bằng cách kiểm tra giá trị trả về của fork ():

Nếu fork () trả về một giá trị âm, thì quá trình tạo con đã không thành công. fork () trả về số 0 cho tiến trình con mới được tạo. fork () trả về một giá trị dương, ID tiến trình của tiến trình con, cho cha mẹ. ID quy trình trả về thuộc loại pid_t được định nghĩa trong sys / styles.h. Thông thường, ID tiến trình là một số nguyên. Hơn nữa, một tiến trình có thể sử dụng hàm getpid () để truy xuất ID tiến trình được gán cho tiến trình này. Do đó, sau khi hệ thống gọi đến fork (), một bài kiểm tra đơn giản có thể cho biết tiến trình nào là con. Xin lưu ý rằng Unix sẽ tạo một bản sao chính xác của vùng địa chỉ của cha mẹ và đưa nó cho con. Do đó, các tiến trình cha và con có không gian địa chỉ riêng biệt.

Hãy để chúng tôi hiểu nó với một ví dụ để làm rõ những điểm trên. Ví dụ này không phân biệt các tiến trình cha và con.

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  main(void)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     fork();
     pid = getpid();
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
          write(1, buf, strlen(buf));
     } 
}

Giả sử chương trình trên thực hiện đến điểm của lệnh gọi fork ().

Nếu lệnh gọi đến fork () được thực hiện thành công, Unix sẽ tạo ra hai bản sao giống hệt nhau của không gian địa chỉ, một bản dành cho cha và bản còn lại dành cho con. Cả hai tiến trình sẽ bắt đầu thực thi ở câu lệnh tiếp theo sau lệnh gọi fork (). Trong trường hợp này, cả hai quy trình sẽ bắt đầu thực hiện tại nhiệm vụ

pid = .....;

Cả hai tiến trình đều bắt đầu thực thi ngay sau lệnh gọi hệ thống (). Vì cả hai tiến trình đều có không gian địa chỉ giống hệt nhau nhưng riêng biệt, các biến đó được khởi tạo trước lệnh gọi fork () có cùng giá trị trong cả hai không gian địa chỉ. Vì mọi quy trình đều có không gian địa chỉ riêng nên mọi sửa đổi sẽ độc lập với các quy trình khác. Nói cách khác, nếu cha thay đổi giá trị của biến của nó, thì việc sửa đổi sẽ chỉ ảnh hưởng đến biến trong không gian địa chỉ của tiến trình cha. Các không gian địa chỉ khác được tạo bởi các lệnh gọi fork () sẽ không bị ảnh hưởng mặc dù chúng có tên biến giống hệt nhau.

Lý do sử dụng write thay vì printf là gì? Đó là bởi vì printf () là "bộ đệm", có nghĩa là printf () sẽ nhóm đầu ra của một quá trình với nhau. Trong khi đệm đầu ra cho tiến trình mẹ, con cũng có thể sử dụng printf để in ra một số thông tin, thông tin này cũng sẽ được lưu vào bộ đệm. Kết quả là, vì đầu ra sẽ không được gửi đến màn hình ngay lập tức, bạn có thể không nhận được đúng thứ tự của kết quả mong đợi. Tệ hơn, đầu ra từ hai quy trình có thể bị trộn lẫn theo những cách kỳ lạ. Để khắc phục sự cố này, bạn có thể cân nhắc sử dụng cách viết "unbuffered".

Nếu bạn chạy chương trình này, bạn có thể thấy những điều sau trên màn hình:

................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
     ................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
     ................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
     ................

ID quy trình 3456 có thể là ID được chỉ định cho cha mẹ hoặc con. Do thực tế là các quá trình này được chạy đồng thời, các dòng đầu ra của chúng được trộn lẫn với nhau theo một cách khá khó đoán. Hơn nữa, thứ tự của các dòng này được xác định bởi bộ lập lịch CPU. Do đó, nếu bạn chạy lại chương trình này, bạn có thể nhận được một kết quả hoàn toàn khác.


3
Thay vì sao chép dán văn bản mà bạn có thể nhận xét một liên kết: csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
Chaitanya lakkundi

3

Đa xử lý là trọng tâm của máy tính. Ví dụ: IE hoặc Firefox của bạn có thể tạo quy trình tải tệp xuống cho bạn khi bạn vẫn đang duyệt internet. Hoặc, trong khi bạn đang in tài liệu trong trình xử lý văn bản, bạn vẫn có thể xem các trang khác nhau và vẫn thực hiện một số chỉnh sửa với nó.


3

Fork () được sử dụng để tạo các quy trình mới như mọi phần thân đã viết.

Đây là mã của tôi tạo ra các quy trình ở dạng cây nhị phân ....... Nó sẽ yêu cầu quét số cấp tối đa mà bạn muốn tạo các quy trình trong cây nhị phân

#include<unistd.h> 
#include<fcntl.h> 
#include<stdlib.h>   
int main() 
{
int t1,t2,p,i,n,ab;
p=getpid();                
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);                
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)    
{        
    t1=fork();

    if(t1!=0)
        t2=fork();        
    if(t1!=0 && t2!=0)        
        break;            
    printf("child pid %d   parent pid %d\n",getpid(),getppid());fflush(stdout);
}   
    waitpid(t1,&ab,0);
    waitpid(t2,&ab,0);
return 0;
}

ĐẦU RA

  enter the number of levels
  3
  root 20665
  child pid 20670   parent pid 20665
  child pid 20669   parent pid 20665
  child pid 20672   parent pid 20670
  child pid 20671   parent pid 20670
  child pid 20674   parent pid 20669
  child pid 20673   parent pid 20669

2

Đầu tiên, người ta cần hiểu lời gọi hệ thống fork () là gì. Hãy để tôi giải thích

  1. lệnh gọi hệ thống fork () tạo bản sao chính xác của quy trình mẹ, Nó tạo bản sao của ngăn xếp mẹ, đống, dữ liệu được khởi tạo, dữ liệu chưa được khởi tạo và chia sẻ mã ở chế độ chỉ đọc với quy trình mẹ.

  2. Lệnh gọi hệ thống Fork sao chép bộ nhớ trên cơ sở sao chép-ghi, nghĩa là con tạo trong trang bộ nhớ ảo khi có yêu cầu sao chép.

Bây giờ Mục đích của fork ():

  1. Fork () có thể được sử dụng ở nơi có sự phân chia công việc như một máy chủ phải xử lý nhiều máy khách, Vì vậy, cha mẹ phải chấp nhận kết nối một cách thường xuyên, Vì vậy máy chủ thực hiện fork cho mỗi máy khách để thực hiện đọc-ghi.

1

fork()được sử dụng để sinh ra một quy trình con. Thông thường, nó được sử dụng trong các loại tình huống tương tự như phân luồng, nhưng có sự khác biệt. Không giống như các luồng, fork()tạo ra toàn bộ các quy trình riêng biệt, có nghĩa là con và cha trong khi chúng là bản sao trực tiếp của nhau tại điểm fork()được gọi, chúng hoàn toàn tách biệt, không thể truy cập không gian bộ nhớ của người kia (mà không gặp phải những rắc rối thông thường bạn truy cập bộ nhớ của chương trình khác).

fork()vẫn được sử dụng bởi một số ứng dụng máy chủ, chủ yếu là những ứng dụng chạy dưới dạng root trên máy * NIX, loại bỏ quyền trước khi xử lý yêu cầu của người dùng. Vẫn còn một số usecases khác, nhưng hầu hết mọi người đã chuyển sang đa luồng.


2
Tôi không hiểu nhận thức rằng "hầu hết mọi người" đã chuyển sang đa luồng. Các quy trình ở đây để tồn tại, và các chủ đề cũng vậy. Không ai đã "chuyển sang" từ cả hai. Trong lập trình song song, các mã lớn nhất và đồng thời nhất là các chương trình đa quá trình bộ nhớ phân tán (ví dụ: MapReduce và MPI). Tuy nhiên, hầu hết mọi người sẽ chọn OpenMP hoặc một số mô hình bộ nhớ chia sẻ cho máy đa lõi và GPU đang sử dụng các luồng ngày nay, nhưng còn rất nhiều điều khác ngoài đó. Tuy nhiên, tôi cá rằng nhiều lập trình viên trên trang web này gặp phải quá trình xử lý song song ở phía máy chủ hơn bất kỳ thứ gì đa luồng.
Todd Gamblin

1

Lý do đằng sau fork () so với việc chỉ có một hàm execute () để bắt đầu một quy trình mới được giải thích trong câu trả lời cho một câu hỏi tương tự trên trao đổi ngăn xếp unix .

Về cơ bản, vì fork sao chép quy trình hiện tại, tất cả các tùy chọn có thể có khác nhau cho một quy trình đều được thiết lập theo mặc định, vì vậy lập trình viên không cung cấp chúng.

Ngược lại, trong hệ điều hành Windows, các lập trình viên phải sử dụng hàm CreateProcess phức tạp hơn RẤT NHIỀU và yêu cầu điền vào một cấu trúc đa biến để xác định các tham số của tiến trình mới.

Vì vậy, tóm lại, lý do của việc phân nhánh (so với thực thi) là sự đơn giản trong việc tạo các quy trình mới.


0

Sử dụng lệnh gọi hệ thống Fork () để tạo một tiến trình con. Nó là bản sao chính xác của quy trình mẹ. Fork sao chép phần ngăn xếp, phần đống, phần dữ liệu, biến môi trường, các đối số dòng lệnh từ cha.

tham khảo: http://man7.org/linux/man-pages/man2/fork.2.html


0

Hàm fork () được sử dụng để tạo một quy trình mới bằng cách sao chép quy trình hiện có mà từ đó nó được gọi. Quá trình hiện có mà từ đó hàm này được gọi sẽ trở thành quá trình mẹ và quá trình mới được tạo trở thành quá trình con. Như đã nói rằng con là một bản sao của cha mẹ nhưng có một số ngoại lệ đối với nó.

  • Đứa trẻ có một PID duy nhất giống như bất kỳ quá trình nào khác đang chạy trong hệ điều hành.

  • Con có ID quy trình cha giống như PID của
    quy trình đã tạo ra nó.

  • Sử dụng tài nguyên và bộ đếm thời gian CPU được đặt lại về 0 trong quy trình con.

  • Tập hợp các tín hiệu đang chờ xử lý trong con trống.

  • Con không kế thừa bất kỳ bộ hẹn giờ nào từ cha mẹ của nó

Thí dụ :

    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdio.h>
    #include <sys/wait.h>
    #include <stdlib.h>

    int var_glb; /* A global variable*/

int main(void)
{
    pid_t childPID;
    int var_lcl = 0;

    childPID = fork();

    if(childPID >= 0) // fork was successful
    {
        if(childPID == 0) // child process
        {
            var_lcl++;
            var_glb++;
            printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
        else //Parent process
        {
            var_lcl = 10;
            var_glb = 20;
            printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
    }
    else // fork failed
    {
        printf("\n Fork failed, quitting!!!!!!\n");
        return 1;
    }

    return 0;
}

Bây giờ, khi đoạn mã trên được biên dịch và chạy:

$ ./fork

Parent process :: var_lcl = [10], var_glb[20]

Child Process :: var_lcl = [1], var_glb[1]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.