一、信号介绍
什么是中断:
-
当进程接受到消息后中止当前正在执行的任务,转而执行其他任务,等待其他任务执行完毕后再返回继续执行,这种执行模式称为中断,分为硬件中断和软件中断俩种。
什么是信号:
-
信号式UNIX、类UNIX以及其他POSIX兼容的系统中,为了完成不同进程之间通讯的一种方式,是一种软中断,是一种异步处理机制,用于提醒进程某个事件发生了,可能要去处理。
-
当一个信号发送给一个进程,操作系统就会中断该进程的正常控制流程,如果中断前预先设置过如何处理该信号(绑定过该信号的信号处理函数),那么中断后会执行该函数,执行完返回中断点继续执行,否则就按信号的默认处理方式进行
# 执行 kill -l 查看当前系统可以产生的所有信号
常见的信号:
SIGINT(2) ctrl+c 终止
SIGQUIT(3) CTRL+/ 终止+core
SIGFPE(8) 除0 终止+core
SIGSEGV(11) 段错误 终止+core
SIGTSTP(20) ctrl+z 暂停
SIGCONT(18) fg 继续/忽略
不可靠与可靠信号
不可靠信号:
-
建立在早期信号处理机制上的信号被称为不可靠信号(1~31)
-
非实时信号,不支持排队机制,可能会丢失信号,同一个信号产生多次,进程可能只收到一次
-
当进程收到这类信号,执行了第一次用户设置的信号处理函数后,会还原回默认的处理方式
可靠信号:
-
位于(34~64)的信号是可靠信号
-
实时信号,支持排队机制,信号只要产生一次就必定会被处理,因此不会丢失
信号的来源:
-
硬件异常:由硬件设备产生的信号,例如除零、非法访问内存、总线错误、未定义指令
-
软件异常:命令kill\函数产生信号
信号处理的默认动作:
-
忽略
-
终止进程
-
终止并产生core文件
-
捕获并处理
二、捕获信号
#include <signal.h>
// 信号处理函数的格式
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:向内核注册一个信号和它的信号处理函数,相当于捕获该信号(绑定信号)
signum:信号码(可以写整数值也可以用宏名)
-
在某些UNIX系统中,通过signal注册信号处理函数后,只有一次有效,后面会变回默认处理方式,为了在这种系统中得到持久的信号绑定,可以在信号处理函数的末尾再次通过signal重新注册一次。
-
可以通过命令kill 信号码 进程号 给该进程发送信号
练习:测试一下哪些信号不可以被捕获处理
9和19号信号不能被忽略也不能被捕获
-
普通用户只能给自己的进程发送信号,只有root用户可以给任何进程发送信号
-
当信号处理完信号处理函数后是会回到产生信号的代码位置继续执行,如果我们捕获并处理的是段错误、除零这种信号,就会产生死循环,因为这些错误并没有因为捕获了信号就消失,而是一直存在并产生信号,正确做法是在他们的信号处理函数中进行数据保存然后直接结束程序。exit(int num);
三、发送信号的方式
键盘:
-
给当前终端控制下的活跃进程发送信号
Ctrl+c Ctrl+\ Ctrl+z fg
命令:
kill <-信号码> 进程号
killall <-信号码> 进程名
给所有的同名进程发送信号
事件:
-
当程序执行了某种非法操作,被操作系统发现了,操作系统会给进程发送对应的信号,例如段错误、除零、总线错误、错误指令等。
函数:
int kill(pid_t pid,int sig);
功能:给指定的进程发送信号
pid:给进程号为pid的进程发送
pid > 0 给pid号进程发送信号
pid = 0 向同组的所有进程发送信号sig
pid = -1 给所有进程发送信号sig,前提是由向该进程发送信号的权限
pid < -1 向进程组id等于pid的绝对值的所有进程发送信号sig
int raise(int sig);
功能:给调用进程自己发送信号
void abort(void);
功能:给调用进程自己发送信号SIGABRT(6)
unsigned int alarm(unsigned int seconds);
功能:让内核在seconds秒后,向调用进程发送SIGALRM(14)信号
返回值:上一次alarm剩余的时间
如果是正常走完时间返回0,如果时间还没走完,又再次调用alarm,会覆盖之前的时间重新计时,并不会产生多个闹钟信号
四、暂停和休眠
int pause(void);
功能:让调用进程进入暂停态执行,进入睡眠状态,直到有信号终止进程或者有信号被捕获,会唤醒并执行信号处理函数,继续后面的执行流程,类似于不限时的sleep
返回值:要么一直睡眠不返回,睡醒返回-1
unsigned int sleep(unsigned int seconds);
功能:让调用进程睡眠seconds秒,除非有信号终止进程或者有信号被捕获,也会唤醒
返回值:剩余的睡眠秒数
int usleep(useconds_t usec)
功能:让调用进程睡眠usec微秒,除非有信号终止进程或者有信号被捕获,也会唤醒
返回值:剩余的睡眠秒数
五、信号集与信号屏蔽
什么是信号集:
-
是一种专门用于存储多个信号的数据类型sigset_t
-
该类型占128字节,每个字节代表了一种信号的有或无
int sigemptyset(sigset_t *set);
功能:将信号集set中的所有信号置0 清空信号集
int sigfillset(sigset_t *set);
功能:把信号集set中所有信号置1
int sigaddset(sigset_t *set, int signum);
功能:将信号集set中的信号signum置1
int sigdelset(sigset_t *set, int signum);
功能:将信号集set中的信号signum置0
int sigismember(const sigset_t *set, int signum);
功能:测试信号集中是否存在信号signum
返回值:存在返回1,不存在返回0,非法信号返回-1
#include <stdio.h>
#include <signal.h>
int main(int argc,const char* argv[])
{
sigset_t set;
sigfillset(&set);
sigemptyset(&set);
sigaddset(&set,2);
sigaddset(&set,7);
for(int i=1;i<=128;i++)
{
printf("信号%d 状态:%d\n",i,sigismember(&set,i));
}
}
信号的递送与未决:
-
当信号产生后,系统内核会在其内部维护的进程表中,给响应信号的进程设置一个对应的标志位,这整个过程称为信号的递送
-
当信号产生到完成递送之间会存在一段时间间隔,处于这个时间间隔的信号状态是”未决“
信号屏蔽:
-
每个进程都有一个信号掩码(signal mask,就是一个信号集),其中存在的信号是需要被该进程屏蔽的信号
-
让需要屏蔽的信号处于未决状态,当可以接受信号时,让其退出未决状态完成递送
-
当执行一些特殊的且不想被干扰中断的操作时,例如:更新数据库敏感操作,此时可以把信号放入信号屏蔽集中,等操作完成后,再从信号屏蔽集中删除,继续处理信号,保证敏感操作的安全性
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:修改当前进程的信号掩码(屏蔽集)
how:修改信号掩码的方式
SIG_BLOCK 将set中的信号加入到信号掩码中
SIG_UNBLOCK 从信号掩码中把set中的信号删除
SIG_SETMASK 把set中的信号替换掉信号掩码的所有信号
set:信号集 用于设置
oldset:信号集 用于获取旧信号集 NULL则不获取