Physical Address:
ChongQing,China.
WebSite:
在涉及到Linux环境或者是类Linux的环境做开发时,信号处理是必不可少的。为什么需要信号,其存在的意义何在,如何发送信号或者捕获信号,在此做一些记录。
实际上,信号对于Linux系统非常重要,Linux内核通过信号来管理我们每个进程的行为,并且我们在日常开发或者调试时也需要对信号进行操作。一般我们会涉及到两个操作,信号的发送与信号的捕获。
关于信号是怎么产生的,大体分为硬件方式与软件方式,硬件方式比如我们键盘内按下Ctrl+C组合键,此时即产生中断信号;而软件方式则通过Kill/Sigqueue等函数/命令进行发送即可。
查看系统所有的信号,我们可以通过kill命令来查看,在shell终端内输入kill -l命令即可查看所有的Signal:
当我们的程序收到这些信号时,都会存在对应的软中断来对做出相应的响应。而进程对于不同的信号的响应是各不相同的。当前存在的默认行为存在以下几类:
TERM:默认收到信号后结束该进程
IGN:默认收到信号后进行忽略,不做任何响应
CORE:默认收到信号后结束进程,并进行core dump
STOP:默认收到信号后停止进程
CONT:默认收到信号后如进程处理停止状态,则继续运行。
以下是一些标准信号及其默认行为的一个整理:
Signal | Action | Remark |
---|---|---|
SIGABRT | CORE | 结束进程,由abort()函数产生 |
SIGALRM | TERM | 结束进程,由alarm()函数产生 |
SIGBUS | CORE | Bus Error(bad memory access),地址总线寻址错误 |
SIGCHILD | IGN | 子进程停止或者结束 |
SIGCONT | CONT | 如果当前处于停止状态则继续 |
SIGINT | TERM | 从键盘接收的中断输入 |
SIGKILL | TERM | 强制终止进程信号 |
SIGPIPE | TERM | Broken pipe:写入数据输出至管道却没有reader |
SIGSEGV | CORE | 无效的地址使用 |
SIGSYS | CORE | 无效的系统调用 |
SIGTERM | TERM | 结束进程 |
SIGTRAP | CORE | 程序陷入硬/软中断而被Reset |
SIGXCPU | TERM | CPU时间片使用超过限定 |
SIGXFSZ | CORE | 文件大小超过限定 |
SIGPWR | TERM | 终止进程,系统关机 |
信号发送通常发送至系统内核或者其他进程,用于状态同步。
1.使用raise函数
使用raise函数需要引入<signal.h>头文件。
函数原型:int raise(int sig);
raise函数将会给发送信号给调用方,在单线程/进程中等价于kill(getpid(),sig),在多线程环境中等价于pkill,其中sig代表发送的信号。
返回值:返回0代表成功,非0则代表失败
2.使用kill函数
使用kill函数同样地需要引入<signal.h>头文件。
函数原型:int kill(pid_t pid,int sig);
kill函数将会发送特定信号给特定的进程,具体是哪些进程将会由pid参数决定。
关于pid的参数设定,遵循如下规则:
pid>0:发送信号到指定进程
pid=0:发送信号到该进程所在进程组内的所有进程
pid=-1:发送信号给当前用户有权限发送信号的所有进程
pid<-1:发送信号给指定进程组内的所有进程,其中进程组取pid的绝对值
返回值:如果调用成功,则返回0,否则返回-1.同时产生相应的errno信号,使用strerror函数打印即可查看,这里列出各个errno信号的错误信息:
EINVAL:发送的sig为无效值。
EPERM: 表明信号发送方无权发送信号给待接收方。
ESRCH: 表明没有匹配id的进程或者进程组。
3.使用sigqueue函数
sigqueue相对于kill是具有更高灵活度的一个函数,使用sigqueue函数同样需要引入<signal.h>头文件。
函数原型:int sigqueue(pid_t pid, int sig, const union sigval value);
其中pid用于指定将要发送信号的进程id,sig用于指定信号,value则是一个联合体,用于在发送信号时传递一些附加内容,关于sigval联合体的定义如下:
union sigval {
int sival_int;
void *sival_ptr;
};
返回值:若调用成功,则返回0;返回-1则调用失败,并生成对应的errno信号。各个errno错误信号的定义如下:
EAGAIN:表明当前进入queue状态的信号已达上限。
EINVAL:表明sig参数无效。
EPERM:表明信号发送方无权发送信号给待接收方。
ESRCH:表明没有匹配id的进程或者进程组。
除了以上这些比较通用的信号发送函数外,还有一些专用的信号发送函数,如alarm函数、abort函数等,此处不做展开。
有些时候我们的程序会不可避免的崩溃或者被系统Kill或者被中断,此时需要捕获信号来做一些异常处理,或者说是进程退出前的一些清理工作。这里可以举一个例子,在Android系统中,Vold子系统会监控记录每个进程对系统资源的占用,比如U盘,如在拔出U盘时还存在资源占用就会先发送SIGINT信号提示应用进行资源释放,如未释放资源则会发送SIGKILL信号进行强制Kill。那么这时候我们就需要去进行信号的捕获。
1.使用signal函数
使用signal函数同样需要引入头文件 <signal.h> 。
函数原型:sighandler_t signal(int signum, sighandler_t handler);
这里 sighandler_t 指代一个函数指针,一般定义为typedef void (*sighandler_t)(int);
其中默认的handler分为两类:SIG_IGN与SIG_DFL,前者表示忽略信号,后者表示采取默认行为。使用signal函数,就可以捕获到信号,按照我们设定的handler进行信号捕获后的处理。这里需要说明,SIGKILL与SIGSTOP是不可被捕获的。
返回值:正常返回 sighandler 之前的值,否则返回SIG_ERR错误。
使用示例:
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
//function declaration
void signalHander(int signum);
void signalHandler(int signum)
{
printf("We received a signal:%d\r\n",signum);
exit(0);
}
void main(void)
{
printf("Now we start run\r\n");
//捕获SIGINT信号
signal(SIGINT,signalHandler);
for(;;)
{
printf("Not received any signal,just sleep 1 s\r\n");
sleep(1);
}
}
2.使用sigaction函数
使用sigaction函数同样需要引入头文件 <signal.h> 。
函数原型:int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
其中signum参数用于指定要捕获的信号,act为捕获信号后的行为,而oldact用于储存之前的act。这表明,实际上我们是可以多次使用 sigaction 并设置不同的act来实现不同的响应。
其中 sigaction 的定义如下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
//siginfo_t定义如下
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) */
}
sigaction函数返回值的定义如下:
EFAULT:act/oldact的内存地址并非有效的地址空间。
EINVAL:指定的信号无效(同样的,SIGKILL与SIGSTOP无法被捕获)。
使用示例:
void sigHandler(int sig)
{
……
exit(EXIT_FAILURE);
}
void registerSigHandler()
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
//设定接收到特定信号之后的handler处理
sa.sa_handler = sigHandler;
//捕获SIGABRT
sigaction(SIGABRT, &sa, nullptr);
//捕获SIGTERM
sigaction(SIGTERM, &sa, nullptr);
//捕获SIGINT
sigaction(SIGINT, &sa, nullptr);
}