一、進程創建(PCB、虛擬地址空間、頁表、數據(映射關係加載好))
1、fork
fork可以從已有的進程中創建出一個新進程也叫作子進程,原來的那個進程就叫做父進程。一般,fork創建出子進程後,子進程一般是和父進程代碼實現共享,數據實現寫時拷貝的。
進程調用fork,當控制轉移到內核中的fork代碼後,內核會(1)將分配新的內存塊和內核數據結構給子進程。(2)將父進程部分數據結構拷貝至子進程。(3)添加子進程到系統進程列表當中。(4)fork返回,開始調度器調度
fork的返回值:(這個我有在前面的博客中具體寫過原因,可以戳此鏈接:https://blog.csdn.net/apt1203jn/article/details/79779888)
(1)子進程返回0;(2)父進程返回子進程的pidfork的常規用法:(1)一個父進程希望複製自己,使得父子進程同時執行不同的代碼段(eg:父進程等待客戶端請求,創建子進程來處理請求)。(2)一個進程要執行一個不同的程序(eg:子進程從fork返回之後,調用exec函數 )
調用fork失敗的原因:(1)內存不夠(2)實際用戶的進程數超過了限制
2、vfork
vfork()創建子進程的特點:
* 子進程和父進程共享地址空間(fork的子進程具有獨立的地址空間)
* vfork保證子進程先運行,在它調用exec或(exit)之後父進程纔可能被調度運行
測試用例:
(1)驗證一下vfork()創建出的子進程會先於它的父進程調度運行(讓子進程sleep一下使它的生命週期變長)
先寫Makefile
.OPHNY:test
test:test.c
gcc -o test test.c
.OPHNY:clean
clean:
rm -f test
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 100;
int main()
{
//pid_t id = fork();
pid_t id = vfork();
if (id == 0){
printf("child, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), get ppid(), g_val, &g_val);
sleep(3);
exit(1);
}
else if (id > 0){
printf("father, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), ge tppid(), g_val, &g_val);
exit(2);
}
return 0;
}
先來看一下調用fork()創建子進程的運行結果
註釋掉fork(),看一下vfork()創建子進程之後的調度順序
運行結果:
由圖可見,vfork() 創建的子進程會先運行,在子進程調用exit函數之後父進程纔可能被調度運行。
大家可以自行驗證一下,我沒有開另外的終端檢測,所以運行效果不是非常非常明顯,大家自己動手驗證一下哦!
(2)驗證父進程和子進程共用一塊地址空間
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 100;
int main()
{
pid_t id = fork();
//pid_t id = vfork();
if (id == 0){
printf("befor:child, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);
g_val = 200;
printf("after:child, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
exit(1);
}
else if (id > 0){
printf("father, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), ge tppid(), g_val, &g_val);
}
return 0;
}
注:Makefile文件同上一個
下面是用fork()創建子進程的運行結果
於此可以看出,fork()創建出的子進程並沒有直接改變父進程的變量值,那麼vfork()創建出的子進程會不會改變父進程的變量值嘞?
下面放開vfork() 的註釋,看一下運行結果
解析:
上圖結果可見子進程直接改變了父進程的變量值,這主要是因爲子進程在父進程的地址空間中運行。