赞
踩
目录
信号是进程之间事件异步通知的一种方式,属于软中断。
首先看看有哪些信号 kill -l
编号为34以后的信号为实时信号。
之前在进程操作的文章中讲过kill -9是用来杀掉一个进程,给指定的进程发送SIGKILL信号,好比告诉进程你该结束了。
还有在进程间通信中,在利用管道进行进程间通信时,当读数据的进程退出后,写数据的进程会收到系统发来的13号信号SIGPIPE。
或者平时在命令行输入ctrl c 其实是前台向正在运行的进程发送了2号信号SIGINT。
其实每个信号都有自己的默认的处理函数,对捕捉到的信号进行执行默认的处理函数,部分信号可以自定义处理函数。
第一个参数signum是信号的编号,表明要重新定义几号信号
第二个参数是一个函数指针,返回值是void,参数是int类型。这个函数指针就是指向用户要自定义处理动作的函数。
- #include<iostream>
- #include<signal.h>
- #include<unistd.h>
- using namespace std;
-
- void handler(int sig) //自定义处理函数
- {
- cout<<"catch a signal "<<sig<<endl;
- }
-
- int main()
- {
- signal(2,handler);//自定义捕捉
- while(true)
- {
- cout<<"i am running..."<<endl;
- sleep(1);
- }
- return 0;
- }
此时在ctrl c发现终止不了程序,原因是我们已经修改了2号信号默认的处理函数。(此时在退出用ctrl \(这个是发送的3号信号SIGQUIT))
注意:9号信号不能自定义捕捉
所有的信号都必须经过OS发出给各个进程。
1、通过键盘输入
例如ctrl c,ctrl \
2、通过系统函数
kill函数
kill - 信号名或者信号编号 进程pid
- #include<iostream>
- #include<signal.h>
- #include<unistd.h>
- using namespace std;
-
- void handler(int sig)
- {
- cout<<"catch a signal "<<sig<<endl;
- }
-
- int main()
- {
- //把2、3号信号自定义捕捉
- signal(2,handler);
- signal(3,handler);
- while(true)
- {
- cout<<"i am running..."<<endl;
- sleep(1);
- }
- return 0;
- }
运行程序,先看一下他的pid,
此时ctrl c和ctrl \都不可以结束掉进程。
可以发送9号信号,kill -9
3、通过软中断
SIGPIPE信号(管道相关)
SIGALRM信号
通过alarm函数进行定时,时间到了会给当前进程发送SIGALRM信号,该信号的默认操作是终止当前进程。
4、硬件错误
比如说越界发生的段错误
- #include<iostream>
- #include<signal.h>
- #include<unistd.h>
- using namespace std;
-
- void handler(int sig)
- {
- cout<<"catch a signal "<<sig<<endl;
- sleep(1);
- }
-
- int main()
- {
- //signal(11,handler);//将11号信号自定义捕捉
- int *p=nullptr;
- *p = 10;
- return 0;
- }
-
如果自定义捕捉,可以发现是11号信号。
虚拟地址映射真实地址的过程出错,实际上是硬件mmu等报错,由操作系统接收。
当信号产生时,并不是立即处理的,会先进入一个状态(未决),等到合适的时机才会进行处理。
信号递达(handler)
信号的处理动作,包括默认、自定义捕捉和忽略。
信号未决(pending)
是一种状态(产生到递达之间的状态)。
信号阻塞(block)
进程可以阻塞一个信号,被阻塞的信号一旦产生,将一直处于未决状态,直到进程解除对此信号的阻塞,才会执行递达。
由于信号是发送给进程的,进程肯定有相关的数据结构
举个例子:
例如1号信号SIGHUP的block =0,pending = 0,说明信号未被阻塞,信号也未产生。
2号信号SIGINT的block = 1,pending = 1,说明信号已经产生,处于未决状态,但是又由于信号被阻塞了,所以在等待解除阻塞。
3号信号SIGQUIT的block = 1,pending = 0,信号未产生,但是可以被阻塞。
在用户这一端,我们只能去修改block标志位,使得让某些信号进入阻塞状态。
sigprocmask函数
用来修改block的函数
第一个参数how决定要以哪种方式修改block。
假设当前信号屏蔽字为mask
SIG_BLOCK方式的意思是:mask = mask | set (add set)
SIG_UNBLOCK :mask = mask&~set (remove set)
SIG_SETMASK:mask= set (equal)
第二个参数set,用户传入设置block,具体意思根据第一个参数
第三个参数oldset,记录前一次的屏蔽字
信号集操作函数
sigset_t声明的变量,sigset_t其实是 unsigened long
接下来这一组函数本质上是用来位操作的,设置好block的值之后,传入上面的sigprocmask函数的set。
sigpending函数
用来读取当前进程未决信号集。
例子
- #include<iostream>
- #include<signal.h>
- #include<unistd.h>
- using namespace std;
-
- void show_pending(sigset_t pending)//按位显示pending信号集
- {
- for(int i=1;i < 32;i++)
- {
- if(sigismember(&pending,i))
- cout<<"1";
- else
- cout<<0;
- }
- cout<<endl;
- }
- void handler(int sig)
- {
- cout<<"catch a signal "<<sig<<endl;
- }
-
- int main()
- {
- //定义信号集
- sigset_t pending;
- sigset_t block,oldblock;
- //初始化
- sigemptyset(&pending);
- sigemptyset(&block);
- sigemptyset(&oldblock);
- //将2号信号阻塞
- sigaddset(&block,2);
- sigprocmask(SIG_BLOCK,&block,&oldblock);
-
- cout<<"开始阻塞"<<endl;
- //打印pending信号集
- for(int i=0;i < 5;i++)
- {
- sigpending(&pending);
- show_pending(pending);
- sleep(1);
- }
- //自定义捕捉
- signal(2,handler);
- //解除阻塞
- sigprocmask(SIG_UNBLOCK,&block,nullptr);
- cout<<"解除阻塞"<<endl;
- while(1);
- return 0;
- }
可以看到当发送2号信号后,2号信号处于pending状态。一旦解除阻塞,就会递达(合适的时机)
相对应的未决信号就会被清空
合适的时机:从内核态切换到用户态时,才进行相关的检测。
捕捉信号:信号处理动作是用户自定义的函数。
如果不是用户自定义的函数,即系统默认的处理函数,只需要一次用户到内核状态切换的过程。
如果是用户自定义的捕捉函数
在自定义捕捉函数时,总共需要四次切换,用户到内核、内核到用户切换各两次。
举个例子: 用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数, sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行。
问题
为什么要这么复杂而不直接在内核调用信号处理函数?
内核权限更高,调用用户自定义的处理函数有风险,之所以引入内核和用户两种模式,也是为了操作系统的安全。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。