信号处理
基本概念
1、中断
当程序接受到信息中止当前执行的程序,转而执行其他任务,等其他任务执行完成后再返回,这种执行模式叫中断,分为硬件中断和软件中断
2、信号
是一种软件中断,由操作系统发出,程序接收后会执行相应的操作
3、常见信号
信号 | 产生形式 | 结果 |
---|---|---|
SIGINT(2) | Ctrl+c | |
SIGQUIT(3) | Ctrl+\ | 终止+core |
SIGFPE(8) | 终止 | |
SIGKILL(9) | ||
SIGSEGV(11) | 终止+core |
4、不可靠信号和可靠信号
建立在早期的信号处理机制上的信号(1-31)是不可靠的信号
原因:不支持排队,有丢失的可能,同一个信号如果产生多次,进程可能只接收到一次
建立在新信号处理机制上的信号(34-64)是不可靠的信号
原因:支持排队,不会丢失
5、信号来源
硬件异常:
1、除0:CPU无法除0
2、无效的内存访问
3、未定义的指令
4、总线错误
软件异常:
通过一些命令、函数产生的信号
6、信号处理方式
1、忽略
2、终止进程
3、终止进程并产生core文件
4、捕获信号并处理(当信号发生前,先向内核注册一个函数,当信号发生时让系统自动执行该函数)
信号捕获
#include <signal.h>
typedef void (*sighandler_t)(int);
功能:定义信号处理函数的格式
sighandler_t signal(int signum, sighandler_t handler);
功能:向内核注册一个信号处理函数
signum:信号的编号
handler:函数指针
还可以用一次啊参数:
SIG_IGN 忽略处理
SIG_DFL 按照默认方式
返回值:之前的信号处理的方式
注意:有些系统通过signal注册的函数只能执行一次,如果想要次序有效,可以在信号处理函数最好再注册一次
子进程会继承父进程的信号处理方式,如果通过exec系列函数来创建子进程,子进程会恢复默认的信号处理
如果我们捕获并处理段错误或者算数异常信号可能产生死循环,因为信号处理完后会返回到信号产生的代码处处理,就可能导致死循换,正确的段错误和算数异常的处理方式应该先备份数据并结束进程
信号发送
键盘:
Ctrl+c
Ctrl+\
Ctrl+z 暂停/挂起 可以恢复(fg唤醒)
错误:
除0
非法访问内存
硬件故障
命令:
kill -信号编号 进程编号
killall 信号编号 进程名
函数:
int kill(pid_t pid, int sig);
功能:向指定的进程发送信号
int raise(int sig);
功能:给进程自己发送信号
void abort(void);
功能:给进程自己发送SIGABRT信号
unsigned int alarm(unsigned int seconds);
功能:让内核在seconds后向进程发送SIGALRM信号
返回值:上一个alarm剩余的时间
注意:如果再次调用alarm会覆盖之前的设置,而不会产生两个闹钟信号
例子1: kill
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
for(int i=1; i<=31; i++)
{
if(9 == i || 19 == i) continue;
kill(3364,i);
sleep(1);
}
}
休眠信号
int pause(void);
功能:让调用者进入休眠状态,直到进程遇到任何信号
返回值:要么休眠不返回,要么结束休眠返回-1
相当于没有时间限制的sleep
unsigned int sleep(unsigned int seconds);
功能:让调用者进入指定秒数的休眠状态,当遇到信号也会提前返回
返回值:返回剩余的休眠时间
信号集与信号阻塞
信号集:
是一种数据类型,可以存储多个信号
sigset_t 128位的二进制数,每一位都代表一个信号
int sigemptyset(sigset_t *set);
功能:清空信号集
返回值:成功0 失败-1
int sigfillset(sigset_t *set);
功能:填满信号集
返回值:成功0 失败-1
int sigaddset(sigset_t *set, int signum);
功能:向信号集添加信号
返回值:成功0 失败-1
int sigdelset(sigset_t *set, int signum);
功能:从信号集中删除信号
返回值:成功0 失败-1
int sigismember(const sigset_t *set, int signum);
功能:测试信号集中是否有某个信号
返回值:1 存在 0 不存在 -1 返回错误/非法信号
信号阻塞
当程序执行一些特殊操作时,不适合处理信号,此时可以让内核先屏蔽信号,等这些操作结束后再发送该信号
当信号产生时,内核会在其维护的信号表中未进程设置一个与信号相对应的一个标记,该过程叫递送
从信号产生到完成递送有一个时间间隔,处于这个间隔的信号状态叫做未决
信号的屏蔽就是让信号先处于未决状态,暂停递送,当屏蔽解除是继续递送
每个进程都有一个信号集,专门用于储存要被屏蔽的信号
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:设置要屏蔽的信号,这些信号存储在信号集中
how:屏蔽信号的方式
SIG_BLOCK 把set中的信号添加到要屏蔽的信号集中
SIG_UNBLOCK 从信号集中删除set的信号
SIG_SETMASK 用set替换之前的信号集
set:要设置的set信号集
old:获取旧的屏蔽信号集
带附加信号的信号处理
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:向内核注册一个信号的处理方式
signum:处理的信号
act:信号处理方式函数
oldset:获取旧的信号处理方式
struct sigaction
{
void (*sa_handler)(int); //不带附加数据信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); //带附加数据信号处理函数
sigset_t sa_mask; //信号屏蔽集
//在信号函数执行过程中,会默认屏蔽当前信号,如果想要屏蔽其他信号可以向sa_mask添加信号
int sa_flags; //信号处理的标志
SA_NODEFER 在信号处理过程中不要屏蔽当前信号
SA_RESETHAND 该信号处理完成后会还原成默认处理方式
SA_RESTART
SA_SIGINFO 使用函数2来处理信号
void (*sa_restorer)(void); //保留,NULL
};
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) */
}
int sigqueue(pid_t pid, int sig, const union sigval 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 实际计时器 用户态+内核态运行时间
真实时间 = 实际时间 + 休眠时间
struct itimerval
{
struct timeval it_interval; /* next value */
//每次时钟信号产生的时间间隔
struct timeval it_value; /* current value */
//第一次产生时钟的时间
};
struct timeval
{
long tv_sec; /* seconds 秒*/
long tv_usec; /* microseconds 微妙*/
};
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:设置新的定时方案