對於fork()用法的初步探討

前言

進程和線程是兩種CPU資源以及調度的描述。
這句話可能有點拗口,因此簡單展開說明一下。數據結構

現代CPU太快了,全部的資源(RAM, device, bus..)都沒法填滿它。因此有多個任務時,CPU就輪流着來處理。獲得CPU資源時,其餘總線,顯卡,RAM等等都要準備好,這樣就構成了咱們程序執行的上下文。等執行完或CPU分配給它的時間用完了,它就要被切換出去,等待CPU的下一次臨幸。固然切換出去以前會保存上下文。
因此從CPU的角度看它成天就在: 加載A的上下文,執行A,保存A的上下文,調入B的上下文,執行B,保存B的上下文。。。函數

進程就是包括上下文切換在內的程序執行時間總和 = cpu加載上下文+CPU執行+CPU保存上下文學習

進程顆粒度太大,每次都有上下文的調入調出開銷。而一段程序必定有多個塊,能夠把它分紅A, B, C多個塊,而後這樣執行:
CPU加載程序上下文,執行A, 執行B,執行C, 執行A, 執行B,執行C。。。, CPU保存程序上下文。
也就是線程共享了進程的上下文。優化

Fork基礎

一個進程包括代碼,數據和分配給進程的資源。fork函數調用會使內核建立一個與原來進程幾乎徹底相同的進程。內核會給新進程分配新的資源,而後把原來進程的值拷貝到新進程中,只有少數如fork()返回值不一樣。
通常咱們稱原進程爲父進程,fork()出來的新進程爲子進程。
注意: 父進程和子進程的執行順序是不定的
因此運行下面的例子,可能輸出結果會有差別,關鍵是理解fork()的機制原理。this

在fork.c裏主要的函數包括
圖片描述spa

  • verify_area.net

void verify_area(void * addr,int size) // addr 是虛擬地址 ,size是須要寫入的字節大小
  • copy_mem線程

int copy_mem(int nr,struct task_struct * p) //拷貝內存頁表,把進程p的數據段copy到nr*TASK的線性地址處
  • copy_process3d

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
        long ebx,long ecx,long edx, long orig_eax,
        long fs,long es,long ds,
        long eip,long cs,long eflags,long esp,long ss)
        //拷貝系統進程信息,數據段,並設置必要的registers,
  • find_empty_processcode

int find_empty_process(void) //爲新進程獲取不重複的pid

在Linux中,對fork進行了優化,調用時採用寫時複製 (COW,copy on write)的方式,在系統調用fork生成子進程的時候,不立刻爲子進程複製父進程的資源,而是在遇到「寫入」(對資源進行修改)操做時才複製資源。

語法

#include <sys/types.h>  //define pid_t
#include <unistd.h>
pid_t fork(void)

返回值

0: 返回給子進程,子進程的ID返回給父進程
-1: 出錯, 返回給父進程,錯誤緣由返回到errno
>0: 子進程的ID返回給父進程

EAGAIN: 進程ID號達到最大可以使用值
ENOMEM: 內存不足,沒法配置核心所需的數據結構

例子

初級示例

//
// Created by         : Harris Zhu
// Filename           : test.c
// Author             : Harris Zhu
// Created On         : 2017-08-19 17:36
// Last Modified      :
// Update Count       : 2017-08-19 17:36
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

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

int main (int argc, char**argv)
{
    pid_t pid;
    int count=0;
    pid=fork();
    if (pid < 0)
        perror("fork error!");
    else if (pid == 0) {
        printf("the pid of son is %d\n",getpid());
        count++;
    }
    else {
        printf("the pid of parent is %d\n",getpid());
        count++;
    }
    printf("count = %d\n",count);
    return 0;
}

輸出:

the pid of parent is 20081
count = 1
the pid of son is 20082
count = 1

fork函數執行結束後,若是創新成功,則出現兩個進程,父進程(原來進程),子進程。
在子進程中,fork()返回0, 在父進程中返回子進程的ID。
fork後的兩個進程沒有固定的前後順序。
能夠經過getpid()函數來得到本身的進程ID,能夠經過getppid()函數來得到本身父進程ID

中級示例

代碼

//
// Created by         : Harris Zhu
// Filename           : test.c
// Author             : Harris Zhu
// Created On         : 2017-08-19 17:55
// Last Modified      :
// Update Count       : 2017-08-19 17:55
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

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

int main(int argc, char** argv)
{
   int i=0;
   printf("i\tson/father\tppid\tpid\trpid\n");
   for(i=0;i<2;i++){
       pid_t rpid=fork();
       if(rpid==0)
           printf("%d\tson\t\t%4d\t%4d\t%4d\n",i,getppid(),getpid(),rpid);
       else
           printf("%d\tfather\t\t%4d\t%4d\t%4d\n",i,getppid(),getpid(),rpid);
   }
   sleep(1);
   return 0;
}

注意 程序中我sleep 1s是爲了防止子進程還未結束,父進程先結束,這時ppid會顯示1

輸出

爲了閱讀方便,我這裏用表格

i son/father ppid pid rpid
0 father 20425 20426 20427
0 son 20426 20427 0
1 father 20425 20426 20428
1 father 20426 20427 20429
1 son 20426 20428 0
1 son 20427 20429 0

關係圖

圖片描述

規律總結

for循環次數爲N,
執行print次數: $2*(1+2+4+...2^{N-1})$
生成的新進程爲: $(1+2+4+...+2^{N-1})$

統計進程

能夠用printf("%d\n", getpid())printf("+\n")來判斷

中高級示例

代碼

//
// Created by         : Harris Zhu
// Filename           : test.c
// Author             : Harris Zhu
// Created On         : 2017-08-19 21:30
// Last Modified      :
// Update Count       : 2017-08-19 21:30
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

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

int main(int argc, char ** argv)
{
    pid_t pid;
    static int a=0;
    printf("a=%d current pid=%d\n\n", a, getpid());
    pid = fork();
    if(pid <0)
        perror("fork error!");
    printf("a=%d pid=%d, ppid=%d\n", a, pid, getppid());
    sleep(0.1);

    a++;

    pid = fork();
    if(pid <0)
        perror("fork error!");
    printf("a=%d pid=%d, ppid=%d\n", a, pid, getppid());
    sleep(0.1);

    a++;

    pid = fork();
    if(pid <0)
        perror("fork error!");
    printf("a=%d pid=%d, ppid=%d\n", a, pid, getppid());
    sleep(0.1);


    printf("getpid = %d getppid= %d \n\n", getpid(), getppid());

    sleep(1);

    return 0;
}

輸出

a=0 current pid=24369

a=0 pid=24370, ppid=24368
a=0 pid=0, ppid=24369
a=1 pid=24371, ppid=24368
a=1 pid=24372, ppid=24369
a=2 pid=24373, ppid=24368
getpid = 24369 getppid= 24368

a=2 pid=0, ppid=24369
getpid = 24373 getppid= 24369

a=2 pid=24374, ppid=24369
getpid = 24370 getppid= 24369

a=1 pid=0, ppid=24369
a=2 pid=0, ppid=24370
getpid = 24374 getppid= 24370

a=2 pid=24375, ppid=24369
a=1 pid=0, ppid=24370
getpid = 24371 getppid= 24369

a=2 pid=0, ppid=24371
getpid = 24375 getppid= 24371

a=2 pid=24376, ppid=24370
getpid = 24372 getppid= 24370

a=2 pid=0, ppid=24372
getpid = 24376 getppid= 24372

分析圖

對照輸出,能夠畫出下面的繼承關係圖,以及執行順序
圖片描述
我不展開講解這個運行結果如何來的,做爲學習,若是能本身畫個框圖搞清楚執行的順序和邏輯,那麼就算是學懂fork()了

高級示例

代碼

這是網上很是著名的一個示例。理解這個例子感受就像在解初中的數學道,特別有趣。

//
// Created by         : Harris Zhu
// Filename           : test.c
// Author             : Harris Zhu
// Created On         : 2017-08-19 20:08
// Last Modified      :
// Update Count       : 2017-08-19 20:08
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

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

int main(int argc, char** argv)
{
    fork();
    fork() && fork() || fork();
    fork();
    printf("+\n");
    sleep(1);  //if no this lien, it prints out 19 +
    return 0
}

分析

第一個和最後一個是確定執行的
下面分析一下中間3個fork()的執行
圖片描述
A&&B: 若是A爲0, 則忽略B的執行
A||B: 若是A爲1, 則忽略B的執行
上圖中一個5個進程, 總共2*5*2=20個進程
完整分支圖以下,空間緣由fork #0未展開,它和fork #1是對稱的
圖片描述

後序

內核對於進程的建立,調度和清理等細節就不展開,有興趣的能夠自行搜索。若是我的有興趣討論,能夠發信給我郵箱
本文主要參考這裏

相關文章
相關標籤/搜索