一.信号的概念和特点
1.信号:Linux系统响应某些状况而产生的事件。进程在接收信号后会采取相应的动作
2.查看所有信号的命令:kill -l
3.信号常见的处理方式:
a)忽略此信号
b)执行该信号的默认处理动作
c)提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式成为捕捉一个信号。
d)SIGKILL SIGSTOP不能被忽略也不能被捕获
4.几种常见的信号
2)ctrl+c 3)ctrl + / 4)非法指令 5)陷入内核 6)abort自杀 7)地址间映射 8)浮点数溢出 9)杀死进程 11)段错误
13)管道破裂 14)闹钟 15)缺省 17)子进程变成僵尸进程 18)继续 19)暂停 23)紧急数据 29)异步IO
二.产生信号的几种方式
1.通过终端按键产生信号(像ctrl+c...)
Core Dump(当一个进程异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core)
默认不允许产生core文件,因为文件中可能包含用户密码等,不安全。
2.硬件异常:CPU--除数0 SIGALFPE信号 ; mmu--内幕才能访问错误(段错误)--11号信号
3.调用系统函数向进程发信号 kill raise absort(assert)
4.软件条件 向读端都关闭了的管道中写数据,就会触发13号信号
三.API(系统函数)
1.向指定的进程发送指定的信号
a)头文件:#include <sys/types.h>
#include <signal.h>
b)函数原型:int kill(pid_t pid, int sig);
c)参数:pid>0给指定的pid进程发送信号 pid==0给本进程组的所有进程法信号 pid=-1给有权发送的所有进程法信号
pid<-1 给|pid|进程组的任何一个进程法信号
sig:发送哪一条信号
d)返回值:成功返回0,失败返回-1
2.给本进程发信号
a)头文件:#include <signal.h>
b)函数原型:int raise(int sig);
c)参数:sig发送哪一个信号
d)返回值:成功返回0,失败返回-1
3.给进程组发送信号(进程组id就是第一个进程id)
a)头文件:#include <signal.h>
b)函数原型:int killpg(int pgrp, int sig);
c)参数:pgrp:指定进程组的id sig:几号信号
d)返回值:成功返回0,失败返回-1
e)进程组:管道连接的多个进程属于同一进程组。fork出来的子进程也属于同一进程组。
4.接收到信号终止进程
a)头文件:#include <stdlib.h>
b)函数原型:void abort(void);
c)返回值:像exit函数一样,总是会执行成功,没有返回值
四.API(软件条件)
1.设定闹钟
a)头文件:#include <unistd.h>
b)函数原型:unsigned int alarm(unsigned int seconds);
c)函数参数:seconds以秒为单位 ,若值为0,取消设置的闹钟。
d)返回值:正确返回闹钟到期之前的秒数 ,没有闹钟返回0
e)目的:调用alarm函数设置一个闹钟,告诉内核在seconds秒之后给当前进程发送一个SIGALRM信号,该信号的默认动作 是终止当前进程。
f)例子:(在2秒内能打印出多少数字)
1 #include <unistd.h>
2 #include <stdio.h>
3
4 int main(void){
5 int i=0;
6 alarm(2);
7 for(i=0;;i++){
8 printf("%d\n",i);
9 }
10 return 0;
11
12 }
设置一个定时器(setitimer)
1 #include <stdio.h>
2 #include <sys/time.h>
3 #include <signal.h>
4 #include <unistd.h>
5 #include <string.h>
6
7 void print(){
8 printf("hello world\n");
9 }
10
11
12 int main(void){
13 signal(SIGALRM,print);
14 struct itimerval timer;
15 //每隔1秒钟发送一个SIGALRM信号
16 timer.it_interval.tv_sec=1;
17 timer.it_interval.tv_usec=0;
18 //3秒0微秒后开始启动定时器
19 timer.it_value.tv_sec=3;//秒
20 timer.it_value.tv_usec=0;//微秒
21
22 int set=setitimer(ITIMER_REAL,&timer,NULL);
23 if(set==-1){
24 perror("setitimer");
25 return 1;
26 }
27 printf("begin wait for 3 seconds\n");
28 while(1);
29
30
31 return 0;
32 }
五.阻塞信号
1)信号相关的概念
a)信号递达:实际执行信号的处理动作
b)信号未决:信号从产生到递达之间的状态
c)进程可以选择阻塞某个信号
d)被阻塞的信号将保持未决状态,直到解除阻塞,才能执行递达的动作
e)阻塞和忽略是不同的,只要被阻塞就不会递达,而忽略是在递达之后可选的一种处理方式
2)信号在内核中的表示
每个信号都有两个标志位表示阻塞和未决,还有一个函数指针表示处理的动作,信号产生时会设置未决标志,直到递达清除该标志。常规信号在递达之前虽产生多次但只记一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
六.信号集操作函数(每一个bit代表一个信号,1有效,0无效)
1)头文件:#include <signal.h>
2)将信号集中的信号清空:int sigemptyset(sigset_t *set);
3)初始化set所指向的信号集:int sigfillset(sigset_t *set);
4)向信号集中添加信号:int sigaddset(sigset_t *set, int signum);
6)从信号集中删除信号:int sigdelset(sigset_t *set, int signum);
7)判断某个信号是否在信号集中:int sigismember(const sigset_t *set, int signum);
8)注意:在调用之前一定要将sigset_t的变量清空或者初始化
七.读取或更改进程的信号屏蔽字(sigprocmask)
1)头文件: #include <signal.h>
2)函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
3)参数: oldset是非空指针,读取进程的当前屏蔽字通过oldset传出
set非空,根据how修改当前的信号屏蔽字
set和oldset都非空,将原来的信号屏蔽字备份到oldset中在根据how和set修改
4)返回值:成功返回0,失败返回-1
5)how的三种情况:SIG_BLOCK set中包含希望添加到当前信号的信号屏蔽字 当前信号|set
SIG_UNBLOCK set中包含希望从当前信号的信号屏蔽字接触的信号 当前信号&~set
SIG_SETMASK 设置当前的信号屏蔽字为set指向的值 当前信号=set
6)如果函数解除了多个信号的阻塞,则他在返回前至少要将一个信号递达
八.读取当前的未决信号集(sigpending)
1)头文件:#include <signal.h>
2)函数原型:int sigpending(sigset_t *set);
3)参数:读取当前的未决信号集,通过set传出
4)返回值:成功返回0,失败返回-1
例子:(屏蔽了SIGINT信号)
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 //判断当前的未决信号集
6 void print(const sigset_t set){
7 int i=0;
8 for(;i<32;i++){
9 if(sigismember(&set,i)==1)
10 printf("1");
11 else
12 printf("0");
13 }
14 printf("\n");
15 return;
16 }
17
18 int main(void){
19 sigset_t t;
20 sigemptyset(&t);
21 sigaddset(&t,SIGINT);
22 sigprocmask(SIG_BLOCK,&t,NULL);
23
24 while(1){
25 sigpending(&t);
26 print(t);
27 sleep(2);
28 }
29 return 0;
30 }
运行结果:
九.信号的捕捉
1)信号递达时调用用户自定义函数,这就叫捕捉信号。用户自定义函数和主函数是两个独立的流程,互不影响,没有调用和被调用的关系,使用不同的堆栈空间。
2)sigaction(读取和修改与指定信号相关联的处理动作)
a)头文件:#include <signal.h>
b)函数原型:int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
c)函数参数:signum 指定的信号编号 ,act非空 根据act修改该信号的处理动作,oldact非空,通过oact传出该信号原来 的处理动作
d)返回值:成功返回0,失败返回-1
3)pause(使调用进程挂起直到有信号递达)(只有出错的返回值)
实现一把mysleep
1 #include <stdio.h>
2 #include <signal.h>
3 #include <unistd.h>
4
5 void sig_alrm(){
6 }
7
8 void mysleep(int seconds){
9 struct sigaction new,old;
10 new.sa_handler=sig_alrm; //ALRM信号处理函数
11 sigemptyset(&new.sa_mask); //清空信号集
12 new.sa_flags=0;
13
14 sigaction(SIGALRM,&new,&old); //SIGALRM信号触发执行new的处理动作
15 alarm(seconds);
16 pause();//使进程挂起直到有信号递达
17 alarm(0); //清除闹钟
18 sigaction(SIGALRM,&old,NULL); //根据old修改动作,就是不做动作
19 }
20
21 int main(void){
22
23 printf("I'm going to sleep!\n");
24 mysleep(3);
25 printf("I'm awake!\n");
26
27 }
十.不可重入条件(在执行过程中不能被中断)
1.函数内部调用了malloc或free
2.调用了标准IO
3.使用了静态变量
可重入函数:多个执行流调用同一个函数时没有逻辑错误
竞态条件:由于进程/线程的切换造成了程序逻辑上的错误
volatile:防止编译器在多执行流的情况下对代码过度优化
十二.SIGCHLD(处理僵尸进程)
通过捕捉这个信号处理僵尸进程,信号捕捉函数中调用waitpid
十一.