Linux进程信号
信号是进程之间事件异步通知的一种方式,属于软中断。
只是告诉有这样一个信号,但是具体这个信号怎么处理,什么时候处理由进程决定的。所以是软中断
硬件产生:
1.ctrl+c:2号信号 SIGINT(键盘当中按下ctrl+c结束一个进程的时候,其实是进程收到了2号信号。2号信号导致了进程的退出。 )
2.ctrl+z:20号信号 SIGTSTP
3.ctrl+|:3号信号 SIGQUIT
4.kill命令向进程发送信号:kill -[信号值] [pid]
软件产生:
- kill函数:#include
int kill(pid_t pid,int sig);
pid:进程号,给哪个进程发就填这个进程的进程号
sig:要发送的信号的值- raise函数:int raise(int sig);
谁调用给谁发信号
sig:要发送的信号值
该函数的实现实际时调用kill函数
int raise(int sig){ return kill(getpid(),sig); }- kill -[num] [pid] 可以给进程发信号
int main(){
raise(2);
printf("If you saw that, you'd be damned\n");
kill(getpid(), 3);
while(1){
printf("i am siganl test process...\n");
sleep(1);
}
return 0;
}
程序崩溃收到的信号
- 解引用空指针:11号信号,解引用空指针,悬垂指针
- 内存访问越界:11号信号,操作系统容忍进程访问不属于自己的内存,但前提条件是越界访问的内存没有分配给其他进程使用
- 除0:8号信号
- double free:6号信号
1.默认处理方式:SIG_DFL,操作系统中已经定义好处理信号的方式了
比如:2->终止进程,11->终止进程并核心转储文件
2.忽略处理方式:SIG_IGN(僵尸进程)
进程收到忽略处方式的信号后,是不会进行处理的
SIGCHLD信号:子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD,而父进程收到这个信号后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程
3.自定义处理方式:程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序员自己写的函数
概念:
一个进程收到一个信号,这个过程称之为注册
信号的注册与注销并不是一个过程,是两个独立的过程
内核中的定义:
操作系统并非当作数组使用,而是当作位图进行使用
signal数组当中一个元素有64个比特位
注册过程:
位图更改为1,添加sigqueue节点到sigqueue队列
信号注册的时候会将对应信号的比特位从0置为1,表示在当前进程受到了该信号
还需要在sigaqueue队列中添加一个sigqueue节点,队列在操作系统内核中本质就是一个双向链表(先进先出)
区别:
- 非实时信号的注册
第一次注册:修改sig位图(0-1),修改sigaueue队列
第二次注册相同信号值的信号:修改sig位图(1-1),并不会添加siaqueue节点
- 实时信号的注册
第一次注册:修改sig位图(0-1),修改sigqueue队列
第二次注册相同信号值的信号:修改sig位图(1-1),添加siaqueue节点到sigqueue队列当中
- 非可靠信号:
1.将信号对应的sig位图中的比特位(1-0)
2.将对应的信号sigqueue节点进行出队操作
- 可靠信号:
1.将对应的信号sigqueue节点进行出队操作
2.判断sigqueue队列当中还有相同信号的sigqueue节点吗如果有:则比特位不变,如果没有:则比特位改变位0
1.第一种signal
- 函数:sighandler_t signal(int signum,sighadler_t handler);
signum:信号值
handler:更改为一个函数处理,接收一个函数的地址,函数指针
typedef void (*sighandler_t)(int) ;无返回值,参数为int- 注意:在调用signal函数的时候,给第二个参数传递函数地址的时候并没有调用传递的函数。而是,等到进程收到了某个信号之后,才回调刚刚注册的函数
9号信号(强杀〉就是不能被程序员自定义处理的信号。
代码:
#include
#include
#include
void sigcallback(int sig){
//sig : 触发调用该函数, 收到的信号值
printf("recv signal num is %d\n", sig);
}
int main(){
//1.自定义2号信号的处理方式
signal(2, sigcallback);
signal(3, sigcallback);
signal(9, sigcallback);
while(1){
//printf("test signal process...\n");
sleep(1);
}
return 0;
}
函数:int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
signum:信号值
act:将信号的处理方式更改为act
oldact:原来信号的处理方式
struct sigaction{
void (*sa_handler)(int);//保存信号处理方式的函数指针
void (*sa_sigaction)(int,siginfo_t *,void *);//也是保存信号的处理方式的函数指针,但是没有使用,使用的时候配合sa_flags一起使用,当sa_flags的值为SA_SIGIFC的时候,信号按照sa_sigaction保存的函数地址进行处理
sigset_t sa_mask;//当进程处理信号的时候,如果还有收到信号,则放到该信号位图中,后续再放到进程的信号位图中
int sa_flags;
void (*sa_restorer)(void);//保留字段
};
#include
#include
#include
void sigcallback(int sig){
printf("rcv signalnum is %d\n",sig);
}
int main()
{
//自定义二号函数处理方式sigaction
//定义一个结构体对象
struct sigaction act;
act.sa_handler=sigcallback;
sigemptyset(&act.sa_mask);
struct sigaction oldact;
sigaction(2,&act,&oldact);
while(1){
sleep(1);
}
return 0;
}
当从内核切换回用户态时,会调用do_signal函数处理信号
有,就处理信号
没有则直接返回用户态
3.处理信号的不同处理方式
默认,忽略直接在内核就处理结束。
自定义处理:
执行用户自定义的处理函数(用户空间)
调用sigreturn()再次回到操作系统内核(内核空间)
再次调用会调用do_signal函数处理信号
调用sys_sigreturn函数回到用户空间,继续执行代码
4.常见的进入内核的方式
调用系统调用函数
内存访问越界,访问空指针
调用库函数
1.信号的注册是信号的注册,信号阻塞是信号阻塞,信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号后,暂时不处理这个信号,而是等信号不阻塞之后再进行注册
3.函数接口:
- how:想让ssigprocmask做什么事情
SIG_BLOCK:设置某个信号的阻塞状态,计算新的阻塞位图方式为:block(new)=block(old)|set
SIG_UNBLACK:设置某个信号为非阻塞状态,计算新的阻塞位图方式为:block(new)=block(old)&(~set)
SIG_SETMASK:用第二个参数替换原来的阻塞位图,计算新的阻塞位图方式为:block(new)=set
set:新设置的阻塞位图
oldset:原来的老的阻塞位图
4.代码:
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);
return 0;
}
前面提到:子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD,而父进程收到这个信号后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。
之前调用wait/wait_pid解决,但是前者会导致父进程阻塞,无法运行,后者虽然设置了非阻塞,但是需要循环,子进程不退出之前,父进程只有循环体内的代码可以运行,那么该如何完美解决呢?
父子进程+进程等待+自定义信号处理方式
#include
#include
#include
#include
#include
void sigcallback(int sig){
//打印这句话说明子进程退出
printf("recv sig num is %d\n",sig);
//非阻塞,上面子进程已经退出了
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
//不需要阻塞,也不需要循环
signal(SIGCHLD,sigcallback);
while(1)
{
printf("i am father process\n");
}
}
return 0;
}