fork() 基礎(chǔ) "Fork",除了它是一個(gè)當(dāng)你不停地敲入后看起來非常奇怪的單詞以外,通常是指 Unix 產(chǎn)生新進(jìn)程的方式。由于系統(tǒng)調(diào)用的用法將會(huì)在其他 IPC 的文檔中出現(xiàn),本文只是一個(gè)快速的,不太精確的 fork() 初級(jí)讀本。如果你已經(jīng)通曉 fork() ,最好跳過此節(jié)。 "Seek ye the Gorge of Eternal Peril" fork() 可被想象為力量的象征,力量有時(shí)可以被想象為毀滅的象征。因此,當(dāng)你的系統(tǒng)由于 fork() 而雜亂無章時(shí),一定要小心。這并不是說永遠(yuǎn)也別碰 fork(), 你只需要保持謹(jǐn)慎。它是象劍刃之類的一種東西,如果你很小心,就不會(huì)被開膛破肚。 如果你還在看的話,我想應(yīng)該拿出點(diǎn)實(shí)際東西出來了。正如我所說, fork() 是 Unix 啟動(dòng)新進(jìn)程的方式。最基本的,它是這樣工作的:父進(jìn)程(已經(jīng)存在的那一個(gè)) fork() 一個(gè)子進(jìn)程(新的一個(gè))。子進(jìn)程得到父進(jìn)程數(shù)據(jù)的一個(gè) 拷貝.瞧!以前只有一個(gè)進(jìn)程而現(xiàn)在有了兩個(gè)。 當(dāng)然,在 fork() 進(jìn)程時(shí) 你必須得應(yīng)付各種各樣的情況,否則,你的系統(tǒng)管理員會(huì)對(duì)你怒目而視,因?yàn)槟闾顫M了系統(tǒng)的進(jìn)程表 ,而他們不得不按下機(jī)器的重啟鍵 。 首先,你必須知道在 Unix 下的一些進(jìn)程的運(yùn)作方式。當(dāng)一個(gè)進(jìn)程死亡時(shí),它并不是完全的消失了。進(jìn)程終止,它不再運(yùn)行,但是還有一些殘留的小東西等待父進(jìn)程收回。這些殘留的東西包括子進(jìn)程的返回值和其他的一些東西。當(dāng)父進(jìn)程 fork() 一個(gè)子進(jìn)程后,它必須用 wait() (或者 waitpid())等待子進(jìn)程退出。正是這個(gè) wait() 動(dòng)作來讓子進(jìn)程的殘留物消失。 自然的,在上述規(guī)則之外有個(gè)例外:父進(jìn)程可以忽略 SIGCLD 軟中斷而不必要 wait()。可以這樣做到(在支持它的系統(tǒng)上): main() { signal(SIGCLD, SIG_IGN); /* now I don't have to wait()! */ . . fork();fork();fork(); /* Rabbits, rabbits, rabbits! */ 現(xiàn)在,子進(jìn)程死亡時(shí)父進(jìn)程沒有 wait(),通常用 ps 可以看到它被顯示為“<defunct>”。它將永遠(yuǎn)保持這樣只到父進(jìn)程 wait(),或者按以下方法處理。 這里是你必須知道的另一個(gè)規(guī)則:當(dāng)父進(jìn)程在它 wait() 子進(jìn)程之前死亡了(假定它沒有忽略 SIGCLD),子進(jìn)程將把 init (PID 1)進(jìn)程作為它的父進(jìn)程。如果子進(jìn)程工作得很好并能夠控制,這并不是問題。但如果子進(jìn)程已經(jīng)是 defunct,我們就有了一點(diǎn)小麻煩??矗鹊母高M(jìn)程不可能再 wait(),因?yàn)樗呀?jīng)消亡了。這樣,init 怎么知道 wait() 這些 zombie 進(jìn)程。 答案:不可預(yù)料的。在一些系統(tǒng)上,init 周期性的破壞掉它所有的 defunct 進(jìn)程。在另外一些系統(tǒng)中,它干脆拒絕成為任何 defunct 進(jìn)程的父進(jìn)程,而是馬上毀滅它們。如果你使用上述系統(tǒng)的一種,可以寫一個(gè)簡單的循環(huán),用 屬于 init 的 defunct 進(jìn)程填滿進(jìn)程表。這大概不會(huì)令你的系統(tǒng)管理員很高興吧? 你的任務(wù):確定你的父進(jìn)程不要忽略 SIGCLD,也不要 wait() 它 fork() 的所有進(jìn)程。不過,你也每必要 總是這樣做(比如,你要起一個(gè) daemon 或是別的什么東西),但是你必須小心編程,如果你是一個(gè) fork() 的新手。另外,也不要在心理上有任何束縛。 總結(jié):子進(jìn)程成為 defunct 只到父進(jìn)程 wait(),除非父進(jìn)程忽略了 SIGCLD 。更進(jìn)一步,父進(jìn)程沒有 wait() 就消亡(仍假設(shè)父進(jìn)程沒有忽略 SIGCLD )的子進(jìn)程(活動(dòng)的或者 defunct)成為 init 的子進(jìn)程,init 用重手法處理它們。 “我已經(jīng)有了心理準(zhǔn)備,把按鈕給我” 好的!這里是怎樣使用 fork() 的例子: #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> main() { pid_t pid; int rv; switch(pid=fork()) { case -1: perror("fork"); /* something went wrong */ exit(1); /* parent exits */ case 0: printf(" CHILD: This is the child process!\n"); printf(" CHILD: My PID is %d\n", getpid()); printf(" CHILD: My parent's PID is %d\n", getppid()); printf(" CHILD: Enter my exit status (make it small): "); scanf(" %d", &rv); printf(" CHILD: I'm outta here!\n"); exit(rv); default: printf("PARENT: This is the parent process!\n"); printf("PARENT: My PID is %d\n", getpid()); printf("PARENT: My child's PID is %d\n", pid); printf("PARENT: I'm now waiting for my child to exit()...\n"); wait(&rv); printf("PARENT: My child's exit status is: %d\n", WEXITSTATUS(rv)); printf("PARENT: I'm outta here!\n"); } } 這個(gè)例子中有大量的地方需要講解,我們從頭開始。 pid_t 是通用的進(jìn)程類型。在 Unix 中,它是一個(gè) short。因此,我調(diào)用 fork() 并把返回值存入 pid 變量。 fork() 很容易,它的返回值只可能是三種情況: 0: 如果返回 0,你正在子進(jìn)程中。你可以通過調(diào)用 getppid() 得到父進(jìn)程的 PID 。當(dāng)然,你也可以通過調(diào)用 getpid 得到自己的 PID 。 -1: 如果返回 -1,有些東西出了錯(cuò),將不會(huì)產(chǎn)生子進(jìn)程。用 perror() 看看發(fā)生了什么事情。你可能已經(jīng)填滿了進(jìn)程表——如果你一轉(zhuǎn)身,發(fā)現(xiàn)系統(tǒng)管理員拿著消防斧正沖你走過來。 其他: fork() 返回的其他值意味著你正在父進(jìn)程中,返回值是子進(jìn)程的 PID 。這是得到子進(jìn)程 PID 的唯一方法,因?yàn)闆]有諸如 getcpid() 之類的系統(tǒng)調(diào)用(顯而易見是因?yàn)楦高M(jìn)程與子進(jìn)程之間的一對(duì)多關(guān)系)。 當(dāng)子進(jìn)程最終調(diào)用 exit() 時(shí),返回值將被傳遞到正在 wait() 的父進(jìn)程。正如你從 wait() 調(diào)用所看到的那樣,當(dāng)我們打印返回值時(shí)會(huì)有些古怪。WEXITSTATUS() 到底是什么東西?它是一個(gè)宏,從 wait() 返回值中提取實(shí)際的返回值。對(duì),在這個(gè) int 中隱藏了太多的信息,你需要自己來挖掘。 現(xiàn)在你發(fā)問了,“wait() 怎么知道在哪個(gè)進(jìn)程上等待?我的意思是,由于父進(jìn)程可以有多個(gè)子進(jìn)程, wait() 實(shí)際等待地是哪一個(gè)?”我的朋友,答案非常簡單,它等待最先退出的那一個(gè)。你可以通過以子進(jìn)程的 PID 為參數(shù)調(diào)用 waitpid() 指明是哪一個(gè)子進(jìn)程。 上例中另一個(gè)需要注意的有趣的地方是父進(jìn)程和子進(jìn)程都使用 rv 變量。難道這意味著它被進(jìn)程共享了嗎?不!如果是這樣,我就不會(huì)寫這篇 IPC 的文章了。每個(gè)進(jìn)程對(duì)所有變量都有自己的一個(gè)拷貝。還復(fù)制了其他的很多東西,你必須閱讀 man 看看究竟是什么。 上述程序中最后要注意的一個(gè)問題:我使用了一個(gè) switch 語句來處理 fork(),這并不是非常典型,大多數(shù)情況下,你將會(huì)看到 if 語句;有時(shí)短得象這樣:ere; sometimes it's as short as: if (!fork()) { printf("I'm the child!\n"); exit(0); } else { printf("I'm the parent!\n"); wait(NULL); } Oh yeah——上述程序也示范了如果你不關(guān)心返回值的話,怎樣使用 wait():你只須用 NULL 參數(shù)調(diào)用它。 結(jié)論 現(xiàn)在你知道了 fork() 的所有的有力的功能!它不光在計(jì)算機(jī)相關(guān)領(lǐng)域非常有用,而且會(huì)令你的朋友在 party 上大開眼界。另外,它會(huì)幫助你在異性面前更有魅力,男的除外。 |
|