信号
信号实质是软中断。
信号作用:通知事件的发生,信号产生之后第一时间也不是直接处理,而是先存储下来,再处理信号。
信号的产生 ---> 信号的注册 ---> 信号的阻塞 ---> 信号的注销 ---> 信号的处理
在Linux下,有62种信号:
信号分了两类:
- 1~31不可靠信号(非实时信号)
- 34~64 可靠信号(实时信号)
- 信号产生
- 通过硬件中断; // SIGINT(2号信号,Ctrl + C产生)、SIGQUIT(3号信号,Ctrl + \产生)和SIGTSTP(20号信号,Ctrl+Z产生)
#include <iostream>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;
int main(){
pid_t pid = fork();
if(pid < 0){
cerr << "fork error" << endl;
exit(0);
}
else if(pid == 0){
while(1){
sleep(1);
cout << "The Child Pid = " << getpid() << ", PPid = " << getppid() << endl;
}
}
else{
int status;
wait(&status);
cout << "exit num is = " << status << endl;
}
return 0;
}
在程序执行时,通过ctrl+[c \ z]的结果:
上面结果并未返回子进程退出信息,这是因为按键操作使得父进程也退出,且父进程没机会能打印出子进程退出信息。可以通过给子进程发送退出信号的方式来得到希望结果(在后续展示。。。)。
- 程序异常产生; // 比如当前进程访问了非法地址空间,MMU(内存管理单元)会产生异常,内核将这个异常解释为SIGSEGV信号(11号信号)发送给进程、若程序中有除0错误,也会给进程发送一个信号(SIGFPE -- 8号信号)。
int main(){
pid_t pid = fork();
if(pid < 0){
cerr << "fork error" << endl;
exit(0);
}
else if(pid == 0){
int n = 0;
n = 10/0;
}
else{
int status;
wait(&status);
cout << "exit num is = " << status << endl;
}
return 0;
}
- 软件条件产生:
如: int kill (pid_t pid, int sig); // 向指定进程发送指定信号
int raise (int sig); // 向自身发送信号
unsigned int alarm (unsigned int n); // 定时器,在n秒后向进程发送SIGALARM信号,在设置一个定时器,会取消上一个定时器,并返回上一个定时器剩余时间。
函数abort,是为了让进程异常终止的。
#include <stdlib.h> void abort (void);
int main(){
pid_t pid = fork();
if(pid < 0){
cerr << "fork error" << endl;
exit(0);
}
else if(pid == 0){
int count = 3;
while(count--){
sleep(1);
cout << "The Child Pid = " << getpid() << ", PPid = " << getppid() << endl;
}
raise(3); // 向自身发送3信号
abort();
}
else{
int status;
kill(pid, 9); // 向子进程发送9号信号
wait(&status);
cout << "exit num is = " << status << endl;
}
return 0;
}
调用kill函数:
调用raise()函数:
调用abort():
- 信号注册
- 信号的注册即给一个进程发送信号,就是修改这个进程pcb中关于信号的pending位图,将相应的信号位置1;
- 信号的阻塞:暂时不处理信号即阻止信号的递达,并不是不接收信号;
原理: 要阻塞一个信号那么就是将 pcb 中关于信号的block位图再修改,将相应信号位置1,以说明此信号不处理。
- 信号的递达即处理信号
- 信号未决:是一种状态,信号从注册成功到信号的递达之间的一种状态。
int sigemptyset (sigset_t *set); // 清空一个信号集合
int sigfillset(sigset_t *set); // 将所有信号都添加到set集合
int sigaddset(sigset_t *set,int signum); // 添加指定的单个信号到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); // 阻塞信号/解除阻塞
说明:
set: 要阻塞/解除阻塞的集合
signum:指定信号
how:
- SIG_BLOCK 阻塞集合中的信号
- SIG_UNBOCK 对集合中的信号解除阻塞
- SIG_SETMASK 将集合中的信号设置到阻塞集合中
oldset: 保存原先阻塞集合中的信号
扩充:
有两个信号不会被阻塞:SIGKILL 和 SIGSTOP
sigpending 获取未决信号
- 注销
注销就是从pending 集合中将即将要处理的信号相应位置0。
可靠信号:
对于可靠信号就是不管当前信号是否注册都要置1,且添加到链表中,所以不会弄丢信号。
注销就是删除节点,且判断是否有相同信号节点,若没有则位图置0,否则就不置0.
非可靠信号:
对应非可靠信号注册就是在pending位图相应位置1,然后添加一个sigqueue结构到链表中,如果之后有相同信号到来,而相应位是1,则不做操作,也就不会重复注册,相当于丢了。
注销就是删除链表节点,且相应位置0.
- 处理
sighandler_t signal (int signum, sighandler_t handler);
说明:
- signum:信号的编号
- handler:处理方式:1. SIG_IGN 忽略 2. SIG_DFL 默认
修改信号处理方式:
- 默认处理:按照操作系统中对信号事件的既定处理方式
- 忽略处理:直接将信号丢掉。
- 自定义处理:用户自定义事件的处理方式。
信号是当我们发起系统调用/程序异常/中断当前程序从用户态运行切换到内核态,去处理这些事情,处理完毕之后,要
从内核态返回用户态,但是返回之前会看(在pending位图)一下是否有信号需要被处理,如果有,就处理信号(切换到用
户态执行信号的自定义处理方式),处理完毕之后再次返回到内核态,判如果没有信号要处理了就调用sys_sigreturn返回
用户态(我们程序之前的运行位置)。
sigaction接口(较为多用):自定义信号的处理方式,并且signal函数内部也是通过sigaction实现的。
struct sigaction{
void (*sa_handler) (int); //处理函数
void (*sa_sigaction) (int, siginfo_ *, void *); // 处理函数
sigset_t sa_mask; //在处理信号的时候可以通过这个mask 暂时阻塞一些信号,处理完毕之后会还原回去
int sa_flags; //决定了我们使用那个回调接口,并且换有其他选项信息
void (*sa_restorer) (void);
}
int sigcation(int signum, const struct sigaction *act, struct sigaction *oldact);
// signum:信号编码
// act:新的处理方式
// oldacr:保存原因处理方式
关于僵尸进程的避免:
操作系统如何通知父进程,子进程退出呢?
信号:SIGCHLD - 17号信号
在没有学习信号,为避免僵尸进程,只能让父进程一直等待子进程的退出(因为实在是不知道子进程到底什么时候退出),浪费了父进程资源。现在可以自定义SIGCHILD的处理方式,相当于提前告诉进程,当接收到这个信号时使用waitpid,这样就不用一直等了。
使用非阻塞的循环来处理SIGCHILD信号
一位SIGCHILD信号不是可靠信号,就有可能丢失,因此就有可能漏掉僵尸子进程没有处理,所有一旦收到信号就处理到不能处理为止。