Linux Signal信号处理:信号发送与捕获

关于Linux环境下信号发送与捕获处理的一些知识记录。
Views: 815
0 0
Read Time:2 Minute, 48 Second

在涉及到Linux环境或者是类Linux的环境做开发时,信号处理是必不可少的。为什么需要信号,其存在的意义何在,如何发送信号或者捕获信号,在此做一些记录。

实际上,信号对于Linux系统非常重要,Linux内核通过信号来管理我们每个进程的行为,并且我们在日常开发或者调试时也需要对信号进行操作。一般我们会涉及到两个操作,信号的发送信号的捕获

关于信号是怎么产生的,大体分为硬件方式与软件方式,硬件方式比如我们键盘内按下Ctrl+C组合键,此时即产生中断信号;而软件方式则通过Kill/Sigqueue等函数/命令进行发送即可。

查看系统所有的信号,我们可以通过kill命令来查看,在shell终端内输入kill -l命令即可查看所有的Signal:

当我们的程序收到这些信号时,都会存在对应的软中断来对做出相应的响应。而进程对于不同的信号的响应是各不相同的。当前存在的默认行为存在以下几类:

TERM:默认收到信号后结束该进程

IGN:默认收到信号后进行忽略,不做任何响应

CORE:默认收到信号后结束进程,并进行core dump

STOP:默认收到信号后停止进程

CONT:默认收到信号后如进程处理停止状态,则继续运行。

以下是一些标准信号及其默认行为的一个整理:

SignalActionRemark
SIGABRTCORE结束进程,由abort()函数产生
SIGALRMTERM结束进程,由alarm()函数产生
SIGBUSCOREBus Error(bad memory access),地址总线寻址错误
SIGCHILDIGN子进程停止或者结束
SIGCONTCONT如果当前处于停止状态则继续
SIGINTTERM从键盘接收的中断输入
SIGKILLTERM强制终止进程信号
SIGPIPETERMBroken pipe:写入数据输出至管道却没有reader
SIGSEGVCORE无效的地址使用
SIGSYSCORE无效的系统调用
SIGTERMTERM结束进程
SIGTRAPCORE程序陷入硬/软中断而被Reset
SIGXCPUTERM CPU时间片使用超过限定
SIGXFSZCORE文件大小超过限定
SIGPWRTERM终止进程,系统关机

信号发送

信号发送通常发送至系统内核或者其他进程,用于状态同步。

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);
}

Happy
Happy
0 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

文章: 86

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注

zh_CNCN