Unix信号知识恶补

工作范围离linux系统比较远,这方面弱弱的,赶紧恶补下。。

从不同地方摘的,有点散。


信号是传送给进程的一种事件通知,生成信号的事件有三大类:

1.       程序错误:除零,非法内存访问 …

2.       外部信号:终端 Ctrl-C 产生 SGINT 信号,定时器到期产生 SIGALRM…

3.       显示请求: kill 函数允许进程发送任何信号给其他进程或进程组。

信号生成既可以是同步的(信号与程序中的某个具体操作相关并在那个操作同时产生),也可以是异步的。通常程序错误生成信号为同步的,进程显式请求给自己的信号也是同步的。

外部事件总是异步的,来自其他进程的显示请求也是异步的。

信号发生时,我们可以告诉 unix 内核采取下面三种动作中的任一种:

1.       忽略信号:大部分信号可被忽略,除 SIGSTOP 和 SIGKILL 信号外(这是超级用户杀掉或停掉任意进程的手段)。

2.       捕获信号:注册信号处理函数,它对产生的特定信号做处理。

3.       让信号默认动作起作用: unix 内核定义的默认动作,有 5 种情况:

a)         流产 abort :终止进程并产生 core 文件。

b)        终止 stop :终止进程但不生成 core 文件。

c)         忽略:忽略信号。

d)        挂起 suspend :挂起进程。

e)         继续 continue :若进程是挂起的,则 resume 进程,否则忽略此信号。

任意时刻,进程可以为信号指定动作。

信号处理涉及两个过程,生成与交付。

信号生成出现在事件发生时,此时内核检查接收进程的相关数据结构,此结构中记录了信号的布局,悬挂信号集和处理动作。如果信号是要被忽 略的,内核不做任何动作就返回。否则,将此信号加入悬挂信号集合中。(悬挂信号集合通常用位串表示,每位对应一个信号,内核无法记录同一信号的多个实 例)。

       如果进程处于可中断的睡眠状态,并且该信号非阻塞,内核唤醒进 程。被唤醒进程一旦运行则在返回用户态前优先处理悬挂信号,当有悬挂信号并且非阻塞时,内核查看是否有处理句柄,如果没有注册句柄,则采取默认动作(通常 为终止进程)。如果有句柄,则将此信号加入阻塞信号屏蔽中。

       最后内核安排进程返回到用户态并执行信号句柄,同时保证句柄执行完时,进程从被中断处代码执行。

       由异步事件产生的信号可能在任一条指令后发生,当信号句柄完成时,进程从中断之处起执行。如果信号是在进程处于系统调用期间到达的,内核通常 abort 此系统调用并返回错误码 EINTR 。

       进程可以有选择的阻塞信号交付,当一个被阻塞的信号生成时,如果进程指定的动作为默认或者捕获,则此信号一直悬挂于该进程直到对此信号的阻塞放开,或者信号动作改为忽略。 系统对阻塞信号的判定是在信号交付时而非生成时,这样可以允许进程在信号被交付前改变信号动作。

       每个进程有一个阻塞信号屏蔽,它定义当前被阻塞交付的那些信号。可认为它是一个位串,每位对应一个信号。如果某信号对应的位被设置,则该信号当前阻塞,进程可调用

       sigprocmask 函数来检查或设置屏蔽。

Unix信号列表

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE

9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS

13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGUSR1

17) SIGUSR2 18) SIGCHLD 19) SIGPWR 20) SIGWINCH

21) SIGURG 22) SIGIO 23) SIGSTOP 24) SIGTSTP

25) SIGCONT 26) SIGTTIN 27) SIGTTOU 28) SIGVTALRM

29) SIGPROF 30) SIGXCPU 31) SIGXFSZ 32) SIGWAITING

33) SIGLWP 34) SIGFREEZE 35) SIGTHAW 36) SIGCANCEL

37) SIGLOST 39) SIGRTMIN 40) SIGRTMIN+1 41) SIGRTMIN+2

42) SIGRTMIN+3 43) SIGRTMAX-3 44) SIGRTMAX-2 45) SIGRTMAX-1

46) SIGRTMAX

程序错误类信号
:默认动作使进程流产,产生core文件。

SIGABRT:   调用abort函数生成的信号。

SIGFPE:      浮点计算错误。

SIGILL:      非法指令错误。

SIGBUS/SIGSEGV: 硬件错误-非法地址访问。

SIGEMT:    硬件错误

SIGSYS:       非法系统调用。

SIGTRAP:   硬件错误(通常为断点指令)。

 

程序终止类信号
:默认动作使进程终止,我们通常要处理这类信号,做一些清理工作,句柄函数应在结束时为此信号指定默认动作,然后再次生成该信号,使得程序终止。

SIGHUP:终端断开连接时,生成此信号给控制进程。

SIGINT:Ctrl-C或Delete按下时,由终端驱动生成,并发送给前台进程组中的所有进程。

SIGKILL:使程序立即终止,不能被捕获或忽略,也不能被阻塞。

SIGQUIT:Ctrl-\,如SIGINT,并且产生core。

SIGTERM:该信号使程序终止,但是可以阻塞、捕获、忽略。

 

闹钟类信号
:通知定时器到期,默认动作是终止程序,但通常会设置句柄。

SIGALRM:alarm/setitimer函数设置定时到期后,会产生此信号。

SIGPROF:

SIGVTALRM:

 

I/O类信号
:通知进程在描述字上发生了感兴趣事件,支持信号驱动IO。

SIGIO: fd准备执行输入输出时发送此信号。

SIGPOLL:异步I/O信号。

SIGURG:网络收到带外数据时可选择生成此信号。

 

作业控制类信号

SIGCHLD:   进程终止或停止时会向其父进程发送该信号,默认动作为忽略。

SIGCONT:     使停止的进程恢复运行。

SIGSTOP:      停止进程。

SIGTSTP/SIGTTIN/SIGTTOU:

 

操作错误类信号
:默认动作终止程序。

SIGPIPE:    管道破裂。

SIGXCPU/SIGXFSZ:

 

signal函数:

void (* signal(int sig, void (*func)(int)))(int);

sig指明是哪一种信号。

func指明动作:SIG_DFL, SIG_IGN,或者信号句柄地址。

当信号发生时,如果func指向信号句柄,系统在将控制转往句柄前,先将该信号动作置为DFL,或者阻塞该信号直到句柄完成。

Signal函数返回值指向前一次有效动作指针:SIG_DFL,SIG_IGN,或信号地址,这提供了恢复信号动作的机制。 如果signal调用出错,返回SIG_ERR并设置errno。

 

进程初启时的信号动作:

fork:继承父进程的动作

exec:所有信号动作要么是忽略要么是默认。

 

不可靠信号:

早期版本Unix中使用signal,每当信号交付时,其动作总是由系统重置为默认动作,因此为了使信号句柄执行期间,仍能对同一信号后续做反应,需要再次调用signal。

Catch_Signal(){

       // 如果第二次信号刚好在此时发生,将导致进程终止core掉。

       signal(SIGQUIT,Catch_Signal);

}

Main(){                   

       signal(SIGQUIT, Catch_Signal);

       …

}

使用signal的另一个问题是,对于信号,进程要么忽略,要么捕获,无法在一段时间内阻塞信号(推迟信号的交付),为了克服signal兼容性问题,现代unix均实现了POSIX定义的sigaction函数,该函数采用一个sigaction结构,除定义信号交付时要采取的动作外,还包含其他一些动作控制信息。

sigaction还允许调用进程检测或指定与特定信号相关的动作。

int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);

sig指定信号-除SIGKILL和SIGSTOP。

如果act为NULL,则不改变信号动作,只查询当前动作。

成功返回0,失败则不安装新信号动作,返回-1,设置errno。

struct sigaction{

       void (*sa_handler)(int);                        // 同signal的第二参数func。          

       void (*sa_sigaction)(int, siginfo_t*, void*); // 仅当flags设置SA_SIGINFO起作用。

       sigset_t sa_mask;    // 指明信号执行期间要阻塞的一组信号,除此之外导致信号句柄执

行的信号也自动阻塞,除非指定了SA_NODEFER。信号句柄正常返回时,屏蔽恢复到原先状态。          

       int sa_flags;           //

};

sa_flags是一个位串,可以通过或运算生成。可设置下列标志:

SA_NOCLDSTOP: 只对CHLD信号起作用,子进程暂停时不发信号给父进程。

SA_RESTART: 信号句柄返回时,自动恢复被该信号中断的系统调用。否则该系统调用将中断返回-1,并设置errno为EINTR。

SA_ONSTACK:

SA_RESETHAND:信号句柄入口,系统将重置信号动作为SIG_DFL.

SA_NODEFER:句柄执行期间,不自动阻塞该信号。

SA_NOCLDWAIT:只对CHLD起作用,调用进程的所有子进程在终止时不会成为Zombe。这种情况下,父进程无需要wait子进程,并且子进程终止也不向父进程发SIGCHLD信号。

如果父进程调用wait,将阻塞到所有子进程终止,并返回-1,errno设为ECHILD.

SA_SIGINFO:如果未设置此标志,则信号句柄原型为:

 void func(int signo);

如果设置此标志,则句柄原型为:

void func(int signo, singinfo_t* info, void* context);

Info- 解释信号生成的原因。

Context-信号被交付时所中断进程的上下文。

一旦用sigaction为特定信号建立了动作,该动作就一直保持,直到另一次调用sigaction,或者调用exec,或者因设置了SA_RESETHAND导致系统自动改变动作为默认为止。

 

除了外部中断产生信号外,程序可以显式的调用raise函数给他自己发送信号,或调用kill向自己或其他进程发送信号。

 

阻塞信号意味着保持该信号并推迟它的交付,可以防止程序中的关键代码被信号中断。

信号集操作:

int sigemptyset(sigset_t* set); // 清空信号集

int sigfillset(sigset_t* set);   // 包含所有信号集

sigaddset/sigdelset/sigismember;

sigprocmask用来检测或改变调用进程的信号屏蔽。

int sigprocmask(int how, const sigset_t* set, sigset_t* oset);

How: SIGBLOCK SIG_UNBLOCK   SIG_SETMASK

如果调用sigprocmask放开某个信号而导致任何悬挂信号被解除阻塞,则函数返回前,这些信号中至少有一个被交付。

检查悬挂信号:int sigpending(sigset_t* set);

等待信号:int pause(void);

悬挂调用进程直到有一个信号到达。仅当句柄执行并返回时,pause函数才返回:此时返回-1,并设置errno为EINTR。所有其他情况下pause不返回。

 

如果多个相同信号在信号句柄运行前发给了进程,则句柄只被运行一次。换句话说,默认情况下unix信号是非排队的,只有当实现支持实时信号并且sa_flags设置SA_SIGINFO时,由sigqueue生成的后续信号才排队。

 

I/O执行期间,有可能到达信号,此时有两种情况:重新开始系统调用还是返回失败.

早期unix特征为,进程执行慢系统调用期间捕获信号时,该调用被中断并设置errno为EINTR。现代unix增加了sa_flags选项SA_RESTART可对单个信号要求自动恢复被中断的系统调用。

原则如下:如果进程阻塞于慢系统调用,并且进程捕获信号且该信号句柄返回,系统调用可能返回EINTR。 虽然有些unix能自动恢复系统调用,但是为了兼容性,我们必须准备慢系统调用返回EINTR,当检测到EINTR,要么重新开始系统调用,要么做其他处理。