Linux信号处理
一、基本概念
1、中断
当程序接收到消息后中止当前正在执行的程序,转而执行其它任务,等其它任务执行完成后再返回,这种执行模式叫做中断
分为硬件中断和软件中断
2、信号
是一种软件中断,由操作系统发出,程序接收后会执行相应的操作
3、常见的信号
命令 kill -l 查看所有信号
SIGINT(2) Ctrl+c 终止
SIGQUIT(3) Ctrl+\ 终止+core
SIGFPE(8) 除零 终止+core
SIGKILL(9) 终止信号 终止 不能捕获、忽略
SIGSEGV(11) 非法访问内存 终止+core
4、不可靠信号和可靠信号
建立在早期的信号处理机制上(1~31)的信号是不可靠信号
不支持排队、可能会丢失,如果同一个信号连续产生多次,进程可能只相应了一次
建立在新的信号处理机制上(34~64)的信号是可靠信号
支持排队、信号不会丢失
5、信号的来源
硬件异常:除零、非法访问内存、未定义的指令、总线错误
软件异常:通过一些命令、函数产生的信号
6、信号的处理方式
1、忽略
2、终止进程
3、终止进程并产生core文件(记录内存映像)
4、停止
5、继续
6、捕获并处理 (在信号发生前,先向内核注册一个函数,当信号来临时系统会自动执行该函数)
二、信号捕获
typedef void (*sighandler_t)(int);
功能:说明信号处理函数的格式
sighandler_t signal(int signum, sighandler_t handler);
功能:向内核注册一个信号处理函数
signum:信号编号
handler:函数指针
也可使用以下参数
SIG_IGN 忽略
SIG_DFL 按默认方式处理
返回值:
之前的信号处理方式
tap:
1、SIGKILL(9)、SIGSTOP(19)信号不能被捕获和忽略处理
2、当信号处理完后可能会返回产生信号的代码继续运行,如果我们捕获并处理段错误、算术异常等信号可能会产生死循环,正确的处理段错误、算数异常信号应该是备份数据并直接结束程序
3、有些系统通过signal注册的信号处理函数只能执行一次,如果想要持续有效,可以在信号处理函数中再重新注册一次
4、子进程会继承父进程的信号处理方式,但是通过exec系列函数创建的子进程,会恢复默认的信号处理方式
信号的发送方式:
1、键盘
Ctrl+c
Ctrl+\
Ctrl+z 暂停\挂起 fg 继续
2、错误
除零
非法访问内存
总线错误
3、命令
kill -信号编号 进程号
功能:向指定的进程发送信号
killall -信号编号 进程名
可以给同名进程发送同一个信号
4、函数
int kill(pid_t pid, int sig);
功能:向指定进程发送指定信号
pid:进程号
sig:信号编号
int raise(int sig);
功能:向进程自己发送信号sig
void abort(void);
功能:向进程自己发送信号SIGABRT
unsigned int alarm(unsigned int seconds);
功能:让内核在seconds秒后向进程发送SIGALRM信号
返回值:上次alarm设置的剩余秒数
三、进程休眠信号
int pause(void);
功能:让调用者进入休眠状态,直到进程遇到信号才会唤醒
返回值:要么不返回在休眠,要么唤醒后返回-1
相当于没有时间限制的sleep
unsigned int sleep(unsigned int seconds);
功能:让调用者进入休眠指定的秒数,当遇到信号时会提前唤醒返回
返回值:剩余的休眠时间
四、信号集和信号阻塞
信号集:是一种数据类型,定义的变量可以存储多个信号
sigset_t 128位的二进制数,每一位都固定代表了一种信号
相关函数:
int sigemptyset(sigset_t *set);
功能:清空信号集
int sigfillset(sigset_t *set);
功能:填满信号集
int sigaddset(sigset_t *set, int signum);
功能:向信号集set中添加信号signum
int sigdelset(sigset_t *set, int signum);
功能:从信号集set中删除信号signum
int sigismember(const sigset_t *set, int signum);
功能:测试信号集中是否存在signum信号
返回值:
0 不存在
1 存在
-1 signum信号非法
信号阻塞:
当程序执行到一些特殊操作时,不适合处理信号,此时可以让内核先屏蔽信号,等操作执行完成后再解除屏蔽重新发送信号
当信号产生时,内核会在其维护的信号表中为对应的进程设置与该信号对应的标记,这个过程叫做递送
从信号产生到完成递送有个时间间隔,处于这个间隔的信号状态称为未决
信号屏蔽\阻塞就是让被屏蔽的信号先处于未决状态、暂停递送,当屏蔽解除时在继续递送
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:设置要屏蔽的信号、这些信号是存储在信号集里面
how:信号屏蔽的方式
SIG_BLOCK 把set中的信号添加到要屏蔽的信号集里
SIG_UNBLOCK 从信号屏蔽集中删除set中的信号 解除
SIG_SETMASK 把set替换之前的信号屏蔽集
set:准备好的信号集
oldset:获取旧的信号屏蔽集
五、附带数据信息的信号处理
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:向内核注册一个信号处理函数
signum:要捕获的信号编号
act: 设置要处理的动作
olact: 获取原来的处理动作
struct sigaction {
void (*sa_handler)(int);// 不附带其他信息的信号处理函数
void (*sa_sigaction)(int,siginfo_t *,void *);
// 附带其他信息的信号处理函数
sigset_t sa_mask; // 信号屏蔽集
int sa_flags;// 信号处理动作标志
SA_NODEFER 在信号处理过程中不要屏蔽当前信号
SA_SIGINFO 使用 sa_sigaction 函数指针
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;
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 Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
int sigqueue(pid_t pid,int sig, const union sigval value);
功能:向指定进程发送信号并附加信息一起发送
value:附加的信息
union sigval {
int sival_int; // 整数
void *sival_ptr; // 指针
};
六、定时器
#include <sys/time.h>
int getitimer(int which,struct itimerval *curr_value);
功能:获取当前的定时方案
which:选择使用的计时器
ITIMER_REAL 真实计时器 程序总的计时时间
ITIMER_VIRTUAL 虚拟计时器 用户态的计时
ITIMER_PROF 实际计时器 用户态+内核态计时
真实计时器 = 实际计时器 + 休眠时间 + 切换时间
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:设置定时器计时方案
which:选择使用的计时器
ITIMER_REAL 真实计时器 程序总的计时时间SIGALRM(14)
ITIMER_VIRTUAL 虚拟计时器 用户态的计时SIGVTALRM(26)
ITIMER_PROF 实际计时器 用户态+内核态计时
SIGPROF(27)
struct itimerval {
struct timeval it_interval;
// 每次时钟信号产生的间隔时间
struct timeval it_value;
// 第一次产生时钟信号的时间
};
struct timeval {
time_t tv_sec; // 设置秒3
suseconds_t tv_usec; // 设置微秒50000
};
信号捕获
#include <stdio.h>
#include <signal.h>
void sigint(int num)
{
printf("你别乱按!%d\n",num);
}
int main(int argc,const char* argv[])
{
// signal(SIGINT,sigint);
// signal(SIGQUIT,sigint);
for(int i=1; i<=31; i++)
{
signal(i,sigint);
}
for(;;);
}
信号中断
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,const char* argv[])
{
for(int i=10; i<=31; i++)
{
printf("%d\n",kill(4127,i));
sleep(1);
}
}
信号屏蔽
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigint(int num)
{
printf("你别乱按!\n");
}
void sigquit(int num)
{
printf("你还乱按!\n");
}
int main(int argc,const char* argv[])
{
signal(SIGINT,sigint);
signal(SIGQUIT,sigquit);
// 定义信号集变量
sigset_t set, old_set;
// 清空信号集
sigemptyset(&set);
// 添加要屏蔽的信号到信号集
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
sigaddset(&set,SIGKILL);
// 屏蔽信号
sigprocmask(SIG_BLOCK,&set,&old_set);
sleep(5);
// 解除屏蔽
sigprocmask(SIG_SETMASK,&old_set,NULL);
for(;;);
}
定时器
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
void sigalrm(int num)
{
printf("定时器响了%d\n",num);
}
int main(int argc,const char* argv[])
{
signal(SIGALRM,sigalrm);
signal(SIGVTALRM,sigalrm);
struct itimerval value = {{5,0},{3,0}};
setitimer(ITIMER_VIRTUAL,&value,NULL);
for(;;);
}