2013-02-27 20 views
6

我真的很陌生,在這段代碼中做什麼pid?有人能解釋一下在X線和Y線出現什麼嗎?fork()如何工作?

#include <sys/types.h> 
#include <stdio.h> 
#include <unistd.h> 
#define SIZE 5 
int nums[SIZE] = {0,1,2,3,4}; 
int main() 
{ 
    int i; 
    pid_t pid; 
    pid = fork(); 
    if (pid == 0) { 
     for (i = 0; i < SIZE; i++) { 
      nums[i] *= -i; 
      printf("CHILD: %d ",nums[i]); /* LINE X */ 
     } 
    } 
    else if (pid > 0) { 
     wait(NULL); 
     for (i = 0; i < SIZE; i++) 
      printf("PARENT: %d ",nums[i]); /* LINE Y */ 
    } 
    return 0; 
} 
+1

你是否嘗試過編譯並運行它?你認爲會發生什麼? – 2013-02-27 00:56:45

+0

你可以看看這裏:http://ideone.com/DasYqa – jxh 2013-02-27 00:58:15

+3

[man fork](http://linux.die.net/man/2/fork) – RageD 2013-02-27 00:59:15

回答

21

fork()複製的過程,所以調用fork後,居然有你的程序運行的兩個實例。

你怎麼知道哪個過程是原始(父母)過程,哪個過程是新的(孩子)過程?

在父進程中,子進程的PID(將是一個正整數)從fork()返回。這就是爲什麼if (pid > 0) { /* PARENT */ }的代碼工作。在子進程中,fork()只返回0

因此,由於if (pid > 0)檢查,父進程和子進程將產生不同的輸出,您可以看到here(由註釋中的@jxh提供)。

+1

該代碼中還有第三個分支(未展開)。 如果fork()失敗怎麼辦? = P – gEdringer 2015-06-09 13:38:20

+0

@gEdringer不是你不知道的,而是對於其他人可能不知道的 - 你可能希望使用'perror(fork)'作爲fork返回的負值來指示錯誤。 'man perror' – Donbhupi 2015-10-06 19:01:21

4

fork()函數是特殊的,因爲它實際上返回兩次:一次是父進程,一次是子進程。在父進程中,fork()返回孩子的PID。在子進程中,它返回0.如果發生錯誤,則不會創建子進程,並將-1返回給父進程。

成功調用fork()後,子進程基本上是父進程的完全重複。兩者都有自己的所有本地和全局變量的副本,以及它們自己的任何打開文件描述符的副本。兩個進程併發運行,並且由於它們共享相同的文件描述符,每個進程的輸出可能會相互交錯。

以在示例中的問題,仔細一看:

pid_t pid; 
pid = fork(); 
// When we reach this line, two processes now exist, 
// with each one continuing to run from this point 
if (pid == 0) {      
    // The child runs this part because fork returns 0 to the child 
    for (i = 0; i < SIZE; i++) { 
     nums[i] *= -i; 
     printf("CHILD: %d ",nums[i]); /* LINE X */ 
    } 
} 
else if (pid > 0) { 
    // The parent runs this part because fork returns the child's pid to the parent 
    wait(NULL);  // this causes the parent to wait until the child exits 
    for (i = 0; i < SIZE; i++) 
     printf("PARENT: %d ",nums[i]); /* LINE Y */ 
} 

這將輸出如下:

CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16 PARENT: 0 PARENT: 1 PARENT: 2 PARENT: 3 PARENT: 4 

因爲父進程調用wait()它會暫停在這一點上,直到孩子退出。所以孩子的產量首先出現。然後,在孩子退出之後,父母繼續撥打wait()後繼續打印其輸出。

1

fork()是創建進程的函數調用。調用fork()的過程稱爲父進程,新創建的進程稱爲子進程

在從fork()系統調用返回時,這兩個過程具有他們的用戶級上下文,除了返回值,pid的相同副本。

父進程pidChild process ID(新創建子進程的進程ID)。
子進程,pid0


內核對fork()執行以下操作順序。

  1. 它在進程表中爲新進程分配一個插槽。
  2. 它爲子進程分配一個唯一的ID號碼。
  3. 它生成父進程的上下文的邏輯副本。由於工藝的某些 部分,諸如文本區域,可以 進程之間共享,內核可以有時遞增的區域引用計數 代替區域複製到存儲器中的新的物理位置的,
  4. 它增加文件以及與 進程關聯的文件的模式表計數器。
  5. 它返回ID數孩子的父進程0價值的子進程


現在讓我們看看在你的代碼會發生什麼,當你調用 fork()

01: pid = fork(); 
02: if (pid == 0) { 
03:  for (i = 0; i < SIZE; i++) { 
04:   nums[i] *= -i; 
05:   printf("CHILD: %d ",nums[i]); /* LINE X */ 
06:  } 
07: } 
08: else if (pid > 0) { 
09:  wait(NULL); 
10:  for (i = 0; i < SIZE; i++) 
11:   printf("PARENT: %d ",nums[i]); /* LINE Y */ 
12: } 

01號線:fork()被調用時,將創建子進程fork()返回並且返回值存儲在pid中。
[注:由於在OP的代碼中沒有錯誤檢查,這將在後面討論]

02號線:pid值與值0檢查。請注意,此檢查在父進程和新創建的子進程完成。如上所述,pid的值將是0中的子進程child process ID中的父進程。因此,此條件檢查評估爲True中的子進程False中的父進程。因此,行03-07子進程中執行。

Line 03-07:這些線條非常簡單。 子過程num[]數組被更改(nums[i] *= -i;),並使用printf("CHILD: %d ",nums[i]);打印出來。

這裏要注意的一點是,正在打印的值是num[]陣列子進程的。父進程num[]數組到目前爲止與以前相同。

這裏有一個巧妙的把戲,叫做copy-on-write。雖然這個問題沒有提到,但它仍然是一個有趣的閱讀。

行08:此行現在在父進程中檢查。由於之前的if成功,因此不會檢查子進程。一個進程ID總是一個正數,所以當父進程得到新創建的子進程的進程ID時,它總是會通過測試else if (pid > 0),並進入該塊。

[注意:它不能是0,因爲0保留。閱讀here]

09行:此行使得父進程等到子進程已完成執行。這就是您將在父進程printf()之前看到所有printf()子進程的原因。

第10-12行:這也是一個相當不錯的for循環,它打印出num[]數組的值。請注意,父進程的值未改變。由於它以前由子進程更改,它擁有自己的數組num[]的副本。


fork()失敗。

有可能fork()呼叫可能會失敗。在這種情況下,返回值是-1。這應該也是爲了使程序正確處理。

pid = fork(); 
if (pid == -1) 
    perror("Fork failed"); 

從書The Design of the UNIX Operating System採取了一些內容。爲叉

11

最簡單的例子()

printf("I'm printed once!\n"); 
fork(); 
// Now there are two processes running one is parent and another child. 
// and each process will print out the next line. 
printf("You see this line twice!\n"); 

叉的返回值()。返回值-1 =失敗; 0 =在子進程中; positive =在父進程中(並且返回值是子進程ID)

pid_t id = fork(); 
if (id == -1) exit(1); // fork failed 
if (id > 0) 
{ 
// I'm the original parent and 
// I just created a child process with id 'id' 
// Use waitpid to wait for the child to finish 
} else { // returned zero 
// I must be the newly made child process 
} 

子進程與父進程有什麼不同?

  • 當子進程結束時,通過信號通知父進程,但反之亦然。
  • 孩子不會繼承未決信號或計時器警報。有關完整列表,請參見fork()
  • 此處的進程ID可以由getpid()返回。父進程ID可以由getppid()返回。

現在讓我們想像你的程序代碼

pid_t pid; 
pid = fork(); 

現在OS使地址空間的兩個相同副本,一個家長和其他的孩子。

enter image description here

父母和子女有過程開始其執行系統調用fork之後向右()。由於這兩個進程具有相同但分離的地址空間,因此在fork()調用之前初始化的這些變量在兩個地址空間中都具有相同的值。每個進程都有自己的地址空間,因此任何修改都將獨立於其他地址空間。如果父級更改其變量的值,則修改只會影響父級進程的地址空間中的變量。即使它們具有相同的變量名稱,由fork()sysem調用創建的其他地址空間也不會受到影響。

enter image description here

這裏父pid是非零,它調用函數ParentProcess()。在另一方面,孩子有一個零PID和調用子進程(),如下所示: enter image description here

在你的代碼的父進程調用wait()它會暫停在這一點上,直到孩子退出。所以孩子的產量首先出現。從子進程

what comes out at line X

CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16 

if (pid == 0) {      
    // The child runs this part because fork returns 0 to the child 
    for (i = 0; i < SIZE; i++) { 
     nums[i] *= -i; 
     printf("CHILD: %d ",nums[i]); /* LINE X */ 
    } 
} 

輸出,則孩子退出後,父母從wait()調用之後繼續和旁邊打印輸出。從父進程

else if (pid > 0) { 
     wait(NULL); 
     for (i = 0; i < SIZE; i++) 
      printf("PARENT: %d ",nums[i]); /* LINE Y */ 
    } 

OUTPUT:

what comes out at line Y

PARENT: 0 PARENT: 1 PARENT: 2 PARENT: 3 PARENT: 4 

最後輸出都通過子和父進程相結合,將在端子被示出如下:

​​

對於更多信息refer this link

+2

看起來你從http://www.csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html複製了一些圖片。請注意,這種複製需要歸因;我鼓勵你在http://stackoverflow.com/help/referencing上閱讀我們的抄襲政策。 – josliber 2015-12-23 23:59:01

+0

是的,我同意你的建議。我很快會更新答案的適當參考。感謝您的建議 – 2015-12-24 07:06:37

+0

這是否足夠,還是我必須改善答案?所有建議都歡迎:-) – 2015-12-24 11:24:45

1

在最簡單的情況下,fork()的行爲非常簡單 - 如果您在第一次遇到它時有點頭腦發熱。它要麼返回一次錯誤,要麼返回兩次,一次在原始(父)進程中,一次在原始進程(子進程)的全新幾乎完全相同的副本中。返回後,這兩個進程名義上是獨立的,儘管它們共享大量資源。

pid_t original = getpid(); 
pid_t pid = fork(); 
if (pid == -1) 
{ 
    /* Failed to fork - one return */ 
    …handle error situation… 
} 
else if (pid == 0) 
{ 
    /* Child process - distinct from original process */ 
    assert(original == getppid() || getppid() == 1); 
    assert(original != getpid()); 
    …be childish here… 
} 
else 
{ 
    /* Parent process - distinct from child process */ 
    assert(original != pid); 
    …be parental here… 
} 

子進程是父進程的副本。例如,它具有相同的一組打開的文件描述符;在父項中打開的每個文件描述符N都在子項中打開,並且它們共享相同的打開文件描述。這意味着如果其中一個進程更改了文件中的讀取或寫入位置,這也會影響其他進程。另一方面,如果其中一個進程關閉了一個文件,這對另一個進程中的文件沒有直接影響。這也意味着,如果在父進程中存在標準I/O包中緩衝的數據(例如,某些數據已從標準輸入文件描述符(STDIN_FILENO)讀入數據緩衝區stdin中,那麼該數據對於父和子都是可用的,並且兩者都可以讀取緩衝的數據而不會影響另一個,這也會看到相同的數據。另一方面,一旦讀取了緩衝數據,如果父節點讀取另一個緩衝區滿,它會移動父級和子級的當前文件位置,以便孩子不會再看到父級剛剛讀取的數據(但是如果孩子也讀取數據塊,則父級將不會看到該數據)。這可能會造成混淆,因此,確保在分叉之前沒有待處理的標準I/O通常是個好主意 - fflush(0) i這是做到這一點的一種方法。

在代碼片段中,assert(original == getppid() || getppid() == 1);允許孩子執行語句時,父進程可能已退出,在這種情況下,子進程已被系統進程繼承 - 通常具有PID 1(我知道沒有POSIX系統,其中孤兒由另一個PID繼承,但可能有一個)。

其他共享資源(例如內存映射文件或共享內存)在兩者中仍然可用。內存映射文件的後續行爲取決於用於創建映射的選項; MAP_PRIVATE表示這兩個進程具有獨立的數據副本,MAP_SHARED表示它們共享數據的同一副本,並且一個進程所做的更改將在另一個進程中可見。

但是,並不是每一個分支程序都像迄今所描述的故事那樣簡單。例如,父進程可能已經獲得了一些(建議)鎖;這些鎖不是由孩子繼承的。父母可能是多線程的;孩子有一個單一的執行線程 - 並且對孩子可以安全地做什麼有限制。

fork()的POSIX規範指定詳細的差異:

The fork() function shall create a new process. The new process (child process) shall be an exact copy of the calling process (parent process) except as detailed below:

  • The child process shall have a unique process ID.

  • The child process ID also shall not match any active process group ID.

  • The child process shall have a different parent process ID, which shall be the process ID of the calling process.

  • The child process shall have its own copy of the parent's file descriptors. Each of the child's file descriptors shall refer to the same open file description with the corresponding file descriptor of the parent.

  • The child process shall have its own copy of the parent's open directory streams. Each open directory stream in the child process may share directory stream positioning with the corresponding directory stream of the parent.

  • The child process shall have its own copy of the parent's message catalog descriptors.

  • The child process values of tms_utime , tms_stime , tms_cutime , and tms_cstime shall be set to 0.

  • The time left until an alarm clock signal shall be reset to zero, and the alarm, if any, shall be canceled; see alarm.

  • [XSI] ⌦ All semadj values shall be cleared. ⌫

  • File locks set by the parent process shall not be inherited by the child process.

  • The set of signals pending for the child process shall be initialized to the empty set.

  • [XSI] ⌦ Interval timers shall be reset in the child process. ⌫

  • Any semaphores that are open in the parent process shall also be open in the child process.

  • [ML] ⌦ The child process shall not inherit any address space memory locks established by the parent process via calls to mlockall() or mlock() . ⌫

  • Memory mappings created in the parent shall be retained in the child process. MAP_PRIVATE mappings inherited from the parent shall also be MAP_PRIVATE mappings in the child, and any modifications to the data in these mappings made by the parent prior to calling fork() shall be visible to the child. Any modifications to the data in MAP_PRIVATE mappings made by the parent after fork() returns shall be visible only to the parent. Modifications to the data in MAP_PRIVATE mappings made by the child shall be visible only to the child.

  • [PS] ⌦ For the SCHED_FIFO and SCHED_RR scheduling policies, the child process shall inherit the policy and priority settings of the parent process during a fork() function. For other scheduling policies, the policy and priority settings on fork() are implementation-defined. ⌫

  • Per-process timers created by the parent shall not be inherited by the child process.

  • [MSG] ⌦ The child process shall have its own copy of the message queue descriptors of the parent. Each of the message descriptors of the child shall refer to the same open message queue description as the corresponding message descriptor of the parent. ⌫

  • No asynchronous input or asynchronous output operations shall be inherited by the child process. Any use of asynchronous control blocks created by the parent produces undefined behavior.

  • A process shall be created with a single thread. If a multi-threaded process calls fork() , the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called. Fork handlers may be established by means of the pthread_atfork() function in order to maintain application invariants across fork() calls.

  • When the application calls fork() from a signal handler and any of the fork handlers registered by pthread_atfork() calls a function that is not async-signal-safe, the behavior is undefined.

  • [OB TRC TRI] ⌦ If the Trace option and the Trace Inherit option are both supported:

    If the calling process was being traced in a trace stream that had its inheritance policy set to POSIX_TRACE_INHERITED, the child process shall be traced into that trace stream, and the child process shall inherit the parent's mapping of trace event names to trace event type identifiers. If the trace stream in which the calling process was being traced had its inheritance policy set to POSIX_TRACE_CLOSE_FOR_CHILD, the child process shall not be traced into that trace stream. The inheritance policy is set by a call to the posix_trace_attr_setinherited() function. ⌫

  • [OB TRC] ⌦ If the Trace option is supported, but the Trace Inherit option is not supported:

    The child process shall not be traced into any of the trace streams of its parent process. ⌫

  • [OB TRC] ⌦ If the Trace option is supported, the child process of a trace controller process shall not control the trace streams controlled by its parent process. ⌫

  • [CPT] ⌦ The initial value of the CPU-time clock of the child process shall be set to zero. ⌫

  • [TCT] The initial value of the CPU-time clock of the single thread of the child process shall be set to zero.⌫

    All other process characteristics defined by POSIX.1-2008 shall be the same in the parent and child processes. The inheritance of process characteristics not defined by POSIX.1-2008 is unspecified by POSIX.1-2008.

    After fork() , both the parent and the child processes shall be capable of executing independently before either one terminates.

大多數的這些問題不影響大多數程序,但多線程程序,叉需要非常小心。值得閱讀POSIX定義fork()的理論部分。

在內核中,系統管理上面定義中突出顯示的所有問題。內存頁映射表必須被複制。內核通常會將(可寫入)內存頁面標記爲COW(在寫入時複製),以便在其中一個或另一個進程修改內存之前,它們可以訪問相同的內存。這最大限度地降低了複製流程的成本;內存頁面只有在修改時纔會變得截然不同。儘管如此,許多資源(如文件描述符)必須被複制,因此fork()是相當昂貴的操作(儘管不像exec*()函數那樣昂貴)。請注意,複製文件描述符會將兩個描述符引用相同的打開文件描述 - 請參閱open()dup2()系統調用,以討論文件描述符與打開文件描述之間的區別。