Linux信号(Signal)处理

Linux中的信号(Signal)全称为软中断信号,故又被称为软中断,常被用作进程之间进行简单通信,或系统内核用来通知进程某个事件的发生。一般情况下,进程仅能从信号中获知信号编号和少量其他信息(如信号发送者的真实用户ID/内存异常发生的地址/文件描述符等)。

1. 信号类型

Linux系统中,支持POSIX标准的规则信号(regular signal,编号1-31)和实时信号(real-time signal,编号32-63)。对于regular signal来说,无论发送多少次,在接收进程处理之前,重复的信号会被合并为一个(每一种regular signal对应于系统进程表项中软中断字段的一个比特,因此不同的信号可以同时存在,同一信号仅能表示有或无而不能表示重复的次数);而real-time signal发送多少次,就会在接收进程的信号队列中出现多少次。

Linux在i386上的31个规则信号(regular signal)

编号信号名称缺省动作说明
1SIGHUP终止终止控制终端或进程
2SIGINT终止键盘产生的中断(Ctrl-C)
3SIGQUITdump键盘产生的退出
4SIGILLdump非法指令
5SIGTRAPdumpdebug中断
6SIGABRT/SIGIOTdump异常中止
7SIGBUS/SIGEMTdump总线异常/EMT指令
8SIGFPEdump浮点运算溢出
9SIGKILL终止强制进程终止
10SIGUSR1终止用户信号,进程可自定义用途
11SIGSEGVdump非法内存地址引用
12SIGUSR2终止用户信号,进程可自定义用途
13SIGPIPE终止向某个没有读取的管道中写入数据
14SIGALRM终止时钟中断(闹钟)
15SIGTERM终止进程终止
16SIGSTKFLT终止协处理器栈错误
17SIGCHLD忽略子进程退出或中断
18SIGCONT继续如进程停止状态则开始运行
19SIGSTOP停止停止进程运行
20SIGSTP停止键盘产生的停止
21SIGTTIN停止后台进程请求输入
22SIGTTOU停止后台进程请求输出
23SIGURG忽略socket发生紧急情况
24SIGXCPUdumpCPU时间限制被打破
25SIGXFSZdump文件大小限制被打破
26SIGVTALRM终止虚拟定时时钟
27SIGPROF终止profile timer clock
28SIGWINCH忽略窗口尺寸调整
29SIGIO/SIGPOLL终止I/O可用
30SIGPWR终止电源异常
31SIGSYS/SYSUNUSEDdump系统调用异常

编号为0的信号,用以测试进程是否拥有信号发送的权限,并不会被实际发送。

同一信号在不同系统中的值可能会不一样(或者说在不同系统中同一数值可能会代表不同的信号),因此,建议使用信号名而不是信号值。

信号的数值越小,则优先级越高。当进程收到多个待处理信号时,总是先处理优先级别高的信号。

2. 信号处理

通常,进程对信号的处理方式可以是以下三种方式之一:默认方式(default,交由系统默认信号处理函数处理);忽略(ignore,不进行任何处理);进程捕获信号并处理(capture)。

SIGKILL,SIGSTOP不能被用户程序捕获,也不能被忽略。也就是说,SIGKILL/SIGSTOP总是会由系统默认的处理函数进行处理(最终结果是进程被中止)。

3. 信号相关系统调用函数

Linux中提供了kill()/tkill()/tgkill()/pause/alarm/setitimer()/signal()/sigaction()/sigpending/sigprocmask()/sigsuspend()/rt_sigaction()/rt_sigpending()/rt_sigprocmask()/rt_sigqueueinfo()/rt_sigsuspend()/rt_sigtimedwait()等函数供进程使用。

3.1 kill()/tkill()/tgkill()

kill()/tkill()/tgkill()分别用于向进程(进程组)/线程(线程组)发送信号。

3.1.1 进程信号发送:kill()

函数声明

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

glibc功能测试宏定义:

kill(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

参数及返回值

  • 入参pid:指定信号发送的目标进程
    • pid>0:指定目标进程的ID
    • pid=0:目标为当前进程(函数调用进程)所在进程组内的所有进程
    • pid=-1:目标为当前进程有发送信号权限的所有进程*(除自身和进程1(init)外**)
    • pid<-1:目标进程ID值为-pid
  • 入参sig:指定要发送的信号值。sig=0表示仅执行错误检查而不实际发送任何信号,常用于检查目标进程/进程组是否存在。
  • 返回值:0表示成功;-1表示失败,失败原因由errno给出:
    • EINVAL:无效信号
    • EPERM:发送进程没有权限进行操作
    • ESRCH:目标进程/进程组不存在(“存在”的进程则可以是僵尸进程(zombie),或已经提交退出申请的进程)

*发送进程拥有向目标进程发送信号的权限,是指发送进程为特权进程(拥有CAP_KILL权限),或者发送进程的有效用户ID/真实用户ID(effective UID/real UID)等于目标进程的真实用户ID(real UID)或saved set-user-ID。对于SIGCONT信号,发送进程和目标属于同一个会话(session),就拥有发送的权限。

**不允许向进程0(swapper)发送任何信号;进程1(init)仅处理那些自己感兴趣的信号(capture),而丢弃其他信号。

3.1.2 线程信号发送:tkill()和tgkill()

tkill与tgkill用于向指定线程发送信号。相比较而言,kill()则是向进程发送信号,如果进程拥有多条线程,则无法确定究竟哪一条线程会进行信号响应)。

函数声明

int tkill(int tid, int sig);
int tgkill(int tgid, int tid, int sig);

由于tkill()仅通过线程ID来指定目标,有可能发送给错误的线程(比如欲发送的目标线程已退出,而另一条新线程使用了同一ID),因此,tkill()已废弃。推荐使用tgkill()。

当线程组ID(tgid)为-1时,tgkill()等同于tkill()。

3.2 挂起操作:pause()

pause()用于挂起进程,直到收到信号后继续执行。

函数声明

#include
int pause(void);
3.3 signal()

signal()用于设置自定义的信号处理响应函数。

#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数及返回值

  • signum:要捕获处理的信号值
  • handler:指定信号处理函数。handler可以是
    • 自定义的信号处理函数:入参为int(信号值),无返回值(void)
    • SIG_IGN:表示忽略此信号
    • SIG_DFT:表示使用系统默认处理
  • 返回值:
    • SIG_ERR:错误,错误码由errno给出:EINVAL表示指定的信号值错误
    • 此前的信号处理函数

例:进程捕获SIGINT/SIGUSR1/SIGUSR2信号并打印相应信息,使用命令kill发送信号进行测试或退出。

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <stdio.h>  
  2. #include <signal.h>  
  3. #include <errno.h>  
  4. #include <stdlib.h>  
  5.   
  6. typedef void (*sighandler_t)(int);  
  7.   
  8. void sighandler(int signo)  
  9. {  
  10.     switch (signo)  
  11.     {  
  12.         case SIGINT:  
  13.             printf("Got a SIGINT\n");  
  14.             break;  
  15.         case SIGUSR1:  
  16.             printf("Got a SIGUSR1\n");  
  17.             break;  
  18.         case SIGUSR2:  
  19.             printf("Got a SIGUSR2\n");  
  20.             break;  
  21.         default:  
  22.             break;  
  23.     }  
  24. }  
  25.   
  26. int main(void)  
  27. {  
  28.     sighandler_t oldhandler = signal(SIGINT, sighandler);  
  29.     if ( oldhandler == SIG_ERR )  
  30.     {  
  31.         printf("set signal handler error! errno = %d\n", errno);  
  32.         return -1;  
  33.     }  
  34.     signal(SIGUSR1, sighandler);  
  35.     signal(SIGUSR2, sighandler);  
  36.   
  37.     printf("pid = %d\n", (int)getpid());  
  38.   
  39.     while (1)  
  40.     {  
  41.         usleep(1000000);  
  42.     }  
  43.   
  44.     return 0;  
  45. }  
3.4 sigaction()

使用signal()可以自定义信号处理方式,但通过signal()所设置的信号处理函数所能获取的信息太少-仅仅只有信号类型/值,过于简单。某些情况下进程需要获取与此信号相关的更详尽的信息,如对于因内存访问异常而产生的SIGSEGV信号,非法访问的地值址;再如信号的发送者是谁等等。这时候,就需要sigaction()。

函数声明

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);

glibc功能测试宏:

sigaction(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

结构体sigaction

  1. struct sigaction {  
  2.     void     (*sa_handler)(int);  
  3.     void     (*sa_sigaction)(int, siginfo_t *, void *);  
  4.     sigset_t   sa_mask;  
  5.     int        sa_flags;  
  6.     void     (*sa_restorer)(void);  
  7. };  

结构体sigaction字段:

  • sa_handler:与signal()中相同,指定一个入参为int(信号值)/无返回值的自定义函数作为信号处理函数;也可以是SIG_DFT/SIG_IGN分别表示使用系统默认处理/忽略
  • sa_sigaction:入参为int(信号值)/siginfo_t*(siginfo_t指针,给出与信号相关的其他信息)/void*(被转换的ucontext_t指针,给出信号相关的用户上下文信息),无返回值的用户信号处理函数地址。
    sa_handler与sa_sigaction均可以指定用户信号处理函数。当sa_flags字段中设置了SA_SIGINFO位时,系统使用sa_sigaction指定的函数;否则使用sa_handler。考虑到不同系统兼容性的问题(sa_handler和sa_sigaction共用一个union),不要同时指定两个字段。
  • sa_mask:信号处理期间需要阻塞的信号掩码。此外,触发了信号处理函数的信号本身也会被阻塞,除非设置了SA_NODEFER标志
  • sa_flags:信号行为标识,可以是以下标置位(bit)或组合:
    • SA_NOCLDSTOP:仅当信号值(signum)为SIGCHLD时有效(为SIGCHLD设置自定义处理函数时),表示不接收子进程停/启(stop/resume)信号
    • SA_NOCLDWAIT:当信号值(sginum)为SIGCHILD时,表示当子进程终止时不要将子进程变为僵尸进程(zombie)。此标志位仅当为SIGCHLD设置自定义处理函数或将SIGCHLD处理函数恢复为系统缺省时(SIG_DFL)时有效。
    • SA_NODEFER:在触发处理函数的信号处理过程中,不要阻止再次接收此信号(缺省情况下,正在处理中的信号会被系统阻塞)。SA_NOMASK含义与之相同,但已废弃。
    • SA_ONSTACK:调用使用sigaltstack()设置的另一个信号处理函数,若其不可用则调用缺省栈内处理函数。
    • SA_RESETHAND:在某个信号触发过自定义处理函数后,将此信号的处理方式恢复为系统默认。SA_ONSHOT含义相同,但已废弃
    • SA_RESTART:与BSD兼容,在信号处理中,将某些系统调用标记为可重用
    • SA_SIGINFO:使用sa_sigaction指定的3参数信号处理函数而非sa_handler指定的单参数处理函数。
  • sa_restorer:已废弃

结构体siginfo_t

  1. siginfo_t {  
  2.     int      si_signo;    /* Signal number */  
  3.     int      si_errno;    /* An errno value */  
  4.     int      si_code;     /* Signal code */  
  5.     int      si_trapno;   /* Trap number that caused 
  6.                           hardware-generated signal 
  7.                           (unused on most architectures) */  
  8.     pid_t    si_pid;      /* Sending process ID */  
  9.     uid_t    si_uid;      /* Real user ID of sending process */  
  10.     int      si_status;   /* Exit value or signal */  
  11.     clock_t  si_utime;    /* User time consumed */  
  12.     clock_t  si_stime;    /* System time consumed */  
  13.     sigval_t si_value;    /* Signal value */  
  14.     int      si_int;      /* POSIX.1b signal */  
  15.     void    *si_ptr;      /* POSIX.1b signal */  
  16.     int      si_overrun;  /* Timer overrun count; POSIX.1b timers */  
  17.     int      si_timerid;  /* Timer ID; POSIX.1b timers */  
  18.     void    *si_addr;     /* Memory location which caused fault */  
  19.     int      si_band;     /* Band event */  
  20.     int      si_fd;       /* File descriptor */  
  21. }  

siginfo_t字段说明:

  • si_signo/si_sigerrno/si_sigcode字段对所有信号均有效(Linux中未使用si_sigerrno)
  • POSIX.1b信号和SIGCHLD会填充si_pid和si_uid字段
  • POSIX.1b定时器会填充si_overrun和si_timerid(内核用于标识定时器的ID,与timer_create()所返回ID并不相同)

    SIGCHLD填充si_status/si_utime/si_stime字段,单位为sysconf(_SC_CLK_TCK)

  • si_int/si_ptr由POSIX.1b信号发送方指定。
  • SIGILL, SIGFPE, SIGSEGV, SIGBUS信号会填充si_addr字段; SIGPOLL填充si_band和si_fd;
  • si_code字段值给出信号发送的原因:
    • SI_USER:由kill()或raise()发送
    • SI_KERNEL:由系统内核发送
    • SI_QUEUE:sigqueue()
    • SI_TIMER:POSIX定时器超时
    • SI_MESGQ:POSIX消息队列状态改变
    • SI_ASYNCIO:AIO完成
    • SI_SIGIO:排队的SIGIO
    • SI_TKILL:tkill()或tgkill()

    对SIGILL/SIGFPE/SIGSEGV/SIGBUS/SIGTRAP/SIGPOLL/SIGCHLD等信号,有各自相应的si_code值

返回值:0:成功;-1:失败

例:使用sigaction()设置信号SIGSEGV的的自定义处理。在主程序中通过非法内存访问触发SIGSEGV信号,在信号处理中给出非法访问的地址。

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <stdio.h>  
  2. #include <signal.h>  
  3. #include <errno.h>  
  4. #include <string.h>  
  5. #include <stdlib.h>  
  6.   
  7. void sigact(int signo, siginfo_t *siginfo, void *utx)  
  8. {  
  9.     printf("signo = %d\n", signo);  
  10.   
  11.     if ( signo == SIGSEGV )  
  12.     {  
  13.         printf("siginfo.si_addr = %p\n", siginfo->si_addr);  
  14.         printf("siginfo.si_code = %d\n", siginfo->si_code);  
  15.     }  
  16.   
  17.     exit(-1);  
  18. }  
  19.   
  20. int main(void)  
  21. {  
  22.     int ret = 0;  
  23.     char *ptr = (char *)0x80000000;  
  24.   
  25.     struct sigaction sanew, saold;  
  26.     memset(&sanew, 0, sizeof(struct sigaction));  
  27.     memset(&saold, 0, sizeof(struct sigaction));  
  28.   
  29.     sanew.sa_sigaction = sigact;  
  30.     sanew.sa_flags = SA_SIGINFO;  
  31.   
  32.     ret = sigaction(SIGSEGV, &sanew, &saold);  
  33.     if ( ret )  
  34.     {  
  35.         printf("sigaction() error! errno = %d\n", errno);  
  36.         return ret;  
  37.     }  
  38.   
  39.     *ptr = 0;   /*to trigger a signal - SIGSEGV */  
  40.   
  41.     return ret;  
  42. }  
3.4 定时器:alarm()/setitimer()

alarm()/setitimer()用于为进程设置定时器,按照指定的时间系统发送指定信号给进程。

3.4.1 alarm()

函数声明

#include
unsigned int alarm(unsigned int seconds);

入参及返回值

  • 入参seconds:指定定时器时延,单位为秒。若为0,不设置定时器,用于获取返回值;若不为0,设置延迟时长,到指定时间后系统发送SIGALRM信号给进程
  • 返回值:如进程已设置有定时器,函数返回值为距定时器被触发所剩余的秒数;若进程还未设置定时,则返回0

alarm()仅产生一次定时,定时信号一旦发送定时器即失效。如果在一次定时后仍需获取定时信号,则需要在定时信号处理函数中再次调用alarm()。

3.4.2 setitimer()/getitimer()

setitimer()/getitimer()用于设置/获取固定间隔的定时信号。

函数声明

#include
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

结构体itimerval定义:

  1. struct itimerval {  
  2.     struct timeval it_interval; /* 第一次超时后的时间间隔值 */  
  3.     struct timeval it_value;    /* 首次超时时长 */  
  4. };  
  5.   
  6. struct timeval {  
  7.     long tv_sec;                /* 秒 */  
  8.     long tv_usec;               /* 微秒(us), 必须<1s*/  
  9. };  

参数及返回值

  • which:指定定时间隔类型
    • ITIMER_REAL:定时器按照真实时间运行,超时后发送SIGALRM信号
    • ITIMER_VIRTUAL:定时器仅在进程执行时运行,超时后发送SIGVALRM信号
    • ITIMER_PROF:定时器在进程运行和系统为进程工作时运行(进程所使用的用户时间和内核时间),超时后发送SIGPROF信号
  • newvalue: 设定定时器时间间隔值。其中it_value指定从设定开始至首次发送超时信号的时间间隔值;it_interval指定以后的定时器时间间隔(>0)
  • old_value/curr_value:当前定时设置。old_value可以为NULL,表示不需要返回旧的设置
  • 返回值:0表示成功;-1表示失败,失败原因由errno给出:
    • EFAULT:newvalue/old_value/curr_value指针无效
    • EINVAL:which值无效;或tv_usec值不在允许范围之内(0-999999)

例,设置定时器5秒之后发送第一个超时信号SIGALRM,以后每隔1秒发送一次

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <signal.h>  
  4. #include <sys/time.h>  
  5. #include <errno.h>  
  6. #include <stdlib.h>  
  7.   
  8. static long int g_cnt = 0;  
  9.   
  10. void tmhandler(int signum)  
  11. {  
  12.     printf("cnt = %ld, Received signal %d\n", g_cnt, signum);  
  13.     signal(SIGALRM, tmhandler);  
  14.     ++g_cnt;  
  15. }  
  16.   
  17. int main(void)  
  18. {  
  19.     int ret = 0;  
  20.   
  21.     struct itimerval itmv = {0};  
  22.   
  23.     signal(SIGALRM, tmhandler);  
  24.   
  25.     itmv.it_interval.tv_sec = 1;  
  26.     itmv.it_value.tv_sec = 5;  
  27.     ret = setitimer(ITIMER_REAL, &itmv, NULL);  
  28.     if ( ret )  
  29.     {  
  30.         printf("setitimer() failed, errno = %d\n", errno);  
  31.         exit(ret);  
  32.     }  
  33.   
  34.     while (1)  
  35.     {  
  36.         usleep(10000000);  
  37.     }  
  38.   
  39.     return 0;  

http://blog.xuyu.org/?p=1051
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值