1.信号
信号主要用来通知进程发生了异步事件,而不会给进程传递任何数据。信号总共有62个,前32个被称为普通信号,34-64被称为实时信号。通常只关心普通信号。
2.信号的产生
1> 键盘
通过组合键发送一个信号,一定是给前台进程的。例如ctrl+c
2>用系统函数发送信号
可以给指定进程发送信号,例如kill命令用kill()函数实现,abort函数是当前进程接收到信号而异常终止。raise自己给自己发送信号。
3>由软件条件产生
alarm(时间数),当alarm完成后直接终止进程。
3.信号的处理
1>忽略
2>执行信号默认处理方式
3>自定义方式:捕捉信号
使用sighandler_t signal(int signum, sighandler_t handler);
注:第一个参数是哪个信号,第二个参数是捕捉信号的函数名
4.阻塞信号
1>实际执行信号处理动作称为信号递达。
2>信号从产生到递达之间的状态称为信号未决。
进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。(忽略与阻塞不同,忽略是在信号递达之后)
信号不是立即处理的,是在一个合适的点上处理的。除了9号信号。
信号在内核中表示示意图:
一个信号没有pending,可以被block。block与pending没有关系。
信号代码举例:
1 #include<stdio.h> 2 #include<signal.h> 3 void show(sigset_t* sig_pending)//展示未决的信号 4 { 5 int i=1; 6 for(i=1;i<32;++i) 7 { 8 if(sigismember(sig_pending,i)) 9 { 10 printf("1"); 11 } 12 13 else 14 { 15 printf("0"); 16 } 17 18 19 } 20 printf("\n"); 21 } 22 23 void handler(int sig)//捕捉递达的信号 24 { 25 printf("I see you! %d\n",sig); 26 27 28 } 29 30 int main() 31 { 32 sigset_t sig_mask; 33 sigset_t sig_old; 34 sigset_t sig_pending; 35 sigemptyset(&sig_mask); 36 sigemptyset(&sig_old); 37 sigemptyset(&sig_pending); 38 sigaddset(&sig_mask,SIGINT);//将2号信号放入信号集中阻塞 39 sigprocmask(SIG_SETMASK,&sig_mask,&sig_old); 40 int count=10; 41 signal(SIGINT,handler); 42 while(1) 43 { 44 sigpending(&sig_pending);//获取当前进程信号集,与pending有关 45 show(&sig_pending); 46 sleep(1); 47 if(count<0) 48 { 49 sigprocmask(SIG_SETMASK,&sig_old,NULL);//恢复2号信号 50 } 51 count--; 52 } 53 return 0; 54 }
用户模式与内核模式切换关于信号
5.捕捉信号
1>sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct
sigaction *oact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则
返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。
若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结
构体
.
1 #include<stdio.h> 2 #include<signal.h> 3 #include<string.h> 4 void handler(int sig) 5 { 6 printf("I am that signal...%d\n",sig); 7 } 8 int main() 9 { 10 struct sigaction old; 11 struct sigaction act; 12 act.sa_handler=handler;//自定义处理信号动作 13 act.sa_flags=0; 14 sigemptyset(&act.sa_mask); 15 memset(&old,‘\0‘,sizeof(old)); 16 sigaction(SIGINT,&act,&old);对2号信号设置 17 while(1) 18 { 19 printf("I am waiting a signal....\n"); 20 sleep(2); 21 } 22 return 0; 23 }
2>pause
int pause(void);
pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终
止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause
不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为
EINTR, 所以pause只有出错的返回值(想想以前还学过什么函数只有出错返回值?)。错误码
EINTR表 示“被信号中断”。
下面我们用alarm和pause实现sleep(3)函数,称为mysleep。
1 #include<stdio.h>
2 #include<signal.h>
3 #include<string.h>
4 void handler(int sig)
5 {
6 //donothing
7 }
8 void my_sleep(int time)
9 {
10 struct sigaction act,old;
11 act.sa_handler=handler;
12 act.sa_flags=0;
13 sigemptyset(&act.sa_mask);
14 sigaction(SIGALRM,&act,&old);
15 alarm(time);//设置闹钟
16 pause();
17 int ret=alarm(0);//清理闹钟
18 sigaction(SIGALRM,&old,NULL);恢复信号
19
20 }
21 int main()
22 {
23 while(1)
26 {
27 printf("I am waiting a signal....\n");
28 my_sleep(5);
29 }
30 return 0;
31 }
程序结果:每隔5秒打印一次信息,相当于调用了sleep()函数。
程序分析:
1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数
sig_alrm。
2. 调用alarm(nsecs)设定闹钟。
3. 调用pause等待,内核切换到别的进程运行。
4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数
是sig_alrm。
6. 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,
从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入 内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号
以前的处理 动作。