偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

fork() 背后的秘密:一次系統(tǒng)調(diào)用如何"變出"兩個進程?

系統(tǒng)
你有沒有想過,當你在Linux系統(tǒng)中運行一個程序時,計算機內(nèi)部到底發(fā)生了什么?今天我們來聊聊一個看似簡單卻非常神奇的函數(shù)——fork()。

大家好,我是小康。

你有沒有想過,當你在Linux系統(tǒng)中運行一個程序時,計算機內(nèi)部到底發(fā)生了什么?今天我們來聊聊一個看似簡單卻非常神奇的函數(shù)——fork()。

說它神奇,是因為它能做到一件讓人匪夷所思的事情:一個進程調(diào)用它,卻能"變出"兩個進程!

一、先來個小實驗,震撼一下

不多說,我們先來看個簡單的例子:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("調(diào)用fork()之前,我只有一個進程\n");
    
    int pid = fork();
    
    printf("調(diào)用fork()之后,我變成了兩個進程!我的返回值是:%d\n", pid);
    
    return 0;
}

運行這段代碼,你會看到這樣的輸出:

調(diào)用fork()之前,我只有一個進程
調(diào)用fork()之后,我變成了兩個進程!我的返回值是:4814
調(diào)用fork()之后,我變成了兩個進程!我的返回值是:0

看到了嗎?第一行只打印了一次,但第二行竟然打印了兩次!而且返回值還不一樣!

這就是fork()的魔法所在。

二、fork()到底做了什么?

簡單來說,fork()就像是給進程照了個鏡子。調(diào)用fork()的那一刻,系統(tǒng)會創(chuàng)建一個和當前進程幾乎完全一樣的副本。

想象一下,你正在看一本書的第50頁,突然有個魔法師對你施了分身術(shù)?,F(xiàn)在有兩個你,都在看同一本書的第50頁,都有相同的記憶,連手里拿著的筆的顏色都一樣。

這就是fork()做的事情。

三、父進程和子進程:一母同胞的兄弟

fork()創(chuàng)建出來的兩個進程,我們稱為父進程和子進程。

  • 父進程:就是那個調(diào)用fork()的原始進程
  • 子進程:就是被創(chuàng)建出來的新進程

但是,系統(tǒng)怎么讓這兩個長得一模一樣的進程知道自己是誰呢?答案就在fork()的返回值上:

  • 在父進程中,fork()返回子進程的PID(進程ID)
  • 在子進程中,fork()返回0
  • 如果創(chuàng)建失敗,返回-1

這就解釋了為什么前面的例子中,同樣的printf語句會打印出不同的返回值。

四、深入內(nèi)核:fork()背后的完整流程

現(xiàn)在我們來揭開fork()的神秘面紗,看看Linux內(nèi)核到底是怎么實現(xiàn)這個"魔法"的。

第一步:系統(tǒng)調(diào)用入口

當你在用戶空間調(diào)用fork()時,實際上觸發(fā)了一個系統(tǒng)調(diào)用。在x86_64架構(gòu)下,這個調(diào)用會通過中斷門進入內(nèi)核空間。

用戶空間: fork() 
    ↓
系統(tǒng)調(diào)用: sys_fork()
    ↓
內(nèi)核空間: do_fork()

第二步:準備創(chuàng)建新進程

內(nèi)核首先會做一些準備工作。我們來看看簡化版的內(nèi)核邏輯:

// 簡化版的內(nèi)核邏輯
long do_fork(unsigned long clone_flags, ...) {
    struct task_struct *p;// 新進程的"身份證"
    
    // 1. 分配新的進程描述符
    p = copy_process(clone_flags, ...);
    
    // 2. 分配新的PID
    pid = get_pid(p);
    
    // 3. 喚醒新進程
    wake_up_new_task(p);
    
    return pid;  // 返回給父進程
}

這個過程分為幾個關(guān)鍵步驟:

(1) 創(chuàng)建進程描述符(task_struct)

這個task_struct就是我們常說的PCB(進程控制塊),它就像是新進程的"身份證檔案"。里面記錄了進程的所有重要信息:

  • 進程狀態(tài)(運行、睡眠、停止等)
  • 內(nèi)存布局信息
  • 打開的文件列表
  • 信號處理方式
  • 調(diào)度信息

想象一下,這就像給新生兒辦戶口本,得把所有信息都登記清楚。

(2) 分配獨一無二的PID

每個進程都需要一個身份證號,這個號碼在整個系統(tǒng)中必須是唯一的。內(nèi)核維護著一個PID分配器,確保不會重復(fù)。

(3) 準備調(diào)度

新進程創(chuàng)建好了,但還在"睡覺"。wake_up_new_task()就是叫醒它,告訴調(diào)度器:"嘿,這里有個新進程可以運行了!"

第三步:復(fù)制進程的"基因"

這是最關(guān)鍵的一步,也就是前面代碼中的copy_process()函數(shù)要做的事情:

// copy_process()的核心工作
struct task_struct *copy_process(...) {
    //分配新的task_struct結(jié)構(gòu)體(進程控制塊)
    p = dup_task_struct(current);  // current是當前進程(父進程)
    
    // 1. 復(fù)制內(nèi)存空間
    copy_mm(clone_flags, p);
    
    // 2. 復(fù)制文件描述符
    copy_files(clone_flags, p);
    
    // 3. 復(fù)制信號處理
    copy_sighand(clone_flags, p);
    
    return p;  // 返回新進程
}

讓我們看看每一步都做了什么:

(1) 內(nèi)存空間的復(fù)制(copy_mm)

這里有個很巧妙的設(shè)計叫做寫時復(fù)制(Copy-on-Write,COW)。

想象一下,如果真的把父進程的所有內(nèi)存都復(fù)制一遍,那得多浪費?。?/p>

所以Linux采用了一個聰明的策略:

  • 剛開始,父子進程共享同樣的內(nèi)存頁面
  • 只有當其中一個進程要修改內(nèi)存時,才真正復(fù)制那個頁面
  • 這樣既節(jié)省了內(nèi)存,又提高了效率
父進程內(nèi)存: [Page1] [Page2] [Page3]
           ↓ fork()后共享
子進程內(nèi)存: [Page1] [Page2] [Page3]  (實際指向同一物理內(nèi)存)

當子進程要修改Page2時:
父進程內(nèi)存: [Page1] [Page2原] [Page3]
子進程內(nèi)存: [Page1] [Page2新] [Page3]  (Page2被真正復(fù)制了)

(2) 文件描述符的繼承(copy_files)

所有打開的文件、網(wǎng)絡(luò)連接等,子進程都會繼承父進程的。

(3) 信號處理方式的復(fù)制(copy_sighand)

父進程怎么處理各種信號(比如Ctrl+C),子進程也會照樣處理。

第四步:設(shè)置進程關(guān)系

內(nèi)核會建立父子進程之間的關(guān)系:

  • 子進程的父進程ID(PPID)指向父進程
  • 父進程的子進程列表中添加新的子進程

第五步:調(diào)度新進程

一切準備就緒后,新的子進程就可以被CPU調(diào)度執(zhí)行了。

五、來看一個例子

讓我們用一個更實際的例子來理解這個過程:

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

int main() {
    int count = 0;
    
    printf("準備創(chuàng)建子進程...\n");
    
    int pid = fork();
    
    if (pid == 0) {
        // 子進程的代碼
        printf("我是子進程,我的PID是:%d,我的父進程PID是:%d\n", 
               getpid(), getppid());
        for (int i = 0; i < 3; i++) {
            printf("子進程正在工作:%d\n", ++count);
            sleep(1);
        }
        printf("子進程工作完成!\n");
    } elseif (pid > 0) {
        // 父進程的代碼
        printf("我是父進程,我的PID是:%d,我創(chuàng)建了子進程:%d\n", 
               getpid(), pid);
        for (int i = 0; i < 3; i++) {
            printf("父進程正在工作:%d\n", ++count);
            sleep(1);
        }
        printf("父進程等待子進程結(jié)束...\n");
        wait(NULL);  // 等待子進程結(jié)束
        printf("父進程工作完成!\n");
    } else {
        // fork失敗
        printf("創(chuàng)建子進程失敗!\n");
        return1;
    }
    
    return0;
}

運行這個程序,你會看到父子進程并行執(zhí)行,各自維護著自己的count變量。

六、fork()的經(jīng)典應(yīng)用場景

1. Shell命令執(zhí)行

當你在終端輸入一個命令時,Shell就是用fork()+exec()來執(zhí)行的:

// 簡化的Shell實現(xiàn)
int pid = fork();
if (pid == 0) {
    // 子進程執(zhí)行命令
    exec("/bin/ls", "ls", "-l", NULL);
} else {
    // 父進程等待命令完成
    wait(NULL);
}

2. 服務(wù)器處理并發(fā)請求

// 簡化的服務(wù)器模型
while (1) {
    int client = accept(server_socket, ...);
    
    int pid = fork();
    if (pid == 0) {
        // 子進程處理客戶端請求
        handle_client(client);
        exit(0);
    } else {
        // 父進程繼續(xù)監(jiān)聽新連接
        close(client);
    }
}

七、性能優(yōu)化:vfork()和clone()

Linux還提供了其他一些進程創(chuàng)建的方式:

  • vfork():專門為fork()+exec()場景優(yōu)化的版本,不復(fù)制內(nèi)存空間,但有一些限制。
  • clone():更底層的接口,可以精確控制哪些資源需要共享,哪些需要復(fù)制。實際上,fork()就是對clone()的封裝。

八、小心!fork()的陷阱

1. fork炸彈

永遠不要這樣寫代碼:

// 危險!不要運行!
while(1) {
    fork();
}

這會無限制地創(chuàng)建進程,直到系統(tǒng)崩潰。

2. 僵尸進程

如果父進程不回收子進程,子進程就會變成僵尸進程:

int pid = fork();
if (pid == 0) {
    printf("子進程結(jié)束\n");
    exit(0);
} else {
    // 如果父進程不調(diào)用wait(),子進程就會變成僵尸
    sleep(100);  // 父進程干別的去了,忘記收尸了
}

九、總結(jié)

fork()看似簡單,背后卻包含了操作系統(tǒng)設(shè)計的諸多精妙之處:

  • 寫時復(fù)制機制讓內(nèi)存使用更高效
  • 進程樹結(jié)構(gòu)讓系統(tǒng)管理更清晰
  • 資源繼承讓進程間通信更簡單

理解了fork(),你就理解了Unix/Linux系統(tǒng)進程管理的核心思想。下次當你看到程序啟動時,不妨想想這背后的原理。

每一個運行中的進程,都是從某個父進程fork出來的。追根溯源,所有的進程都可以追溯到系統(tǒng)啟動時的第一個進程——init進程(PID為1)。

這就是Linux進程的家族譜系,而fork()就是這個家族繁衍生息的秘密武器!

責(zé)任編輯:趙寧寧 來源: 跟著小康學(xué)編程
相關(guān)推薦

2021-11-01 17:29:02

Windows系統(tǒng)Fork

2021-09-15 08:30:28

命令Linux代碼

2024-03-11 08:35:25

Python工程幻燈片

2020-04-15 13:55:28

Kubernetes容器

2025-06-04 08:20:30

2019-09-03 18:16:44

Android 10Google長甜品

2013-08-08 09:50:30

2022-12-29 08:00:00

Transforme架構(gòu)深度學(xué)習(xí)

2011-06-28 10:41:50

DBA

2021-12-13 10:53:49

GoogleChrome遮擋

2010-04-06 18:04:09

Oracle數(shù)據(jù)庫

2009-12-03 11:10:32

SMONARCHOracle

2009-12-04 10:20:53

2020-10-18 12:53:29

黑科技網(wǎng)站軟件

2023-06-07 07:31:04

PC端app脫殼技巧

2012-05-21 21:53:05

2010-11-25 09:54:14

云計算MapReduce

2010-11-25 10:05:51

云計算GFS

2017-09-18 08:52:34

2010-05-24 18:22:56

SNMP協(xié)議
點贊
收藏

51CTO技術(shù)棧公眾號