fork、父進程和子進程
- 2021 年 7 月 23 日
- 筆記
進程
什麼是進程?進程是一個運行中的程式實體,擁有獨立的堆棧、記憶體空間和邏輯控制流。
這是標準的進程概念。讓我們通過作業系統的fork
函數看看這個抽象的概念是怎麼在進程的實現中體現出來的。
構成要素
創建一個進程,需要進程體、進程表和數據空間。
進程體在C程式碼中對應一個函數,編譯成二進位程式碼後就是一組指令。
進程表用來記錄進程的進程ID、進程名稱、暫存器快照空間。簡單說,當中斷髮生時,會保存此刻CPU的狀態,然後記錄到進程表中。
進程表的作用就是用來存儲進程快照。
進程堆棧的作用是什麼?存儲進程中函數的參數,存儲進程運行過程中的局部數據。
數據空間呢?先看一段簡單的程式碼。
char *f(int a, int b);
int main(int argc, char **argv)
{
f(5, 6);
return 0;
}
char *f(int a, int b)
{
int c = a + b;
char *str = "Hello, World!";
return str;
}
- 兩個參數a和b存儲在進程的堆棧中。
- 指針
char *str
指向的記憶體中的數據STR存儲在進程的數據空間中。
為什麼STR不是存儲在進程的堆棧中呢?
函數f的返回值是STR的記憶體地址。執行這段程式碼,我們會發現:調用函數f能正確獲得STR。
試想一下,假如STR存儲在進程的堆棧中,當f執行結束後,堆棧中的數據會被清空,我們調用函數f是不能正確獲得STR的。
STR存儲在進程的數據空間中,存儲在進程堆棧中的只是存儲STR的記憶體空間的記憶體地址。
fork
進程A調用fork新建進程B,A是B的父進程,B是A的子進程。
fork執行結束後,如果能成功創建B進程,B進程的數據空間、堆棧和進程表和A進程的這些要素完全相同。
差異
B進程畢竟是不同於A進程的獨立進程,所以:
- B進程的數據空間中的數據和A進程的數據空間的數據一致,但是,兩個進程的數據空間卻是不同的記憶體空間。
- B進程表中,指向LDT的選擇子和A進程表中的LDT選擇子不同。
- B進程表中的進程ID和A進程表中的進程ID不同。
堆棧
猜猜看,子進程的堆棧是在進程表中還是在數據空間中?
回答是:在進程的數據空間中。
在前面,我們雖然把堆棧和數據空間分開說,那是為了強調兩個要素在保存數據時的差異。堆棧中的數據隨時變化,例如,進程中的一個函數執行結束,堆棧中的數據就會發生變化。
進程的數據空間呢?我以為,當進程結束執行的時候,進程的數據空間中的數據才會消失。這是我的猜測,暫時不知道怎麼去驗證。
認為堆棧保存在數據空間中的依據是什麼?因為暫存器ss
中的選擇子指向的描述符描述的那段記憶體空間就是數據空間。
進程的ds、es、ss
的選擇子相同,指向相同的數據空間。
LDT、GDT和LDT選擇子
每個進程都有一個LDT。LDT存儲在進程的進程表中。
在進程的進程表中,有一個LDT選擇子。根據LDT選擇子,能從GDT中找到指向LDT的描述符。
有點繞。連起來再說一次:通過進程表中的LDT選擇子,從GDT中找到指向LDT的描述符,根據描述符找到LDT,LDT也在進程表中。
我的收穫
- 進程的堆棧存儲在進程的數據空間中。
- 堆棧是動態變化的,例如進程中的一個函數執行結束。堆棧中的數據容易消失,所以不能函數的返回值不能是指向堆棧的記憶體地址。
- 在函數中創建字元串變數、結構體變數,數據存儲在進程的數據空間中,存儲在堆棧中的只是數據的記憶體地址。
- 每個進程的堆棧棧頂可以是相同的。我的作業系統在初始化進程時,之所以使用不同的堆棧棧頂,是因為我的作業系統沒有開啟虛擬記憶體地址,使用的是相同的記憶體空間。如果使用相同的堆棧棧頂,不同進程的堆棧會相互覆蓋。
- fork的實現:
- 子進程複製父進程的進程表,但是要使用不同的LDT選擇子。
- 子進程要複製父進程的數據空間,同時要修改子進程的LDT。