信号
信号的相关概念
- 是什么? 信号是进程间事件异步通知的一种方式,属于软中断。
- 查看信号列表: kill -l
- 给指定进程发信号: kill -signum +进程id
信号的产生方式
1)通过终端按键产生信号
SIGINT默认动作是终止进程,SIGQUIT默认动作是终止进程且core dump。
2)调用系统函数向进程发信号
- int kill(pid_t pid, int signo):给指定进程发送信号
- int raise(int signo):给自己发送signo信号
- void abort(void):是当前进程接收到信号而异常终止,abort函数总会成功
- main函数中使用命令行参数:kill(atoi(argv[1]), atoi(argv[2])):
int main(int argc, char* argv[]){ //argv[0]表示可执行程序
printf("My pid id %d", getpid());
signal(2, handler);
if(argc==3){
kill(atoi(argv[1]), atoi(argv[2]));
}
}
3)由软件条件产生信号
调用alarm(unsigned int seconds)函数设定一个闹钟,告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
4)硬件异常产生信号
内核检测到硬件异常会向进程发送适当的信号,比如:
除0操作----CPU运算单元异常
访问非法地址、野指针----MMU异常
注:野指针不存在虚拟地址,不会分配物理内存,在页表+MMU转换时就会发现地址转换错误。
信号的处理方式
- 忽略此信号
- 执行该信号默认的处理动作
- 捕捉一个信号:提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行该函数。
void handler(int signo){ //自定义一个信号处理函数
printf("catch a signal: %d\n", signo);
}
int main(int argc, char* argv[]){
printf("My pid id %d", getpid());
signal(2, handler); //当向进程发送2号信号时,就会调用handler函数去处理该信号
}
信号的产生流程
信号在内核中的示意图
1)信号的发送及记录
- 信号的记录是在进程的PCB,用sigbitmap位图来保存信号数据,记录信号是否产生:比特位的位置表示信号的编号,比特位的内容表示是否收到信号;
- 所以说,信号的发送本质是OS直接去修改目标进程PCB中的信号位图,只有OS有资格进行信号的发送。
2)信号相关操作
- sigset_t表示信号集,sigset mask表示阻塞信号集,也叫信号屏蔽字;
- int sigpending(sigset_t *set)----获取当前进程未决的信号集,set为输出型参数;
- int sigprocmask(int how, const sigset_t *set, sigset_t *oset)----读取或者更改信号屏蔽字,若set、oset非空,在设置信号屏蔽字之前,先把原来的信号屏蔽字备份到oset中,再根据set和how的参数更改信号屏蔽字。
3)信号集操作函数
- int sigemptyset(sigset_t *set)----清空信号集
- int sigismember(const sigset_t *set, int signo)----判断signo信号是否在信号集中
- int sigaddset(sigset_t *set, int signo)----添加signo信号到信号集中
4)测试用例
用例说明:屏蔽2号信号,获取其未决状态的信息(pending表中2号信号的比特位内容为1);一段时间之后取消对2号信号的阻塞,获取其递达状态的信息(pending表中2号信号的比特位内容为0)
//普通信号:1-31号
void show_pending(sigset_t *pending){ //方便我们观察pending表中各信号的状态
int sig=1;
for(; sig<=31; sig++){
if(sigismember(pending, sig)){
printf("1");
}
else{
printf("0");
}
}
printf("\n");
}
void handler(int signo){ //自定义信号处理函数
printf("catch a signo: %d\n");
}
int main(){
//ctrl+C之后,2号信号产生
//此时可以使用信号捕捉,继续获取其后的pending表的状态信息
signal(2, handler);
//一旦接收到2号信号(未决状态),对应Pending表中的内容就会由0变为1
sigset_t block;
sigset_t oblock;
sigemptyset(&block);
sigemptyset(&oblock);
sigaddset(&block, 2); //屏蔽2号信号集:将其block表的比特位内容由0置为1
//在设置信号屏蔽字之前,先把原来的信号屏蔽字备份到oblock中
sigprocmask(SIG_SETMASK, &block, &oblock);
//展示pending表
sigset_t pending;
int count = 0;
while(1){
sigemptyset(&pending);
sigpending(&pending); //获取当前进程的pending信号集
show_pending(&pending);
sleep(1);
count++;
if(count == 10){ //一段时间之后,解除对2号信号的阻塞
printf("recover sig mask!\n");
sigprocmask(SIG_SETMASK, &oblock, NULL);
}
}
}