目录
前言
一、信号的产生
1.1 热键
1.2 kill函数
1.3 异常出错
1.4 软件条件
二、信号保存(阻塞信号)
2.1 信号过程状态
2.2 信号在内核的表示
2.3 信号阻塞设置
三、信号捕捉
3.1 进程状态(用户&内核)
3.2 内核级内存空间
3.3 内核态中捕捉信号
3.3.1默认处理信号进程用户状态变化
3.3.2 自定义方式处理信号进程用户状态变化
3.4 信号处理
3.4.1 signal函数
3.4.2 sigaction函数
四、信号相关题目
Q1:一个进程无法被kill杀死的可能有哪些?[多选](ABCD)
Q2:以下描述正确的有:(B)
学习信号之前,我们联系一下生活中有哪些信号?比如红绿灯告诉你行驶条件、上课老师朝你瞪了一眼“暗示”你别东张西望、烽火狼烟告知边境战火等等。这些信号本身具有通知作用,告诉我们接下来应该怎么做(1.默认处理 2.自定义 3.忽略 ),做之前前提是我们对信号有所认识。带着这些生活的基本常识,我们联系计算机术语,开始学习Linux信号,深入了解信号的产生、保存、处理。
Linux信号种类:
当我们运行程序后按下 ctl + c 组合键,程序会终止!这里我们的组合键OS会识别成2号信号!2号的默认处理方式是终止前台进程。
通过命令行参数获取kill参数:
基于kill,又衍生出raise、abort函数,他们本质是回调kill,只不过pid确认为本进程!
raise:可以给自己本进程发送任意信号
abort:可以给自己本进程发送指定的6号信号
我们常遇到÷0错误、野指针非法访问
当我们除零,OS会发出8号SIGFPE信号默认终止进程!
OS怎么知道我们除零了呢?--》硬件CPU内有状态寄存器,如果CPU发现÷0则将该寄存器内的位图溢出标记位设置为1!OS检查溢出标记位发现其为1-》发送8号信号!
当我们非法访问,OS会发出11号SIGSEGV信号终止进程!
基于代码满足某些条件时就会触发信号!
定时器软件条件:
#include
#include
#include
int cnt = 0;
static void Handler(int signo)
{
std::cout<<"我收到了一个信号,cnt = "<
1.实际执行信号的处理动作称为信号递达(Delivery)
2.信号从产生到递达之间的状态,称为信号未决(Pending)。
3.进程可以选择阻塞 (Block )某个信号。
4.被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
每个信号都有两个位图标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
相关函数:http://t.csdn.cn/ufns9
#include
#include
#include
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
static void show_pending(const sigset_t &pending)
{
for(int signo=MAX_SIGNUM;signo>=1;signo--)
{
if(sigismember(&pending,signo))
std::cout<<"1";
else std::cout<<"0";
}
std::cout<<"\n";
}
int main()
{
sigset_t block,oblock,pending;
//1.1初始化 必须要!
sigemptyset(&block);
sigemptyset(&oblock);
//1.2 添加要屏蔽的信号 这里我屏蔽2号信号
sigaddset(&block,BLOCK_SIGNAL);
//1.3 开始屏蔽,设置进内核
sigprocmask(SIG_SETMASK,&block,&oblock);
//2.遍历打印pending信号集
while(true)
{
sigemptyset(&pending);
//获取信号集
sigpending(&pending);
//打印位图
show_pending(pending);
sleep(1);
}
return 0;
}
进程状态分为内核态与用户态,我们一般在没有申请内存资源或者访问底层硬件时的状态是用户态,当我们使用系统调用时,OS会将我们的进程状态由用户态切换为内核态,此时这个切换的过程是有时间成本的!
可以很明显由下列图中看出系统调用可以决定用户状态的切换!
但需要注意的是系统调用并不是唯一的切换条件:
每个进程都会向OS申请4G的物理内存空间,其中用户态内存每个进程独有3G空间,都有独立的页表映射!但内核态内存空间是每个进程所共有的,内核空间页表映射是只有一份被所有进程所共有的!
当我们因为系统调用由用户态进入内核态后,系统将会进行查看信号的位图结构,如果是处于未达且未阻塞,那么OS会进行对该信号的捕捉,执行信号的处理方式!当信号被捕捉后,信号位图状态由1->0!
在CPU内,有一个寄存器作用是判定进程用户状态!
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。
signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
#include
#include
#include
#include
using namespace std;
void Count(int cnt)
{
while(cnt)
{
printf("cnt: %2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
void handler(int signo)
{
int cnt = 10;
cout << "get a signo: "<
A.这个进程阻塞了信号
B.用户有可能自定义了信号的处理方式
C.这个进程有可能是僵尸进程
D.这个进程当前状态是停止状态
A正确 信号被阻塞,则暂时不被处理(SIGKILL/SIGSTOP除外,因为无法被阻塞,这里说的是可能性,因此不做太多纠结)
B正确 自定义处理之后,信号的处理方式有可能不再是进程退出
C正确 僵尸进程因为已经退出,因此不做任何处理
D正确 进程停止运行,则将不再处理信号
A.未决信号指的是已经被处理的信号
B.未决信号指的是还未被处理的信号
C.同一个信号可以在未决信号集合中添加多次
D.每一个信号处理完毕后都会从pending集合中移除
未决信号指的是,进程收到了信号,被添加到未决信号集合中,但是暂时还没有被处理的信号。
非可靠信号在进行注册时,会查看是否已经有相同信号添加到未决集合中,如果有则什么都不做,因此非可靠信号只会添加一次,因此处理完毕后会直接移除(准确来说是先移除,后处理)。而可靠信号会重复添加信号信息到sigqueue链表中,相当于可靠信号可以重复添加,处理完毕后,因为有可能还有相同的信号信息待处理,因此并不会直接移除,而是检测没有相同信号信息后才会从pending集合中移除