linuxbingc(进程信号)
Posted 月屯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linuxbingc(进程信号)相关的知识,希望对你有一定的参考价值。
信号
一个软中断(只是有这样的一个信号,具体怎么做还是有进程决定,硬中断与之相反)
种类
kill -l 可以罗列信号
非实时信号(非可靠信号)特点:信号可能会丢失1一31
实时信号(可靠信号)
特点:信号不会丢失33一64
总共定义了62个信号
产生
硬件产生:
ctrl+c :2号信号SIGINT,键盘当中按下ctrl+c结束一个进程的时候,其实是进程收到了2号信号。2号信号导致了进程的退出。
ctrl+z : 20号信号SIGTSTP
ctrl+| :3号信号SIGQUIT
kill命令向进程发送信号
kill-[信号值][pid]
软件产生
kill函数
#include <signal.h>
int kill(pid_t pid,int sig) :
pid :进程号,要给那个进程发送信号,则填写那个进程的进程号sig :要发送信号的值
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
kill(getpid(),2);
while(1)
printf("test***");
sleep(1);
return 0;
raise函数
int raise(int sig);(自己个自己发送)
sig :要发送的信号值,该函数的实现当中是调用kill函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
raise(2);
while(1)
printf("test***");
sleep(1);
return 0;
扩展
使用gdb查看
ulimit -c unlimited//修改coredump政策
gdb sigtest core.31339
内存访问越界 11信号
除0 8信号
double free 6信号(free函数清除两次指针)
信号处理
- 默认处理方式:SIG_DFL,操作系统当中已经定义信号的处理方式了:2-》终止进程;11 -》终止进程,并且产生核心转储文件
- 忽略处理方式:SIG_IGN,该信号为忽略处理(僵尸进程)
进程收到忽略处理方式的信号后,是不进行处理的。如:SIGCHLD信号,子进程先于父进程退出,子进程退出的时候会给父进程发送SICCHLD,而父进程接收到这个信号之后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。 - 自定义处理方式:程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序猿自己写的函数。
操作系统定义的信号处理方式
man 7 singal
注册
一个进程收到一个信号,这个过程称之为注册,信号的注册和注销并不是一个过程是两个独立的过程。内核中信号注册位图以及sigqueue队列的的了解
task_struct结构体内部
struct sigpending pending;
siget_t
位图更改为1,添加sigqueue节点到sigqueue队列
信号注册的时候,会将信号对应的比特位从0修改为1,表示当前进程收到了该信号。还需要在sigqueue队列当中添加一个sigqueue节点,队列在操作系统内核当中本质上就是一个双向链表(先进先出的特性)
区别:
- 非实时信号的注册
第一次注册:
修改sig位图(0-1),修改sigqueue队列
第二次注册相同信号值的信号:
修改sig位图(1-1),并不会添加sigqueue节点 - 实时信号的注册
第一次注册:
修改sig位图(0-1) ,修改sigqueue队列
第二次注册相同信号值的信号:
修改sig位图(1-1),添加siaqueue节点到sigqueue队列当中
注销
- 非可靠信号
1.将信号对应的sig位图当中的比特位置为0 ( 1-0)
2.将对应的信号的sigqueue节点进行出队操作 - 可靠信号
1.将对应的信号的sigqueue节点进行出队操作
2.判断sigqueue队列当中还有相同信号的sigqueue节点吗
如果有:则比特位不变
如果没有:则比特位改变位0
信号自定义
自定义处理方式,就是让程序猿自己定跌某一个信号的处理方式
函数
signal
sighandler_t signal(int signum,sighandler_t handler);
signum:信号值
handler:更改为哪一个函数处理,接受一个函数地址,函数指针typedef void (*sighandler_t)(int);
返回值:
成功:之前的handler
失败:SIG_ERR
9号信号(强杀)就是不能被程序员自定义处理的信号。
注意回调
在调用signal函数的时候,给第二个参数传递函数地址的时候并没有调用传递的函数。而是,等到进程收到了某个信号之后,才回调刚刚注册的函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void signalback(int sig)
printf("recv signal is:%d\\n",sig);
int main()
//自定义2号信号的处理方式
signal(2,signalback);
while(1)
printf("process\\n");
sleep(1);
return 0;
sigaction
int sigaction(int signum,const struct sigaction *act/*输入性函数*/,
struct sigaction*oldact/*输出型函数*/)
signum:信号值
act:将信号的处理方式更改为act
oldact:原来信号的处理方式
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigcallback(int sig)
printf("sig num %d\\n",sig);
int main()
//1.调用sigaction函数, 自定义2号信号的处理方式; /
///1.1 定义 struct sigaction 结构体对象
struct sigaction act;
act.sa_handler=sigcallback;
sigemptyset(&act.sa_mask);
//将act.sa_mask的内存修改为全0 , 防止后续误导进程收到了某些信号。 /
///int sigemptyset(sigset_t *set);
struct sigaction oldact;
sigaction(2,&act,&oldact);
//2.程序进入死循环, 等待进程收到2号信号, 触发调用自定义的函数
while(1)
sleep(1);
return 0;
原理
信号的捕捉流程
信号的处理时机
当从内核态切换会用户态的时候,会调用do_signal函数处理信号
- 有,就处理信号(信号的处理方式(默认,忽略,自定义) ) ;判断当前信号是否被阻塞, 信号的注销过程,调用信号的处理方式函数;
- 没有,就直接返回会用户态
处理信号
- 默认,忽略:直接在内核就处理结束。
- 自定义处理:调用程序猿自己定义的处理函数进行处理
1.执行用户自定义的处理函数(用户空间)
2.调用sigreturn()再次回到操作系统内核(内核空间)
3.再次调用会调用do_signal函数处理信号
4.调用sys_sigreturn函数回到用户空间,继续执行代码
常见的进入到内核的方式:
调用系统调用函数,
内存访问越界,
访问空指针调用库函数
信号的阻塞
信号的阻塞并不会干扰信号注册
而是说进程收到这个信号之后,由于阻塞,暂时不处理该信号。
加上信号阻塞之后,理解信号的处理;
·进入内核,返回之前,会调用do_signal函数处理信号
·有信号要处理,则先判断该信号是否阻塞,如果没阻塞,在处理信号。如果阻塞,则不处理。
sigprocmask(int how,const sigset_ t *set,sigset_t *oldset);
how:想让sigprocmask做什么事情
- SG_BLOCK:设置某个信号为阻塞状态
- SIG_UNBLOCK :设置某个信号为非阻塞状态
- SIG_SETMASK :用第二个参数“set”,替换原来的阻塞位图。(替换的意思)
set :新设置的阻塞位图
oldset :原来老的阻塞位图
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
//1.阻塞全部信号
sigset_t set;
//将 set 的所有比特位全部设置为1 : int sigfillset(sigset_t *set);
sigfillset(&set);
sigprocmask(SIG_BLOCK,&set,NULL);
//死循环,验证进程收到信号的场景
while(1)
sleep(1);
return 0;
测试
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
for(int i = 1; i <= 64; i++)
if(i == 32 || i == 33 || i == 19)
continue;
kill(12803, i);
return 0;
结论:
9号信号和19号信号是不能被阻塞的
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigcallback(int sig)
printf("recv sig num is %d\\n", sig);
int main()
//1.将40号/2号信号自定义信号的处理方式
signal(40, sigcallback);
signal(2, sigcallback);
//2. 阻塞"全部"信号(40/2)
sigset_t set;
//将 set 的所有比特位全部设置为1 : int sigfillset(sigset_t *set);
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL);
//3.人为给该进程发送多次, 40/2号信号
//4.解除所有信号的阻塞(40/2)
//getchar 就是让程序等待到这里, 给用户发送信号争取时间
getchar();
sigprocmask(SIG_UNBLOCK, &set, NULL);
return 0;
其他扩展内容
父子进程+进程等待+自定义信号处理方式
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
void sigcallback(int sig)
//1.当能调用到该函数, 那么一定说明子进程向父进程发送了SIGCHLD信号
// 隐含的含义就是, 子进程现在退出了。
printf("recv sig num is %d\\n", sig);
//2. 基于上面子进程的情况, 进行wait等到的时候, 一定不会阻塞, 因为子进程一定退出了
int status;
wait(&status);
int main()
pid_t pid = fork();
if(pid < 0)
perror("fork");
return 0;
else if(pid == 0)
//child
sleep(5);
exit(1);
else
//father
//1.这样做就解放了父进程的生产力, 不需要阻塞, 或者循环调用waitpid了
signal(SIGCHLD, sigcallback);
while(1)
printf("i am father process\\n");
sleep(1);
return 0;
volatile关键字-作用:保证内存可见性
每次CPU要计算的数据都是从内存当中获取,拒绝编译时优化的方案(从寄存器当中获取)
gcc/g++的编译选项“-O0, -O1,-O2,-O3,优化级别时越来越高。(理解优化级别越高,程序可能执行的越快)
#include <stdio.h>
#include <signal.h>
volatile int g_val = 1;
void sigcallback(int sig)
printf("recv sig num is %d\\n", sig);
g_val = 0;
int main()
signal(2, sigcallback);
while(g_val)
return 0;
以上是关于linuxbingc(进程信号)的主要内容,如果未能解决你的问题,请参考以下文章