信号

最近有个child process block main process的问题,希望能够用信号跳出block,所以搜索到了一个比较好的资料,在这里mark!


来源:http://www.crazyhadoop.com/2011/04/04/%E5%85%AB%E4%BF%A1%E5%8F%B7/

========================================================================================================================

原文:


(八)信号

1.概念

信号是软件中断,提供了一种处理异步事件的方法。

信号都以SIG开头,并且都被定义成不为0的正数(为0的是NULL信号,在后面的kill中有应用)。

信号生成时所采取的动作取决于那个信号当前使用的信号处理程序和进程信号掩码(process signal mask).信号掩码中包含一个当前被阻塞信号的列表。

产生信号的方法:

  1. 硬件—硬件产生异常时,会通知内核,内核产生信号并发送到相应的进程( SIGBUS, SIGSEGV, SIGFPE, SIGEMT, SIGILL, SIGIOT )
  2. 软件—当检查到某些事件发生时,软件会通知相应进程。(SIGURG,SIGPIPE,SIGALRM)。
  3. 按键—前面的章节已经提到过,当用户在终端中按下特定的按键,会导致终端产生特定的信号,并发送给session中的前台进程组 ( SIGINT,  SIGTSTP,SIGQUIT)。
  4. 函数—在程序中,调用kill函数可以向其他进程发送信号。但是你必须拥有该进程或者是root。
  5. 命令行—kill函数有一个命令行的界面,kill命令。

收到信号可以采取的措施:

  1. SIG_IGN—忽略该信号,但是SIGSTOP和SIGKILL不能被忽略,另外忽略硬件发送的信号会导致不可预料的错误。
  2. SIG_DFL—采用系统默认处理,默认处理一般都是结束进程。
  3. 用户函数—设置一个用户函数来处理信号,SIGSTOP和SIGKILL也不能被捕捉。
  4. SIG_ERR—设置信号捕捉函数失败时的返回值。

信号和信号产生条件

  • SIGBRT—调用abort函数时产生此信号,进程异常终止。
  • SIGALM—再用alarm函数设置的计时器超时时或者setitimer设置的间隔时间超时时产生此信号,
  • SIGBUS—与平台相关的硬件错误,一般是内存错误。
  • SIGCANCEL,SIGLWP, SIGWAITING—Solaris的thread模块内部使用。
  • SIGCHLD—子进程结束或者暂停时产生,注意和SIGCLD区分。
  • SIGCONT—作业控制信号,接收到的进程中,已暂停的会继续执行,而未暂停的会忽略该信号。
  • SIGFREEZE—Solaris特有信号,通知进程系统正要进入挂起或休眠状态。
  • SIGHUP—-连接断开,控制终端会发送该信号给session leader。如果session leader已经终止,则发送给前台进程组中的所有进程。该信号也被用来通知daemon进程读取配置文件(daemon进程没有控制终端)。
  • SIGINFO—当用户按下control-t时,该信号被发送给前台组中的所有进程。一般会打印前台进程组的进程打印当前信息。
  • SIGIO—-异步IO事件,系统默认处理信息可能是忽略,也可能是终止,由系统而定。
  • SIGPIPE—-当进程向pipe内写数据,但是pipe的读数据端已经终止。或者向已断开的SOCK_STREAM类型的套接字写数据。
  • SIGPOLL—pollable设备上发生特定的事件。Linux中,该信号被定义为SIGIO。
  • SIGPWR—-UPS电池低电,系统将在15-30S内关机。
  • SIGSEGV—segment violation,无效内存引用。
  • SIGSTKFLT—出现在早期的Linux中,标志数学协处理器栈错误。现在已经不在产生,而仅用来保持向后兼容性。
  • SIGSTOP—-作业控制信号,用来暂停进程,注意和SIGTSTP区分,该信号不能被捕捉。
  • SIGSYS—无效的系统调用。
  • SIGTERM—-终止信号,kill命令默认发送此信号。
  • SIGTHAW—-Solaris特有,系统退出挂起状态。
  • SIGTRAP—-平台相关的硬件错误,现在多用来实现调试时的断点。
  • SIGTTIN—-后台进程请求读取终端,如果a)该信号被忽略或阻塞或b)该进程属于孤儿进程组,则不产生此信号,而是read立即出错返回(errno设置为EIO)。
  • SIGTTOU—后台进程请求向终端输出,如果a)该信号被忽略或阻塞或b)该进程属于孤儿进程组,则不产生此信号,而是write立即出错返回(errno设置为EIO)。不管后台输出是否被允许,tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow和tcsetpgrp还是可以产生该信号。
  • SIGURG—-通知进程发生了紧急情况,比如收到了非规定波特率的数据。
  • SIGWINCH—-内核会维护终端的窗口大小,当使用ioctl来设置窗口大小时,内核发送该信号到所有前台进程组中的进程。
  • SIGXCPU—–进程超出其软CPU资源使用上限。默认处理方式因系统而异。
  • SIGXFSZ—-进程超出了其软文件大小上限。默认处理方式因系统而异。
  • SIGXRES—-Solaris特有,进程超出了一个既定的资源限定值。主要用来管理机器共享资源。

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

成功返回以前的信号处理函数,失败返回SIG_ERR。
函数定义比较复杂,可以使用typedef简化如下:

  • typedef void Sigfunc(int);
  • Sigfunc* signal(int signo , Sigfunc* func);

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

就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数), 而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.其中signo为要捕捉的信号,func为接收到信号signo时调用的处理函数。

程序启动

当fork一个程序时,子进程继承父进程的信号处理函数
当exec一个程序时,父进程忽略的信号,子进程也忽略,使用其他类型处理方法(SIG_IGN/用户捕捉函数)的信号,都被设置回系统默认(SIG_DEF)。

不可靠的信号

为什么不可靠:1)信号可能丢失 , 2)进程对信号没有控制能力,例如不能阻塞信号。
导致不可靠的原因主要是,进程每次处理信号时,会将信号处理方法置回系统默认。
int     sig_int();        /* my signal handling function */

signal(SIGINT, sig_int);  /* establish handler */

sig_int()
{

signal(SIGINT, sig_int);  /* reestablish handler for next time */
…                       /* process the signal … */
}
上面的代码便成了早期Unix信号处理过程中的样板。
上面的代码问题出在①处:假设代码执行到①处时,又收到一个SIGINT信号,则该信号会被系统默认处理。即相当于此信号丢失,用户没有能捕捉到。

这种信号模型还会带来另一个问题:
int     sig_int_flag;         /* set nonzero when signal occurs */
main()
{

int      sig_int();       /* my signal handling function */
signal(SIGINT, sig_int);  /* establish handler */
while (sig_int_flag == 0)

pause();              /* go to sleep, waiting for signal */

}
sig_int()
{

signal(SIGINT, sig_int);  /* reestablish handler for next time */
sig_int_flag = 1;         /* set flag for main loop to examine */

}

假设上面的代码执行到①处,发生了SIGINT信号,sig_int_flag被置1。但是接下来还是会调用pause函数,则此次信号又被忽略,这时会导致进程挂起。

系统调用被中断

如果进程再执行一个低速系统调用时捕捉到信号,则该调用会被中断并出错返回,errno会被设置为EINTR。
哪些系统调用是低速的:

  • 1:读操作会阻塞到有数据可读(pipe , network , terminal)。
  • 2:写操作会阻塞到数据被接受。
  • 3:打开某些类型的文件会阻塞(使用网络登录的终端?)。
  • 4:pause和wait函数。
  • 5:进程通信的一些函数。

注意,通常被认为是低速的磁盘读写函数,并没有被列入低速的系统调用。
那是因为磁盘IO只会短暂的阻塞,除非出现硬件错误,磁盘读写函数都会立即返回。
新的标准对read和write操作的语义进行了一些修改。
当用户要求read读取1024个字节,但read只读取了512就被信号打断。此时系统既可以:
成功返回已读取到的数据 或者失败返回,并置errno为EINTR

系统调用可以被中断,由此会带来了一个新的问题:
again:
if ((n = read(fd, buf, BUFFSIZE)) < 0) {
if (errno == EINTR)
goto again;     /* just an interrupted system call */
/* handle other errors */
}
程序不知道自己操作的是否是低速设备。而在读写出错时,必须每次都检查是否为EINTR。
如果操作是被信号中断,则程序有可能需要重新启动系统调用。
为了帮助用户程序,使其不必处理这个细节,Unix提供了自动恢复系统调用。
自动恢复的系统调用主要有:
ioctl,read,readv , write , writev , wait , waitpid。

前面5个调用只在操作低速设备阻塞时才会被中断,而后2者则每次收到信号后,都会中断。
如果应用程序不需要恢复系统调用,可以在sigaction函数中SA_RESTART参数。

可重入函数

由于信号是异步消息,随时可能收到。main函数的执行过程可能随时被打断,并转向信号处理函数。
假设main函数和信号处理函数同时使用了:

  • 1:全局变量
  • 2:静态变量(例如getpwname)
  • 3:标准IO流(其缓存是全局的)
  • 4:malloc(其内存池维护信息是全局的)

则会导致程序出现重入错误。
即使是可重入的信号处理函数,也还有另外一个问题:errno!它也是一个全局的变量,表中列出来的可重入函数也会对其赋值。
所以编写信号处理函数时,用户要先保持以前的errno,然后在退出时恢复它。

 

SIGCLD vs SIGCHLD

SIGCLD是SYSTEM V中的信号名。
SIGCHLD是BSD中的信号名,后来被POSIX标准采用。
由于历史原因,SYSTEM V对SIGCLD信号的处理有些不同:

  • 1:如果将SIGCLD信号的处理设置为SIG_IGN,则子进程结束后不会产生僵死进程。

注意这里的SIG_IGN与SIGCLD的默认处理方法不同,虽然默认处理也是忽略。
进程终止后,其终止状态会被丢弃,如果调用wait函数,会导致阻塞到最后一个子进程结束,然后返回-1 , 并置errno为ECHILD。

  • 2:在设置捕捉(signal)SIGCLD的时候,系统会查看是否有已结束的子进程。如果有,则调用刚刚设置的信号处理函数。

这里的第2条和不可靠信号模型有一个冲突的地方:
void sig_cld(int signo)
{
pid_t   pid;
int     status;
if (signal(SIGCLD, sig_cld) == SIG_ERR) /* reestablish handler */
perror(“signal error”);
}

收到SIGCLD后,sig_cld被调用。执行到使用signal函数再次注册处理函数时,还未调用wait函数。系统又检查到有已结束的子进程,再次调用sig_cld,形成递归调用。在wait函数后再重新注册处理函数,可以避免这个问题,但是重新注册捕捉函数前的时间窗口就更加大。SIGCLD的移植性非常差。

可靠信号

  1. 内核在进程表内设置某种形式的标志,代表着信号产生了(generated)。
  2. 内核调用信号处理函数时,我们说信号被分发(delivered)。
  3. 当一个信号的状态处在generated和delivered之间,我们称其处在未决(pending)阶段。

当某种信号被进程阻塞时,这种信号产生了,如果该信号的处理方式是a)默认 或者 b)用户处理函数,
则该信号会保持未决,直到a)处理方式变成忽略 或者 b)取消阻塞。
在信号被阻塞时,如果同一种信号产生多次:
如果generate多次,就delivere多次,这就是queued
多个信号要delivere时,POSIX建议与程序当前状态相关的信号优先。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);

kill可以向其他进程发生信号,而raise只能向进程本身发送。成功返回0,失败返回-1。并设置errno
kill(getpid(), signo); 的效果等同于 raise(signo);

  • pid>0——发送给进程id等于pid的进程
  • pid=0——发送给进程组getpgid()中的所有进程。
  • pid<-1—–发送给进程组abs(pid)中的所有进程。
  • pid=-1—–发送给所有进程

上面的列表中忽略了权限限制:

  • 1:root可以给所有进程发生信号。
  • 2:非root权限进程只能给进程RID/EID相同的进程发送信号。如果定义了_POSIX_SAVED_IDS,则检查saved-user-id,而不检查EID。
  • 3:如果信号是SIGCONT,则一进程可以发送给相同session中的任何进程。

signo=0的信号被POSIX定义为NULL信号。发送null信号可以用来检查进程ID等于pid的进程是否终止
如果进程已终止,则kill失败返回-1,置errno为ESRCH。
如果信号不被阻塞,则在kill返回前会被deliver到相应的进程。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
int pause(void);

alarm设置一个seconds的闹钟,返回上一个闹钟剩余的秒数。闹钟超时会产生SIGALRM信号。
如果seconds等于0,则取消闹钟,并返回上一个闹钟的剩余秒数(如果有设置)。
每个进程只有一个闹钟,且闹钟时间可能会延时,SIGALRM的默认操作时终止进程。
pause挂起当前进程,直被信号中断。返回-1 , errno置为EINTR。

信号使用实例

假设使用alarm和pause函数实现sleep函数如下:
static void sig_alrm(int signo){ }
unsigned int sleep1(unsigned int nsecs)
{

if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
alarm(nsecs);       /* start the timer */
pause();            /* next caught signal wakes us up */
return(alarm(0));   /* turn off timer, return unslept time */

}
上面的程序有几个问题:

  • 1:sleep1没有处理,alarm设置闹钟前已经有闹钟的情况。
  • 2:如果signal之前已经设置了SIGALRM的处理函数,则会被此次调用覆盖。。
  • 3:alarm设置的闹钟可能在pause调用前就超时。

static jmp_buf  env_alrm;
static void sig_alrm(int signo){
longjmp(env_alrm, 1);
}
unsigned int sleep2(unsigned int nsecs){
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
if (setjmp(env_alrm) == 0) {
alarm(nsecs);       /* start the timer */
pause();            /* next caught signal wakes us up */
}
return(alarm(0));       /* turn off timer, return unslept time */
}
上面的函数可以用来解决上面的问题3,但是又引入了一个新的问题:
sleep2会终止其他信号的处理程序。

信号集合

#include <signal.h>

  • int sigemptyset(sigset_t *set);
  • int sigfillset(sigset_t *set);
  • int sigaddset(sigset_t *set, int signo);
  • int sigdelset(sigset_t *set, int signo);

成功返回0 , 失败返回-1。
int sigismember(const sigset_t *set, int signo);
1为真,0为假,-1为出错。

sigset_t操作函数,sigset_t为信号集合类型。

  • sigemptyset—初始化空集。//使其不包含任何信号
  • sigfillset——-初始化全集。//使其包含所有的信号
  • sigaddset—–将signo加入集合。
  • sigdelset——将signo从集合中移除。

sigismember–判断signo是否属于集合。是返回1,不是返回0.

sigprocmask函数

#include <signal.h>

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

sigprocmask函数用来检测或者更改当前进程信号屏蔽字
set是传入参数,oset为传出参数。oset返回以前的屏蔽字
至于如何使用set来设置新的屏蔽字则由how参数确定:

  • SIG_BLOCK——用set和当前的屏蔽字做并集,结果作为新的屏蔽字。
  • SIG_UNBLOCK—用当前的屏蔽字和set做减集,结果作为新的屏蔽字。
  • SIG_SETMASK–直接用set作为新的屏蔽字。

在调用sigprocmask后,内核会立即检查是否有未决信号可以deliver给进程。
如果有,则在sigprocmask返回前至少deliver一个(有点像kill和raise)。

sigpending函数

  • int sigpending(sigset_t *set);

sigpending返回由于被阻塞而没有deliver的pending信号。

struct sigaction {

void      (*sa_handler)(int);//处理信号的函数指针
sigset_t sa_mask;//处理程序的执行过程中需要阻塞的额外的信号
int      sa_flags;//特殊标志和选项
void     (*sa_sigaction)(int, siginfo_t *, void *);//实时处理程序

};

  • int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

signo为要处理的信号,act为传入参数表明要采取的动作,oact为传出参数负责接收与信号相关的前一个动作。
sa_handler为信号处理函数指针。
sa_mask会在调用信号捕捉函数前添加到进程的信号屏蔽字,捕捉函数返回时进程的信号屏蔽字被复原。
其中当前正在被deliver的信号也会被系统自动设置为阻塞。这样可以保证,若这种信号再次发生,会被阻塞到前一个处理函数返回为止。
sa_flags可以为:

  • SA_INTERRUPT—–被信号中断的系统调用不再自动重新启动。
  • SA_NOCLDSTOP—-进程被暂停时,不产生SIGCHLD信号。
  • SA_NOCLDWAIT—-进程结束后,不产生僵死进程,直接丢弃其终止状态信息。

设置此标志后调用wait,则会阻塞到最后一个子进程结束后返回-1,置errno为ECHILD。

  • SA_NODEFER——-不自动阻塞当前deliver的信号,sig_mask内设置屏蔽除外(与早期信号处理模型对应)。
  • SA_ONSTACK——-如果使用sigaltstack声明了替换栈,则将deliver到替换栈。//TODO
  • SA_RESETHAND—-自动重置信号处理函数为SIG_DEF(与早期信号处理模型对应)。
  • SA_RESTART——–自动重启被信号打断的系统调用,与SA_INTERRUPT相反。
  • SA_SIGINFO——–使用sa_sigaction结构。

sa_sigaction参数是加强版的sa_handler,当sa_flags设置为SA_SIGINFO时启用。
当调用信号处理函数时,一般是使用:

  • void      (*sa_handler)(int);

而当sa_flags标志设置了SA_SIGINFO时,则使用:

  • void     (*sa_sigaction)(int, siginfo_t *, void *);

struct siginfo {

int    si_signo;  /* signal number */
int    si_errno;  /* if nonzero, errno value from <errno.h> */
int    si_code;   /* additional info (depends on signal) */
pid_t  si_pid;    /* sending process ID */
uid_t  si_uid;    /* sending process real user ID */
void  *si_addr;   /* address that caused the fault */
int    si_status; /* exit value or signal number */
long   si_band;   /* band number for SIGPOLL */
/* possibly other fields also */

};
siginfo结构能提供被捕捉到信号的更多信息,比如si_code可以提供为什么信号会产生的具体原因:
比如拿SIGFPE信号来说,sig_code可以为:

  • FPE_FLTUND—浮点数下溢出
  • FPE_FLTRES—-不精确的结果
  • FPE_FLTINV—-不存在的浮点数运算符
  • FPE_FLTSUB—-下标出界

如果信号是SIGCHLD,则si_pid,si_uid,si_status能返回额外的信息。
如果信号是SIGILL或SIGSEGV,则si_addr能返回导致出错的地址,虽然不是非常精确。
如果信号是SIGPOLL,si_band也会提供额外信息。但是这些都是因系统实现而异。
而sa_sigaction处理函数中的context参数(3rd,void*)可以被转换为ucontext_t,能提供信号deliver时的一些上下文信息。

sigsetjmp和siglongjmp

#include <setjmp.h>

  • int sigsetjmp(sigjmp_buf env, int savemask);
  • void siglongjmp(sigjmp_buf env, int val);

如果savemask为0,则不保存进程信号屏蔽字,否则保存。
由于setjmp和longjmp函数不一定保存进程的信号屏蔽字,如果从信号处理函数中跳出,可能导致信号屏蔽字不能恢复。
于是POSIX定义了这两个函数,直接调用sigsetjmp返回0,siglongjmp跳转返回val。
由于信号是异步信息,随时可能发生,所以在使用这两个函数时,要设置一个跳转使能标志,书中使用的是:

  • static volatile sig_atomic_t        canjump;

在sigsetjmp后,立即设置其为允许跳转,可以避免sigaction和sigsetjmp间的时间窗口间发生要捕捉的信号。
sig_atomic_t是ANSI C定义的写时不会被中断的变量类型。
volatile变量能防止编译器优化,因为该变量由两条控制线读写。

sigsuspend,pause,sigwait函数

#include <signal.h>

  • int sigsuspend(const sigset_t *sigmask);

总是返回-1,并置errno为EINTR,表示被系统调用被中断。
既然信号可能随时会打断程序的执行,那么我们可以先阻塞信号,再执行不想被打断的代码。
接下来再解除阻塞并调用pause函数,等待被先前被阻塞的信号。于是我们会写下这样的代码:
//阻塞信号
//执行不想被信号打断的代码…..
//解除阻塞

  • pause();  /* wait for signal to occur */

但是由于解除阻塞和pause之间会有时间间隔,如果在这段间隔内,先前阻塞的信号被deliver,那么pause就永远也等不到要等的信号。
sigsuspend就是用来解决这个问题,sigsuspend将挂起进程,并将其信号屏蔽字设置为sigmask,且是原子操作。

使用sig_atomic_t标志和sigsuspend组合,可以让一个进程无限等待一个特定的信号。

  • 1:全局变量volatile sig_atomic_t sig_int_delivered=0。
  • 2:使用sigprocmask屏蔽SIGINT信号,消除sigaction和sigsuspend间的时间窗口。
  • 3:设置捕捉函数sig_int捕捉SIGINT,并在sig_int中执行sig_int_delivered=1。
  • 4:main函数中使用:

while(0==sig_int_delivered)
sigsuspend(&zeromask);
等待SIGINT信号。sigsuspend中的zeromask为空信号集,所以会解除对SIGINT信号的阻塞。

  • 5:使用sigprocmask将信号屏蔽字恢复到第1个步骤时的值。

这个流程可以用来实现TELL_WAIT, WAIT_PARRENT, WAIT_CHILD, TELL_PARRENT, TELL_CHILD:

int sigwait(const sigset_t *restrict sigmask,int *restrict signo)

sigwait函数一直阻塞到 sigmask指定的任何一个信号被挂起为之,然后从挂起信号集中删除那个信号,并解除对他的阻塞,当sigwait返回时,从挂起信号集中删除的信号的个数被存在signo指向的位置中。

sigwait和sigsuspend区别:第一个参数都是指向信号集的指针,sigsuspend这个信号集里面装载的是新的信号掩码,因此不在信号集中的信号是能使sigsuspend返回的信号,sigwait这个信号集是需要等待的信号,在信号集中的信号可以使sigwait返回。且sigwait没有改变进程的信号掩码,sigmask中的信号在调用之前被阻塞起来。

#include <stdlib.h>

  • void abort(void);

abort会发送SIGABRT到当前进程,并导致程序异常终止。
根据POSIX标准要求,abort实现如下:

  • 1:如果SIGABRT的处理方式被设置为SIG_IGN,abort会将其设置为SIG_DEF。
  • 2:刷新所有标准IO流。
  • 3:设置进程屏蔽字,阻塞除开SIGABRT外的所有信号(当然,不能阻塞的SIGTERM和SIGSTOP除外)。
  • 4:kill(getpid(), SIGABRT);
  • 5:如果用户设置了捕捉函数,则会执行捕捉函数(第1步时,只有当处理方式为SIG_IGN时,才将其设置为SIG_DEF)。
  • 6:设置SIGABRT的处理方式为SIG_DEF,发送SIGABRT信号。
  • 7:再次kill(getpid(), SIGABRT),使用SIG_DEF方式处理SIGABRT信号,进程意外终止。

system函数

POSIX.2要求system函数忽略SIGINT, SIGQUIT并阻塞SIGCHLD:

  • a)忽略SIGINT, SIGQUIT:控制终端产生的这两个信号,会发送到前台进程组中的所有进程,并使其终止(系统默认处理)。假设用户程序调用system,那么用户程序和system产生的子进程会在前台进程组,则两者都会被信号终止。
  • b)阻塞SIGCHLD:如果用户程序注册了SIGCHLD处理函数sig_chld,且在sig_chld函数中调用了waitpid来获取子进程终止状态。则system产生出来的子进程的终止状态,可能被sig_chld中的waitpid获取,而不是system中的waitpid。从而导致 system出错。

另外,必须在fork之前执行a),从而避免竞态条件。
system的返回值是Shell的退出状态。

sleep函数

展示了不使用setjmp和longjmp实现的alarm:

  • 1:保存SIGALRM信号的处理函数,并设置新的处理函数(SIGALRM系统默认处理为终止进程)。
  • 2:使用sigprocmask保存现在的信号屏蔽字,并设置新的屏蔽字(将SIGALRM阻塞)。
  • 3:调用alarm函数
  • 4:使用sigsuspend函数恢复先前的信号屏蔽字,并挂起进程。
  • 5:恢复SIGALRM的处理函数,恢复信号屏蔽字。

作业控制信号(SIGCHLD, SIGCONT, SIGTSTOP, SIGSTOP, SIGTTOU, SIGTTIN)
作业控制信号间有一些相互作用:
1:如果deliver了4种(除SIGCHLD和SIGCONT)停止信号的任意一种到一个进程,则该进程的未决(pending)SIGCONT信号被舍弃。
2:同理,如果deliver了SIGCONT信号到一个进程,则该进程的未决(pending)停止信号被舍弃。
作业控制系统中,处理SIGTSTOP的方法(与abort类似):
static void sig_tstp(int signo)
{
sigset_t    mask;
//挂起当前进程的要处理的事情(对于vi来说是恢复Shell界面)
//从信号屏蔽字中去除SIGTSTP,因为系统默认会阻塞正在处理的信号。
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);

signal(SIGTSTP, SIG_DFL);   //SIGTSTP系统默认的处理方式是挂起
kill(getpid(), SIGTSTP);    //kill返回之前,SIGTSTP就会被deliver到当前程序,导致其挂起。
/* we won’t return from the kill until we’re continued */
signal(SIGTSTP, sig_tstp);  /* reestablish signal handler */
//用户切换回被挂起的进程(对于vi来说是重绘vi界面)
}
另外,系统在启动时,init进程会将作业控制信号的处理方式设置为SIG_IGN。
如果Shell支持作业控制,则重新设置作业控制信号的处理方式。不支持的Shell不用理会。

其他特性
extern char *sys_siglist[];
信号名字符串表(查表法,信号值为key,返回的value为信号描述字符串)。

  • void psignal(int signo, const char *msg);

打印形如msg: signo信号描述的打印信息。
#include <string.h>

  • char *strsignal(int signo);

根据signo返回对应信号的字符串描述。
#include <signal.h>

  • int sig2str(int signo, char *str);
  • int str2sig(const char *str, int *signop);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值