linux高编之信号signal

来源:互联网 发布:千牛店铺数据 编辑:程序博客网 时间:2024/06/10 01:12

一、信号概念

信号就是在进程在运行过程中,由自身产生或由进程外部发过来的消息。(信号与程序的控制流程是异步的)

用kill -l可查看信号列表:

二、信号处理函数

1、信号注册函数signal

可以用函数signal注册一个信号捕捉函数。原型为:
#include 
typedef
   void (*sighandler_t)(int);
sighandler_t
  signal(int signum, sighandler_t handler);
signal 的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回 SIG_ERR。
sighandler_t是信号捕捉函数
(可由用户自定义),由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个参数,表示信号值。

下面写一个小程序练习:

#include <stdio.h>#include <signal.h>#include <stdlib.h>void do_it(int signal){printf("receive signal\n");return;}int main(void){signal(3,do_it);while(1);return 0;}

在main函数中用signal函数将用户自定义的函数do_it注册到内核中,当有3号信号SIGQUIT到来时("Ctrl+\"),执行do_it函数,运行结果如下:


2、alarm()函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个时间闹钟,告诉内核在seconds秒后发一个SIGALRM信号,该信号的默认动作是终止进程,当seconds被设置为0式,表示取消以前设定的闹钟,函数的返回值是以前设定的闹钟时间剩余的秒数。

3、阻塞信号

信号的执行动作叫信号递达,信号从产生到递达的状态叫信号未决(pending),当信号被阻塞时,信号保持在阻塞状态,直到阻塞解除。

3.1 信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set); 初始化set所指向的信号集,使其中所有信号的对应bit清零
int sigfillset(sigset_t *set); 初始化set所指向的信号集,使其中所有信号的对应bit置1
int sigaddset(sigset_t *set, int signo); 增加信号到set信号集中
int sigdelset(sigset_t *set, int signo); 将信号在set信号集中删除
int sigismember(const sigset_t *set, int signo)
; 检测信号是否在set信号集中

3.2 读取或更改进程的信号屏蔽字函数sigprocmask()

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

how: SIG_BLOCK :set包含了我们希望添加到当前信号屏蔽字的信号

SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号

SIG_UNBLOC 设置当前信号屏蔽字为set所指向的值

set: 函数处理之后的信号集

oset: 保存函数处理之前的信号集

3.3  读取当前进程的未决信号集sigpending

#include <signal.h>
int sigpending(sigset_t *set);

下面写个小程序联系:

#include <signal.h>#include <stdio.h>void printsigset(const sigset_t *set){int i;for(i=1;i<32;i++){if(sigismember(set,i)==1)putchar('1');elseputchar('0');}puts("");}int main(void){sigset_t s,p;sigemptyset(&s);sigaddset(&s,SIGINT);sigprocmask(SIG_BLOCK,&s,NULL);while(1){sigpending(&p);printsigset(&p);sleep(1);}return 0;}

程序中,将SIGINT信号写入信号集中,将该信号屏蔽,在子函数中将set信号集中所有信号打印(阻塞为1,非阻塞为0),执行结果如下:



4、捕捉信号

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

1. 用户程序注册了SIGQUIT信号的处理函数sighandler。
2. 当前正在执行main函数,这时发生中断或异常切换到内核态。
3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和 被调用的关系,是两个独立的控制流程。
5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。


4.2 sigaction

sigaction函数可以读取和修改与指定信号相关联的处理动作

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体

struct sigaction {
void (*sa_handler)(int); /* addr of signal handler, or SIG_IGN, or SIG_DFL */
sigset_t sa_mask; /* additional signals to block */
int sa_flags; /* signal options, Figure 10.16 
*/

/*alternate handler */
void (*sa_sigaction)(int, siginfo_t *, void *);
};

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,本章的代码都把sa_flags设为0

4.3 pause

#include <unistd.h>
int pause(void);

pause函数使调用进程挂起直到有信号递达。

下面通过alarm和pause函数写一个sleep函数:

#include <stdio.h>#include <unistd.h>#include <signal.h>void sig_alrm(int signo){//nothing to do}unsigned int mysleep(unsigned int nsecs){struct sigaction newact,oldact;unsigned int unslept;newact.sa_handler=sig_alrm;   //将sig_alrm函数绑定在sa_handlersigemptyset(&newact.sa_mask);//清空setnewact.sa_flags=0;sigaction(SIGALRM,&newact,&oldact); //调用sigaction注册了SIGALRM信号的处理函数sig_alrmalarm(nsecs);//alarm设置闹钟pause();//进程等待,直到SIGALRM信号到来unslept=alarm(0);//清空闹钟sigaction(SIGALRM,&oldact,NULL);  //恢复SIGALRM信号以前的处理动作return unslept;}int main(void){while(1){mysleep(2);printf("Two seconds passed\n");}return 0;}
程序执行结果:


三、可重用函数

看一段程序:

#include <stdio.h>#include <signal.h>int a=0;int main(void){a=5;printf("a=%d\n",a);return 0;}
程序很简单,都可以看懂,可是,最后输出a的值真的是5吗?有没有不是5的情况?

虽然在c程序中a=5;只有一行代码,可是在汇编中可能有一条,也可能有两条指令,下面做个实验验证:

#include <stdio.h>#include <signal.h>long long a=0;int main(void){a=5;printf("a=%lld\n",a);return 0;}
我们在程序中将a定义为一个long long型的变量,现在看一下它的反汇编代码:

在命令行中输入:gcc main.c -g
然后输入: objdump -dS a.
看到a=5在汇编中是有两行代码组成的:

又知,a是全局变量,在内存布局中,全部变量存放在.data段中,而且我们还知道虽然函数进程控制和信号占用不同的堆,但是他们的.data段是共用的,所以,当上述程序中的5给a赋值的过程中,来了一个中断信号,要调用一个函数,巧了,调用的这个函数也要给a复制,试想一下,这样不就乱了吗?

这时,我们成这样的函数为不可重入函数,为了防止这样的情况出现,一般不要在程序中使用全局变量。



现在想想,刚才我们写的那个sleep函数中,就有些问题了,当调用alarm函数设置闹钟后,内核调度优先级更高的进程取代当前进程执行,而且执行很长时间,nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数sig_alrm之后再次进入内核,然后返回主函数中执行pause()挂起等待,这样,pause就会一直等待下去了,因为SIGALRM信号已经来过了,不再来了。怎么办啊?

现在这样解决:

在调用alarm定时闹钟之前,先把SIGALRM信号屏蔽,在alarm执行完之后将信号解除屏蔽,这样可以解决问题吗,这样在信号解除屏蔽和pause之间还有可能出问题,要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作(在汇编中是一条指令)就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

新的sleep程序如下:

#include <stdio.h>#include <unistd.h>#include <signal.h>void sig_alrm(int signo){printf("aaaaaaa\n");}unsigned int mysleep(unsigned int nsecs){struct sigaction newact,oldact;sigset_t newmask,oldmask,suspmask;unsigned int unslept;newact.sa_handler=sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags=0;sigaction(SIGALRM,&newact,&oldact);sigemptyset(&newmask);sigaddset(&newmask,SIGALRM);sigprocmask(SIG_BLOCK,&newmask,&oldmask);alarm(nsecs);//pause();suspmask=oldmask;sigdelset(&suspmask,SIGALRM);sigsuspend(&suspmask);unslept=alarm(0);sigaction(SIGALRM,&oldact,NULL);sigprocmask(SIG_SETMASK,&oldmask,NULL);return unslept;}int main(void){while(1){mysleep(2);printf("Two seconds passed\n");}return 0;}



本文主要参考《linux c编程一站式学习》


原创粉丝点击