【linux 編程】linux/unix 進程的創建
在 UNIX 系統中,用戶創建一個新進程的唯一方法就是調用系統調用 fork。調用 fork 的進程稱為父進程,而新創建的進程叫做子進程。系統調用的語法格式:
pid = fork();
在從系統調用 fork 中返回時,兩個進程除了返回值 pid 不同外,具有 完全一樣的用戶級上下文。在子進程中,pid 的值為零。在系統啟動時由核心內 部地創建的進程0是唯一不通過系統調用 fork 而創建的進程。
核心為系統調用 fork 完成下列操作:
1. 為新進程在進程表中分配一個空項。
2. 為子進程賦一個唯一的進程標識號 (PID)。
3. 做一個父進程上下文的邏輯副本。由于進程的某些部分,如正文區,可能被幾個進程所共享,所以核心有時只要增加某個區的引用數即可,而不是真的將該區拷貝到一個 新的內存物理區。
4. 增加與該進程相關聯的文件表和索引節點表的引用數。
5. 對父進程返回子進程的進程號,對子進程返回零。
理解系統調用 fork 的實現是十分重要的,因為子進程就象從天而降一樣地開始 它的執行序列。
下面是系統調用 fork 的算法。核心首先確信有足夠的資源來成功完成 fork。 如果資源不滿足要求,則系統調用 fork 失敗。如果資源滿足要求,核心在進程 表中找一個空項,并開始構造子進程的上下文。
算法:fork
輸入:無
輸出:對父進程是子進程的 PID
對子進程是0
{
檢查可用的核心資源
取一個空閑的進程表項和唯一的 PID 號
檢查用戶沒有過多的運行進程
將子進程的狀態設置為“創建”狀態
將父進程的進程表中的數據拷貝到子進程表中
當前目錄的索引節點和改變的根目錄(如果可以)的引用數加1
文件表中的打開文件的引用數加1
在內存中作父進程上下文的拷貝
在子進程的系統級上下文中壓入虛設系統級上下文層
/* 虛設上下文層中含有使子進程能
* 識別自己的數據,并使子進程被調度時
* 從這里開始運行
*/
if (正在執行的進程是父進程) {
將子進程的狀態設置為“就緒”狀態
return (子進程的 PID) // 從系統到用戶
}
else {
初始化計時區
return 0;
}
}
我們來看看下面的例子。該程序說明的是經過系統調用 fork 之后,對文件的 共享存取。用戶調用該程序時應有兩個參數,一個是已經有的文件名,另外一個是要 創建的新文件名。該進程打開已有的文件,創建一個新文件,然后,假定沒有遇見過錯誤,它調用 fork 來創建一個子進程。子進程可以通過使用相同的文件描述符而繼承地存取父進程的文件(即父進程已經打開和創建的文件)。
當然,父進程和子進程要分別獨立地調用 rdwrt 函數,并執行一個循環,即從 源文件中讀一個字節,然后寫一個字節到目標文件中區。當系統調用 read 遇見 文件尾時,函數 rdwrt 立即返回。
#include <fcntl.h>
int fdrd, fdwt;
char c;
main(int argc, char *argv[])
{
if (argc != 3) {
exit(1);
}
if ((fdrd = open(argv[1], O_RDONLY)) == -1) {
exit(1);
}
if ((fdwt = creat(argv[2], 0666)) == -1) {
exit(1);
}
fork();
// 兩個進程執行同樣的代碼
rdwrt();
exit(0);
}
rdwrt()
{
for (;;) {
if (read(fdrd, &c, 1) != 1) {
return ;
}
write(fdwt, &c, 1);
}
}
在這個例子中,兩個進程的文件描述符都指向相同的文件表項。這兩個進程永遠 不會讀或寫到相同的文件偏移
量,因為核心在每次 read 和 write 調用 之后,都要增加文件的偏移量。盡管兩個進程似乎是將源文件拷貝了
兩次,但因為他們分擔了工作任務,因此,目標文件的內容依賴于核心調度兩個進程的次序。
如果 核心這樣調度兩個進程:使他們交替地執行他們的系統調用,或甚至使他們交替地 執行每對 read 和 write 調用,則目標文件的內容和源文件的內容完全一致。
但考慮這樣的情況:兩個進程正要讀源文件中的兩個連續的字符 "ab"。假定父進程讀了字 符 "a",這時,核心在父進程寫之前,做了上下文切換來執行子進程。
如果子進程 讀到字符 "b",并在父進程被調度前,將它寫到目標文件,那么目標文件將不再含有 字符串 "ab",而是含有 "ba"了。核心并不保證進程執行的相對速率。
再來看看另外一個例子:
#include <string.h>
char string[] = "Hello, world";
main()
{
int count, i;
int to_par[2], to_chil[2]; // 到父、子進程的管道
char buf[256];
pipe(to_par);
pipe(to_chil);
if (fork() == 0) {
// 子進程在此執行
close(0); // 關閉老的標準輸入
dup(to_child[0]); // 將管道的讀復制到標準輸入
close(1); // 關閉老的標準輸出
dup(to_par[1]); // 將管道的寫復制到標準輸出
close(to_par[1]); // 關閉不必要的管道描述符
close(to_chil[0]);
close(to_par[0]);
close(to_chil[1]);
for (;;) {
if ((count = read(0, buf, sizeof(buf)) == 0)
exit();
write(1, buf, count);
}
}
// 父進程在此執行
close(1); // 重新設置標準輸入、輸出
dup(to_chil[1]);
close(0);
dup(to_par[0]);
close(to_chil[1]);
close(to_par[0]);
close(to_chil[0]);
close(to_par[1]);
for (i = 0; i < 15; i++) {
write(1, string, strlen(string));
read(0, buf, sizeof(buf));
}
}
子進程從父進程繼承了文件描述符0和1(標準輸入和標準輸出)。
兩次執行系統調用 pipe 分別在數組 to_par 和 to_chil 中分配了兩個文件描述符。然后該進程 執行系統調用 fork,并復制進程上下文:象前一個例子一樣,每個進程存取 自己的私有數據。
父進程關閉他的標準輸出文件(文件描述符1),并復制(dup)從管道線 to_chil 返回的寫文件描述符。因為在父進程文件描述符表中的第一個空槽是剛剛 由關閉騰出來的,所以核心將管道線寫文件描述符復制到了文件描述符表中的第一項中,這樣,標準輸出文件描述符變成了管道線 to_chil 的寫文件描述符。
父進程以類似的操作將標準輸入文件描述符替換為管道線 to_par 的讀文件 描述符。與此類似,子進程關閉他的標準輸入文件(文件描述符0),然后復制 (dup) 管道 線 to_chil 的讀文件描述符。
由于文件描述符表的第一個空項是原先的標準輸入項,所以子進程的標準輸入變成了管道線 to_chil 的讀文件描述符。子進程做一組類似的操作使他的標準輸出變成管道線 to_par 的寫文件描述符。
然后兩個進程關閉從 pipe 返回的文件描述符。上述操作的結果是:當父進程向標準輸出寫東西的時候,他實際上是寫向 to_chil--向子進程發送數據,而子進程則從他的標準輸入讀管道線。當子進程向他的標準輸出寫的時候, 他實際上是寫入 to_par--向父進程發送數據,而父進程則從他的標準輸入 接收來自管道線的數據。兩個進程通過兩條管道線交換消息。
無論兩個進程執行的順序如何,這個程序執行的結果是不變的。他們可能去執行睡眠和喚醒來等待對方。父進程在15次循環后退出。然后子進程因管道線沒有寫進程而讀 到“文件尾”標志,并退出。
關鍵字:進程、標識號
新文章:
- CentOS7下圖形配置網絡的方法
- CentOS 7如何添加刪除用戶
- 如何解決centos7雙系統后丟失windows啟動項
- CentOS單網卡如何批量添加不同IP段
- CentOS下iconv命令的介紹
- Centos7 SSH密鑰登陸及密碼密鑰雙重驗證詳解
- CentOS 7.1添加刪除用戶的方法
- CentOS查找/掃描局域網打印機IP講解
- CentOS7使用hostapd實現無AP模式的詳解
- su命令不能切換root的解決方法
- 解決VMware下CentOS7網絡重啟出錯
- 解決Centos7雙系統后丟失windows啟動項
- CentOS下如何避免文件覆蓋
- CentOS7和CentOS6系統有什么不同呢
- Centos 6.6默認iptable規則詳解