5.信号的接收与处理
一.基本的概念
1.中断
终止或者暂停当前正在执行的进程,转而去执行其他的任务。
硬中断:来自硬件设备的中断
软中断:来自其他程序的中断
2.信号
信号是一种软中断,可以把他看作是进程与进程,内核与进程通信的一种方式,它为程序的异步执行提供了技术支持。
3.常见的信号
SIGINI(2) 终端中断ctrl+c
SIGQUIE(3) 终端退出信号ctrl+/
SIGABRT(6) 调用abort函数产生的信号
SIGFPE(8) 算术信号
SIGKILL(9) 死亡信号
SIGSEGV(11) 段错误信号
SIGALRM(14) 闹钟信号
SIGCHLD(17) 子进程结束信号
SIGCONT(18) 进程继续信号
SIGSTOP(19) 进程结束信号
SIGTSTP(20) 终端停止
4.不可靠信号(非实时)
1.编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个时间的发送可能会产生多次信号。
2.不可靠信号不支持排队,因此在接收信号的时候可能会丢失,如果一个信号发送个一个进程多次,它可能只接收到一次,其他的可能就丢失了。
3.进程在处理这种信号的时候,哪怕进行设置了信号处理函数,当信号处理函数完毕后,会再次恢复成默认的信号处理函数。
5.可靠信号(实时)
1.位于[SIGRGMI(34),SIGRGMI(64)]区间的都是可靠信号。
2.可靠信号支持排队,不会丢失。
3.无论是可靠信号还是不可靠信号,都是通过:kill,signal,sigqueue,sigaction进行处理
6.信号的来源
硬件来源:
键盘:Ctrl+c,Ctrl+/,Ctrl+z
驱动:硬件设备被激活,使用,失效。
内存:非法访问内存
软件来源:
命令:kill,killall
函数:kill/raise/alarm/setitimer/sigqueue
7.信号的处理方式
1.忽略
2.终止
3.终止+core
core dump把内存的使用情况扔出来
core文件是一种二进制文件,需要一些调试工具才能打印出来(gdb)
a.gcc -g code.c生成带调试信息的可执行文件
b.运行可执行文件产生core文件
c.gdb ./a.out core程序会停止在产生错误的位置
ubuntu默认不产生core文件,需要使用命令设置:
ulimit -c unlimited
4.捕获并处理
二.信号的捕获和处理
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:向内核注册一个信号处理函数
signum:信号的编号,可以直接写数字,也可以用系统提供的宏。
handler:函数指针,
SIG_IGN 忽略信号
,这个忽略是接收方这边不接受这个信号,并且告诉内核也不要发这个信号了,因此这个对应的一些系统操作就不执行了。
SIG_DFL 恢复信号的默认处理方式
返回值:返回之前信号的处理方式
函数指针SIG_IGN,SIG_DFL,SIG_ERR
几个不能捕获不能忽略
练习:1.实现一个杀不死的程序。
1.在有些系统中,向内核注册的信号处理函数只执行一次(在执行前就被还原成默认的处理方式了),如果想持续处理信号,可以在每次的信号处理函数结束时再次注册。
2.SIGKILL,SIGSTOP不能被捕获,也不能被忽略
3.普通用户只能给自己的进程发信号超级用户可以给任意进程发送信号。
三.子进程的信号处理
1.通过fork创建的子进程会继承父进程的信号处理方式。
练习:测试通过vfork+exec创建的子进程是否会继承父进程的信号处理方式
2.通过vfork+exec创建的子进程不会继承父进程的信号处理方式,会恢复成默认的。
四.信号的发送
1.键盘
C+c 终端中断信号
C+z 终端暂停信号,fg命令再次开启
C+/ 终端退出信号
2.错误产生的信号
除0
非法内存访问
硬件总线
3.命令产生的信号
kill -信号 进程号
killall -信号 程序名(杀死所有叫这个命令的程序)
4.函数产生的信号
int kill(pid_t pid,int sig);
功能:向指定的进程发送信号
pid:与waitpid一样
sig:信号
0表示空信号,不会向进程发送信号,但是会测试是否能向pid发送发送信号,这样可以检测一个进程是否存在,返回-1表示进程不存在,errno为ESRCH。
返回值:-1,表示进程不存在
int raise(int sig);
功能:向自己发送信号
练习:实现一个kill命令
五.pause
#include <unistd.h>
int pause();
功能:休眠,不会占用系统资源
1.进程调用pause函数后会进入睡眠状态,直到有信号把它叫醒(不被忽略的信号)
2.当信号来临后,是先执行信号处理函数,信号处理函数结束后,pause再返回。
3.pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。
4.从功能上讲它相当于没有时间限制的sleep函数。
六.sleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:使调用的进程睡眠seconds秒
1.调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。
2.sleep的返回值是0,或剩余的睡眠秒数。
3.有时间限制的睡眠函数
int usleep(useconds_t usec);
功能:睡眠usec微秒
1s = 1000ms = 10^6us
-std=c99 不建议使用
-std=gnu99 在使用系统调用时一定要使用此标准
(因为一些系统的函数或者系统调用不是标准C中的,是gnu中加进去的)
七.alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:定义一个闹钟信号
1.让内核向调用它的进程,在seconds秒后发送一个时钟信号SIGALARM信号
2.SIGALRM信号的默认处理方式是直接退出。
练习:实现一个闹钟命令,./alarm sec
八.信号集和信号屏蔽
1.信号集:
多个信号的集合,sigset_t类型
由128个二进制位组成,每个二进制位表示一个信号
#include <signal.h>
int sigemptyset(sigset_t *set);
功能:清空信号集
int sigfillset(sigset_t *set);
功能:填满信号集
int sigaddset(sigset_t *set, int signum);
功能:向信号集中添加信号
int sigdelset(sigset_t *set, int signum);
功能:从信号集中删除信号
int sigismember(const sigset_t *set, int signum);
功能:测试一个信号集中是否有某个信号
返回值:有返回1,没有返回0,失败返回-1
2.屏蔽信号集中的信号
每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:设置进程的信号掩码
how:修改信号掩码的方式
SIG_BLOCK:向信号掩码中添加信号
SIG_UNBLOCK:向信号掩码中删除信号
SIG_SETMASK:用新的信号集来替换旧的信号掩码
set:新添加的,删除,替换的信号集合,可以为空
oldset:当newset为空时候,就是在备份信号掩码
当进程执行一些敏感操作时,不希望被打扰(原子操作),此时需要先屏蔽信号,屏蔽信号的目的不是为了不接收信号,而是为了延迟接收信号,当处理完要做的事情后,应该把屏蔽的信号再还原
当信号屏蔽时,发送的信号会记录一次,这个信号设置为未决状态,当信号屏蔽结束以后,会再发送一次。
不可靠信号:无论信号屏蔽期间发送多少次,信号解除屏蔽后只发送一次。
可靠信号:在信号屏蔽期间发送的信号会排队记录,在信号解除屏蔽后逐个处理。
在执行信号处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
#include <signal.h>
int sigpending(sigset_t *set);
功能:获取未决状态的信号
九.信号处理
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
功能:设置或处理信号处理方式
struct sigaction {
void (*sa_handler)(int); //信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);//信号处理函数指针,需要使用sigqueue发送信号
sigset_t sa_mask;//信号屏蔽码
int sa_flags;//
SA_NOCLDSTOP:忽略SIGCHLD信号
SA_NOCLDSTOP/SA_NOMASK:处理信号时,不屏蔽信号
SA_ONSTACK:
SA_RESETHAND:处理完信号后,恢复成系统默认处理方式
SA_RESTART:当信号处理函数中断的系统调用,则重启
SA_SIGINFO:用sa_sigaction处理信号
void (*sa_restorer)(void);//目前保留没有用,给一个空就好
};
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);