linux 信号简介

信号是一种软件中断,提供了一种处理异步的方法,信号发生是随机的。可以被发送到一个进程或一组进程;使用信号的主要目的有两个:

  1. 让进程知道已经发生了一个特定的事件;
  2. 强迫进程执行它自己代码中的信号处理程序;


(一 )信号处理流程

对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个阶段:信号产生,信号在进程中注册,信号的执行和注销;

(1)信号产生

信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

(2) 信号注册

在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可;

进程的task_struct结构中有关于本进程中未决信号的数据成员: struct sigpending pending:

struct sigpending{

        struct sigqueue *head, *tail;

        sigset_t signal;

};

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为”未决信号信息链”)的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

struct sigqueue{

        struct sigqueue *next;

        siginfo_t info;

}

信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做”可靠信号”。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。

当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做”不可靠信号”。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。

总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)

(3) 信号的执行和注销

内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当其由于被信号唤醒或者正常调度重新获得CPU时,在其从内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。

对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。

当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。

内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。

处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。

(4) 信号的安装

如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。

linux主要有两个函数实现信号的安装:signal()、sigaction()。

#include <signal.h>

void (*signal(int signum, void (*handler))(int)))(int);

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。

#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

(5)信号的发送

发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

int kill(pid_t pid,int signo)
//该系统调用可以用来向任何进程或进程组发送任何信号。参数pid的值为信号的接收进程;
int sigqueue(pid_t pid, int sig, const union sigval val)
//sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组
unsigned int alarm(unsigned int seconds)
//系统调用alarm安排内核为调用进程在指定的seconds秒后发出一个SIGALRM的信号。如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。
int getitimer(int which, struct itimerval *value);

int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);

//使用setitimer调用来设置定时器,用getitimer来得到定时器的状态
//which指定了系统提供的三个计时器:TIMER_REAL,ITIMER_VIRTUAL,ITIMER_PROF;

//栗子
signal(SIGALRM, sigroutine);//sigroutine 为处理函数;

value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 1;
value.it_interval.tv_usec = 0;

setitimer(ITIMER_REAL, &value, &ovalue);
//参数ovalue如果不为空,则其中保留的是上次调用设定的值;
void abort(void);
//向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。
int raise(int signo);
//向进程本身发送信号,参数为即将发送的信号值。


(二 )常见信号和原因

在linux系统中,kernel可以传递更多信号相关的信息给native层,通过ptrace或waitid函数可以获取信号详细信息,信息保存在siginfo结构体:

 48 typedef struct siginfo {
 49     int si_signo;//表示哪一种信号;
 50     int si_errno; //error number;
 51     int si_code;//表示产生该信号的具体原因;
 52 
 53     union {
 54         int _pad[SI_PAD_SIZE];
 55 
 56         /* kill() */
 57         struct {
 58             __kernel_pid_t _pid;    /* sender's pid */
 59             __ARCH_SI_UID_T _uid;   /* sender's uid */
 60         } _kill;
 61     ...
107         } _sigsys;
108     } _sifields;
109 } __ARCH_SI_ATTRIBUTES siginfo_t;

如下为一些常见的信号和原因:

SIGILL

执行了非法指令,通常是可执行文件本身出现错误,或者试图执行数据段,堆栈溢出时也有可能发生;默认处理:终止然后coredump;

SIGILL si_codes:

174 #define ILL_ILLOPC  (__SI_FAULT|1)  /* illegal opcode */
175 #define ILL_ILLOPN  (__SI_FAULT|2)  /* illegal operand */
176 #define ILL_ILLADR  (__SI_FAULT|3)  /* illegal addressing mode */
177 #define ILL_ILLTRP  (__SI_FAULT|4)  /* illegal trap */
178 #define ILL_PRVOPC  (__SI_FAULT|5)  /* privileged opcode */
179 #define ILL_PRVREG  (__SI_FAULT|6)  /* privileged register */
180 #define ILL_COPROC  (__SI_FAULT|7)  /* coprocessor error */
181 #define ILL_BADSTK  (__SI_FAULT|8)  /* internal stack error */
182 #define NSIGILL     8

SIGABRT

调用abort函数生成的信号;默认处理:终止然后coredump;

SIGBUS

非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。默认处理:终止然后coredump;

SIGBUS si_codes:

204 /*
205  * SIGBUS si_codes
206  */
207 #define BUS_ADRALN  (__SI_FAULT|1)  /* invalid address alignment */
208 #define BUS_ADRERR  (__SI_FAULT|2)  /* non-existent physical address */
209 #define BUS_OBJERR  (__SI_FAULT|3)  /* object specific hardware error */
210 /* hardware memory error consumed on a machine check: action required */
211 #define BUS_MCEERR_AR   (__SI_FAULT|4)
212 /* hardware memory error detected in process but not consumed: action optional*/
213 #define BUS_MCEERR_AO   (__SI_FAULT|5)
214 #define NSIGBUS     5

SIGFPE

在发生致命的算术运算错误时发出比如整除0. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。默认处理:终止然后coredump;

184 /*
185  * SIGFPE si_codes
186  */
187 #define FPE_INTDIV  (__SI_FAULT|1)  /* integer divide by zero */
188 #define FPE_INTOVF  (__SI_FAULT|2)  /* integer overflow */
189 #define FPE_FLTDIV  (__SI_FAULT|3)  /* floating point divide by zero */
190 #define FPE_FLTOVF  (__SI_FAULT|4)  /* floating point overflow */
191 #define FPE_FLTUND  (__SI_FAULT|5)  /* floating point underflow */
192 #define FPE_FLTRES  (__SI_FAULT|6)  /* floating point inexact result */
193 #define FPE_FLTINV  (__SI_FAULT|7)  /* floating point invalid operation */
194 #define FPE_FLTSUB  (__SI_FAULT|8)  /* subscript out of range */
195 #define NSIGFPE     8

SIGSEGV

试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据. 默认处理:终止然后coredump;

197 /*
198  * SIGSEGV si_codes
199  */
200 #define SEGV_MAPERR (__SI_FAULT|1)  /* address not mapped to object */ //地址没有映射到对象;
201 #define SEGV_ACCERR (__SI_FAULT|2)  /* invalid permissions for mapped object */
202 #define NSIGSEGV    2

SIGPIPE

管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。默认处理:终止;

SIGTRAP

由断点指令或其它trap指令产生. 由debugger使用。默认处理:终止然后coredump;

216 /*
217  * SIGTRAP si_codes
218  */
219 #define TRAP_BRKPT  (__SI_FAULT|1)  /* process breakpoint */
220 #define TRAP_TRACE  (__SI_FAULT|2)  /* process trace trap */
221 #define TRAP_BRANCH     (__SI_FAULT|3)  /* process taken branch trap */
222 #define TRAP_HWBKPT     (__SI_FAULT|4)  /* hardware breakpoint/watchpoint */
223 #define NSIGTRAP    4

SIGKILL

用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。默认处理:终止;

SIGCHLD

子进程结束时, 父进程会收到这个信号。

如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。默认处理:忽略;

225 /*
226  * SIGCHLD si_codes
227  */
228 #define CLD_EXITED  (__SI_CHLD|1)   /* child has exited */
229 #define CLD_KILLED  (__SI_CHLD|2)   /* child was killed */
230 #define CLD_DUMPED  (__SI_CHLD|3)   /* child terminated abnormally */
231 #define CLD_TRAPPED (__SI_CHLD|4)   /* traced child has trapped */
232 #define CLD_STOPPED (__SI_CHLD|5)   /* child has stopped */
233 #define CLD_CONTINUED   (__SI_CHLD|6)   /* stopped child has continued */
234 #define NSIGCHLD    6

SIGPOLL

当某个事件发送给Pollable Device的时候发送 ;

236 /*
237  * SIGPOLL si_codes
238  */
239 #define POLL_IN     (__SI_POLL|1)   /* data input available */
240 #define POLL_OUT    (__SI_POLL|2)   /* output buffers available */
241 #define POLL_MSG    (__SI_POLL|3)   /* input message available */
242 #define POLL_ERR    (__SI_POLL|4)   /* i/o error */
243 #define POLL_PRI    (__SI_POLL|5)   /* high priority input available */
244 #define POLL_HUP    (__SI_POLL|6)   /* device disconnected */
245 #define NSIGPOLL    6
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值