进程信号 **
信号:事件通知---软中断
信号基本认识:每个信号都对应了一个事件
linux信号的种类:kill -l 查看信号种类--62种信号
1 ~31 是非可靠信号(非实时信号)
34~64 是可靠信号(实时信号)
信号的生命周期
(1)信号的产生
诞生一个信号(硬件/软件)
硬件产生:
ctrl+c ctrl+z 20 ctrl+|
软件产生:
#include <signal.h>
#include <abort.h>
int kill(pid_t pid,int sig)
pid:指定信号要发送给哪个进程的进程pid
sig:要发送的信号
void abort()
//给调用进程发送SIGABORT信号
int raise(int sig)
//给调用进程发送指定信号
unsigned int alarm(unsgned int seconds)
//定时器:在seconds秒后给调用进程发送SIGALARM信号
大部分信号都会导致进程退出
SIGCHLD :子进程退出后系统给父进程发送的信号
而且不会引起进程退出
信号的到来会打断当前进程的阻塞状态
core dumped: 核心转储--退出时保存程序的运行信息
默认是关闭状态
core文件命名方式 core.pid pid:进程pid
ulimit -a 查看核心转储文件大小
ulimit -c 1024 设置核心转储文件大小为1024
core文件的使用:
gdb ./loop --> core-file ./core.pid -->bt查看调用栈信息
先加载运行程序->加载程序运行信息->开始调试:bt查看调用栈信息
(2)信号的注册
将信号添加到pcb中
PCB->struct sigpending --> sigset_t(信号集合:位图)
信号到来之后都会添加到未决信号集合中
操作系统给一个进程发送信号,实际上就是向这个进程pcb的
信号pending 集合中添加信号(修改位图),因为位图只能标记
信号是否存在,不能标记信号的个数
pcb中有一个sigqueue链表,信号到来就会组织一个节点添加到这个链表中
可靠信号到来:修改位图,每个信号都组织节点添加到链表
非可靠信号到来:修改位图,若位图为1,则什么都不做
否则,添加一个节点,修改位图
(3)信号的注销
从pcb中将信号移除
可靠信号:因为节点可能有多个,删除节点后判断是否还有相同节点
判断是否修改位图
非可靠信号:因为节点只有一个,因此删除节点后,位图直接修改为0
(4)信号的处理
(1)默认: 因为每个信号都对应了一个事件,这些事件在操作系统中都有
既定义完毕的处理方式
(2)忽略: 什么都不干
(3)自定义: 用户定义处理方式(函数)--修改内核中对信号的处理方式
sighandler_t signal(int signum,sighandler_t handler);
void sigcb(int signo);
//signum: 信号编号
//handler: 处理方式
//SIG_DFL: 默认处理方式
//SIG_IGN: 忽略处理方式
信号的捕捉流程:自定义信号
信号的捕捉处理是在从程序运行内核态到用户态切换之前完成
一个进程如何从用户态切换到内核态(系统调用接口,程序异常,中断)
信号的处理方式选择一个合适的时机去处理--从内核态切换到用户态之
前看看有没有信号需要被处理:如果有信号处理,并且是自定义处理方式,
调用do_signal处理信号,返回用户态执行我们自定义的信号处理函数,用户
自定义信号的处理函数执行完毕后,调用sig_return返回内核态,再次查看
是否有信号待处理,如果没有了,则返回用户态回到程序主控流程,执行程序
sigaction(int signo,struct sigaction* act,struct sigaction* old)
struct sigaction: sa_handler sa_sigaction sa_mask
sa_flags=0 SA_AIGINFO
void (*sa_sigaction)(int ,siginfo_t*,void*);
sa_sigaction: 有三个参数--不仅有触发回调信号值,还有信号的相关参数/附加信息
void (*sa_handler)(int);---只有一个参数,就是触发回调的信号值
如果给某个进程发送信号时,顺带一些其他信息,这时候只能使用sa_sigaction才能处理
信号的阻塞
能收到信号,但是暂时不处理--阻止信号递达
信号的递达: 动作--信号处理
信号的未决: 状态--从产生到递达之间的状态
未决信号: 还没有被处理的信号
sigprocmask(houw,set,old);
SIG_BLOCK mask=mask|set
SIG_UNBLOCK mask&(~set)
SIG_SETMASK mask=set
sigemptyset 清空信号集合
sigfillset 向集合中填充所有信号
sigaddset 向集合中添加指定信号
sigdelset 从集合中删除指定信号
sigismember 判断指定信号是否在集合中 mask&(1<<num)
sigpending 获取进程当前未决信号
信号进行阻塞:实际上就是在 blocked 位图中标记哪些信号到来之后暂不处理
有两个信号不会被阻塞,也不会被自定义: SIGKILL 9 SIGSTOP 19(ctrl+z)
进程杀不死:
(1)没有被阻塞,但是也杀不死的可能僵尸进程
(2)信号被阻塞(可用 kill -9 杀死)
如何阻塞信号/解除信号:--sigmptyset sigfillset sigaddset sigprocmask
如何处理未决信号:--sigpending sigismember
先阻塞所有信号->getchar()->解除阻塞--信号的阻塞:可靠/非可靠信号
mysleep实现:信号会打断当前进程的阻塞操作
alarm+pause---pause使进程阻塞,alarm 保证n秒后发送信号打断阻塞
sigsuspend(mask)--临时使用mask替换阻塞集合,阻塞mask中的信号--陷入休眠--唤醒
后还原阻塞集合
先阻塞SIGALARM防止定时器与休眠之间SIGALARM被处理,使用sigsuspend对SIGALARM
解除阻塞,并且陷入休眠(原子操作)
竞态条件
竞态条件:多个执行时序之间出现的资源竞争关系
可重入函数/不可重入函数:
能否在多个运行时序重复调用,而不造成数据二义/程序异常
能否重入的关键:是否对非全局数据进行原子操作
原子性操作:操作不可被打断
当用户进行函数接口及调用时,就要考虑接口是否可重入
malloc free --不可重入--内部有不受保护的全局链表操作
关键字: volatie---保持内存可见性--防止编译器对代码进行过度优化
若要进行代码优化,就需要用户对程序有更多的可控--需要明白哪些变量需要修饰
gcc -o2 test.c -o test 代码优化
volatile int i=0;//防止 i 被过度优化( i 在使用代码优化的时候,使用频率少的
时候就会把 i 放在寄存器中,当我们在代码中更改了 i 的值,只会改变内存中i的值
就不会改变寄存器中 i 的值 )
SIGHLD信号:17信号
一个进程退出后,操作系统通知父进程子进程已经退出(操作系统通过SIGCHLD信号
通知父进程)
问题来了:SIGCHLD信号的默认处理方式就是忽略处理;
因为父进程不知道SIGCHLD什么时候来(不知道子进程什么时候退出),因此创建子进程
之后就要进行等待,但是这样造成了父进程的浪费(什么都没做,就是死等子进程);
如果父进程对SIGCHLD信号进行了自定义处理方式,并且在自定义方式中包含进程等待;
这个时候就不需要空等待
因为SIGCHLD信号是一个非可靠信号,只注册一次,有可能丢失(大量子进程同时退出)
意味着sigcd只会被回调一次,然而waitpid调用一次,只能处理一个进程退出;因此我
们尽可能的在一次回调中将退出的进程处理掉 while(waitpid(-1,NULL,WNOHANG)>0){}
只要返回值大于0就表示有子进程退出,那就一直处理,直到没有子进程退出(返回0)
或者出错(返回-1),退出while循环