进程信号
一、信号的基本概念
生活中的信号:红绿灯
信号更多的是通知事件的发生,信号产生以后第一时间也不是直接处理而是先存储下来,处理信号。
这节课学习的信号实际是一个软中断。
linux下62种信号(宏),使用kill -l查看。
信号分为两类:1-31不可靠信号(非实时信号) 34-64可靠信号(实时信号)
信号的产生->信号的注册->信号的注销->信号的处理。
二、产生信号
1.通过终端按键产生信号(硬件中断)ctrl + c
2.调用系统函数向进程发信号(程序异常)《unix环境高级编程》
3.由软件条件产生信号
int kill(pid_t pid, int sig); 向指定进程发送指定信号
kill (getpid(), SIGINT);
int raise(int sig);向自身发送信号
raise(SIGTREM);
void abort(void);向自身发送SIGABRT信号
unsigned int alarm(unsigned int seconds);在n秒之后向进程发送SIGALRM信号
alarm(3);设置一个定时器,取消上一个定时,并且返回上一个定时器剩余时间。
int sigqueue(pid_t pid, int sig, const union sigval value);
核心转储(core dump;
保存当前程序运行的数据以及调用栈信息,用于错误原因定位调试。
如果程序运行出现错误,可以直接通过core文件来gdb调试(有些错误是偶然发生的)
coredump默认关闭:隐私安全/资源占用
coredump打开:ulimit
三、信号注册
信号的注册/给一个进程发送信号,就是修改这个进程pcb关于信号的pending位图,将相应的信号置为1。
四、信号阻塞
暂时不处理信号(阻止信号递达),并不是不接收信号。
信号的递达:信号的处理
要阻塞一个信号,那么就是将pcb中关于信号的block位图,将相应的信号置为1,这个位图就像一个备注,说明若接收到信号暂时不处理。
信号未决:这是一种状态,信号从注册成功到信号递达之间的一种状态。
信号阻塞的实现:
阻塞这个集合中的信号:sigset_t new_block;
用于保存原来阻塞集合中的信号,防止后续要将阻塞集合还原回去:
sigset_t old_block;
int sigemptyset(sigset_t *set);清空一个信号集合
int sigfillset(sigset_t *set);将所有的信号都添加到set集合中
int sigaddset(sigset_t *set, int sigunm);添加指定的单个信号到set集合中
int sigdelset(sigset_t *set, int signum);从集合中一处一个指定的信号
int sigismember(const sigset_t *set, int signum);判断一个信号是否在一个集合中
int sigprocmask(int how, sigset_t *set, sigset_t *oldset);阻塞信号/接触阻塞
阻塞信号/解除阻塞
how:
SIG_BLOCK 阻塞集合中的信号
SIG_UNBLOCK 将集合中的信号解除阻塞
SIG_SETMASK 将集合中的信号设置到阻塞集合中
set:要阻塞/解除阻塞的集合
oldset:保存原先阻塞集合中的信号
有两个信号是不会被阻塞的:SIGKILL, SIGSTOP
sigpending 获取未决信号
五、注销:
就是从pending集合中将即将要处理的信号相应位置0(从pcb的pending集合中移除)
非可靠信号注册就是将相应pending位图置1,然后添加一个sigqueue结构到链表中,之后如果有相同信号到来,一看位图已经置1了,就不做操作了,意味着后来的信号在前一个信号未处理之前不会重复注册,代表丢了。
可靠信号不管有没有注册都要置1,并且添加节点到链表,所以不会丢信号。
非可靠信号注销就是删除链表节点,相应位图置为0
可靠信号注销时删除节点,判断是否有相同的信号节点,如果没有位图置为0,有就不置为0.
六、处理
默认操作----按照操作系统中对信号事件的既定处理方式
忽略操作----直接将信号丢掉
自定义处理:用户自己定义事件的处理方式
sighandler_t signal(int signum, sighandler_t handler);
signum:信号的编号
handler:处理方式(函数指针数组)
SIG_IGN:忽略
SIG_DFL:默认
信号的捕捉流程:主要是针对信号的自定义处理方式
信号并不是立即处理的。而是选择一个合适的时机去处理,合适的时机就是当前程序从内核态切换到用户态的时候。
程序如何从用户态切换到内核态?程序异常、中断、系统调用
当我们发起系统调用/程序异常/中断当前程序从用户态运行切换到内核态,去处理这些事情,处理完毕后,要从内核态返回用户态,但是在返回之前会看一下是否有信号需要被处理,如果有就处理(切换到用户态执行信号的自定义处理方式),处理完成后再次返回内核态,判断如果没有信号要处理就调用sys_sigreturn返回用户态(我们程序之前运行的位置)。
signal这个接口有linux版本的差异性,一般用sigaction这个接口替代signal。它的功能也是自定义信号处理方式,signal函数内部也是通过sigaction实现的
struct sigaction {
void (*sa_handler)(int); 处理函数
void (*sa_ sigaction)(int, siginfo_t *,void *); 处理函数
sigset_t sa_mask; 在处理信号的时候可以通过这个mask暂时阻塞一些信号,处理完毕之后会还原回去
int sa_ flags; 决定了我们使用哪个回调接口,并且还有一些其他的选项信息
};
重新定义处理方式的信号是:SIGINT SIGQUIT
int sigaction(int sihnum, const struct sigaction *act, struct sigaction *oldact);
signum:信号编号
act: 新的处理方式
oldact:保存原有的处理方式
这个操作是我们一般性的更改一个信号的处理方式,这种处理方式sa_flags=0代表我们使用的回调接口shisa_handler;
sigemptyset(&new_act.sa_mask);
new_act.sa_flags = 0;
new_act.sa_handler = sigcb;
sigaction(SIGINT, &new_act, &old_act);
这个操作是用于传递信号同时携带参数的情况,sa_flags需要被指定为:SA_SIGINFO,并且这时候回调需要使用sa_sigaction
sigemptyset(&new_act.sa_mask);
new_act.sa_flags = SA_SIGINFO;
new_act.sa_sigaction = sigcb1;
sigaction(SIGOUIT, &new_act, &old_act);
int sigqueue(pid_t, int sig, union sigval)这个函数不仅可以发送信号,还可以顺便携带一个参数
pid:进程ID
sig:信号编号
sigval:参数
七、可重入/不可重入函数
不可重入函数:如果函数在不同的地方/时序进行调用,会对函数的功能逻辑造成影响
可重入函数:不管怎么调用,都不会对函数内部功能/程序逻辑造成影响
不可重入函数的要点:
函数内部包含有对全局性变量的修改操作
函数传参的参数跟其他地方共同使用同一变量
因为这些对全局变量的操作不是原子性的,因此这些修改操作有可能在同时在不同地方进行修改
一个函数是否可重入:
1.是否对全局性的数据进行修改
2.这个操作是否是原子的
八、volatile关键字:修饰变量
保持变量内存可见性
一个变量数据被cpu进行处理,每次都要重新从内存中获取变量的数据
如果写的高性能程序需要对程序进行编译优化,那么我们就必须对一些敏感的变量进行内存可见性的修饰
九、僵尸进程的避免
僵尸进程是子进程先于父进程推出,操作系统通知父进程,但父进程不作为。
操作系统通过信号通知父进程(17号信号)
SIGCHLD
以前为避免僵尸进程,只能让父进程一直等待子进程的推出,浪费了父进程资源。
现在信号可以这样做:
自定义信号:SIGCHLD的处理方式,相当于提前告诉进程,当接收到这个信号的时候使用waitpid,这样就不用一直等待。
使用非阻塞的循环来处理SIGCHLD信号,因为SIGCHLD信号不是可靠信号,有可能丢失,因此就有可能漏掉僵尸子进程没有处理,所以一旦接收到信号就处理到不能处理为止。
深入理解Linux信号机制
1090

被折叠的 条评论
为什么被折叠?



