1. 程式人生 > >程序建立與fork()的恩怨情仇

程序建立與fork()的恩怨情仇

一、述說程序:

1、程序(process)是個什麼?

狹義定義:程序是正在執行的程式的例項(an instance of a computer program that is being executed),或者更加簡稱之為“執行中的程式”(但並非一個程式這麼簡單)。
廣義定義:程序是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是作業系統動態執行的基本單元(CPU排程單位)。

2、概念解析:

首先,程序是一個實體,任何一個程序都有自己獨立的記憶體空間:文字區、資料區、堆、棧等。其次,程序是一個“執行中的程式”。程式是一個沒有生命的實體,只有在它獲得一系列系統資源而被系統執行時,它才能成為一個活動的實體,我們稱其為程序。

我們可以將一個程序拆分成三部分:程式、資料、程序控制模組(Processing Control Block,PCB)。PCB是一種資料結構而且PCB是系統感知程序存在的唯一標誌(因為PCB儲存程序標識、現場資訊、排程資訊),常駐記憶體以提高速度。PCB的存在使得多道程式可以併發執行。

3、程序的幾種狀態:

這裡寫圖片描述

(1)、三種(或者五種)狀態:

新建:新建程序;
結束:程序結束;
就緒狀態:除了CPU之外的其他資源都已經分配;
執行狀態:已經獲取CPU正在執行;
等待狀態(阻塞):CPU以及主要資源都缺少,需要I/O。

(2)、狀態轉換的步驟:

①、新建程序,分配資源除CPU之外的主要資源;
②、分配CPU資源;
③、時間片到;
④、需要I/O;
⑤、獲取I/O結束;
⑥、程序執行結束,釋放回收資源。

注意:等待—>執行,就緒—>等待這兩種情況不可能出現。一旦進入等待就必須重新進入就緒狀態(等待狀態各種資源都缺少,而就緒狀態只差CPU,而要進入執行狀態只能是從已經擁有除了CPU之外的各種資源的就緒狀態開始)

二、fork()函式:

1、函式原型:

#include<unistd.h>
pid_t fork(void);//pid_t:(Process ID _ Type)int型別

(1)、函式作用:
一個現有的程序呼叫fork函式,建立一個新的程序。現有(原有)程序成為新建程序的父程序(父程序),新建程序成為原有程序的子程序(子程序)。父子程序共享正文段,但並不共享儲存空間,每個程序有自己獨立的資料空間、堆和棧的副本。
(2)、返回值(return value):
這個返回值有點奇怪:三種返回值。其實並不奇怪,三中返回值並非同時返回,也並不是在一個程序中返回。

先來看看老外咋解釋的(man fork中原話):

RETURN VALUE:
On success, the PID of the child process is return int the parent, and 0 is return int child; 
On failure -1 is return in the parent, No child process is created,and errno is set approprivately.

成功:在父程序中返回子程序的PID,在子程序中返回0(因為他自己沒兒子,所以是0);
失敗:在父程序中返回-1,子程序沒有被建立,並且設定合適的errno值。

2、舉例說明返回值:

這裡寫圖片描述

其中getpid()以及getppid()函式分別是:獲取當前程序pid(程序識別符號)和獲取當前程序父程序的pid。
我們看到父程序pid=3371,由於3371呼叫fork,所以它建立了一個3372的子程序(3372緊接著3371),父程序中接收到的返回值為子程序pid,子程序中接收到的返回值為0;

三、fork()的恩怨情仇例項分析:

1、fork()建立子程序以後,子程序與父程序誰先執行:

一般來說這是不確定的,取決於系統內和中所採用的排程演算法,根據上例,我們發現,在我所用測試環境下(Redhat 6.4,之後的情況都已該環境為例),是父程序先執行,子程序在父程序結束以後再執行。但是對於父程序休眠,而子程序不休眠的情況,我們再作以測試:

這裡寫圖片描述

測試中,我們發現先執行pid=3365的程序,再執行pid=3364的程序,由於父程序先於子程序建立,父程序pid自然大於子程序pid。所以在父程序休眠時,子程序搶佔到CPU資源優先執行了。

2、子程序與父程序真的不共享記憶體空間嗎?

我們根據下面這個例子來測試:

這裡寫圖片描述

子程序只能共享父程序在建立子程序之後的正文段(即呼叫fork()函式之後的正文段),所以子程序不會輸出“process is begin!”但是會輸出“process is over!”(這就像兒子在出生之後,並不會知道父親年輕時幹過什麼,但是他會有繼承自父親的一套DNA系統與血肉之軀)。由於父程序先睡眠1秒,所以子程序先執行,在執行過程中,由於子程序有父程序資料與記憶體空間的副本,所以子程序先輸出tag = 5,這是拷貝父程序的資料。而子程序修改tag = 10以後,在父程序甦醒之後再輸出tag,並不會輸出tag = 10,因為他們並不共享資料與記憶體空間,各自修改資料值自然不會影響對方的資料。

3、若是子程序結束以前父程序結束了,會有什麼問題產生?

我們將返回值測試那個例子稍作修改,如圖所示:
這裡寫圖片描述

我們將return 0;之前的sleep去掉,會發現有怪異的現象出現:首先為什麼在父程序與子程序執行過程中會出現shell的輸入提示?其次為什麼子程序輸出完畢之後“停”在那兒不動了?它是死了嗎?也就是說為什麼本應在子程序輸出之後才出現的shell命令輸入提示出現在了子程序與父程序之間?
其實我這樣一問,問題也就解決了:首先明確shell也是一個程序。它(shell)為什麼該出現時不出現,不該出現時出現了?說明它把CPU資源搶去了,所以shell會先執行。
過程是這樣的:父程序申請了CPU資源,本應在父程序執行完之後CPU的許可權就不屬於他了,而shell就搶佔CPU資源輸出“[[email protected] 桌面]”這句話等待shell輸入。但是父程序“不老實”,在執行過程中他生了個兒子,他想把CPU資源交給他兒子使用,但是他命不好,死得早,還沒等到他兒子長大,他就已經作古了,他兒子手無縛雞之力搶不過shell(shell優先順序高),所以shell就先輸出,之後CPU空閒了,就讓這個沒有爸比的可憐孩子(孤兒程序)佔用一下吧。死了爸爸的子程序才能在“[[email protected] 桌面]”之後執行並輸出。但是shell依舊在等待輸入,所以會顯示第二個異常(這本就不是異常,在輸出螢幕上只能這樣表示而已),我們依舊可以在命令列下正常輸入命令與執行。
但是一個新的問題產生了,CPU並不認識這個孤兒程序,並不會給他分配資源,他為什麼這麼可愛,能得到CPU資源並執行呢?那就是下一個問題。

4、init“孤兒所”:

首先,我們用程式碼測試一下:

這裡寫圖片描述

我們修改程式碼之後,在原來基礎上編譯,可以執行,這就印證了上面的說法(shell依舊在等待狀態)。再者我們看這個孤兒程序他父親不是死了麼?“pid = 1”的程序是誰呢?是人販子嗎?當然不是了,這個pid等於1的程序是個好叔叔,還是一個有錢有權的好叔叔。他就是init程序,想必大家在有桌面的系統下經常用“init 3”和“init 5”。
那麼init是幹啥的?他為什麼成了這個孤兒程序的父程序?pid=1就是init,他就是爸爸,所有程序的爸爸(作業系統啟動時建立的第一個程序,其他程序都是由他建立或者由它的子孫建立),在init看見這個沒有父親的孤兒時,他就把孤兒收養了(畢竟是自己子子孫孫),並給這個孤兒CPU資源讓其執行完畢後替其“收屍”,所以說init程序是所有孤兒程序的父程序,他家也是個“孤兒所”了。

其實程序排程是個複雜的問題,筆者當前只能理解到這一步,記錄下痛苦的愉快的(痛並快樂著)的學習過程,希望能對這塊生疏的朋友有所幫助。