文章目錄
-
- 1.什麼是程序
- 2.父子程序
- 3.fork()建立程序
- 4.vfork()建立并執行程序
1.什麼是程序
程序是執行中的程式。當一個程式被加載到記憶體之後就變為了程序。 程序=程式+執行
2.父子程序
- 建立程序稱為父程序,而新的程序稱為子程序
- 當程序建立新程序時,可有兩種執行可能: 父程序與子程序并發執行。 父程序等待,直到某個或全部子程序執行完。
- 新程序的位址空間也有兩種可能: 子程序是父程序的複制品(它具有與父程序同樣的程式和資料)。子程序加載另一個新程式。
3.fork()建立程序
pid_t fork(void);
函數的傳回值:
<0 出錯
=0 子程序
>0 父程序(其值為子程序的程序号)
- 先傳回誰是不确定的,父子程序的排程的順序是由排程器決定的,與程序的建立順序無關。
- fork函數所建立的子程序是父程序的完整副本,複制了父程序的資源,包括記憶體的task_struct内容。
- 子程序擁有自己的虛拟位址空間,父子程序資料獨有,(寫時複制)代碼共享。
- fork函數的傳回值起到分流的作用,可以用fork的傳回值判斷哪個是父程序或子程序。
下面的兩張圖就表示父程序和相對應的子程序的記憶體映射:
程序的資料區也就是未初始化或者初始化為0的資料(.bss)、已初始化且不為0的資料(.data);程序的棧區也就是程序的使用者棧和堆;程序程式代碼就是程序的程式檔案(.text);除此之外還有程序的系統堆棧區
代碼測試:用fork()建立子程序,在子程序中修改變量的值,然後在父子程序中分别列印輸出
//fork
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int count = 1;
pid_t pid ;
pid = fork();
if(pid < 0)
{
perror("fork error:");
return -1;
}
else if(pid == 0)
{
count++;
printf("this is child, pid=%d,count=%d(%p)\n",getpid(),count,&count);
}
else
{
printf("this is father, pid=%d,count=%d(%p)\n",getpid(),count,&count);
}
return 0;
}
測試結果:
代碼分析:
- 父子程序pid是不一樣的;count的值也不一樣,但是它們的位址卻相同。
- count值相等,因為父子兩個程序是各自擁有自己的資料空間,也就是他們的資料是不共享的。
- 兩個count值不相等,位址卻相等(這裡列印出來位址其實是虛拟位址),原因在于虛拟位址和計算機記憶體中實體位址之間是通過MMU和頁表聯系在一塊的,(虛拟位址映射到實體位址的方法不一樣)雖然虛拟位址一樣,他們映射到記憶體中的實體位址不一樣,列印出來的count值也就不一樣。
4.vfork()建立并執行程序
pid_t vfork(void);
函數的傳回值:
< 0 出錯
=0 子程序
>0父程序(其值為子程序的程序号)
在使用fork函數建立一個新程序之後,可以不使用exec系列函數來執行新的程式,如果要執行新的程式則必須手動調用exec系列函數,在這種情況下可以使用vfork函數,vfork函數在建立完一個新的程序之後自動實作exec系列函數的功能
- vfork函數相比fork函數更加粗暴,核心連子程序的虛拟位址都不建立了,而是直接共享父程序的,進而實體位址也就被理所當然的共享了。
- 父程序會保證子程序先運作,在子程序調用exec(程序替換)或exit後才可能被排程運作
- vfork()建立的程序并不将父程序的位址空間完全複制到子程序中,因為子程序會立即調用exec (或exit)于是也就不會存放該位址空間。
- 用 vfork() 建立程序,子程序裡一定要調用 exec(程序替換) 或 exit(退出程序),否則,程式會出問題,發生死鎖,沒有意義。
用vfork()建立子程序且調用execve()之後,子程序的記憶體映像如下所示:
把上面fork的測試代碼改成vfork,列印結果又會怎麼樣呢?
//vfork
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
int main()
{
int count = 1;
pid_t pid ;
pid = vfork();
if(pid < 0)
{
perror("fork error:");
return -1;
}
else if(pid == 0)
{
count++;
printf("this is child, pid=%d,count=%d(%p)\n",getpid(),count,&count);
exit(0);
}
else
{
printf("this is father, pid=%d,count=%d(%p)\n",getpid(),count,&count);
}
return 0;
}
測試結果:
代碼分析
- pid,count值以及它們的虛拟位址都是相同的。因為vfork函數的父子程序是共享虛拟位址空間的,是以count的值肯定是一樣的
- 但是vfork函數的代碼裡面,子程序最後加了一條語句exit(0);這是因為vfork這個函數在建立了子程序之後,父程序它會一直等待子程序退出,如果子程序沒有退出,那麼父程序将一直等待,直到子程序退出之後,父程序才會運作
-
如果不加exit(0)這個函數的話,或者在子程序中用return
0傳回的話,那麼建立的子程序在傳回後,會再次回到vfork函數調用處,進而會再次建立子程序,一直到把系統的pid用完,無法建立,然後報錯退出。
總結:
fork()與vfork()差別:
(1)fork: 子程序拷貝父程序的資料段
vfork: 子程序與父程序共享資料段
(2)fork: 父子程序的執行次序不确定
vfork: 子程序先運作,子程序運作退出後父程序再運作