UNIX-Linux信号处理
信号的基本概念
中断
中止(不是终止)当前正在执行的任务,转而执行其他任务(可能返回也可能不返回),中断分为硬件中断(硬件设备产生的中断)和软件中断(其他程序产生的中断)。
信号
是一种软件中断,提供了一种异步执行任务的机制。
常见的信号
SIGINT(2) Ctrl+C 产生的信号
SIGQUIT(3) Ctrl+\ 产生的信号
SIGABRT(6) 调用abort函数产生此信号
SIGFPE(8) 例如除以0、浮点溢出等
SIGKILL(9) 不能被捕获或忽略。常用于杀死进程
SIGSEGV(11)段错误信号,非法访问内存产生的信号
SIGCHLD(17)子进程状态改变信号
SIGTSTP(20) Ctrl+z 产生的信号,强制退出
注意:在终端中执行 kill -l 可以显示出所有信号
不可靠信号
建立在早期机制上的信号被称为不可靠信号,SIGHUP(1)~SIGSYS(31),不支持排队,可能会丢失,同一个信号产生多次,进程可能只接收到一次。
可靠信号
采用新的机制产生的信号,34~64之间,支持排队,不会丢失。
信号的来源
硬件产生:除0、非法内存访问。
这些异常时硬件的驱动检测到,并通知内核,然后内核在向引发这些异常的进程发送相应信号。
硬件产生:通过kill/raise/alarm/setitmer/sigqueue函数产生。
信号的处理
1、忽略
2、终止进程
3、终止进程并产生core文件
4、捕获信号并处理
信号的捕获
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理注册函数
signum:信号的编号,1~31也可以是宏
handler:
SIG_IGN 忽略该信号。
SIG_DEL 默认处理
函数指针:当signum信号编号来了会执行handler函数
注意:在UNIX系统上,signal注册的函数只执行一次,执行完后就恢复成默认处理方式,如果想长期使用该函数处理信号,可以在函数结束前再注册一次。
SIGKILL/SIGSTOP 既不能被捕获也不能被处理,SIGSTOP会让进程暂停,当再次受到SIGCONT信号时会继续执行。
普通用户只能给自己的进程发送信号,而root用户可以给任何进程发送信号
发送信号
键盘
Ctrl+c SIGINT
Ctrl+\ SIGQUIT
Ctrl+z SIGTSTP
错误
除零 SIGFPE
非法访问内存 SIGSEGV
命令
kill -signum pid
ps -aux 查看所有进程编号
函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:向指定的进程发送信号
pid:进程id
pid>0 向进程号为pid的进程发送信号
pid=0 向同一进程组的进程发送信号
pid=-1 向所有有权利发送信号的进程发送信号
pid<-1 向进程号为abs(pid)的进程组发送信号
sig:信号的编号
sig值为0时,kill不会发送信号,但会进行错误检查(检查进程号或进程组id号是否存在)。
#include <signal.h>
int raise(int sig);
功能:向当前进程发送信号
暂停和休眠
#include <unistd.h>
int pause(void);
功能:一旦执行进程就会进入无限休眠(暂停),直到遇到信号。
先执行信号处理函数才会从休眠中醒来。
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:休眠指定的秒数,当有信号来临时会提前醒来,提前醒来会返回剩余的秒数,或者睡够了返回0。
时钟
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:告诉内核在seconds秒之后,向当前进程发送SIGALRM信号。
注意:如果之前设定的时间还没有到,则会重新设置(覆盖),并返回之前设置的剩余秒数,返回0表示之前未设置过或之前的还剩下0
信号集与信号屏蔽
信号集
信号的集合,由128位二进制组成,每一位代表一个信号。
#include <signal.h>
int sigemptyset(sigset_t *set);
功能:清空信号集,把所有位设置为0.
int sigfillset(sigset_t *set);
功能:填满信号集,把所有位设置为1.
int sigaddset(sigset_t *set, int signum);
功能:向信号集中添加一个信号
int sigdelset(sigset_t *set, int signum);
功能:从信号集中删除一个信号
int sigismember(const sigset_t *set, int signum);
功能:判断信号集中是否有signum信号
信号屏蔽
当做一些特殊操作时会希望有些信号来,有些信号不要来,而与设置信号忽略不同的是,信号屏蔽只是暂时不来,而可以获取到这一段时间发生了哪些信号。
每个进程都有一个信号掩码(信号集),其中包括了需要屏蔽的信号,可以通过sigprocmask函数,检查、修改进程的信号掩码。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:检查、修改进程的信号掩码
how:SIG_BLOCK 设置当前信号集与set的并集为新的信号掩码,往旧的信号集添加新的信号集
SIG_UNBLOCK 新的信号掩码是当前掩码与set补集的交集,(从信号掩码中删除)
SIG_SETMASK 把set当作新的信号掩码,重新设置。
set:可以为空,则获取信号掩码。
oldset:旧的信号屏蔽掩码
int sigpending(sigset_t *set);
功能:获取信号屏蔽期间发生的信号,当信号屏蔽解除后就没了。
注意:在信号屏蔽期间发生的信号,无论多少次,只能捕获一次(不可靠信号),可靠信号发生多少次捕获多少次。
带附加信息的信号
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:向内核注册信号处理函数
signum:信号编码
act:信号的处理方式
oldact:获取到此信号旧的处理方式,可以为NULL
struct sigaction {
void (*sa_handler)(int); //简单的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *); //可以带附加信息的信号处理函数指针
sigset_t sa_mask; //当执行信号处理函数时需要屏蔽的信号
int sa_flags;
SA_RESETHAND:信号只处理一次,然后就恢复默认处理方式。
SA_RESTART:系统调用如果被signum信号中断,自行重启
SA_NOCLDSTOP: 当子进程暂停时,不用通知父进程。
SA_NODEFER:当执行信号处理函数时不屏蔽正在处理的信号
SA_ONSTACK
SA_SIGINFO:使用第二个函数指针处理信号
void (*sa_restorer)(void); //保留暂不使用
};
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since kernel 2.6.32) */
}
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
功能:信号发送函数,与kill不同的是可以附加一些额外数据
pid:目标进程号
sig:要发送的信号
value:联合,成员可以是整数或指针
计时器
系统为每个进程维护三个计时器
ITIMER_REAL 真实计时器,程序运行实际所用时间
ITIMER_VIRTUAL 虚拟计时器,程序运行在用户态所消耗的时间
ITIMER_PROF 实用计时器,程序在用户态和内核态所消耗的时间
实际时间(真实计时器)=用户时间(虚拟)+内核时间+睡眠时间
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
功能:获取当前进程的定时器
which:选择使用哪一种定时器
curr_value:
struct timeval it_value; /* 第一次触发时钟信号所需要的时间 */
struct timeval it_interval; /* 每次触发时钟信号所需要的时间 */
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:给当前进程设置定时器,与alarm的区别更精确,
which:可以选择哪个时间段计算(选择使用哪个计时器)。