Linux信号signal介绍,signal()函数,sigaction()函数



信号(signal)是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。进程收到一个信号后,会检查对该信号的处理机制。如果是SIG_IGN,就忽略该信号;如果是SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;如果给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。


SIGHUP     终止进程     终端线路挂断
SIGINT     终止进程     中断进程
SIGQUIT   建立CORE文件终止进程,并且生成core文件
SIGILL   建立CORE文件       非法指令
SIGTRAP   建立CORE文件       跟踪自陷
SIGBUS   建立CORE文件       总线错误
SIGSEGV   建立CORE文件       段非法错误
SIGFPE   建立CORE文件       浮点异常
SIGIOT   建立CORE文件       执行I/O自陷
SIGKILL   终止进程     杀死进程
SIGPIPE   终止进程     向一个没有读进程的管道写数据
SIGALARM   终止进程     计时器到时
SIGTERM   终止进程     软件终止信号
SIGSTOP   停止进程     非终端来的停止信号
SIGTSTP   停止进程     终端来的停止信号
SIGCONT   忽略信号     继续执行一个停止的进程
SIGURG   忽略信号     I/O紧急信号
SIGIO     忽略信号     描述符上可以进行I/O
SIGCHLD   忽略信号     当子进程停止或退出时通知父进程
SIGTTOU   停止进程     后台进程写终端
SIGTTIN   停止进程     后台进程读终端
SIGXGPU   终止进程     CPU时限超时
SIGXFSZ   终止进程     文件长度过长
SIGWINCH   忽略信号     窗口大小发生变化
SIGPROF   终止进程     统计分布图用计时器到时
SIGUSR1   终止进程     用户定义信号1
SIGUSR2   终止进程     用户定义信号2
SIGVTALRM 终止进程     虚拟计时器到时

1) SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控 制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联. 
2) SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出 
3) SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号. 
4) SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号. 
5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用. 
6) SIGABRT 程序自己发现错误并调用abort时产生. 
6) SIGIOT 在PDP-11上由iot指令产生, 在其它机器上和SIGABRT一样. 
7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数. 
8) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误. 
9) SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略. 
10) SIGUSR1 留给用户使用 
11) SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据. 
12) SIGUSR2 留给用户使用 
13) SIGPIPE Broken pipe 
14) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号. 
15) SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号. 
17) SIGCHLD 子进程结束时, 父进程会收到这个信号. 
18) SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符 
19) SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别: 该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略. 
20) SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号 
21) SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行. 
22) SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到. 
23) SIGURG 有"紧急"数据或out-of-band数据到达socket时产生. 
24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变 
25) SIGXFSZ 超过文件大小资源限制. 
26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间. 
27) SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间. 
28) SIGWINCH 窗口大小改变时发出. 
29) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作. 
30) SIGPWR Power failure 

有两个信号可以停止进程:SIGTERM和SIGKILL。 SIGTERM比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。在关闭程序之前,您可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。

对于SIGKILL信号,进程是不能忽略的。这是一个 “我不管您在做什么,立刻停止”的信号。假如您发送SIGKILL信号给进程,Linux就将进程停止在那里。 


下面就来说说与信号有关的函数吧。


最简单signal函数


typedef void (*sighandler_t) (int)

sighandler_t signal(int signum, sighandler_t handler);

 

返回原信号处理函数,或SIG_ERR


signal()是最简单的给进程安装信号处理器的函数,第一个参数指定信号,第二个参数为该信号指定一个处理函数。

 

如下是一个最简单的处理信号的程序,它捕捉SIGUSR1,忽略SIGUSR2,按系统默认处理SIGINT,SIGUSR1和SIGUSR2是Linux提供的用户定义信号,可用于任何应用程序。主程序什么都不干,只用pause()循环等待信号。

 
例程1 最简单的信号处理

1.static void pr_mask(const char * string) {
2.    sigset_t procmask;
3.

4.    sigprocmask(SIG_SETMASK, NULL, &procmask);
5.

6.    printf("%s: ", string);
7.    if(sigismember(&procmask, SIGINT))
8.        printf("SIGINT ");
9.    if(sigismember(&procmask, SIGUSR1))
10.        printf("SIGUSR1 ");
11.    if(sigismember(&procmask, SIGUSR2))
12.        printf("SIGUSR2 ");
13.    if(sigismember(&procmask, SIGTERM))
14.        printf("SIGTERM ");
15.    if(sigismember(&procmask, SIGQUIT))
16.        printf("SIGQUIT ");
17.    printf("/n");
18.}
19.

20.static void sigusr(int signum)
21.{
22.    pr_mask(“int sigusr”);
23.

24.    if(signum == SIGUSR1)
25.        printf(“SIGUSR1 received/n”);
26.    else if(signum == SIGUSR2)
27.        printf(“SIGUSR2 received/n”);
28.    else29.        printf(“signal %d received/n”, signum);
30.}
31.

32.int main(void)
33.{
34.    if(signal(SIGUSR1, sig_usr) == SIG_ERR) {
35.        printf(“error catching SIGUSR1/n”);
36.        exit(1);
37.    }
38.

39.    if(signal(SIGUSR2, SIG_IGN) == SIG_ERR) {
40.        printf(“error ignoring SIGUSR2/n”);
41.        exit(1);
42.    }
43.

44.    if(signal(SIGINT, SIG_DFT) == SIG_ERR) {
45.        printf(“error setting SIGINT to default/n”);
46.        exit(1);
47.    }
48.

49.    while(1)
50.        pause();
51.

52.    exit(0);
53.}

 


后台运行该程序,并用kill发送信号给它。


$./a.out &

[1] 3725

$kill -USR1 3725

in sigusr: SIGUSR1

SIGUSR1 received

$kill -USR2 3725

[1]+ User defined signal 2 ./a.out

 

我们可以看到,Linux系统对SIGUSR2的默认动作是终止进程。

 

中断与自动重启动


前面说过,信号是一种软件中断机制,这就产生了一个问题:如果信号到来时进城正在执行某个低速系统调用,系统应该怎么处理?是暂时阻塞系统调用返回,在信号处理程序完成后继续没完成的系统调用呢,还是让系统调用出错返回,同时把errno设置为EINTR,让调用者去做进一步的出错检查呢?用事实说话,让我们做一个试验先吧。

 


下面的程序读取标准输入并把它输出到标准输出,在此期间,我们给进程发送SIGUSR1信号,以此来确定Linux在收到信号后是如何对处理系统调用的。

 

例程2 信号与自动重启动的signal版本

1.int main(void)
2.{
3.    char buf[BUFSIZ];
4.    int n;
5.

6.    signal(SIGUSR1, sig_usr);
7.

8.    while(1) {
9.        if((n = read(STDIN_FILENO, buf, BUFSIZ)) == -1) {
10.            if(errno == EINTR)
11.                printf(“read is interrupted/n”);
12.        }
13.        else {
14.            write(STDOUT_FILENO, buf, n);
15.        }
16.    }
17.

18.    exit(0);
19.}

 

运行该程序,并从另一个终端给该进程发送信号SIGUSR1。

 

$./a.out

first line

first line

in sigusr: SIGUSR1

SIGUSR1 received

second line

second line

in sigusr: SIGUSR1

SIGUSR1 received

^C


可见对由signal()函数安装的信号处理程序,系统默认会自动重启动被中断的系统调用,而不是让它出错返回,所以应用程序不必针对慢速系统调用的errno,做EINTR检查,这就是自动重启动机制。


我们再来看另外一个例子,它使用另一个函数sigaction()来安装信号处理程序。sigaction()允许进程对信号进行更多的控制:

 

例程3 信号与自动重启动的sigaction版本

1.int main(void)
2.{
3.    char buf[BUFSIZ];
4.    int n;
5.

6.    struct sigaction sa_usr;
7.    sa_usr.flags = 0; //SA_RESART

8.    sa_usr.sa_handler = sig_usr;
9.    sigaction(SIGUSR1, &sa_usr, NULL);
10.

11.    //signal(SIGUSR1, sig_usr);

12.

13.    while(1) {
14.        if((n = read(STDIN_FILENO, buf, BUFSIZ)) == -1) {
15.            if(errno == EINTR)
16.                printf(“read is interrupted/n”);
17.        }
18.        else {
19.            write(STDOUT_FILENO, buf, n);
20.      }
21.    }
22.

23.    exit(0);
24.}

 

此时再运行这个程序,并从另一终端给该进程发送信号SIGUSR1,我们会得到如下结果。

 

$./a.out

first line

first line

in sigusr: SIGUSR1

SIGUSR1 received

read is interrupted

second line

second line

in sigusr: SIGUSR1

SIGUSR1 received

read is interrupted

^C

 

由此我们可以得出,Linux对sigaction()的默认动作是不自动重启动被中断的系统调用,因此如果我们在使用sigaction()时需要自动重启动被中断的系统调用,就需要使用sigaction的SA_RESTART选项,见上例注释,关于sigaction(),下文会有更多的描述。这和《UNIX环境高级编程》中对Linux信号处理的描述是一致的。


可重入函数


如前所述,进程在收到信号并对其进行处理时,会暂时中断当前正在执行的指令序列,转而去执行信号处理程序。但是信号的到来,往往是无法预测的,我们无法确定进程会在何时收到信号。如果进程在收到信号时正在执行malloc()调用,而此时捕捉到信号,进城就会转而去执行信号处理程序,而信号处理程序中又再次调用了malloc()函数,那结果将会怎样呢?进程的栈空间很可能就会受到破坏,从而产生无法预料的结果。所以有些函数是不能在信号处理程序中调用的,这些函数被称为不可重入函数,而那些允许在信号处理函数中调用的函数,则称为可重入函数。下表列出了Linux系统中的可重入函数(摘自《UNIX环境高级编程》),对不在该表中的函数,信号处理函数中要慎用。

 


表1 可重入函数

 

 

发送信号的kill和raise函数

 

int kill(pid_t pid, int sig);

int raise(int sig);

 

kill()发送信号给指定进程,raise()发送信号给进程本身。对kill()的pid,有如下描述:

 

pid > 0 将信号发送给ID为pid的进程

pid == 0 将信号发送给与发送进程属于同意个进程组的所有进程

pid < 0 将信号发送给进程组ID等于pid绝对值的所有进程

pid == -1 将信号发送给该进程有权限发送的系统里的所有进程

 
所有信号的发送都要先经过权限检查,如果进程没有相应发送的权限,kill()会出错返回,并把errno设为EPERM。但也有一个例外,对SIGCONT,进程可以将它发送给当前会话的所有进程。

 
产生时钟信号SIGALRM的alarm函数

 
unsigned int alarm(unsigned int seconds);

 

alarm()函数可设置一个计时器,计时器超时就产生SIGALRM信号。由于每个进程只能有一个SIGALRM处理程序,所以只能为一个进程设置一个计时器,所以alarm()和setitimer()会共享同一个SIGALRM信号和该信号的处理函数。也就是说,alarm()和 setitimer()彼此会互相影响。调用alarm(),会使先前设置的计时器失效,并把没有超时的时间作为当前alarm的返回值。如先前设置的时钟为100秒,当前调用alarm()时才经过30秒,剩余的70秒就作为alarm()的返回值,并用alarm()中指定的秒数重新设置计时器。如果 seconds为0,则会取消先前设置的计时器,并将其余留值作为alarm()的返回值。

 


等待信号的pause函数

 

int pause(void);

 

pause()会使当前进程挂起,直到捕捉到一个信号,对指定为忽略的信号,pause()不会返回。只有执行了一个信号处理函数,并从其返回,puase()才返回-1,并将errno设为EINTR。详见前面的第一个例子。

 
信号屏蔽字(process signal mask)

 

每个进程都会有一个信号屏蔽字,它规定了当前进程要阻塞的信号集。对于每种可能的信号,信号屏蔽字中都会有一位与之对应,如果该位被设置,该信号当前就是阻塞的。进程可以通过sigprocmask()来获得和修改当前进程的信号屏蔽字。

 


信号集(signal set)

 


信号集是一种特殊的数据类型,由于无法确定信号的多少,所以不能用简单数据类型来包含所有可能的信号,所以系统就定义了一个 sigset_t的数据类型专门用于信号集。同时还定义了一族用于处理信号集的函数。这样用户可以不必关心信号集的实现,只要使用这组函数来处理信号集就可以了。

 


信号集函数

 
int sigemptyset(sigset_t * set);

int sigfillset(sigset_t * set);

int sigaddset(sigset_t * set, int signum);

int sigdelset(sigset_t * set, int signum);

int sigismember(sigset_t * set, int signum);

 

sigemptyset()和sigfillset()都用于初始化一个信号集,前者用于清空信号集中所有的信号,后者则用于设置信号集中所有的信号;信号集在使用前必须要经过初始化,初始化后,就可以用sigaddset()和sigdelset()往信号集里添加删除信号了。 sigismember()用于判断指定信号是否在信号集中。

 

修改信号屏蔽字的sigprocmask函数

 

int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);

 

sigpromask()根据how指定的方式,设置进程的当前信号屏蔽字为set,并将旧的信号屏蔽字保存在oldset中返回。如果set为 NULL,则不修改当前信号屏蔽字,而将其通过oldset返回;如果oldset为NULL,则不会返回旧的信号屏蔽字。how支持三种方式,见下表。

 
表2 设置信号屏蔽字的方式

 

 
 

 如果我们想阻塞SIGUSR1,有两种方式。

1.// using SIG_BLOCK 2.sigset_t sigset;
3.sigemptyset(&sigset);
4.sigaddset(&sigset, SIGUSR1);
5.sigprocmask(SIG_BLOCK, &sigset, NULL);
6.

7.// or using SIG_SETMASK

8.sigset_t set, oldset;
9.// get current signal mask

10.sigprocmask(SIG_SETMASK, NULL, &set);
11.// add SIGUSR1 into the signal mask

12.sigaddset(&set, SIGUSR1);
13.sigprocmask(SIG_SETMASK, &set, &oldset);
14.
同样,如果要解除阻塞SIGUSR1,也有两种方式。

1.// using SIG_UNBLOCK

2.sigset_t sigset;
3.sigemptyset(&sigset);
4.sigaddset(&sigset, SIGUSR1);
5.sigprocmask(SIG_UNBLOCK, &sigset, NULL);
6.

7.// or using SIG_SETMASK

8.sigset_t set, oldset;
9.// get current signal mask

10.sigprocmask(SIG_SETMASK, NULL, &set);
11.// delete SIGUSR1 from the signal mask

12.sigdelset(&set, SIGUSR1);
13.sigprocmask(SIG_SETMASK, &set, &oldset);
信号未决(pending)

 


信号是由某些事件产生的,这些事件可能是硬件异常(如被零除),软件条件(如计时器超时),终端信号或调用kill()/raise()函数。信号产生时,内核通常会在进程表中设置某种标志,表示当前信号的状态。当内核对信号采取某种动作时,我们说向进程递送(deliver)了一个信号,而在信号产生和递送之间的间隔内,该信号的状态是未决的(pending)。

 


获得未决的信号sigpending

 

int sigpending(sigset_t * set);


该函数在set中返回进程中当前尚未递送的信号集。


功能更全的sigaction函数

 
int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact);

 
struct sigaction {

void (*sa_handler) (int);

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

sigset_t sa_mask;

int sa_flags;

};

 
siginfo_t {

int si_signo; // Signal number

int si_errno; // An errno value

int si_code; // signal code

pid_t si_pid; // sending process ID

pid_t si_uid; // Real user ID of sending process

int si_status; // Exit value or signal

...

};

 
sigaction()的功能是为信号指定相关的处理程序,但是它在执行信号处理程序时,会把当前信号加入到进程的信号屏蔽字中,从而防止在进行信号处理期间信号丢失。从前面的例子我们可以看到,简单的signal()函数也具有同样的功能,这是由于signal()已经被重新实现的缘故,所以如果不在乎对信号的更多的控制,我们尽可放心大胆的使用简单的signal()函数。signum指定将要改变处理行为的信号;act指定该信号的处理动作,oldact用于返回该信号先前的处理动作。

 
在sigaction结构中,sa_handler和sa_sigaction用于指定信号处理函数,但要注意,二者只能用其一,因为它们在内部可能会实现为union结构。除了在为sa_flags指定SA_SIGINFO标志时,会使用sa_sigaction字段外,其他情况下都应该只用sa_handler字段。

 

sa_mask用于指定在当前信号处理程序执行期间,需要阻塞的信号集。如在处理SIGUSR1期间,我们希望暂时阻塞SIGUSR2,就应该把SIGUSR2加到SIGUSR1的sa_mask中。信号处理程序返回后,会自动解除对SIGUSR2的阻塞,详见例程4。

 

sa_flags用于指定信号处理动的选项标志,详见手册。这里我想说的是SA_RESTART和SA_SIGINFO。SA_RESTART用于控制信号的自动重启动机制,如前面例子所示,对signal(),Linux默认会自动重启动被中断的系统调用;而对于 sigaction(),Linux默认并不会自动重启动,所以如果希望执行信号处理后自动重启动先前中断的系统调用,就需要为sa_flags指定 SA_RESTART标志。对于SA_SIGINFO,手册上说此标志可能会导致对信号的可靠排队,但是从下面的例子我们将会看到,Linux并没有对信号进行排队。


例程4 sigaction函数

1.int main(void)
2.{
3.    struct sigaction act_usr;
4.

5.    act_usr.sa_flags = 0;
6.    act_usr.sa_handler = sigusr;
7.    sigemptyset(&act_usr.sa_mask);
8.    // add the signal you want to block while SIGUSR1 is processing here

9.    sigaddset(&act_usr.sa_mask, SIGUSR2);
10.    // we dont care about the old action of SIGUSR1

11.    sigaction   (SIGUSR1, &act_usr, NULL);
12.

13.    while(1)
14.        pause();
15.}

 

运行结果如下:

 

$./a.out &

[1] 16385

$kill -USR1 16385

in sig_usr1: SIGUSR1 SIGUSR2

SIGUSR1 recieved

 
可见在SIGUSR1处理期间,SIGUSR2已经被加入到进程的屏蔽字中了,所以在此期间,SIGUSR2是被暂时阻塞的。

 
信号排队

 


如果进程阻塞了一个信号,在没有对其解除阻塞之前,该信号产生了多次,将会如何处理呢?Linux并不会对信号排队,当信号解除阻塞后,内核只向进程递送一个信号,而不管在其阻塞期间有多少个信号产生。

 


下面是上例的改进版。首先我们阻塞SIGUSR1,然后在SIGUSR2的处理函数里解除对SIGUSR1的阻塞,这样我们就有机会在SIGUSR1阻塞期间,多发送几个SIGUSR1来确定Linux内核是怎样处理的。我们期望能看到Linux对信号的排队。

 


例程5 信号排队

1.static void sig_usr2(int sig)
2.{
3.    sigset_t set;
4.    printf("SIGUSR2 recieved/n");
5.    // unblock SIGUSR1

6.    sigprocmask(SIG_SETMASK, NULL, &set);
7.    sigdelset(&set, SIGUSR1);
8.    sigprocmask(SIG_SETMASK, &set, NULL);
9.}
10.

11.static void handler(int signum, siginfo_t * info, void * context)
12.{
13.    // dump signal information

14.    printf("si_signo: %d/n", info->si_signo);
15.    printf("si_errno: %d/n", info->si_errno);
16.    printf("si_code: %d/n", info->si_code);
17.    printf("si_pid: %d/n", info->si_pid);
18.    printf("si_uid: %d/n", info->si_uid);
19.}
20.

21.int main(void)
22.{
23.    struct sigaction act_usr1;
24.    struct sigaction act_usr2;
25.    sigset_t mask;
26.

27.    act_usr1.sa_flags = SA_SIGINFO;
28.    //act_usr1.sa_handler = sigusr; 

29.    sigemptyset(&act_usr1.sa_mask);
30.    act_usr1.sa_sigaction = handler;
31.    sigaction(SIGUSR1, &act_usr1, NULL);
32.

33.    act_usr2.sa_flags = 0;
34.    act_usr2.sa_handler = sig_usr2;
35.    sigemptyset(&act_usr2.sa_mask);
36.    sigaction(SIGUSR2, &act_usr2, NULL);
37.

38.    // block SIGUSR1

39.    sigprocmask(SIG_SETMASK, NULL, &mask);
40.    sigaddset(&mask, SIGUSR1);
41.    sigprocmask(SIG_SETMASK, &cmask, NULL);
42.

43.    while(1)
44.        pause();
45.

46.    exit(0);
47.}
$ ./a.out &

[1] 17165

$ kill -USR1 17165

$ kill -USR1 17165

$ kill -USR1 17165

$ kill -USR1 17165

$ kill -USR1 17165

$ kill -USR2 17165

SIGUSR2 recieved

si_signo: 10

si_errno: 0

si_code: 0

si_pid: 3945

si_uid: 500

 


$ ps

PID TTY TIME CMD

3945 pts/1 00:00:00 bash

 
在SIGUSR1阻塞期间,我们向进程发送了5个SIGUSR1,而解除阻塞后,内核只递送了一个SIGUSR1,说明Linux并不支持信号排队。另外我们还可以看到,si_signo是收到的信号的数值;si_pid是发送进程的进程ID,ps输出我的终端进程ID正是 3945;si_uid是发送进程的有效用户ID,而我的用户ID也正是500。对于siginfo结构中的其它成员,我没有打印,有兴趣的可以自己研究。

 


信号跳转函数sigsetjump和siglongjump

 
int sigsetjmp(sigjmp_buf env, int savesigs);

void siglongjmp(sigjmp_buf env, int val);

 

sigsetjmp()有多次返回,对于直接调用者(一般是主程序),它返回0;若从siglongjmp()调用(一般是信号处理程序),则返回返回siglongjmp()中的val值。所以为了避免混淆,最好不要在调用siglongjmp()时,让val=0。


另外需要说明的是sigsetjmp()的第二个参数,它用于告诉内核,要不要保存进程的信号屏蔽字。当savesigs为非0时,调用 sigsetjmp()会在env中保存当前的信号屏蔽字,然后在调用siglongjmp()时恢复之前保存的信号屏字。由于信号处理函数使用 siglongjmp()跳转时不属于正常返回,所以在进入信号处理函数时被阻塞的当前信号就没有机会在返回时恢复。sigsetjmp()的 savesigs参数就用于是告诉系统,在调用siglongjmp时,是否需要恢复先前的信号屏蔽字。

 

下例向你展示了如何使用sigsetjmp()和siglongjmp(),注意这里引入了一个全局变量canjmp,它是一种同步保护机制,用于告诉信号处理程序,在进程环境没有准备好之前,不要跳转,否则可能会导致混乱。

 
例程6 信号跳转

1.static sigjmp_buf jmpbuf;
2.// for synchronizing

3.static volatile sig_atomic_t canjmp;
4.

5.static void sigusr1(int signum)
6.{
7.    printf(“SIGUSR1 reveived/n”);
8.

9.    // main process initialization is not completed

10.    if(canjmp == 0)
11.        return;
12.

13.    siglongjmp(jmpbuf, 1);
14.}
15.

16.satic void sigusr2(int signum)
17.{
18.    printf(“SIGUSR2 reveived/n”);
19.

20.    if(canjmp == 0)
21.        return;
22.

23.    siglongjmp(jmpbuf, 2);
24.}
25.

26.int main(void)
27.{
28.    int n;
29.    int savemask = 1;
30.

31.    signal(SIGUSR1, sigusr1);
32.    signal(SIGUSR2, sigusr2);
33.

34.    // need to save the procmask, otherwise, u have to reset the procmask

35.    n = sigsetjmp(jmpbuf, savemask);
36.

37.    if(n == 1) {
38.        // jump from SIGUSR1

39.        printf(“Jump to here from SIGUSR1/n”);
40.        if(savemask == 0) {
41.            // prevent from long jumping

42.            canjmp = 0;
43.            // reset the procmask, unblock SIGUSR1

44.            sigset_t set;
45.            sigprocmask(SIG_SETMASK, NULL, &set);
46.            sigdelset(&set, SIGUSR1);
47.            sigprocmask(SIG_SETMASK, &set, NULL);
48.            canjmp = 1;
49.        }
50.    }
51.    else if(n == 2) {
52.        printf(“Jump to here from SIGUSR2/n”);
53.        if(savemask == 0) {
54.            canjmp = 0;
55.            sigset_t set;
56.            sigprocmask(SIG_SETMASK, NULL, &set);
57.            sigdelset(&set, SIGUSR1);
58.            sigprocmask(SIG_SETMASK, &set, NULL);
59.            canjmp = 1;
60.        }
61.    }
62.

63.    canjmp = 1;
64.

65.    while(1)
66.        pause();
67.

68.    exit(0);
69.}
$ ./a.out &

[1] 5485

$ kill -USR1 5485

SIGUSR1 recieved

Jump to here from SIGUSR1

$ kill -USR2 5485

SIGUSR2 recieved

Jump to here from SIGUSR2

 


例程6告诉我们,根据sigsetjmp()的返回值,我们也可以通过信号实现程序的多分支控制。另外如果没有在sigsetjmp()时设置了savesigs,那么在siglongjmp()返回后,就要重新设置进程的信号屏蔽字,否则该信号在一次siglongjmp()之后将被永久阻塞。

 
难以捉摸的sigsuspend函数

 
int sigsuspend(const sigset_t * sigmask);

 

对于这个函数,我始终无法清晰的理解,关于它的用法,它的作用,它的语义,都让我一头雾水。《UNIX环境高级编程》,Linux手册,看了几遍,都无法开塞,真是愚钝至极啊!

 

从《UNIX环境高级编程》对sigsuspend()的引言看,该函数的出现是为了解决早期不可靠信号,即信号丢失的问题的。在早期的信号机制中,对信号解除阻塞和等待信号需要两步进行: 


sigprocmask(SIG_SETMASK, &unblockmask, NULL);

pause();

 
在对信号解除阻塞之后和调用pause()之前有一个时间窗口,所以在这之间产生的信号就可能会丢失,从而是本该返回的pause()没有返回。

 
sigsuspend()能让解除信号阻塞和等待信号成为一个原子操作,这样就避免了上述的问题。它会把当前进程的信号屏蔽字设定为 sigmask指定的值,所以在等待信号期间,sigmask中的信号会被暂时阻塞,而sigmask之外的信号都会被暂时解除阻塞。然后 sigsuspend()挂起当前进程,等待,直到捕捉到一个信号或发生了一个会终止该进程的信号。如果是捕捉到一个信号并从出来程序中返回,则 sigsuspend()返回-1,把进程信号屏蔽字设回调用sigsuspend()之前的值,并将errno设为EINTR。注意,指定为忽略的信号,并不会导致sissuspend()返回。

 
注意,sigsuspend()只是暂时解除对不在sigmask中的信号的阻塞,在捕捉到一个信号后,以前阻塞的信号还会被重新阻塞,所以如果你要对一个以前阻塞的信号解除阻塞的话,在sigsuspend()返回之后,还要重新用sigprocmask来解除对该信号的阻塞。这就是我的疑问了,如果我无意让进程等待任何信号的话,那这个sigsuspend()不是对我几乎毫无用处吗?

 

sigsuspend()的另一个用途就是让进程等待一个信号处理程序来设置一个全局变量。这个似乎还比较有用,在进程等待某一信号时,可让进程先挂起,直到收到该信号,设置的全局变量并导致sigsuspend()返回,进程才处理相应的任务,避免了CPU的无谓等待。如下例程就展示了如何让进程等待SIGUSR1,并在每收到一次SIGUSR1后去执行一组相同的操作。

 

例程7 sigsuspend的一个应用

1.static volatile sig_atomic_t ok;
2.

3.static void sigusr(int signum)
4.{
5.    // do nothing, set the flag only

6.    ok = 1;
7.}
8.

9.int main(void)
10.{
11.    sigset_t emptymask, waitmask, oldmask;
12.

13.    // block SIGUSR1 first

14.    sigemptyset(&waitmask);
15.    sigaddset(&waitmask, SIGUSR1);
16.    sigprocmask(SIG_BLOCK, &waitmask, &oldmask);
17.

18.    // set SIGUSR1 handler

19.    signal(SIGUSR1, sigusr);
20.

21.    sigemptyset(&emptymask);
22.    while(1) {
23.        // waiting for SIGUSR1 to set ok

24.        while(ok == 0)
25.            sigsuspend(&emptymask);
26.

27.        // sigsuspend return, SIGUSR1 has come, and now SIGUSR1 is blocked

28.        // reset the flag, so the process will keep waiting after the

29.        // following things are done 30.        ok = 0;
31.       

// 32.        // other things need to do here

33.       

// 34.    }
35.

36.    exit(0);
37.}
我对sigsuspend()的理解仅限于此,不敢多卖弄,就此打住吧:)

 


发送SIGABRT的专用函数abort

 


int abort(void); 


abort会向当前进程发送SIGABRT信号,让进程做一些“善后”处理,如刷新流缓冲区,关闭文件等,然后终止进程。

 
其它几个有用的小函数:

 

感觉写的不错,转来看看~~~

 

extern char * sys_siglist[];

这是一个以信号为索引的字符串数组,通过它可以很容易的找到信号的字符串名称。 


void psignal(int signum, const char * msg);

此函数类似于perror(),输出对指定信号的字符串描述。
 

char * strsignal(int signum);

返回指定信号的字符串描述。

 

例程8 其它函数

1.int main(void) {
2.    printf("sys_siglist:/n");
3.    printf("SIGUSR1: %s/n", sys_siglist[SIGUSR1]);
4.    printf("SIGSEGV: %s/n", sys_siglist[SIGSEGV]);
5.    printf("SIGHUP: %s/n/n", sys_siglist[SIGHUP]);
6.

7.    printf("strsignal:/n");
8.    printf("SIGUSR1: %s/n", strsignal(SIGUSR1));
9.    printf("SIGSEGV: %s/n", strsignal(SIGSEGV));
10.    printf("SIGHUP: %s/n/n", strsignal(SIGHUP));
11.

12.    printf("psignal:/n");
13.    psignal(SIGUSR1, "SIGUSR1");
14.    psignal(SIGSEGV, "SIGSEGV");
15.    psignal(SIGHUP, "SIGHUP");
16.

17.    exit(0);
18.}
输出形式如下:

 


sys_siglist:

SIGUSR1: User defined signal 1

SIGSEGV: Segmentation fault

SIGHUP: Hangup

 


strsignal:

SIGUSR1: User defined signal 1

SIGSEGV: Segmentation fault

SIGHUP: Hangup

 


psignal:

SIGUSR1: User defined signal 1

SIGSEGV: Segmentation fault

SIGHUP: Hangup

 


最后的忠告:千万不要去写一个复杂的信号处理程序,那是最出力不讨好的事情,信号处理程序每多一行,你的程序莫名崩溃的可能性就增大一分,谁能保证你在信号处理函数里调用的都是可重入函数呢?本文中的很多例程使用的printf()就不是可重入的……信号处理程序应该尽量简单,对稍微复杂的任务,应该想办法(如siglongjmp(),设置全局标志等)交给主程序处理。

 


吼吼,花了一天时间,终于把它整理完了,乱七八糟,但会方便各位查阅吧!其中大部分内容参考了《UNIX环境高级编程》,有不对的地方大家尽管拍砖,但不要骂俺抄袭,会脸红的^_^。图灵教育说的好,站在巨人的肩膀上,Standing on shoulders of the giants, I think I can fly...



signal()函数理解

<signal.h> 这个头文件中。

signal(参数1,参数2);

参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(64)。其实这些信号时系统定义的宏。

参数2:我们处理的方式(是系统默认还是忽略还是捕获)。

一般有3中方式进行操作。

1eg: signal(SIGINT ,SIG_ING );

//SIG_ING 代表忽略SIGINT信号SIGINT信号代表由InterruptKey产生,通常是CTRL +C 或者是DELETE 。发送给所有ForeGround Group的进程。

下面我们写个死循环:


这时我们保存执行。

按下CTRL _C程序没有反应。这就对了

如果我们想结束该程序可以按下CTRL +\来结束

其实当我们按下CTRL +\组合键时,是产生了SIGQUIT信号

2eg: signal(SIGINT ,SIG_DFL );

//SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作时终止该进程。这与不写此处理函数是一样的。

我们将上面的程序改成


这时就可以按下CTRL +C 来终止该进程了。把signal(SIGINT,SIG_DFL);这句去掉,效果是一样的。

(3) void ( *signal( int sig, void (* handler)( int )))( int );
int (*p)();
这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.
int (*fun())();
这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.

void (*signal(int signo, void (*handler)(int)))(int);就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数),而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.

在写信号处理函数时对于信号处理的函数也是void sig_fun(int signo);这种类型,恰好与上面signal()函数所返回的函数指针所指向的函数是一样的.void ( *signal() )( int );

signal是一个函数, 它返回一个函数指针, 后者所指向的函数接受一个整型参数 且没有返回值, 仔细看, 是不是siganal( int signo, void (*handler)(int) )的第2个参数了,对了,其实他所返回的就是 signal的第2个信号处理函数,指向信号处理函数,就可以执行函数了( signal内部时, signal把信号做为参数传递给handler信号处理函数,接着 signal函数返回指针, 并且又指向信号处理函数, 就开始执行它)

那么,signal函数的参数又是如何呢?signal函数接受两个参数:一个整型的信号编号,以及一个指向用户定义的信号处理函数的指针。我们此前已经定义了指向用户定义的信号处理函数的指针sfp:

void (*sfp)(int);

sfp 的类型可以通过将上面的声明中的sfp去掉而得到,即void (*)(int)。此外,signal函数的返回值是一个指向调用前的用户定义信号处理函数的指针,这个指针的类型与sfp指针类型一致。因此,我们可以如下声明signal函数:

void (*signal(int, void(*)(int)))(int);

同样地,使用typedef可以简化上面的函数声明:

typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);

Ok;看个例子:


此程序是对当我们按下CTRL +C键时,会执行我们定义的信号处理函数。

每当我们按下CTRL +C键时会打印该信号的number.可以看出该信号的num2

要想退出可以按下CTRL +\ 打印结果为最后一行。

一些常用的Signal 如下:

注:下面是从百度文库中找的(*^__^*) 嘻嘻……

SignalDescription
SIGABRT由调用abort函数产生,进程非正常退出
SIGALRM用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS某种特定的硬件异常,通常由内存访问引起
SIGCANCEL由Solaris Thread Library内部使用,通常不会使用
SIGCHLD进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT当被stop的进程恢复运行的时候,自动发送
SIGEMT和实现相关的硬件异常
SIGFPE数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZESolaris专用,Hiberate或者Suspended时候发送
SIGHUP发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL非法指令异常
SIGINFOBSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO异步IO事件
SIGIOT实现相关的硬件异常,一般对应SIGABRT
SIGKILL无法处理和忽略。中止某个进程
SIGLWP由Solaris Thread Libray内部使用
SIGPIPE在reader中止之后写Pipe的时候发送
SIGPOLL当某个事件发送给Pollable Device的时候发送
SIGPROFSetitimer指定的Profiling Interval Timer所产生
SIGPWR和系统相关。和UPS相关。
SIGQUIT输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV非法内存访问
SIGSTKFLTLinux专用,数学协处理器的栈异常
SIGSTOP中止进程。无法处理和忽略。
SIGSYS非法系统调用
SIGTERM请求中止进程,kill命令缺省发送
SIGTHAWSolaris专用,从Suspend恢复时候发送
SIGTRAP实现相关的硬件异常。一般是调试异常
SIGTSTPSuspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU当Background Group的进程尝试写Terminal的时候发送
SIGURG当out-of-band data接收的时候可能发送
SIGUSR1用户自定义signal 1
SIGUSR2用户自定义signal 2
SIGVTALRMsetitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITINGSolaris Thread Library内部实现专用
SIGWINCH当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU当CPU时间限制超时的时候
SIGXFSZ进程超过文件大小限制
SIGXRESSolaris专用,进程超过资源限制的时候发送

Linux信号机制之sigaction结构体浅析

信号安装函数sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)的第二个参数是一个指向sigaction结构的指针(结构体名称与函数名一样,千万别弄混淆了)。在结构sigaction的实例中,指定了对特定信号的处理,信号所传递的信息,信号处理函数执行过程中应屏蔽掉哪些函数等。当然,此指针也可以为NULL,进程会以默认方式处理信号。以下就简单介绍一下sigaction结构以及一般的用法。

  对于内核头文件而言,struct sigaction 结构体定义在kernel/include/asm/signal.h,此头文件又被kernel/include/linux/signal.h包含。
  对于用户空间的头文件而言,struct sigaction定义在 /usr/include/bits/sigaction.h,此头文件又被/usr/include/signal.h包含,所以应用程序中如果用到此结构,只要#include <signal.h>即可。注意内核中的定义和应用程序中的定义是不一样的,内核空间的sigaction结构只支持函数类型为__sighandler_t的信号处理函数,不能处理信号传递的额外信息。具体定义如下:
  /* Type of a signal handler.   */
  typedef void (*__sighandler_t)(int);
  #ifdef __KERNEL__
  struct old_sigaction {
          __sighandler_t sa_handler;
         old_sigset_t sa_mask;
         unsigned long sa_flags;
         void (*sa_restorer)(void);
  };
  struct sigaction {
         __sighandler_t sa_handler;
        unsigned long sa_flags;
        void (*sa_restorer)(void);
        sigset_t sa_mask;   /* mask last for extensibility */
  };
  struct k_sigaction {
        struct sigaction sa;
  };
  #else
  /* Here we must cater to libcs that poke about in kernel headers.   */
  struct sigaction {
          union {
                  __sighandler_t _sa_handler;
                  void (*_sa_sigaction)(int, struct siginfo *, void *);
          } _u;
          sigset_t sa_mask;
          unsigned long sa_flags;
          void (*sa_restorer)(void);
  };
  #define sa_handler   _u._sa_handler
  #define sa_sigaction _u._sa_sigaction
  #endif /* __KERNEL__ */

  sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;

  sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。

  sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

  sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

  sa_restorer已过时,POSIX不支持它,不应再使用。

  因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,类似下面的代码:
     #include <signal.h>
     ……
     void sig_handler_with_arg(int sig,siginfo_t *sig_info,void *unused){……}
    
     int main(int argc,char **argv)
     {
              struct sigaction sig_act;
              ……
              sigemptyset(&sig_act.sa_mask);
              sig_act.sa_sigaction=sig_handler_with_arg;
              sig_act.sa_flags=SA_SIGINFO;
  
               ……
     }
  如果你的应用程序只需要接收信号,而不需要接收额外信息,那你需要的设置的是sa_handler,而不是sa_sigaction,你的程序可能类似下面的代码:
     #include <signal.h>
     ……
     void sig_handler(int sig){……}
    
     int main(int argc,char **argv)
     {
              struct sigaction sig_act;
              ……
              sigemptyset(&sig_act.sa_mask);
              sig_act.sa_handler=sig_handler;
              sig_act.sa_flags=0; 
               ……
      }
 

  例:
  #include<sys/types.h>
  #include<unistd.h>
  #include<signal.h>
  #include<stdio.h>
  #include<stdlib.h>

  int main(void)
  {
    sigset_t set,pendset;
    struct sigaction action;
    //清空信号集
    sigemptyset(&set);        
    //加入SIGTERM呢个信号      
    sigaddset(&set,SIGTERM);           
    //设为阻塞
    sigprocmask(SIG_BLOCK,&set,NULL);     
    //因为阻塞所以就算用kill发送信号都无反应
    kill(getpid(),SIGTERM); 
    //查下有什么阻塞信号,装入&pendset里面
    sigpending(&pendset);  
    //看下面有什么信号在里面
    if(sigismember(&pendset,SIGTERM))     
    {
      printf("yes,the SIGTERM is here\n");
      //清空阻塞信号集
      sigemptyset(&action.sa_mask); 
      //信号涵数处理为ignore(忽略)       
      action.sa_handler=SIG_IGN;        
       //启动同signal功能类的信号涵数 
      sigaction(SIGTERM,&action,NULL);     
    }
    //解除之前的信号阻塞
    sigprocmask(SIG_UNBLOCK,&set,NULL);      
    exit(EXIT_SUCCESS);
  }



  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值