1. 前言
之前的小節中介紹了操作系統的進程,操作系統中有個創建進程的重要方法就是 fork 函數,當需要執行和本進程相關的獨立任務時,一般需要創建一個有血緣關系的子進程。
2. fork 函數
面試官提問: Linux 系統中的 fork 函數是什么,有什么用途?
題目解析:
首先從定義上看,fork 函數的作用是在一個進程的基礎上創建新的進程,原有的進程被定義為父進程,新的進程被定義為子進程。
在 C 語言中調用 fork()
函數即實現 fork 功能,示例:
#include<unistd.h> //包含fork函數的頭文件
pid_t fork(void); //fork的返回類型為pid_t,我們可以看成int類型
認識一個函數,需要從函數的定義入手,了解函數做了什么事情,入參是什么,出參是什么。我們以 C 語言實現的一個典型的 fork 的程序入手,示例:
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid;
int count = 0;
fpid = fork();
if (fpid == 0) { //返回值是0,說明是子進程
printf("i am the child process, process id is %d\n", getpid());
count++;
} else if(fpid > 0) { //返回值>0,說明是父進程
printf("i am the parent process, process id is %d\n", getpid());
count++;
} else { //返回值<0,說明fork發生異常
printf("fork encounter exception, process id is %d\n", getpid());
}
//打印計數器
printf("after fork, counting result : %d\n", count);
return 0;
}
在 MacOS 系統上編譯運行案例示例代碼,運行結果如下圖。
如果是不了解函數原理的前提下,僅僅從代碼層面分析,在調用 fork()
函數之后,代碼會進入 if-else
判斷邏輯,在控制臺輸出一條語句,然后在控制臺打印計數器的數值。但是從真正執行的結果來看,這兩個打印動作都分別執行了兩次,并不符合我們的預期。fork 之后的代碼邏輯被執行了兩次,而且兩次進入的不同的分支,所以重點在于 fork 函數到底有啥作用。
按照定義、入參和出參三步走的框架,首先是分析函數的定義,調用 fork 函數之后發生了什么事情:
(1)分配內存:分配新的內存空間給子進程;
(2)拷貝數據:拷貝父進程的數據結構給子進程;
(3)加入列表:將新生成的子進程添加到操作系統的進程列表;
(4)返回結果:fork 函數調度并且返回。
然后是分析 fork 函數的入參,fork 函數入參是 void,也就是自動同步進程的上下文,不需要手動聲明。
最后是分析 fork 函數的出參,fork 函數和程序員日常接觸的函數不同,我們在 C 或者 Java 中定義的函數只會有一個返回值,fork 函數則是調用一次,返回二次。調用方(例如上述案例的 main 函數)根據返回值的不同判斷處于父進程還是子進程。
(1)返回值 < 0:調用失敗,一般是因為操作系統中的進程個數達到上限或者內存不足以分配給新的進程;
(2)返回值 = 0:調用成功,并且處于子進程;
(3)返回值 > 0:調用成功,并且處于父進程。
現在就不難理解,從調用 fork()
函數,代碼實際上是被父子進程分別執行了一次,父進程的進程 id 是 52331,子進程的進程 id 是 52332。
在掌握原理之后,我們繼續探究 fork 函數的應用場景。fork 函數的本質在原有的進程基礎上創建一個新的進程,所以在網絡通信中使用較多,例如在客戶端發送一個 HTTP 請求打到服務器時,服務器進程 fork 出一個子進程用于處理單個請求,父進程則繼續等待其他的請求。
3. 小結
本章節介紹了 Linux 的 fork 函數,fork 如同其英文名,就是進程的分叉。fork 函數簡化了操作系統的進程管理,又提供了一個簡單的多進程生成方案,在操作系統中的地位非常核心,候選人需要注意 fork 函數調用 1 次,返回 2 次的核心特性以及返回值。