UNIX环境C语言编程(9)-信号

来源:互联网 发布:网络老虎机揭秘 编辑:程序博客网 时间:2024/05/01 19:24

1、信号概念

每个信号有一个名字,以SIG打头,在头文件signal.h中定义为正整数
能够产生信号的情形有:
1、终端按键产生,如Ctrl+C产生的中断信号
2、硬件例外产生,如除以0,或无效的内存访问等
3、通过kill()函数给指定进程或进程组发送信号
4、使用kill命令给指定进程或进程组发送信号
5、软件条件产生,如SIGPIPE(管道)、SIGALARM(定时)
信号是异步事件的典型例子,可以告知内核,信号到达时,执行三种操作之一:
1、忽略信号,但是有两个信号不能被忽略或捕获:SIGKILLSIGSTOP
2、捕获信号,信号发生时,执行事先注册的一个函数
3、系统默认,每个信号对应一个默认动作,大多数信号的默认动作时终止进程
典型信号:SIGALRMSIGCHLDSIGHUPSIGINTSIGKILLSIGQUITSIGTERMSIGUSR1SIGUSR2

 

2、signal函数

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
func参数的取值:1SIG_IGN 2SIG_DFL  3、用户自定义函数
如果定义一个类型别名,typedef voidSigfunc(int);
那么signal函数可以声明为:Sigfunc *signal(int,Sigfunc *);
一个例子,使用kill命令演示信号机制(kill命令的名字并不恰当)
#include <stdio.h>#include <stdlib.h>#include <signal.h>static void sig_usr(int);   /* one handler for both signals */int main(void){    if( signal(SIGUSR1, sig_usr) == SIG_ERR )    {        perror("can't catch SIGUSR1");        exit(0);    }    if( signal(SIGUSR2, sig_usr) == SIG_ERR )    {        perror("can't catch SIGUSR2");        exit(0);    }    for( ; ; )        pause();}static void sig_usr(int signo)      /* argument is signal number */{    switch( signo )    {    case SIGUSR1:        printf("received SIGUSR1\n");        break;    case SIGUSR2:        printf("received SIGUSR2\n");        break;    default:        printf("received signal %d\n", signo);    }}

在后台执行的进程,由shell自动忽略中断与退出信号

 

3、不可靠信号

早期Unix版本中,信号可能会丢失,进程并不知晓
另外,进程对信号的控制能力很弱,不能临时阻塞信号
进程每次处理信号时,系统随即自动将信号动作复置为默认值
不可靠的代码
...
signal(SIGINT,sig_int);  /* establish handler */
...
sig_int()
{
        signal(SIGINT, sig_int); /* reestablish handler for next time */
        ...                                       /* process the signal ... */
}

 

4、可重入函数

我的理解:指使用相同的参数多次调用,其行为一致
记住信号随时可能发生,在信号处理函数中只能使用可重入函数
进程捕捉到信号时,首先执行信号处理程序中的指令,从信号处理程序返回时,继续执行在捕捉到信号时进程正在执行的正常指令
不可重入函数包括:
1、使用静态数据结构
2、函数中调用了mallocfree
3、标准I/O库的一部分,标准I/O库大多使用了全局数据结构
注:在信号处理程序中使用printf其实也是不可靠的,可能产生非预期行为
编写信号处理函数的总体原则就是充分考虑对被中断函数的影响
/* * 在AIX平台演示,程序异常 * 信号处理程序中使用了不可重入函数 */#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <pwd.h>static void my_alarm(int signo){    struct passwd   *rootptr;    printf("in signal handler\n");    signal(SIGALRM, my_alarm);    if((rootptr = getpwnam("root")) == NULL )    {        printf("getpwnam(root) error\n");        exit(0);    }    alarm(1);}int main(void){    struct passwd   *ptr;    signal(SIGALRM, my_alarm);    alarm(1);    for( ;; )    {        if( (ptr = getpwnam("billing")) == NULL )        {            printf("getpwnam error\n");            exit(0);        }        if( strcmp(ptr->pw_name, "billing") != 0 )        {            printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);        }    }}


5、SIGCLD信号语义

早期版本的Unix系统(如System v),对于SIGCLD特殊处理:
1、如果应用程序明确将SIGCLD信号设置为SIG_IGN,其子进程终止时不会变成僵尸,子进程终止时,其状态被丢弃
2、如果将SIGCLD信号的处理动作设置为捕获,内核将立即检查是否有已经终止的子进程,如果有,将调用信号处理程序
看下面这个程序,在某些版本的Unix系统中不能正常运行
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <signal.h>#include <sys/wait.h>static void sig_cld(int);int main(){    pid_t   pid;    signal(SIGCLD, sig_cld);    if ((pid = fork()) < 0)    {        perror("fork error");        exit(0);    }    else if( pid == 0 )/* child */    {        sleep(2);        _exit(0);    }    pause();    /* parent */    exit(0);}static void sig_cld(int signo)        /* interrupts pause() */{    pid_t   pid;    int     status;    printf("SIGCLD received\n");    signal(SIGCLD, sig_cld);          /* reestablish handler */    if ((pid = wait(&status)) < 0)    /* fetch child status */    {        perror("wait error");        exit(0);    }    printf("pid = %d\n", pid);}

了解一下原因

 

6、killraise函数

#include <signal.h>
int kill(pid_tpid, intsigno); # 向指定进程/进程组发送信号
int raise(intsigno); # 给自己发送一个信号
kill函数的参数:
pid > 0 信号发送给指定进程
pid = 0 信号发送给同一进程组内的所有进程
pid < 0 信号发送给特定进程组(ID等于pid绝对值)内的进程
pid = -1 信号发送给当前进程有权限发送的所有进程
如果signo0,并不实际发出信号,可以检测指定进程是否存在

 

7、alarmpause函数

#include <unistd.h>
unsigned int alarm(unsignedint seconds);
alarm设定一个定时器,届时将触发SIGALRM信号
每个进程只有一个定时器,调用alarm时,如果之前的一个定时器尚未触发,那么其剩余时间将被返回
如果参数seconds0,将取消定时器设置
int pause(void); # 阻塞,直到捕获一个信号后返回
有三个问题:
1、如果调用者之前已经设置了一个定时器,将被函数中的alarm取消
2、函数中修改了SIGALRM信号的处理逻辑
3、在第一个alarmpause之间存在竞态条件

例子:sleep的简单实现

#include     <stdio.h>#include     <stdlib.h>#include     <signal.h>#include     <unistd.h>static void sig_alrm(int signo){    /* nothing to do, just return to wake up the pause */}unsigned int sleep1(unsigned int nsecs){    if( signal(SIGALRM, sig_alrm) == SIG_ERR )        return(nsecs);    alarm(nsecs);       /* start the timer */    pause();            /* next caught signal wakes us up */    return(alarm(0));   /* turn off timer, return unslept time */}int main(void){    sleep1(4);}


8、信号集

顾名思义,就是信号的集合表示
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(constsigset_t *set, int signo);
初始化信号集时,必须调用sigemptysetsigfillset之一

 

9、sigprocmask()函数

#include <signal.h>
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrictoset);
操作进程的信号屏蔽,被屏蔽的信号不能递交给进程,被阻塞
how参数的取值:SIG_BLOCKSIG_UNBLOCKSIG_SETMASK
注意:
同一个信号是不能累积的,或者说是不排队的
解除阻塞后,阻塞期间同一个信号的多次发生只能递交一个
#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <errno.h>void pr_mask(const char *str){    sigset_t    sigset;    int         errno_save;    errno_save = errno;     /* we can be called by signal handlers */    if( sigprocmask(0, NULL, &sigset) < 0 )    {        perror("sigprocmask error");        exit(0);    }    printf("%s: ", str);    if( sigismember(&sigset, SIGINT) )   printf("SIGINT ");    if( sigismember(&sigset, SIGQUIT) )  printf("SIGQUIT ");    if( sigismember(&sigset, SIGUSR1) )  printf("SIGUSR1 ");    if( sigismember(&sigset, SIGALRM) )  printf("SIGALRM ");    /* remaining signals can go here */    printf("\n");    errno = errno_save;}int main(void){    sigset_t  st;    pr_mask("main 1");    sigemptyset(&st);    sigaddset(&st, SIGINT);    /* 屏蔽 INT 信号 */    sigprocmask(SIG_BLOCK, &st, NULL);    pr_mask("main 2");    printf("sleep 10 ...\n");    sleep(10);    /* 解除 INT 信号屏蔽 */    sigprocmask(SIG_UNBLOCK, &st, NULL);    pr_mask("main 3");}


10、sigpending函数

#include <signal.h>
int sigpending(sigset_t *set);
获取当前因阻塞而未递交给进程的信号集

 

11、sigaction函数

用以检查或(和)设置与特定信号相关的处理动作,作为signal函数的替代
int sigaction(intsigno, conststruct sigaction *restrict act,struct sigaction *restrictoact);
structsigaction的成员:
1sa_handler,指定处理函数的地址
2sa_mask,在处理函数调用之前,被临时加入进程的信号屏蔽,处理返回后恢复
调用处理函数时,当前正在被处理的信号也会临时加入进程的信号屏蔽
3sa_flags,指定一些可选的处理选项
sigaction函数设置的信号处理动作,除非显示改变,不会自动恢复为缺省动作。
在一些版本的Unix系统中,使用sigaction函数实现signal函数。

 

12、sigsetjmpsiglongjmp函数

setjmp/longjmp函数相比,sigsetjmp/siglongjmp保存/恢复进程的信号屏蔽
在信号处理函数中应该调用这两个sig打头的函数
#include <stdio.h>#include <stdlib.h>#include <setjmp.h>#include <time.h>#include <errno.h>#include <signal.h>static void                         sig_usr1(int), sig_alrm(int);static sigjmp_buf                   jmpbuf;static volatile sig_atomic_t        canjump;void pr_mask(const char *str){    sigset_t    sigset;    int         errno_save;    errno_save = errno;     /* we can be called by signal handlers */    if( sigprocmask(0, NULL, &sigset) < 0 )    {        perror("sigprocmask error");        exit(0);    }    printf("%s: ", str);    if( sigismember(&sigset, SIGINT) )   printf("SIGINT ");    if( sigismember(&sigset, SIGQUIT) )  printf("SIGQUIT ");    if( sigismember(&sigset, SIGUSR1) )  printf("SIGUSR1 ");    if( sigismember(&sigset, SIGALRM) )  printf("SIGALRM ");    /* remaining signals can go here */    printf("\n");    errno = errno_save;}int main(void){    signal(SIGUSR1, sig_usr1);    signal(SIGALRM, sig_alrm);    pr_mask("starting main: ");     /* Figure 10.14 */    if( sigsetjmp(jmpbuf, 1))    {        pr_mask("ending main: ");        exit(0);    }    canjump = 1;         /* now sigsetjmp() is OK */    for( ;; )        pause();}static void sig_usr1(int signo){    time_t  starttime;    if( canjump == 0 )        return;     /* unexpected signal, ignore */    pr_mask("starting sig_usr1: ");    alarm(3);               /* SIGALRM in 3 seconds */    starttime = time(NULL);    for( ;; )               /* busy wait for 5 seconds */        if (time(NULL) > starttime + 5)            break;    pr_mask("finishing sig_usr1: ");    canjump = 0;    siglongjmp(jmpbuf, 1);  /* jump back to main, don't return */}static void sig_alrm(int signo){    pr_mask("in sig_alrm: ");}


13、sigsuspend函数

可以屏蔽/解除屏蔽指定信号,这种技术可以用以保护关键代码段,如:
屏蔽信号
关键代码段
解除屏蔽信号
思考:如果我们想在解除屏蔽之后调用pause,等待之前被阻塞信号的发生,该如何做?
  存在的问题:如果信号在屏蔽期间发生,那么pause将不能被中断。
      sigset_t     newmask, oldmask;      sigemptyset(&newmask);      sigaddset(&newmask, SIGINT);      /* block SIGINT and save current signal mask */      sigprocmask(SIG_BLOCK, &newmask, &oldmask);      /* critical region of code */      /* reset signal mask, which unblocks SIGINT */      sigprocmask(SIG_SETMASK, &oldmask, NULL);      /* window is open */      pause();  /* wait for signal to occur */      /* continue processing */

为解决这个问题,需要在一个原子操作中恢复信号屏蔽,然后睡眠,为此引入:
int sigsuspend(constsigset_t *sigmask);
1、进程的信号屏蔽首先设置为参数sigmask,然后进程休眠直到捕获一个信号
2、从信号处理动作返回时,sigsuspend返回,然后信号屏蔽恢复为调用之前的状态
#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <errno.h>static void sig_int(int);void pr_mask(const char *str){    sigset_t    sigset;    int         errno_save;    errno_save = errno;     /* we can be called by signal handlers */    if( sigprocmask(0, NULL, &sigset) < 0 )    {        perror("sigprocmask error");        exit(0);    }    printf("%s", str);    if( sigismember(&sigset, SIGINT) )   printf("SIGINT ");    if( sigismember(&sigset, SIGQUIT) )  printf("SIGQUIT ");    if( sigismember(&sigset, SIGUSR1) )  printf("SIGUSR1 ");    if( sigismember(&sigset, SIGALRM) )  printf("SIGALRM ");    /* remaining signals can go here */    printf("\n");    errno = errno_save;}int main(void){    sigset_t    newmask, oldmask, waitmask;    pr_mask("program start: ");    signal(SIGINT, sig_int);    sigemptyset(&waitmask);    sigaddset(&waitmask, SIGUSR1);    sigemptyset(&newmask);    sigaddset(&newmask, SIGINT);    /*     * Block SIGINT and save current signal mask.     */    sigprocmask(SIG_BLOCK, &newmask, &oldmask);    /*     * Critical region of code.     */    pr_mask("in critical region: ");    /*     * Pause, allowing all signals except SIGUSR1.     */    sigsuspend(&waitmask);    pr_mask("after return from sigsuspend: ");    /*     * Reset signal mask which unblocks SIGINT.     */    sigprocmask(SIG_SETMASK, &oldmask, NULL);    /*     * And continue processing ...     */    pr_mask("program exit: ");    exit(0);}static void sig_int(int signo){    pr_mask("\nin sig_int: ");}

sigsuspend的应用场景之一:用于进程同步
#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <errno.h>static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */static sigset_t newmask, oldmask, zeromask;static void sig_usr(int signo)   /* one signal handler for SIGUSR1 and SIGUSR2 */{    sigflag = 1;}void TELL_WAIT(void){    signal(SIGUSR1, sig_usr);    signal(SIGUSR2, sig_usr);    sigemptyset(&zeromask);    sigemptyset(&newmask);    sigaddset(&newmask, SIGUSR1);    sigaddset(&newmask, SIGUSR2);    /*     * Block SIGUSR1 and SIGUSR2, and save current signal mask.     */    sigprocmask(SIG_BLOCK, &newmask, &oldmask);}void TELL_PARENT(pid_t pid){    kill(pid, SIGUSR2);              /* tell parent we're done */}void WAIT_PARENT(void){    while( sigflag == 0 )        sigsuspend(&zeromask);   /* and wait for parent */    sigflag = 0;    /*     * Reset signal mask to original value.     */    sigprocmask(SIG_SETMASK, &oldmask, NULL);}void TELL_CHILD(pid_t pid){    kill(pid, SIGUSR1);             /* tell child we're done */}void WAIT_CHILD(void){    while( sigflag == 0 )        sigsuspend(&zeromask);  /* and wait for child */    sigflag = 0;    /*     * Reset signal mask to original value.     */    sigprocmask(SIG_SETMASK, &oldmask, NULL);}static void charatatime(char *);int main(void){    pid_t   pid;    TELL_WAIT();            /* 初始化同步结构 */    setbuf(stdout, NULL);  /* set unbuffered */    if( (pid = fork()) < 0 )    {        perror("fork");        exit(0);    }    else if( pid == 0 )    {        WAIT_PARENT();      /* 等待父进程完成 */        charatatime("output from child\n");    }    else    {        charatatime("output from parent\n");        TELL_CHILD(pid);    /* 通知子进程已完成 */    }    exit(0);}static void charatatime(char *str){    while( *str ) putc(*(str++), stdout);}


14、abort函数

#include <stdlib.h>
void abort(void);
给调用进程自己发送SIGABRT信号

 

15、system函数

system函数的实现需要充分考虑信号的处理
这儿只描述一下system函数的返回值:
system函数所执行的命令被信号中断时,其返回值是128+信号编号

 

16、sleep函数

看似简单,处处留心皆学问
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
两种情况下返回:
1、指定的时间流逝后,此时返回0
2、被信号中断,此时返回剩余的未睡眠时间
如果使用SIGALRM信号实现sleep,需要注意与函数之外的alarm的交互影响

 

17、任务控制信号

SIGCHLD Child process has stopped or terminated.
SIGCONT Continue process, if stopped.
SIGSTOP Stop signal (can't be caught or ignored).
SIGTSTP Interactive stop signal.
SIGTTIN Read from controlling terminal by member of a background process group.
SIGTTOU Write to controlling terminal by member of a background process group.
应用程序通常无需考虑,只有涉及终端管理的程序才需要考虑,如vi

 

0 0
原创粉丝点击