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

1、信号概念

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

 

2、signal函数

#include < signal.h >
void (* signal ( int signo , void (* func )( int )))( int );
func 参数的取值: 1 SIG_IGN  2 SIG_DFL  3 、用户自定义函数
如果定义一个类型别名, typedef void Sigfunc ( 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 、函数中调用了 malloc free
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_t pid , int signo );   # 向指定进程 / 进程组发送信号
int raise ( int signo );   # 给自己发送一个信号
kill 函数的参数:
pid > 0  信号发送给指定进程
pid = 0  信号发送给同一进程组内的所有进程
pid < 0  信号发送给特定进程组( ID 等于 pid 绝对值)内的进程
pid = -1  信号发送给当前进程有权限发送的所有进程
如果 signo 0 ,并不实际发出信号,可以检测指定进程是否存在

 

7、alarmpause函数

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

例子: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 ( const sigset_t *set, int signo );
初始化信号集时,必须调用 sigemptyset sigfillset 之一

 

9、sigprocmask()函数

#include < signal.h >
int sigprocmask ( int how, const sigset_t *restrict set, sigset_t *restrict oset );
操作进程的信号屏蔽,被屏蔽的信号不能递交给进程,被阻塞
how 参数的取值: SIG_BLOCK SIG_UNBLOCK SIG_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 ( int signo , const struct sigaction *restrict act, struct sigaction *restrict oact );
struct sigaction 的成员:
1 sa_handler ,指定处理函数的地址
2 sa_mask ,在处理函数调用之前,被临时加入进程的信号屏蔽,处理返回后恢复
调用处理函数时,当前正在被处理的信号也会临时加入进程的信号屏蔽
3 sa_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 ( const sigset_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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值