Unix信号处理学习笔记

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

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

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

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

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

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

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

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

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

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

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

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

c)         忽略:忽略信号。

d)        挂起suspend:挂起进程。

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

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

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

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

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

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

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

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

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

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


 

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

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

SIGFPE      浮点计算错误。

SIGILL      非法指令错误。

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

SIGEMT    硬件错误

SIGSYS:       非法系统调用。

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

 

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

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

SIGINTCtrl-CDelete按下时,由终端驱动生成,并发送给前台进程组中的所有进程。

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

SIGQUITCtrl-/,SIGINT,并且产生core

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

 

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

SIGALRMalarm/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指定信号-SIGKILLSIGSTOP

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

成功返回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,并设置errnoEINTR

SA_ONSTACK:

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

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

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

如果父进程调用wait,将阻塞到所有子进程终止,并返回-1errno设为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,并设置errnoEINTR。所有其他情况下pause不返回。

 

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

 

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

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

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

Again

       if (n=read(fd,buff, BUFSIZE) < 0) {

              if (errno ==EINTR)

                     goto Again;

              else

                     …}

你可能感兴趣的:(UNIX,unix,signal,struct,终端,数据结构,kill)