也门逊尼派歌手的博客

恪守圣行,谨遵沙里亚法

POSIX信号处理

POSIX信号处理

信号(signal)就是告知某个进程发生了某个事件的通知,有时也称软件中断(software interrupt)。注意:信号是异步发生的,也就是说进程预先并不知道信号的准确发生时刻。

查询僵死子进程的shell命令

ps -A -ostat,ppid,pid | grep -e '^[Zz]'

信号可以
1. 由一个进程发送给另外一个进程(包括自身)
2. 由内核发送给某个进程

信号处理函数

每个信号都有一个与之关联的处置。当有特定信号发生的时候,特定的函数被调用,那么这个特定函数就称为这个特定信号的信号处理函数,调用这个函数的过程称为信号捕获过程。

  1. SIGKILL和SIGSTOP这两个信号并不能被捕获
  2. 通过将信号的处置设置为SIG_IGN来忽略某个信号时需牢记,SIGKILL和SIGSTOP这两个信号同样不能被忽略。
  3. 我们可以把某个信号的处置设置为SIG_DFL来启用它的默认设置,默认处置通常是在收到信号后终止进程。SIGCHLD就是默认处置为忽略的两个信号。

signal函数

signal(int signo, void *func)
signo是信号名,意为我们捕捉到的信号或者我们将要的处理的信号;func是指向函数的指针,意为我们将要处理signo信号的方式。第二个参数可以为常值SIG_IGN(忽略信号)或SIG_DFL(该信号的默认处置)。

POSIX信号的语义

  1. 在一个信号处理函数运行期间,正被递交的信号是阻塞的。意思是如果这里我们正在处理一个SIGCHLD信号,那么其他信号的处理将会滞后。
  2. 如果一个信号在被阻塞期间产生了一次或多此,那么该信号被解阻塞之后通常只递交一次,也就是说Unix信号默认是不排队的。例如,子进程提交了一个SIGCHLD信号,而此时内核正在处理运行另一个进程的信号处理函数,且该子进程再继续递交了多次SIGCHLD信号,那么唤醒时只会递交一次SIGCHLD信号。

处理SIGCHLD信号

子进程向父进程递交一个SIGCHLD信号,若父进程未加处理(即没有调用wait函数),子进程将会进入僵死状态。如果一个进程终止,且该进程有子进程处于僵死状态,那么它的所有僵死子进程的父进程ID将重置为1(init进程),init进程将调用wait函数清理这些僵死子进程。这意味着无论什么时候我们fork子进程后都得wait它们,以防它们变成僵尸进程。
在服务器程序中我们先调用listen函数将未连接套接字sockfd变为监听套接字listenfd之后,然后增加Signal(SIGCHLD, sig_chld);这个函数将会递交SIGCHLD信号并使得父进程阻塞与accept调用,然后sig_child(int signo)函数将会捕获到子进程的PID和终止状态,最后返回。这里在Signal函数中我们设置了SA_RESTART标志,是为了让内核能够自动重启被中断的系统调用。

处理被中断的系统调用–慢系统调用

慢系统调用函数,以accept为例,在服务器程序中,bind和listen执行完毕后,未连接套接字将会变成监听套接字,此时accept函数等待一个来自客户端的连接请求。若客户端一直不运行或是一直不执行connect(tcp三次握手)函数,那么accept函数将会一直阻塞,accept调用有可能不会返回已连接的套接字。
EINTR:标志被中断的系统调用。适用于慢系统调用的基本规则是:当阻塞与某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。当我们编写捕获信号的程序时,我们必须对慢系统调用返回EINTR有所准备,即是说,我们需要重启被中断的系统调用,一般在for循环中利用continue来实现。以下为处理被中断的accept函数的操作:

//因为accept调用需要等待客户端程序的connect连接,这个操作可以处理
        //被中断的accept调用
        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue;       /* back to for() */
            else
                err_sys("accept error");
        }

当我们判断到这个系统调用为慢系统调用即errno = EINTR,则利用continue继续重启accept函数调用。不过有一个函数我们不能重启:connect,重启就报错。

wait和waitpid函数

调用的函数wait来处理已终止的子进程。比如子进程向父进程发送了一个SIGCHLD信号,此时父进程调用函数wait可以清理子进程;若父进程不调用wait函数,则会使子进程变为僵死子进程。

#include <sys/wait.h>
pid_t wait(int *statloc); //&stat,地址
pid_t waitpid(pid_t pid, int *statloc, int options);

示例

int
main()
{
    int                 i, sockfd[5];
    struct sockaddr_in  servaddr;

    char *argv = "192.168.31.54";

    for (i = 0; i < 5; i++) {  //创建5个客户端进程
        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, &servaddr.sin_addr);
        Connect(sockfd[i], (SA *) &servaddr, sizeof(servaddr));
    }

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

在运行了tcpserv04.c和tcpcli04.c之后,在客户端输入EOF标志,即是ctrl+d,所有打开的套接字描述符由内核自动关闭,且所有5个连接基本在同一时间终止,这就意味着同一时刻有5个SIGCHLD信号从子进程发送给父进程。因为客户端的每一次connect调用,都会使得服务器fork一个新的子进程,这里fork了5个子进程,但是信号处理函数wait只调用了一次,那么剩余的四个进程都将成为僵死进程。因此我们要修改之前的sig_chld函数为:

void
sig_chld(int signo)
{
    pid_t   pid;
    int     stat;

    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    return;
}

注意这里waitpid函数的WNOHANG选项,直译为不挂起,这里选择WNOHANG选项可以使得waitpid函数不被尚未终止的子进程阻塞。而wait函数在没有已终止的子进程时将会被阻塞。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ASAP_Diablo/article/details/75539897
文章标签: posix unix网络编程
个人分类: 网络编程
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭