信号

本章函数都是定义在<signal.h>

1.可靠的信号和不可靠的信号

1.1不可靠的信号

信号可能会丢失,但是进程并不知道此事情,这是早期信号的弊端,并且早期实现对信号的控制也是蛮差的,列如用户希望内核阻塞某个信号,但是不能忽略他,在合适的时候进行释放,当时不具备此种能力    

1.2可靠的信号

1.2.1递送和未决:当一个信号产生时,内核通常在进程表中以某种形式设置一个标志,此动作叫做递送,在信号产生和递送的时间间隔内,此时间段内信号是未决的,并且只有在递送了一个信号的时候,才决定进程对信号的处理方式

1.2.2在可靠的信号中,可以对信号进行阻塞,阻塞是把信号阻塞在未决的状态,所以很据1.2.1所述,只要阻塞在未决状态,信号没有进行递送,就可以在次状态中改变信号的处理方式。

1.2.2如果信号在阻塞期间发生多次怎么办

因为是可靠的信号,所以,在这种情况下,应该对信号进行阻塞,但是,当解除阻塞的时候,被阻塞的信号会根据选择,进行递送一次或者多次,如果是进行递送了多次,则说明信号发生了排队,但是此种发生排队的现象,必须支持POSIX.1的实时扩展

2.系统的中断调用

定义:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则系统调用就被中断不在继续执行,该系统调用返回出错,error被设置成EINTR,这样处理是因为一个信号发生了,进程捕捉到他,这意味着某个重要的事情发生了,在此时,不应该进程阻塞在低速系统调用中,而是应该唤醒阻塞进程,对信号进行处理

2.1关于全局变量errno的理解

经常在调用linux 系统api 的时候会出现一些错误,比方说使用open() write() creat()之类的函数有些时候会返回-1,也就是调用失败,这个时候往往需要知道失败的原因。这个时候使用errno这个全局变量就相当有用了。
在程序代码中包含 #include <errno.h>,然后每次程序调用失败的时候,系统会自动用用错误代码填充errno这个对象(下面介绍)

errno这个全局变量在<errno.h>头文件中声明如下:extern int errno;


errno是一个由POSIX和ISO C标准定义的符号,看(用)起来就好像是一个整形变量。当系统调用或库函数发生错误的时候,比如以只读方式打开一个不存在的文件时,它的值将会被改变,根 据errno值的不同,我们就可以知道自己的程序发生了什么错误,然后进行相应的处理。

为什么,要强调errno看起来好像是一个整形变量呢?因为有的标准(如ISO C)只规定了errno的作用,而没有规定它的实现方式,它可能被定义成一个变量,也有可能被定义成一个宏,这个具体要看编译器自己的实现。早些时 候POSIX.1曾把errno定义成extern int errno这种形式,但现在这种方式比较少见了。因为以这种形式来实现errno,在多线程环境下errno变量是被多个线程共享的,这样可能线程A发生 某些错误改变了errno的值,线程B虽然没有发生任何错误,但是当它检测errno的值的时候,线程B会以为自己发生了错误。所以现在errno在 Linux中被实现成extern int * __errno_location(void): #define errno (*__errno_location()),这样每个线程都有自己的errno,不会再发生混乱了。

2.2低速系统调用

系统中有两种系统调用,一种低速系统调用和其他系统调用,低速系统调用是会让进程永远阻塞的一类系统调用

(1)在读某些类型的文件时(这种系统调用是读操作,读操作,读操作!!!)如果数据并不存在则可能会使

调用者永远阻塞(管道、终端设备以及网络设备)。

(2)在写这些类型(指的是写类型的系统调用)指的是管道,终端设备,网络设备)的文件时,如果不能立即

接受这些数据,则也可能会使调用者永远阻塞。

(3)打开文件(一些打开文件的系统调用),在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,

它要等待直到所连接的调制解调器应答)。

(4)pause(按照定义,它使调用进程睡眠直至捕捉到一个信号 )和wait

(5)某种ioctl操作。

(6)某些进程间通信函数(见第 1 4章)

在这些低速系统调用中,有一个比较值得注意的是与磁盘IO有关的系统调用

虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间)

,但是除非发生硬件错误, I / O操作总会很快返回,并使调用者不再处于阻塞状态。

2.3对于如何处理read和write系统调用,当被中断的时候处理方案(POSIX 2001)

(1)如果在read函数时发生中断,但是没有收到全部的信息,系统可以认为是失败的,并且将errno设置为

EINTR

(2)系统液可以认为调用是成功的,并且返回已经处理完的数据

2.4当被中断的系统调用出错返回的时候,我们有可能希望他重新启动

为了帮助应用程序使其不必处理被中断的系统调用, 4 . 2 B S D引进了某些被中断的系统调

用的自动再起动。自动再起动的系统调用包括: i o c t l、r e a d、r e a d v、w r i t e、w r i t e v、w a i t和

w a i t p i d。正如前述,其中前五个函数只有对低速设备进行操作时才会被信号中断。而 w a i t和

w a i t p i d在捕捉到信号时总是被中断。某些应用程序并不希望这些函数被中断后再起动,因为这

种自动再起动的处理方式也会带来问题,为此 4 . 3 B S D允许进程在每个信号各别处理的基础上

不使用此功能。

当sigaction指定为SA_RESTART进行中断的系统调用重新启动,signal,被中断的系统调用是默认启动的,

但是在signal中各种平台处理不一样,应该自己定义signal,可以加强可移植性能

3.可重入函数

注意:当进程正在执行执行正常的指令的时候,如果此时有信号发生,则应该首先执行信号处理程序,执行信号处理的过程应该能返回发生信号的地方,然后按照正常的程序执行流程执行

3.1什么叫做可重入:

比如说进程正在执行malloc函数,此时正好发生一个信号,进入信号处理,在信号处理的过程中,再一次执行malloc函数,此时,因为在堆上就会发生错误,这就叫做不可重入的(在执行某一个函数的时候,信号处理程序发生,处理程序中又有执行此函数,但是此函数会对进程产生破坏)

3.2哪些函数是不可重入的

(1)使用了静态数据结构(2)调用了malloc或者free(3)他们是标准的IO函数(因为标准的IO函数使用了全局数据结构)

3.3errno变量

要注意系统调用中的errno,为了保证函数的可重入性,应该在调用信号处理程序的时候保存errno的值,然后在返回的时候发送

4.signal:

函数原型是 void (*signal(int signo,void (*func) (int))) (int);

此函数因为声明过于复杂,所以使用了typedef进行简化typedef void Sigfunc(int);

Sigfunc *signal(int ,Sigfunc*);

在ubuntu中,signal信号默认不阻塞本信号(如果在信号处理程序中,再次发生了本信号,不进行本次信号的阻塞),并且在有本次信号到来的时候恢复系统的默认动作

5.sigaction函数:

int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact)

5.1signo是信号的编号

5.2sigaction解释

struct sigaction

{

    void (*sa_handler)(int);

    sigset_t sa_mask;

    int sa_flags;

    void (*sa_sigaction)(int,siginfo_t*,void *);

}

5.2.1sa_mask信号屏蔽字,当进程在调用sa_handler之前,sa_mask加入进程的信号屏蔽字中,当从信号处理程序返回的时候,然后恢复原来的信号屏蔽字,注意在信号处理程序中,正在处理的信号被自动加入信号屏蔽字中,若在发生此信号,应该对其进行阻塞,如果一个信号发生多次,一般不将其放入信号队列中,最后,解阻塞的时候,多次相同的信号,只发生一次

5.2.2sa_handler是信号的处理程序,在再次改变信号处理程序的时候,该设置一直有效

5.2.3sa_flags:

SA_NODEFER:

当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号。注意,此种类型的操作对应于早期的不可

靠信号

SA_RESETHAND:

如果加入此标志,在进入信号处理程序的时候将信号的处理方式设置为SIG_DFL,并且清除SA_SIGINFO,并且此种标志对应以前不可靠的信号!!!对于SIGILL和SIGTRAP信号,此设置是无效的

SA_INTERRUPT:

由此信号中断的系统调用不自动重启动

SA_RESTART:

由此信号中断的系统调用自动重启动

SA_SIGINFO:

由此选项对信号处理程序提供了附加信息,一个指向siginfo结构的指针以及一个指向程序上下文标识符的指针

当设置了此标志,调用以下信号处理程序

void handler(int signo,siginfo_t *info,void *context)


6.sigsuspend函数

1)头文件:#include <signal.h>

2)一个保护临界区代码的错误实例:(sigprocmask()和pause()实现)

#include <unistd.h>

#include <signal.h>

#include <stdio.h> 

void handler(int sig)    //信号处理函数的实现

{

   printf("SIGINT sig");

}

int main()

{

    sigset_t new,old;

    struct sigaction act;

    act.sa_handler = handler;  //信号处理函数handler

    sigemptyset(&act.sa_mask);

    act.sa_flags = 0;

    sigaction(SIGINT, &act, 0);  //准备捕捉SIGINT信号

    sigemptyset(&new);

    sigaddset(&new, SIGINT);

    sigprocmask(SIG_BLOCK, &new, &old);  //将SIGINT信号阻塞,同时保存当前信号集

    printf("Blocked");

    sigprocmask(SIG_SETMASK, &old, NULL);  //取消阻塞

    pause();

    return 0;

}

上面实例的问题是:本来期望pause()之后,来SIGINT信号,可以结束程序;可是,如果当“取消阻塞”和“pause”之间,正好来了SIGINT信号,结果程序因为pause的原因会一直挂起。。。

解决的方式,当然是sigsuspend()函数了。

 

3)使用sigsuspend()的程序

#include <unistd.h>

#include <signal.h>

#include <stdio.h>

void handler(int sig)   //信号处理程序

{

   if(sig == SIGINT)

      printf("SIGINT sig");

   else if(sig == SIGQUIT)

      printf("SIGQUIT sig");

   else

      printf("SIGUSR1 sig");

}

 

int main()

{

    sigset_t new,old,wait;   //三个信号集

    struct sigaction act;

    act.sa_handler = handler;

    sigemptyset(&act.sa_mask);

    act.sa_flags = 0;

    sigaction(SIGINT, &act, 0);    //可以捕捉以下三个信号:SIGINT/SIGQUIT/SIGUSR1

    sigaction(SIGQUIT, &act, 0);

    sigaction(SIGUSR1, &act, 0);

   

    sigemptyset(&new);

    sigaddset(&new, SIGINT);  //SIGINT信号加入到new信号集中

    sigemptyset(&wait);

    sigaddset(&wait, SIGUSR1);  //SIGUSR1信号加入wait

    sigprocmask(SIG_BLOCK, &new, &old);       //将SIGINT阻塞,保存当前信号集到old中

   

    //临界区代码执行    

  

    if(sigsuspend(&wait) != -1)  //程序在此处挂起;用wait信号集替换new信号集。即:过来SIGUSR1信  号,阻塞掉,程序继续挂起;过来其他信号,例如SIGINT,则会唤醒程序。执行sigsuspend的原子操作。注意:如果“sigaddset(&wait, SIGUSR1);”这句没有,则此处不会阻塞任何信号,即过来任何信号均会唤醒程序。

        printf("sigsuspend error");

    printf("After sigsuspend");

    sigprocmask(SIG_SETMASK, &old, NULL);

    return 0;

}

sigsuspend的原子操作是:

(1)设置新的mask阻塞当前进程(上面是用wait替换new,即阻塞SIGUSR1信号)

(2)收到SIGUSR1信号,阻塞,程序继续挂起;收到其他信号,继续运行sigsupsend。

(3)调用该进程设置的信号处理函数(程序中如果先来SIGUSR1信号,然后过来SIGINT信号,则信号处理函数会调用两次,打印不同的内容。第一次打印SIGINT,第二次打印SIGUSR1,因为SIGUSR1是前面阻塞的)

(4)待信号处理函数返回,sigsuspend返回了。(sigsuspend将捕捉信号和信号处理函数集成到一起了)恢复原先的mask(即包含SIGINT信号的)














原文地址:https://www.cnblogs.com/SmileLion/p/5863584.html