欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Linux】进程信号

程序员文章站 2022-07-12 12:34:53
...
信号

1.基本概念

信号是操作系统通知某个进程有异常或有某种事件发生的通知机制

注意:

  • ctrc+c产生的信号只能发送给前台进程,一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束,就可以接受新的命令,启动新的进程
  • Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像ctrl+c这种控制键产生的信号
  • 前台进程可以在运行过程中用户随时可能按下ctrl+c而产生一个信号也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的
  • kill  -l 命令可以查看系统定义的信号列表

【Linux】进程信号

编号1-31为普通信号,34-64为实时信号

2.信号的产生方式

  • 键盘产生:用户在终端按下某些键时,终端驱动程序会发送给前台进程
  • 硬件异常产生信号:如CPU除零运算,内核会发送一个SEGFPE,或当前进程非法访问内存地址,内核会发送一个SEGSEVG信号发送给该进程
  • 调用系统函数向进程发信号
  • 软件条件产生
(1)通过按键终端产生信号

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump。

Core Dump

当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有bug,比如非法访问内存导致错误,事后可以用调试器检查core文件查清原因错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resourse Limit (这个信息保存在PCB中),默认是不允许产生core文件的,因为core文件中可能包含用户密码的等敏感信息,不安全。可以用umlit -c 1024来允许产生core文件

【Linux】进程信号

然后写一个死循环程序
【Linux】进程信号

前台运行这个程序,然后在终端键按ctrl+\

【Linux】进程信号

使用core文件

【Linux】进程信号

(2)调用系统函数向进程发信号

首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号

【Linux】进程信号

11号是SIGSEGV的编号,以往遇到的段错误都是由非法访问内存产生的,而这个程序本身没错,给它发送SIGSEGV也能产生段错误

kill命令是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发送信号)

#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
这两个函数都是成功发回0,失败发回-1

absort函数使当前进程收到信号而异常停止
#include <stdlib.h>
void absort(void);
就像exit函数一样,absort函数总会成功的,所以没有返回值

(3)有软件条件产生信号

SIGPIP和SIGALRME是一种由软件条件产生的信号。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之
后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数

【Linux】进程信号

这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止

阻塞信号

1.处理信号

操作系统给一个进程发信号,本质就是操作系统修改了目标进程PCB中的pending表对应的比特位

进程不是在收到信号时立即处理信号,而是先在peding表中记录,等到合适的时候再去处理,合适的时候,就是当进程从内核态返回到用户态时,要对信号信息检测并处理

2.信号递达的方式

  • 默认
  • 忽略
  • 自定义(信号捕捉):9号信号不能被捕捉
【Linux】进程信号
3.总结

不管信号最初是谁发送的,最后一定会经过操作系统将信号发送给目标进程,发送信号实质上就是操作系统先找到目标进程,将目标进程PCB中的pending表所对应的比特位置1;这时,操作系统已经完成向目标进程发送信号的任务,处理信号属于信号自己的工作,处理信号在合适的时候,而不是收到信号就立即去处理,当目标进程因为异常。中断,系统调用等原因从用户态切换到内核态,处理完中断后要返回用户态前,会对信号进行检测和处理,如果pending表中某位被置1,再检查block表中查看对应的信号是否被阻塞,如果没有,就去递达这个信号,如果递达方式是忽略,就返回到用户态继续执行原执行流,如果是默认,因为绝大对数的默认处理动作是终止目标进程,所以该进程直接终止,如果是自定义的。那么就需要从内核态返回到用户态(因为方法是用户自定义的,处于内核态执行会不安全)去执行用户自定义的handler方法,执行完毕后,在通过特殊的系统调用切换到内核态,最后在切到main执行流

4.信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set);//清空
int sigfillset(sigset_t *set);//填充,使其中所有信号对应bit置位,表示该信号量集的有效信号包括系统支持的所有信号
int sigaddset(sigset_t *set,int signo);//添加信号
int sigdelset(sigset_t *set,int signo);//删除信号
int sigismember(const sigset_t *set,int signo);//判断一个信号是否存在
5.sigprocmask

作用:可以读取或更改进程的信号屏蔽字(阻塞信号集)--操控block表

int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
第一个参数有三个选项,SIG_BLOCK向阻塞信号量集中添加信号,SIG_UNBLOCK从当前阻塞信号集中删除信号,SIG_SERMASK
设置新的阻塞信号,第二个参数为信号集,第三个参数为保存旧的信号6.集,不想保存设置为NULL

6.sigpending

#include <signal.h>
int sigpending(sigset_t *set);
获取当前进程的未决信号集,通过set参数传出,成功返回0,失败发返回-1
修改进程pending就是发信号
【Linux】进程信号

运行结果为

【Linux】进程信号

7.sigaction

#include <signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
与signal作用相同,可以读取和修改与指定信号相关联的处理动作

struct sigaction
{

	void (*sa_handler)(int);//自定义的处理方法,为函数指针
	void (*sa_sigaction)(int ,siginfo_t *,void *);
	sigset_t sa_mask;//说明这些需要额外屏蔽的信号
	int sa_flags;//通常设置为0
	void (*sa_restorer)(void);
};

【Linux】进程信号

8.pause

#include <unistd.h> 

int  pause (void ); //pause函数使调用进程挂起直到有信号递达。pause只有出错的返回值

可重入函数

正在执行main函数执行流的时候,假设main函数正在调用insert方法(向全局变量中头插节点),节点还没有插入成功,main函数执行流可能因为中断,异常或者系统调用而从用户态进入内核态,当从内核态返回到用户态时,要对信号信息进行检测或处理假设这里的处理方式为信号捕捉,那么这里返回用户态去执行handler函数。而不是回到主控制流程,这里就有两个执行流,假设handler函数也调用了insert方法,insert函数被不同的的控制流程调用,有可能在第一次调用时还没返回就再次进入该函数,这就称为重入,insert函数访问一个全局链表。有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只能访问自己的局部变量或参数,则称为可重入函数

一个函数如果符合下面条件之一则是不可重入函数

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的
  • 调用标准I/O函数
SIGCHLD信号

子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义,可以用于回收多个自进程

【Linux】进程信号

父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN.这样fork的子进程终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程,系统默认的忽略动作和用户函数自定义的忽略动作通常是没有区别的。但这是一个特例。此方法对Linux可用

【Linux】进程信号

小案例mysleep
【Linux】进程信号



上一篇: Linux进程信号探究

下一篇: 47 Redis