linux fork小解

來源:互聯網
上載者:User

http://www.boluor.com/summary-of-fork-in-linux.html

fork函數在linux中非常重要,因為進程大多是通過它來建立的,比如linux系統在啟動時首先建立了進程0,之後的很多進程藉助do_fork得到建立.這兩天在看匿名管道時瞭解了下fork,其應用畢竟廣,這裡只說些我才學到的吧.
首先來看例1.

#include "stdio.h" 
#include "unistd.h" 
#include "stdlib.h" 
 
int main(){ 
    int i; 
    printf("hello world %d\n",getpid()); 
    i=3; 
    fork(); 
    printf("var %d in  %d\n",i,getpid()); 
    return 0; 
}

輸出是什麼呢?
這是在我的機器上一次執行的結果:

hello world 8168
var 3 in 8169
var 3 in 8168

為什麼會有兩次輸出var 3 一行呢?看似不可思議吧…要解釋原因,就牽涉到了我們要討論的fork,它到底做了什麼?

fork英文是叉的意思.在這裡的意思是進程從這裡開始分叉,分成了兩個進程,一個是父進程,一個子進程.子進程拷貝了父進程的絕大部分.棧阿,緩衝區阿等等.系統為子進程建立一個新的進程表項,其中進程id與父進程是不相同的,這也就是說父子進程是兩個獨立的進程,雖然父子進程共用代碼空間.但是在牽涉到寫資料時子進程有自己的資料空間,這是因為copy on write機制,在有資料修改時,系統會為子進程申請新的頁面.

再來複習下進程的有關知識.系統通過進程式控制制塊PCB來管理進程.進程的執行,可以看作是在它的上下文中執行.一個進程的上下文(context)由三部分組成:使用者級上下文,寄存器上下文和系統級上下文.使用者級上下文中有本文,資料,使用者棧和共用儲存區;寄存器上下文中有個非常重要的程式計數器(傳說中的)PC,還有棧指標和通用寄存器等;系統級上下文分靜態和動態,PCB中進程表項,U區,還有本進程的表項,頁表,系統區表項等都屬於靜態部分,而核心棧等則屬於動態部分.

回到fork上來.fork在核心中對應的是do_fork函數,本來想自己寫下函數說明的,發現已經有了.詳見:核心 do_fork 函數原始碼淺析 . 上面已經提到,fork後,子進程拷貝了父進程的進程表項,還有棧阿,緩衝區,U區等等.當然在這之前會去檢查系統有沒有可用的資源,取一個閒置進程表項和唯一的PID號等工作.(後面的例子會體現子進程到底拷貝了父進程的哪些東西.)需要指出的是,這裡所說的拷貝,並不是說子進程再申請頁面,將父進程中的全部拷貝過來.而是,他們共用一個空間,子進程只是作一層映射而已,這個時候進程頁面標記為唯讀.在有資料修改時,才會申請新的頁面,拷貝過來,並標記為可寫.

fork執行後,對父進程和子進程不同的地方還有,對父進程返回子進程的pid號,對子進程返回的是0.大致的演算法描述為:

if (當前正在執行的是父進程){
將子進程的狀態設定為”就緒狀態”;
return (子進程的pid號);
}else{ /*正在執行的是子進程*/
初始化U區等工作;
return 0;
}

現在來看例1,是不是已經清晰了很多? 在執行了fork之後,父子進程分別都執行了下一步printf語句.由於fork拷貝走了pc,所以在子進程中不會再從main入口重新執行,而是執行fork後的下一條指令.而i是儲存在進程棧空間中的,所以子進程中也存在.
有了前面的基礎,再看下面一個例2:

#include <stdio.h>
#include <unistd.h>
int main()
{
    int i=0;
    pid_t fork_result;
    printf("pid : %d --> main begin()\n",getpid());

    fork_result = fork();

    if(fork_result < 0) {
            printf("Fork Failure\n");
            return 0;
    }
    for(i=0;i<3;i++){
        if(fork_result == 0){   //在子進程中.
            printf("child process : %d\n",i);
        }else{
            printf("Father process : %d\n",i);
        }
    }
    return 0;
}

這次輸出可以更明確的顯示出子進程到底拷貝了些什麼.我機器上的兩次執行結果:

boluor@boluor-laptop:~/programs/pipe/fork$./a.out
pid : 16567 –> mainbegin()
child process : 0
child process : 1
child process : 2
Father process : 0
Father process : 1
Father process : 2
boluor@boluor-laptop:~/programs/pipe/fork$ ./a.out
pid : 16569 –> main begin()
Father process : 0
Father process : 1
Father process : 2
child process : 0
child process : 1
child process : 2

同時也可以說明,父子進程到底哪個先執行,是跟cpu調度有關係的.如果想固定順序,那麼就要用wait或vfork函數.
繼續看例3:

#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"

int main()
{
    printf("hello world %d",getpid());
    //fflush(0);
    fork();
    return 0;
}

執行上面的程式,可以發現輸出了兩遍hello world.而且兩次的pid號都是一樣的.這是為什麼呢? 這其實是因為printf的行緩衝的問題,printf語句執行後,系統將字串放在了緩衝區內,並沒有輸出到stdout.不明白的話看下面的例子:

#include "stdio.h"
int main(){
        printf("hello world");
        while(1);
        return 0;
}

執行上面的程式你會發現,程式陷入死迴圈,並沒有輸出”hello world”.這就是因為把”hello world”放入了緩衝區.我們平常加’\n’的話,就會重新整理緩衝區,那樣就會直接輸出到stdout了.

因為子進程將這些緩衝也拷貝走了,所以子進程也列印了一遍.父進程直到最後才輸出.他們的輸出是一樣的,輸出的pid是一致的,因為子進程拷貝走的是printf語句執行後的結果.如果利用setbuf設定下,或者在printf語句後調用fflush(0);強制重新整理緩衝區,就不會有這個問題了.這個例子從側面顯示出子進程也拷貝了父進程的緩衝區.

關於fork的應用還很多很多,在實際項目中需要了再去深入研究.關於fork和exec的區別,exec是將本進程的映像給替換掉了,跟fork差別還是很大的,其實fork建立子進程後,大部分情況下,子進程會調用exec去執行不同的程式的.

先說到這裡了.如果需要更多fork的知識就google一下^.^.

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.