unix信号转载

来源:互联网 发布:淘宝如何修改店铺名称 编辑:程序博客网 时间:2024/06/01 23:27

引言

信号是软中断,很多比较重要的应用程序都需处理信号。

信号概念

每个信号都有一个名字,都以SIG开头,Linux和Solaris都支持自定义信号,信号定义在中。
信号发生时,可以告诉内核按照下列方式进行处理:

  1. 忽略该信号,两种信号不能忽略,SIGKILL和SIGSTOP
  2. 捕捉信号,调用信号处理函数,不能捕捉SIGKILL和SIGSTOP信号
  3. 按照系统默认的方式进行处理

signal函数

#include <signal.h>void (*signal(int signo, void (*func)(int)))(int);                                // 返回值,成功返回以前的处理配置,出错返回SIG_ERR

func是SIG_DEF, SIG_IGN或处理信号的函数地址
函数原型的意义:该函数返回一个函数指针,该指针接受一个int参数,而函数的参数为signo和一个函数指针,func为当信号signo到来时,执行的程序,返回之前执行的程序的指针。

查看头文件,则有以下声明:

#define SIG_ERR    (void (*)())-1#define SIG_DEF    (void (*)())0#define SIG_IGN    (void (*)())1

在shell上面向进程发信号的指令,如SIGUSR1: kill -USR1 process_id
shell自动将后台进程对中断和退出设置为忽略。

进程创建时,子进程继承父进程的信号处理方式。

不可靠信号

早期的UNIX对信号的控制能力很差,一个信号发生了,但进程可能永远都不知道。早期UNIX对信号进行处理之后,将恢复信号的默认处理方式(假设用户设置了信号处理),为了避免系统恢复默认信号,则程序可以这样设计:

void sigproc(int signo){    if (SIGERR == signal(signo, sigproc))    // 信号发生时再次设置信号处理函数避免系统恢复        // err_proc    // ... signal process}

但是这样会产生一个窗口期,假如在进行信号处理到设置信号之间又产生该信号,而该信号被恢复为默认的方式,则不会产生预期的结果,这种问题还不会被察觉。

中断系统调用

早期UNIX的特性是:如果一个低速系统调用被信号中断,则它不会继续执行。该系统调用返回出错,errno置为EINTR。(低速系统调用是可以被永远阻塞的一种系统调用,读写磁盘I/O不属于低速系统调用)。

对于read、write系统调用,不同的系统会有两种不同的处理方式,一种是失败返回,errno置为EINTR;一种是成功返回,返回已接收到的部分数据量。与被中断的系统调用相关的问题是必须显示的处理系统调用:

again:    if ((n = read(fd, buf, len)) < 0)    {        if (n == EINTR)            goto again;    }

只有低速的系统调用才会被信号中断,为了帮助程序使之不必处理系统中断,有些系统提供了中断系统调用的自动重启动,自动重启动的系统调用包括ioctl, read, write, readv, writev, wait 和 waitpid,前5个只有在低速的系统调用才会被中断,后两个遇到信号就会被中断。

可重入函数

进程捕捉到信号并对信号进行处理时,进程正常执行的指令序列就会被中断,转而进行信号处理,信号处理正常返回时(不调用exit或longjmp),会继续执行原先的指令序列;但是对某些中断会存在问题,比如进程正在执行malloc分配内存,而此时捕捉到一个系统调用,信号处理程序又调用malloc,这样可能会对程序造成破坏。

哪些函数是不可重入的:

  1. 已知它们使用静态数据结构
  2. 它们调用mallo或free
  3. 它们是标准I/O函数(printf等)

若在信号处理程序中调用一个不可重入的函数,则结果是不可以预见的。

可靠信号术语和语义

当进程对某个信号采取某种动作时,称向进程递送了一个信号,在信号的产生和递送的时间间隔内,称信号是未决的。
每个进程都有一个信号屏蔽字,它规定了要阻塞递送到该进程的信号集。对于每种可能的信号,该信号都有一位与之对应。若其对应已被设置,则它当前是被阻塞的。调用sigprocmask检测和更改当前信号屏蔽字,调用sigpending函数判定哪些信号是设置为阻塞并处于未决状态的。POSIX.1定义了一个信号集类型sigset_t,信号屏蔽字就存放在该信号集中。

alarm和pause函数

#include <unistd.h>unsigned int alarm(unsigned int seconds);                                        返回值:0或以前设置闹钟余留的秒数

一个进程只能设置一个时钟,如果在调用alarm时上次设置的时钟还没有到达,则alarm返回上次余留秒数,并且alarm设置为新值。
如果当前设置的秒数为0,而且上次时钟没有到达,则取消当前设置的时钟,返回余留秒数。

#include <unistd.h>int pause(void);                                        返回值:-1并将errno设置为EINTR

只有执行了一个信号处理程序并返回时,pause才返回。

用alarm和pause实现sleep函数:

#include <signal.h>#include <unistd.h> void sigalarm(int signo){    // 只是为了唤醒pause      } unsigned intsleep(unsigned int usecs){    if (SIG_ERR == signal(SIGALRM, sigalarm)) {        printf("signal error!\n");        return usecs;    }     alarm(usecs);    pause();    return alarm(0);}

上面的程序会带来几个问题,其中最不易查觉的问题就是在一个繁忙的系统中,设置了alarm之后,没有等到pause的执行,闹钟就到了,则pause将会永远挂起,解决此问题的方法之一是使用longjmp函数。

#include <setjmp.h>int setjmp(jmp_buf env);                  // 返回值:从setjmp返回0,从longjmp调用返回则返回非0值int longjmp(jmp_buf env, int val);// val所设置的跳转回setjmp处,setjmp的返回值

下面用setmp和longjmp解决上面问题:

static jmp_buf jmp_env;void sigalarm(int signo){    longjmp(jmp_env, 1);} typedef void (*sigproc)(int);unsigned int sleep1(unsigned int nsecs){    sigproc sigpro = signal(SIGALRM, sigalarm);    if (SIG_ERR == sigproc)    {        printf("signal error!\n");        return nsecs;    }    if (setjmp(jmp_env) == 0)    {        alarm(nsecs);        pause();            }     signal(SIGALRM, sigpro);    return alarm(0);}

alarm不仅可以实现sleep,还可以设置低速系统调用的超时,如果低速系统调用超时,则用alarm中断该系统调用:

static jmp_buf jmp_env;char buf[size]; signal(SIGALRM, sigalarm); if (0 != setjmp(jmp_env)){    printf("read time out!\n");   }else{    int nread;    alarm(10);    if ((nread = read(STDIN_FILENO, buf, size))<0)        printf("read error!\n");    alarm(0);} void sigalarm(int signo){    longjmp(jmp_env, 1);}

信号集

#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);                // 四个函数成功返回0,失败返回-1int sigismember(const sigset_t *set, int signo);                // 真返回1,假返回0,出错返回-1

函数sigemptyset清除set中所有信号,sigfillset使set包括所有信号

sigprocmask函数

#include <signal.h>int sigprocmask(int how, const sigset_t *restrict set,                        sigset_t *restrict oset);                                                // 返回值:成功返回0,出错返回-1

若oset非空,则进程当前信号屏蔽字通过oset返回,若set非空,则通过how设置信号屏蔽字。
how的说明:
how
说明
SIG_BLOCK
将信号屏蔽字设置为当前信号屏蔽字和set的并集
SIG_UNBLOCK
从当前信号屏蔽字排除set
SIG_SETMASK
被set指向的信号屏蔽字代替
如果sigprocmask释放了某个未决的信号,则该信号在sigprocmask返回之前将被调用。

sigpending函数

#include <signal.h>int sigpending(sigset_t *set);                                        // 返回值,成功返回0, 失败返回-1

如果set非空,则set返回未决的信号集。

sigaction函数

sigaction函数的功能是检查或修改与指定信号相关联的处理动作,此函数取代了UNIX早期版本使用的signal函数

#include <signal.h>int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);                            // 返回值:成功返回0,失败返回-1

如果act非空,则要修改信号signo的动作,如果oact非空,则会返回该信号上一个动作

struct sigactin{    void (*sa_handler)(int);    sigset_t sa_mask;    int sa_flags;     void (*sa_sigaction)(int, siginfo_t *, void*);};

sa_mask是一个信号集,在调用信号捕捉函数之前,这一信号集要加到信号屏蔽字中。仅当信号处理函数返回时,进程的信号屏蔽字复位为原先值。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。
sa_flags字段指定对信号处理的各个选项,见P262表10-5

使用sigaction函数实现signal函数

#include <signal.h>typedef void (SigFunc)(int); SigFunc *signal(int signo, SigFunc *func){    sigaction act;    sigaction oact;     act.sa_handler = func;    sigemptyset(&act.sa_mask);    act.sa_flags = 0;     if (signo == SIGALRM) {#ifdef SA_INTERRUPT    act.sa_flags |= SA_INTERRUPT;#endif    } else {#ifdef SA_RESTART    act.sa_flags |= SA_RESTART;#endif    }    if (sigaction(signo, &act, &oact) < 0)        return (SIG_ERR);      return oact.sa_handler;}

不希望重启动由sigalrm中断的系统调用的原因是sigalrm可以设置低速系统调用的超时。

sigsetjmp和siglongjmp函数

信号处理程序中经常会调用longjmp函数以返回到程序的主循环中,而进入信号处理程序之前当前信号屏蔽字会屏蔽当前信号,再调用longjmp回到主程序时,信号屏蔽字会变成什么呢?POSIX没有说明setjmp和longjmp对信号屏蔽字的作用,而是定义了另外两个函数sigsetjmp和siglongjmp函数,sigsetjmp保存当前信号屏蔽字,当调用siglongjmp跳转到sigsetjmp执行的位置时,信号屏蔽字会恢复。

#include <setjmp.h>int sigsetjmp(sigjmp_buf env, int savemask);                    返回值:若直接调用返回0,从siglongjmp返回则返回非0void siglongjmp(sigjmp_buf env, int val);

实例在sigjmp.c中实现。

sigsuspend函数

sigsuspend函数接受一个信号集指针,将信号屏蔽字设置为信号集中的值,在进程接受到一个信号之前,进程会挂起,当捕捉一个信号,首先执行信号处理程序,然后从sigsuspend返回,最后将信号屏蔽字恢复为调用sigsuspend之前的值。

#include <signal.h>int sigsuspend(const sigset_t *sigmask);                                                            // 返回值:-1,并将errno设置为EINTR

sigsuspend的典型用法就是利用循环等待某一特定信号的到来,当进程捕捉到该信号,则进行特定的处理,例如给全局变量赋值等。

volatile sig_atomic_t quitflag;...void sigproc(int signo){    quitflag = 1;  }...sigset_t zeromask;sigemptyset(&zeromask);signal(SIGINT, sigproc);...while (quitflag == 0){    sigsuspend(&zeromask);}

可用信号实现父子之间的进程同步,它们是TELL_WAIT,TELL_PARENT,WAIT_PARENT,TELL_CHILD,WAIT_CHILD,见程序sync_process.c。

0 0
原创粉丝点击