Linux程序理解與實踐(二)殭屍&孤兒程序 和檔案共享
孤兒程序與殭屍程序
孤兒程序:
如果父程序先退出,子程序還沒退出那麼子程序的父程序將變為init程序。(注:任何一個程序都必須有父程序)
#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 is exited.\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");
- elseif (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");
- elseif (pid > 0)
- {
- strcpy(buf, "Parent...");
- write(fd, buf, strlen(buf));
- close(fd);
- cout << "fd = " << fd << endl;
- exit(0);
- }
- elseif (pid == 0)
- {
- strcpy(buf, "Child...");
- write(fd, buf, strlen(buf));
- close(fd);
- cout << "fd = " << fd << endl;
- exit(0);
- }
- }
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
)