天天看點

waitpid函數_wait和waitpid是幹啥用的?

我們調用wait或waitpid來處理已終止的子程式。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
           

函數wait和waitpid均傳回2個值,已終止的子程序的PID,以及出參傳回的INT型終止狀态。我們可以調用宏函數,來檢查終止狀态,可以辨識子程序是正常終止,由某個信号殺死,還是由作業控制停止。如果調用wait的程序,沒有已終止子程序,那麼wait将阻塞直到有一個子程序終止。waitpid函數給我們更多的控制,比如等待哪個程序,是否阻塞。其中,pid參數允許我們指定想等待的程序ID,-1表示等待第一個終止的子程序。option選項設定為

WNOHANG

,則告訴核心在沒有已終止子程序時不要阻塞。

函數wait和waitpid有什麼差別?

用戶端與伺服器建立5個TCP連接配接:

#include    "unp.h"

int
main(int argc, char **argv)
{
    int  i, sockfd[5];
    struct sockaddr_in  servaddr;

    if (argc != 2)
        err_quit("usage: tcpcli <IPaddress>");

    for (i = 0; i < 5; i++) {
        sockfd[i] = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(SERV_PORT);
        Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

        Connect(sockfd[i], (SA *) &servaddr, sizeof(servaddr));
    }

    str_cli(stdin, sockfd[0]);      /* do it all */

    exit(0);
}
           

當客戶終止時,所有打開的描述符由核心自動關閉,且所有5個連接配接基本在同一時刻終止。這樣引發了5個FIN封包,每個連接配接一個,導緻伺服器的5個子程序在同一時刻終止,基本在同一時刻引發了5個SIGCHLD信号遞交給父程序。

  • 第一次咱們跑,5個子程序終止引發的5個SIGCHLD信号都處理了
  • 第二次咱們跑,5個子程序終止引發的2個SIGCHLD型号被處理,剩下3個變成僵屍程序
waitpid函數_wait和waitpid是幹啥用的?
  • 為什麼會出現這種不确定事件呢?

因為建立一個信号處理函數并在其中調用wait并不能完全防止出現僵屍程序,這個問題在于:5個信号都是信号處理函數執行之前産生,而信号處理函數隻執行一次,而産生的信号一般又不排隊,第一個信号還沒執行結束,第二個相同的信号就到達了,導緻後面一些子程序沒法wait。

  • 有什麼解決辦法呢?

正确的解決辦法是調用waitpid代替wait。我們在一個循環裡調用waitpid,以擷取所有已終止子程序的狀态,我們必須指定

WNOHANG

選項,它告訴waitpid,在有尚未終止的子程序運作時不要阻塞。

#include    "unp.h"

void
sig_chld(int signo)
{
    pid_t   pid;
    int     stat;
    int i = 0;
    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
    {
        printf("i = %dtchild %d terminatedn", ++i,pid);
    }
    return;
}
           

嘿嘿嘿,現在是不是輸入EOF之後,所有的僵屍程序都會被幹掉了呀~

waitpid函數_wait和waitpid是幹啥用的?

為了更清晰的檢視效果,我自己加了一個序号i,先看第一次運作結果:

  • 前面2個信号,都分别觸發了信号處理函數。
  • 後面3個信号,隻觸發一次信号處理函數。
這說明什麼問題了呢?

簡單來說,第一個信号被捕獲,處理函數執行完後,第二個信号還沒有被捕獲到,waitpid直接傳回了0。第二個信号被捕獲,同第一個一樣。第三個信号被捕獲,處理函數執行完後,發現剩下的2個信号也被捕獲了,是以while循環繼續。

再看第二次運作結果,正好反過來了。正确的服務端代碼如下:

#include    "unp.h"

int
main(int argc, char **argv)
{
    int                 listenfd, connfd;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_in  cliaddr, servaddr;
    void                sig_chld(int);

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    Signal(SIGCHLD, sig_chld);  /* must call waitpid() */

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue;       /* back to for() */
            else
                err_sys("accept error");
        }

        if ( (childpid = Fork()) == 0) {    /* child process */
            Close(listenfd);    /* close listening socket */
            str_echo(connfd);   /* process the request */
            exit(0);
        }
        Close(connfd);          /* parent closes connected socket */
    }
}
           

參考文獻:《UNIX網絡程式設計 卷1:套接字聯網API》