1. 程式人生 > >Linux程序理解與實踐(二)殭屍&孤兒程序 和檔案共享

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)

測試程式如下所示:

  1. //示例: 避免殭屍程序
  2. int main(int argc, char *argv[])  
  3. {  
  4.     signal(SIGCHLD, SIG_IGN);  
  5.     pid_t pid = fork();  
  6.     if (pid < 0)  
  7.         err_exit("fork error");  
  8.     elseif (pid == 0)  
  9.         exit(0);  
  10.     else
  11.     {  
  12.         sleep(50);  
  13.     }  
  14.     exit(0);  
  15. }  

檔案共享

   父程序的所有檔案描述符都被複制到子程序中, 就好像呼叫了dup函式, 父程序和子程序每個相同的開啟檔案描述符共享一個檔案表項(因此, 父子程序共享同一個檔案偏移量);


  1. //根據上圖: 理解下面這段程式和下圖的演示
  2. int main(int argc, char *argv[])  
  3. {  
  4.     signal(SIGCHLD, SIG_IGN);  
  5.     int fd = open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);  
  6.     if (fd == -1)  
  7.         err_exit("file open error");  
  8.     cout << "We Don`t flash memory\n";  
  9.     char buf[BUFSIZ];  
  10.     bzero(buf, sizeof(buf));  
  11.     pid_t pid = fork();  
  12.     if (pid < 0)  
  13.         err_exit("fork error");  
  14.     elseif (pid > 0)  
  15.     {  
  16.         strcpy(buf, "Parent...");  
  17.         write(fd, buf, strlen(buf));  
  18.         close(fd);  
  19.         cout << "fd = " << fd << endl;  
  20.         exit(0);  
  21.     }  
  22.     elseif (pid == 0)  
  23.     {  
  24.         strcpy(buf, "Child...");  
  25.         write(fd, buf, strlen(buf));  
  26.         close(fd);  
  27.         cout << "fd = " << fd << endl;  
  28.         exit(0);  
  29.     }  
  30. }  
結果是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

)