#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> int main() { pid_t pid; //創建一個進程 pid = fork(); //創建失敗 if (pid < 0) { perror("fork error:"); exit(1); } //子進程 if (pid == 0) { printf("I am the child process.\n"); //輸出進程ID和父進程ID printf("pid: %d\tppid:%d\n",getpid(),getppid()); printf("I will sleep five seconds.\n"); //睡眠5s,保證父進程先退出 sleep(5); printf("pid: %d\tppid:%d\n",getpid(),getppid()); printf("child process is exited.\n"); } //父進程 else { printf("I am father process.\n"); //父進程睡眠1s,保證子進程輸出進程id sleep(1); printf("father process isexited.\n"); } return 0; }
僵屍進程: 如果子進程先退出,父進程還沒退出,那麼子進程必須等到父進程捕獲到了子進程的退出狀態才真正結束,否則這個時候子進程就成為僵屍進程。
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } else if (pid == 0) { printf("I am child process.I am exiting.\n"); exit(0); } printf("I am father process.I will sleep two seconds\n"); //等待子進程先退出 sleep(2); //輸出進程信息 system("ps -o pid,ppid,state,tty,command"); printf("father process is exiting.\n"); return 0; }
<defunct>僵屍進程 孤兒進程由init處理,並不會有什麼危害。但是僵屍進程的大量存在會占用PID等資源,可能會導致系統無法產生新的進程。任何一個子進程(init除外)在exit()之後,並非馬上就消失掉,而是留下一個稱為僵屍進程(Zombie)的數據結構,等待父進程處理。這是每個 子進程在結束時都要經過的階段。如果子進程在exit()之後,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是“Z”。如果父進程能及時 處理,可能用ps命令就來不及看到子進程的僵屍狀態,但這並不等於子進程不經過僵屍狀態。如果父進程在子進程結束之前退出,則子進程將由init接管。init將會以父進程的身份對僵屍狀態的子進程進行處理 避免僵屍進程 通過信號機制 對於某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成為僵屍進程(zombie)從而占用系統資源。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的並發性能。在Linux下可以簡單地將 SIGCHLD信號的操作設為SIG_IGN。 也就是說忽略SIGCHLD信號,這是常用於並發服務器的性能的一個技巧。因為並發服務器常常fork很多子進程,子進程終結之後需要服務器進程去wait清理資源。如果將此信號的處理方式設為忽略,可讓內核把僵屍子進程轉交給init進程去處理,省去了大量僵屍進程占用系統資源。(Linux Only) 測試程序如下所示:
//示例: 避免僵屍進程 int main(int argc, char *argv[]) { signal(SIGCHLD, SIG_IGN); pid_t pid = fork(); if (pid < 0) err_exit("fork error"); else if (pid == 0) exit(0); else { sleep(50); } exit(0); }
文件共享 父進程的所有文件描述符都被復制到子進程中, 就好像調用了dup函數, 父進程和子進程每個相同的打開文件描述符共享一個文件表項(因此, 父子進程共享同一個文件偏移量);
//根據上圖: 理解下面這段程序和下圖的演示 int main(int argc, char *argv[]) { signal(SIGCHLD, SIG_IGN); int fd = open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd == -1) err_exit("file open error"); cout << "We Don`t flash memory\n"; char buf[BUFSIZ]; bzero(buf, sizeof(buf)); pid_t pid = fork(); if (pid < 0) err_exit("fork error"); else if (pid > 0) { strcpy(buf, "Parent..."); write(fd, buf, strlen(buf)); close(fd); cout << "fd = " << fd << endl; exit(0); } else if (pid == 0) { strcpy(buf, "Child..."); write(fd, buf, strlen(buf)); close(fd); cout << "fd = " << fd << endl; exit(0); } }
結果是fd(文件描述符)的值相等。這說明父子進程共享同一個文件描述符,當然文件偏移量等信息也是共享的。 fork與vfork的區別 1. fork子進程拷貝父進程的數據段(但是現在提供了寫時復制技術,只有當子進程真正需要寫內存時,才復制出該內存的一段副本),因此,在父進程/子進程中對全局變量所做的修改並不會影響子進程/父進程的數據內容. vfork子進程與父進程共享數據段,因此父子進程對數據的更新是同步的; 2. fork父、子進程的執行次序是未知的,取決於操作系統的調度算法 vfork:子進程先運行,父進程後運行; 3. 如果創建子進程是為了調用exec執行一個新的程序的時候,就應該使用vfork,但是你在vfork後執行其它語句卻是非常危險的,因為很容易和父進程產生沖突。
#include <unistd.h> #include <stdio.h> int main(void) { pid_t pid; int count = 0; pid=vfork(); count++; printf("count=%d\n",count); return 0; }
在學習linux進程編程的時候遇到一個問題,就是使用vfork()函數以後本以為下面會打印出1和2,但是結果卻出人意料。 打印出的結果是: count=1 count=1 Segmentation fault 出現了段錯誤,經過查證得知,vfork()創建子進程成功後是嚴禁使用return的,只能調用exit()或者exec族的函數,否則後果不可預料,在main函數裡return和exit()效果一樣是有前提的:沒有調用vfork。 (如果return處什麼都沒有也會出現段錯誤,結果如下 count=1 count=9 Segmentation fault )