首先是fork()函數,移步APUE 8.3. 比較清晰的解釋可以參考http://blog.csdn.net/lingdxuyan/article/details/4993883和http://www.oschina.net/question/195301_62902
補充一點是:fork返回後,原進程中的每個檔案或套介面描述符的引用計數加1(相當於被多開啟了一次),每調用一次close,引用計數減1,只有當引用計數減到0時才會真正關閉該通訊端。
可執行檔被linux執行的唯一方式就是調用exec,把當前進程映像替換成新的程式檔案,從該程式的main開始執行。
linux典型做法是,fork之後開始exec,通常用COW技術。
關於殭屍進程
進程在exit()結束後,進程表中任然會保留如進程號、退出狀態、已耗用時間等資訊。雖然它已經放棄了記憶體空間,不能被調度。由於linux對進程數量有限制,過多的殭屍進程將會佔用可用進程號,導致新的進程無法產生,所以必須及時地清除。當時我就納悶了。。。那殭屍進程存在的意義何在?答:保留子進程退出的狀態,等待父進程收屍時確定死因。(在父進程中調用各種宏:WEXITSTATUS(status)等)
處理方式:
1、父進程調用wait()或waitpid()來等待子進程結束,當然這會導致父進程掛起
2、如果父進程並不關心子進程何時結束,可以調用signal(SIGCHLD,SIG_IGN)來通知核心,核心會在子進程結束後自動回收。注意:此方法不是可移植的,Stevent在UNP5.9中提到,這個處理並不是POSIX標準,只是在某些系統上可用
3、同樣使用非同步處理的方式,訊號處理函數signal,安裝handler來處理SIGCHLD訊號。這樣,父進程可以在handler中調用wait()進行回收。但是由於相同的訊號不排隊的原因,要注意處理同時提交的訊號,即對同時終止的子進程做處理。UNP5.10詳細地討論了該問題,並給出了安全的方法。
4、通過給訊號處理函數設定SA_NOCLDWAIT標誌。使調用進程的子進程終止時不建立殭屍進程(APUE10.14)
signala.sa_handler = SIG_IGN;signala.sa_flags = SA_NOCLDWAIT;sigemptyset(&signala.sa_mask);sigaction(SIGCHLD, &signala, NULL);
5、由於進程的特性,在其父進程退出後,子進程將被過繼給init進程,如果子進程已是殭屍進程,init直接將其回收,因為它的管理者父進程已經不存在了,init不關心其死因。因此可以採取連續兩次fork()的方式,子進程在fork之後直接退出,由孫進程來進行處理,孫進程結束後,資源被init回收。但注意子進程的清理還得由父進程來完成。
訊號處理:
在此小結下,每個核心的訊號,作業系統都有預設的處理方式,比如前面的SIGCHLD,預設是忽略,因此不處理會留下殭屍。我們可以自己定義函數來處理訊號,注意SIGKILL和SIGSTOP是不能被捕獲和忽略的。
訊號處理函數是進程相關的,為全進程所有線程公用。
訊號處理函數中,盡量不要使用printf這樣的不可重新進入函數(UNP11.18)
一個訊號被訊號處理函數響應,在處理過程中,該訊號被屏蔽。標準的訊號實現沒有排隊的功能,所以訊號可能會被丟失,多個連續的訊號來不及處理。如果通過sa_mask設定了訊號集,集合中的訊號也會被阻塞。
建立訊號處理函數的POSIX方法是調用sigaction(),簡單些直接調用signal(),雖然它不是POSIX函數,但多數平台用signal()來實現sigaction(),以實現向後相容。
下面是一個簡單的多進程服務端程式:
#include"simon_socket.h"#define SERV_PORT 12345extern pid_t waitpid(pid_t, int *, int);void handler(int signo){pid_t pid;int status;while ((pid = waitpid(-1, &status, WNOHANG)) > 0){printf("Child process %d terminated\nThe WEXITSTATUS return code is %d \nThe WIFEXITED return code is %d\n", pid, WEXITSTATUS(status), WIFEXITED(status));}}int main(){int sockfd, acfd;struct sockaddr_in client_addr;size_t sin_len = sizeof(client_addr);sockfd = init_tcp_psock(SERV_PORT);signal(SIGCHLD, handler);/* another method : *struct sigaction signala;signala.sa_handler = SIG_IGN;signala.sa_flags = SA_NOCLDWAIT;sigemptyset(&signala.sa_mask);sigaction(SIGCHLD, &signala, NULL);*/while (1){if ((acfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_len)) == -1){perror("Accept request failed: ");return 1;}elseprintf("Get a connection from %s:%d !\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));pid_t pid;if ((pid = fork()) > 0){close(acfd);continue;}else if(pid == 0){close(sockfd);process_client(acfd, &client_addr);close(acfd);exit(0); }else{perror("Fork error");exit(0);}}close(sockfd);return 0;}
代碼中一些調用函數的實現,移步我的github:https://github.com/simon-xia/lnp