软件中断:
通知一个进程发生了某个事件,打断进程当前操作,去处理这个事件
信号:多种多样且能够被识别的
查看信号种类:
kill -l
62 种信号
1 ~ 31 :非可靠信号,非实时信号
34 ~ 64:可靠信号,实时信号
信号的生命周期:产生、注册、注销、处理、阻塞
硬件产生
ctrl+c:终止一个进程
ctrl+z:停止一个进程,只是暂停运行
ctrl+\:quit
软件产生
(1)kill 指令
kill 指令
kill 进程号:杀死一个进程,本质上是给指定进程发送一个终止信号,而进程收到信号后,对信号的处理就是退出运行
int kill(pid_t pid,int signum);
给指定进程发送一个指定的信号值
getpid() :获取当前程序进程号
bt:查看函数调用栈
(2)raise
int raise(int signum);
给进程自身发送一个指定的信号值
(3)abort
abort();
给进程自身发送一个 SIGABRT 信号
(4)alarm
int alarm(int sec);
sec 秒之后给进程发送 SIGALRM 信号
定时器,时间到了之和给进程自身发送一个 SIGALRM 信号值
#include
#include
#include
void mysleep(int n)
{
alarm(n); //设置 alarm 信号
pause();
}
void sigcb()
{
printf("3s 已经结束!\n");
}
int main()
{
signal(SIGALRM,sigcb); //信号中断
mysleep(3); // alarm
while(1){ //此时进行了信号中断操作,因此会继续执行下列代码
printf("use alarm!\n");
sleep(2);
}
return 0;
}
有个进程 KILL 杀不死,原因是什么?
(1)当前进程是一个僵尸进程
(2)可能是停止状态(T)----- 对信号不出来
(3)信号可能被阻塞,或者被自定义处理了。
让一个进程能够知道自己收到了某个信号-----在进程中做记号
在进程 pcb 中有个 sigpending 未决信号集合(是个位图);
进程的 PCB 中有个 sigqueue 链表,添加收到的信号链表,收到一次信号添加一个节点。
未决信号---------还没有被处理的信号
非可靠信号:
若没有收到这个信号则注册一下(添加一个新节点,位图置为 1),若之前已经收到了这个信号还未处理则什么都不做(丢弃)
可靠信号:
不管位图是否为 1(是否已经注册,还没处理),都会添加一个信号节点
flag 1 : 将一个进程调到前台运行
在信号被处理之前,消除信号存在的痕迹(主要是防止信号被重复处理)
(1)非可靠信号的注销
删除信号的信息节点,位图置为 0
(2)可靠信号的注销
删除信号的一个信息节点,当没有相同节点则位图置为 0
调用信号的事件处理函数
三种信号处理方式
(1)默认处理:系统中已经预定好的处理方式
(2)忽略处理:空的处理方式
(3)自定义处理方式:自己定义一个处理函数,替换掉处理函数
接口
sighandler_t signal(int signum,sighandler_t handler);
typedef void(*sighandler_t)(int); //函数
handler:
SIG_DFL:信号默认处理方式
SIG_IGN:忽略处理
功能:使用 handler 函数,替换掉 signum 信号当前的处理函数-----意味着进程收到 signum 信号,则使用 handler 函数进行处理
练习:
发现当前程序运行使用 ctrl+c 中断不起作用------因为使用的信号中断----------- ctrl + / 退出 或者 kill (ps -ef | grep signal)
自定义处理方式的信号捕捉流程
信号依然会注册,但是暂时不处理(直到被解除阻塞)
进程 PCB 中有个 block 阻塞信号集合,哪个信号被添加到了 block 集合中,就表示如果收到了也暂时不处理
接口
int sigprocmask(int how,sigset_t *set,sigset_t *old);
(1) how:要对 PCB 中信号阻塞集合进行的操作
SIG_BLOCK:block |=set ; 将 set 集合中的信号添加到 block 中
SIG_UNBLOCK:block &=~set ;将 set 中的信号解除阻塞
SIG_SETMASK:block=set
(2)old:将修改前 block 集合中的信息添加到 old ,便于还原
返回值:失败返回 -1,成功 0
#include
int sigemptyset(sigset_t *set); //清空集合 set
int sigfillset(sigset_t *set); //将所有信号加载到 set 集合
int sigaddset(sigset_t *set, int signum); //将某一个信号添加到 set
int sigdelset(sigset_t *set, int signum); //删除 set 中某个信号
int sigismember(const sigset_t *set, int signum); //查找某个信号
代码练习:
1 #include
2 #include
3 #include
4 #include
5
6 void sigcb(int no)
7 {
8 printf("no = %d\n",no);
9 }
10
11 int main()
12 {
13 signal(SIGINT,sigcb); //修改 2 号信号处理方式-----------非可靠信号 ,多次收到但只会注册一次
14 signal(SIGRTMIN+5,sigcb); //修改 39 号信号的处理方式----------可靠> 信号,多次收到就会注册多次
15
16 sigset_t set,old;
17 sigemptyset(&set); //清空集合
18 sigemptyset(&old);
19 sigfillset(&set); //将所有信号填充到 set 集合
20
21 sigprocmask(SIG_BLOCK,&set,&old); //信号阻塞
22 printf("按回车继续运行!\n");
23 getchar();
24
25 sigprocmask(SIG_UNBLOCK,&set,NULL); //解除阻塞
26 // sigprocmask(SIG_SETMASK,&old,NULL); //还原旧信号
27
28 while(1)
29 sleep(1);
30 return 0;
31 }
实时信号先进行处理,非实时信号后处理
注意
两个特殊信号--------不可被阻塞,不可被自定义,不可被忽略:
kill -9 信号编号:杀死信号
kill -19 信号编号
(1)自定义或忽略 SIGPIPE 信号
管道所有读端被关闭,继续 write 会触发异常
(2)自定义或忽略 SIGCHLD 信号
子进程退出时给父进程发送信号,然后成为僵尸进程,只不过因为信号默认处理为忽略,因此子进程退出并没有得到处理--------------进程等待 wait
signal(SIGCHLD,SIG_IGN);
作用:修饰一个变量,保持变量的内存可见性。
要求 CPU 在进行变量处理时候,每次都重新到内存获取数据进行处理
编译器若认为某个值 cpu 经常访问到,多次从内存读取太慢--------编译器优化:直接将这个值放在寄存器当中,每次不需要从内存中读取数据--------------但这种做法有时候是不合理的
一个程序的运行,可能存在多个执行流程
若一个函数同时在多个执行流程中,进入执行,就叫做函数重入
一个函数在多个执行流程中重入之后,并不会产生一些异常或预期之外的情况。
一个函数在多个执行流程中重入之后,有可能会产生一些数据二义性、导致产生预期之外的结果。
1 #include
2 #include
3 #include
4 #include
5
6 int a=1,b=1;
7 void test(char* str)
8 {
9 printf("%s----start-------\n",str);
10 a++;
11 b++;
12 printf("%s-%d\n",str,a+b); //产生数据二义性
13
14 printf("%s-----end-------\n",str);
15 }
W> 16 void sigcb(int no)
17 {
W> 18 test("sigcb");
19 }
20 int main()
21 {
22 signal(SIGINT,sigcb);
W> 23 test("main");
24 sleep(2);
25 return 0;
26 }
ps:
欢迎评论留言哦~~~