您的位置:首页 > 其它

多进程:信号量的监听与处理函数

2016-05-27 11:28 309 查看
文章结构:

wait()的阻塞问题
signal()函数讲解
示例代码

wait()的阻塞问题

  之前的多进程:父进程监听子进程状态 wait()的使用文章中,父进程为了获取子进程的SIGSTOP、SIGTERM等信号时,由于调用了
wait
而导致主进程一直阻塞。在实际的开发中,主进程在等待子进程状态变化时还会有其它的事情要去执行,所以需要一种异步回调机制,让主进程可以在执行其它任务的时候,又可以监听到子进程的进程状态变化时及时处理。

  
signal()
函数就可以解决以上的问题。

signal()函数讲解

  
signal()
函数原型如下:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

  
typedef
定义了一类函数名叫
sighandler_t
,该函数返回类型为
void
,且只有一个
int
型参数。

  
signal()
函数第一个参数
signum
指所要监听的进程状态的变量信号,所有可监听的信号量的定义可以从
sys/signal.h
头文件中去查阅。本文文章的demo中要处理的信号量有:

SIGSTOP: 发送给父进程的,表示子进程被外部命令所暂停。命令可以是
kill
,也可以在
top
中操作。

SIGCHLD: 发送给父进程的,表示子进程被外部命令所暂停或已经执行完毕退出。这时需要父进程执行
wait
函数让子进程从
僵尸进程
状态彻底被系统回收。

SIGWINCH: 程序窗口大小发生变化。在终端命令行下运行可执行文件时鼠标拖动一下窗口即可获得此信号。

  
signal()
函数第二个参数是指定
signum
的处理函数。该函数的唯一参数将会被赋值为被监听到的信号量。在此函数中可以调用
wait
或其它处理逻辑。也可以赋值为系统的
SIG_IGN
SIG_DFL
函数,分别表示忽略和默认处理方式。但是信号量
SIGKILL
SIGSTOP
的处理方式是不能被忽略处理。

  当自定义了信号量处理函数后,所监听的信号被捕获,则该信号会被设置为阻塞
blocked
,然后再执行处理函数中的逻辑,处理函数执行完毕后,信号量恢复为未阻塞状态
unblocked


  
signal()
函数正常执行,返回值为
signum
的原有处理函数;否则出错返回
SIG_ERR
,并且可以通过
errno
来查看错误原因。

  
signal()
函数在不同的Unix或Linux版本间存在较大的差异,所以一般推荐用
sigaction()
函数来替换。本文不涉及
sigaction()
的内容。

示例代码

  接下来演示
signal()
函数的使用。代码示例中依然用到了对标准输出流的重定向freopen,将子进程的日志输出到
child_signal.txt
,父进程日志输出到
main_signal.txt
中去。

signal.c


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
#include <stdio.h>#include <signal.h>#include <stdlib.h>#include <sys/wait.h>#include <unistd.h>#include <errno.h>#include <time.h>#include <string.h>#ifndef getSigName#define getSigName(sig) (((sig) == SIGCHLD)?"SIGCHLD":(((sig) == SIGCONT)?"SIGCONT":(((sig) == SIGTERM)?"SIGTERM":(((sig) == SIGWINCH)?"SIGWINCH":""))))#endifstatic void printTime() {    time_t calendar_time = time(NULL);    struct tm * tm_local = localtime(&calendar_time);    char str_f_t [50];    strftime(str_f_t, sizeof(str_f_t), "%G-%m-%d %H:%M:%S", tm_local);    printf("%s ", str_f_t);}static void handleSignal(int sig) {    printTime();    printf("PID=%d handleSignal %s=%d\n", getpid(), getSigName(sig), sig);    if (sig == SIGCHLD || sig == SIGTERM) {        // 子进程被暂停或退出了(包括按逻辑执行结束及被kill)        int status = 0;        int childPid = waitpid(-1, &status, WUNTRACED|WCONTINUED);        if (childPid == -1) {            printTime();            printf("Parent w=-1, error=%s \n", strerror(errno));        } else {            int ifExited, ifSignaled, ifStopped, ifContinued;                        ifExited = WIFEXITED(status);            ifSignaled = WIFSIGNALED(status);            ifStopped = WIFSTOPPED(status);            ifContinued = WIFCONTINUED(status);                        printTime();            printf("pid=%ld child=%d exitCode=%d status=%d ifExited=%d ifSignaled=%d ifStopped=%d ifContinued=%d \n",                   (long)getpid(), childPid, status, _WSTATUS(childPid), ifExited, ifSignaled, ifStopped, ifContinued);                        printTime();            if (ifExited) {                printf("PID=%ld exited, status=%d\n", (long)childPid, WEXITSTATUS(status));            } else if (ifSignaled) {                printf("PID=%ld killed by signal %d\n", (long)childPid, WTERMSIG(status));            } else if (ifStopped) {                printf("PID=%ld stopped by signal %d\n", (long)childPid, WSTOPSIG(status));            } else if (ifContinued) {                printf("PID=%ld continued\n", (long)childPid);            }        }    } else if (sig == SIGCONT) {        // sigcont在本人的mac上调用wait是无效的        // do nothing    } else if (sig == SIGWINCH) {        // do nothing    } else {        printTime();        printf("sig=%d is not valid.\n", sig);    }}static void mainProcessDoSomething(FILE* f) {    int count = 0;    while(1) {        sleep(3);        if (f != NULL){            fflush(f);        }        if (count ++ > 50) {            break;        }    }}int main/*11*/ (int argc, char ** argv) {        // SIGWINCH:应用程序窗口发生变化    signal(SIGWINCH, &handleSignal);        // 子进程被暂停运行    signal(SIGSTOP, &handleSignal);    // 子进程被恢复运行(Mac上无效..)    signal(SIGCONT, &handleSignal);    // SIGCHLD:子进程被暂停或退出了(包括按逻辑执行结束及被kill)    signal(SIGCHLD, &handleSignal);    // 不建议对SIGTERM进行设置    // signal(SIGTERM, &handleSignal);        pid_t pId = fork();    if (pId == -1) {        perror("fork error");        exit(EXIT_FAILURE);    } else if (pId == 0) {        FILE* fChild = freopen("/Users/sodino/workspace/xcode/Define/Define/child_signal.txt", "w", stdout);        int myPid = getpid();        int parentPid = getppid();                printTime();        printf("Child:SelfID=%d ParentID=%d \n", myPid, parentPid);        int count = 0;        do{            count ++;            sleep(5);            printTime();            printf("Child:count=%d \n", count);            fflush(fChild);            if (count >= 20) {                break;            }        }while (1);        printTime();        printf("Child:SelfID=%d exit success.\n", myPid);        fflush(fChild);        fclose(fChild);        return EXIT_SUCCESS;    } else {        FILE * fMain = freopen("/Users/sodino/workspace/xcode/Define/Define/main_signal.txt", "w", stdout);        printTime();        printf("Parent:SelfID=%d MyChildPID=%d \n", getpid(), pId);        fflush(fMain);                // 继续往下执行其它任务,而不像原逻辑 会被wait()所阻塞        mainProcessDoSomething(fMain);                printTime();        printf("Parent:SelfID=%d exit success.\n", getpid());                fflush(fMain);        fclose(fMain);        return EXIT_SUCCESS;    }}
  以上代码中,在
main()
函数一开始,就对
SIGWINCH
SIGSTOP
SIGCHLD
进行监听,统一注册其处理函数为
handleSignal(int)
。然后执行
fork()
生成子进程。

  
handleSignal(int)
函数中会对监听到的信号量做出打印及输出,如果是
SIGSTOP
SIGCHLD
的话则会执行
wait
以获取子进程状态。

  在主进程中,以
mainProcessDoSomething()
函数来表示父进程的其它工作任务,不被
wait
所阻塞。

  编译
signal.c
文件,后在命令行终端下执行
./a.out
,然后鼠标拖动改动一个命令行终端窗口的大小,可见
child_signal.txt
main_signal.txt
都输出了
handleSignal SIGWINCH=28
的日志。

  然后
kill -sigstop child_pid
,再恢复
kill -sigcont child_pid
,然后一直等待子进程运行完毕,可完整看到如下两份日志。   

child_signal.txt

2015-04-19 22:31:31 Child:SelfID=4352 ParentID=4351
2015-04-19 22:31:36 Child:count=1
2015-04-19 22:31:39 PID=4352 handleSignal SIGWINCH=28
2015-04-19 22:31:39 Child:count=2
2015-04-19 22:31:39 PID=4352 handleSignal SIGWINCH=28
2015-04-19 22:31:39 Child:count=3
2015-04-19 22:31:39 PID=4352 handleSignal SIGWINCH=28
2015-04-19 22:31:39 Child:count=4
2015-04-19 22:31:54 Child:count=5
2015-04-19 22:31:59 Child:count=6  // 这里,对子进程执行了kill -sigstop命令
2015-04-19 22:32:21 PID=4352 handleSignal SIGCONT=19
2015-04-19 22:32:21 Child:count=7
2015-04-19 22:32:26 Child:count=8
... ...
... ...
2015-04-19 22:33:16 Child:count=20
2015-04-19 22:33:16 Child:SelfID=4352 exit success.

main_signal.txt

2015-04-19 22:31:31 Parent:SelfID=4351 MyChildPID=4352
2015-04-19 22:31:39 PID=4351 handleSignal SIGWINCH=28
2015-04-19 22:31:39 PID=4351 handleSignal SIGWINCH=28
2015-04-19 22:31:39 PID=4351 handleSignal SIGWINCH=28
2015-04-19 22:32:04 PID=4351 handleSignal SIGCHLD=20
2015-04-19 22:32:04 pid=4351 child=4352 exitCode=4479 status=0 ifExited=0 ifSignaled=0 ifStopped=1 ifContinued=0
2015-04-19 22:32:04 PID=4352 stopped by signal 17
2015-04-19 22:33:16 PID=4351 handleSignal SIGCHLD=20
2015-04-19 22:33:16 pid=4351 child=4352 exitCode=0 status=0 ifExited=1 ifSignaled=0 ifStopped=0 ifContinued=0
2015-04-19 22:33:16 PID=4352 exited, status=0  // 父进程监听到子进程执行完毕
2015-04-19 22:33:55 Parent:SelfID=4351 exit success. // 父进程WHILE循环执行完毕

  可以发现发送给子进程的
SIGSTOP
和运行退出对父进程来说都是
SIGCHLD
。而子进程可以接收到父进程
wait
方法中不支持的
SIGCONT
信号。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: