TLPI 第20 章 练习:Signals: Fundamental Concepts
笔记和练习博客总目录见开始读TLPI。原书随书代码位于目录signals。练习 20-1.如第20.3节所述使用sigaction()建立信号处理程序比使用signal()更具可移植性。在清单20-7sig_receiver.c中的程序中用sigaction()替换signal()的使用。 在列表22-1中即signals/signal.c直接用sigaction实现了signal。sigaction的用法可参考nonreentrant.c。代码如下// ex20-1.c#define_GNU_SOURCE#includesignal.h#includesignal_functions.h/* Declaration of printSigset() */#includetlpi_hdr.hstaticintsigCnt[NSIG];/* Counts deliveries of each signal */staticvolatilesig_atomic_tgotSigint0;/* Set nonzero if SIGINT is delivered */staticvoidhandler(intsig){if(sigSIGINT)gotSigint1;elsesigCnt[sig];}intmain(intargc,char*argv[]){intn,numSecs;sigset_tpendingMask,blockingMask,emptyMask;structsigactionsa;printf(%s: PID is %ld\n,argv[0],(long)getpid());sigemptyset(sa.sa_mask);sa.sa_flags0;sa.sa_handlerhandler;for(n1;nNSIG;n){if(sigaction(n,sa,NULL)-1)errMsg(sigaction for signal %d,n);}if(argc1){numSecsgetInt(argv[1],GN_GT_0,NULL);sigfillset(blockingMask);if(sigprocmask(SIG_SETMASK,blockingMask,NULL)-1)errExit(sigprocmask);printf(%s: sleeping for %d seconds\n,argv[0],numSecs);sleep(numSecs);if(sigpending(pendingMask)-1)errExit(sigpending);printf(%s: pending signals are: \n,argv[0]);printSigset(stdout,\t\t,pendingMask);sigemptyset(emptyMask);/* Unblock all signals */if(sigprocmask(SIG_SETMASK,emptyMask,NULL)-1)errExit(sigprocmask);}while(!gotSigint)/* Loop until SIGINT caught */continue;for(n1;nNSIG;n)/* Display number of signals received */if(sigCnt[n]!0)printf(%s: signal %d caught %d time%s\n,argv[0],n,sigCnt[n],(sigCnt[n]1)?:s);exit(EXIT_SUCCESS);}测试$ ./ex20-115./ex20-1: PID is16041ERROR[EINVAL Invalid argument]sigactionforsignal9ERROR[EINVAL Invalid argument]sigactionforsignal19ERROR[EINVAL Invalid argument]sigactionforsignal32ERROR[EINVAL Invalid argument]sigactionforsignal33./ex20-1: sleepingfor15seconds## 此处按3次 Ctrl\## 在另一个终端运行命令3次kill -10 16041^\^\^\./ex20-1: pending signals are:3(Quit)10(User defined signal1)## 此处按3次 Ctrlc^C./ex20-1: signal3caught1time./ex20-1: signal10caught1time注意安装信号处理程序时有4次报错均忽略。32, 33报错是因为无此信号。919报错是因为如下sigaction(2)signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.练习 20-2.编写一个程序证明当某个处于等待状态的信号的处置方式被更改为 SIG_IGN忽略时该程序永远不会收到捕获到这个信号。基于练习20-1的代码稍微改下即可。代码如下// ex20-2.c#define_GNU_SOURCE#includesignal.h#includesignal_functions.h/* Declaration of printSigset() */#includetlpi_hdr.hstaticintsigCnt0;staticvolatilesig_atomic_tgotSigint0;staticvoidhandler(intsig){printf(Signal %d was caught\n,sig);if(sigSIGINT)gotSigint1;elsesigCnt;}intmain(intargc,char*argv[]){intnumSecs;sigset_tpendingMask,blockingMask,emptyMask;structsigactionsa;printf(%s: PID is %ld\n,argv[0],(long)getpid());sigemptyset(sa.sa_mask);sa.sa_flags0;sa.sa_handlerhandler;if(sigaction(SIGUSR1,sa,NULL)-1)errExit(sigaction);if(sigaction(SIGINT,sa,NULL)-1)errExit(sigaction);if(argc1){numSecsgetInt(argv[1],GN_GT_0,NULL);sigfillset(blockingMask);if(sigprocmask(SIG_SETMASK,blockingMask,NULL)-1)errExit(sigprocmask);printf(%s: sleeping for %d seconds\n,argv[0],numSecs);sleep(numSecs);if(sigpending(pendingMask)-1)errExit(sigpending);printf(%s: pending signals are: \n,argv[0]);printSigset(stdout,\t\t,pendingMask);if(argc2){sigemptyset(sa.sa_mask);sa.sa_flags0;sa.sa_handlerSIG_IGN;if(sigaction(SIGUSR1,sa,NULL)-1)errExit(sigaction);}sigemptyset(emptyMask);/* Unblock all signals */if(sigprocmask(SIG_SETMASK,emptyMask,NULL)-1)errExit(sigprocmask);}while(!gotSigint)/* Loop until SIGINT caught */continue;fprintf(stderr,%s: signal SIGUSR1 caught %d time%s\n,argv[0],sigCnt,(sigCnt1)?:s);exit(EXIT_SUCCESS);}2个主要的改动只要进入信号处理函数handler就打印某信号被捕获针对所有信号的处理改为只针对SIGUSR1的处理只有argc大于2时才设置SIG_IGN。否则和练习20-1的行为一致。测试首先不设置SIG_IGN此时Signal 10在解除阻塞后被捕获$ ./ex20-210./ex20-2: PID is16203./ex20-2: sleepingfor10seconds# 此时在另一终端执行多次 kill -10 16203./ex20-2: pending signals are:10(User defined signal1)Signal10was caught ^CSignal2was caught ./ex20-2: signal SIGUSR1 caught1time设置SIG_IGN此时Signal 10在解除阻塞后未被捕获$ ./ex20-210xxx ./ex20-2: PID is16204./ex20-2: sleepingfor10seconds# 此时在另一终端执行多次 kill -10 16204./ex20-2: pending signals are:10(User defined signal1)^CSignal2was caught ./ex20-2: signal SIGUSR1 caught0times做完练习发现原书提供了答案ignore_pending_sig.c。他的证明方法是设置SIG_IGN后打印pendng mask发现被清零了。他的方法更简洁和针对性是用数据说话直击本质。而我的是用行为说话。练习 20-3.编写程序验证在使用sigaction()建立信号处理程序时SA_RESETHAND和SA_NODEFER标志的效果。首先从sigaction(2)中了解SA_RESETHAND和SA_NODEFER的含义。SA_NODEFERDo not add the signal to the thread’s signal mask while the handler is executing, unless thesignal is specified in act.sa_mask. Consequently, a further instance of the signal may be de‐livered to the thread while it is executing the handler. This flag is meaningful only whenestablishing a signal handler.SA_RESETHANDRestore the signal action to the default upon entry to the signal handler.此练习让我们了解了信号的一些默认行为以下重点说下SA_NODEFER在信号A的处理程序期间信号A会被屏蔽直到信号处理程序处理完毕。如果设置了SA_NODEFER在信号A处理期间如果又收到信号A则原信号处理程序被暂时中断直到新的信号处理程序完成以此类推。代码如下// ex20-3.c#define_GNU_SOURCE#includesignal.h#includeunistd.h#includetime.h#includetlpi_hdr.hstaticvolatileintround0;staticvoidhandler(intsig){intr;time_tnow;rround;nowtime(NULL);printf(%s Round %d: sighandler: signal %d was caught\n,ctime(now),r,sig);sleep(5);nowtime(NULL);printf(%s Round %d: sighandler: slept 5 seconds\n,ctime(now),r);fflush(NULL);}intmain(intargc,char*argv[]){intresethand,nodefer;structsigactionsa;resethandnodefer0;for(inti1;iargc;i){if(strcmp(argv[i],SA_RESETHAND)0)resethand1;if(strcmp(argv[i],SA_NODEFER)0)nodefer1;}sigemptyset(sa.sa_mask);sa.sa_flags0;if(resethand)sa.sa_flags|SA_RESETHAND;if(nodefer)sa.sa_flags|SA_NODEFER;sa.sa_handlerhandler;if(sigaction(SIGINT,sa,NULL)-1)errExit(sigaction);while(1)pause();exit(EXIT_SUCCESS);}测试首先是默认行为$ ./ex20-3 ^CWed May608:03:382026Round1: sighandler: signal2was caught## 在信号SIGINT处理期间再次键入CtrlC^CWed May608:03:432026Round1: sighandler: slept5seconds Wed May608:03:432026## 信号处理被defer了直到第一次处理完毕才开始第二次处理Round2: sighandler: signal2was caught Wed May608:03:482026Round2: sighandler: slept5seconds ^\Quit(core dumped)然后设置SA_NODEFER$ ./ex20-3 SA_NODEFER ^CWed May608:05:442026Round1: sighandler: signal2was caught## 在信号SIGINT处理期间再次键入CtrlC## 马上进入新的信号处理程序^CWed May608:05:482026Round2: sighandler: signal2was caught Wed May608:05:532026Round2: sighandler: slept5seconds## 新的信号处理结束后再返回到第一次信号处理程序Wed May608:05:532026Round1: sighandler: slept5seconds ^\Quit(core dumped)## 我们注意到这两次信号处理程序是同时结束的然后设置SA_RESETHAND$ ./ex20-3 SA_RESETHAND ^CWed May608:08:542026Round1: sighandler: signal2was caught## 在信号SIGINT处理期间再次键入CtrlC^CWed May608:08:592026Round1: sighandler: slept5seconds## 等到第一次信号处理完毕程序就被终止了。这个就比较简单了其实SA_RESETHAND的同义词SA_ONESHOT虽然其是非标准并且过时的可以更好的说明此行为。即进入第一次信号处理程序后其被重置为默认行为对于SIGINT而言就是终止程序。练习 20-4.使用sigaction()实现siginterrupt()。尽管siginterrupt是deprecated我们还是先从siginterrupt(3)中了解其行为The siginterrupt() function changes the restart behavior when a system call is interrupted by thesignal sig. If the flag argument is false (0), then system calls will be restarted if interrupted bythe specified signal sig. This is the default behavior in Linux.If the flag argument is true (1) and no data has been transferred, then a system call interrupted bythe signal sig will return -1 and errno will be set to EINTR.If the flag argument is true (1) and data transfer has started, then the system call will be inter‐rupted and will return the actual amount of data transferred.然后从sigaction(2)中可知对应的标志位为SA_RESTARTSA_RESTARTProvide behavior compatible with BSD signal semantics by making certain system callsrestartable across signals. This flag is meaningful only when establishing a signal handler.See signal(7) for a discussion of system call restarting.以上并非故事的全部更细的说明要参见signal(7)Interruption of system calls and library functions by signal handlersIf a signal handler is invoked while a system call or library function call is blocked, then either:• the call is automatically restarted after the signal handler returns; or• the call fails with the error EINTR.Which of these two behaviors occurs depends on the interface and whether or not the signal handlerwas established using the SA_RESTART flag (see sigaction(2)). The details vary across UNIX systems;below, the details for Linux.If a blocked call to one of the following interfaces is interrupted by a signal handler, then thecall is automatically restarted after the signal handler returns if the SA_RESTART flag was used;otherwise the call fails with the error EINTR:…以上省略了对于具体系统调用的说明。但简单来说有些是可以重启的有些是不可重启的总是返回失败。原书提供了答案signals/siginterrupt.c如下我就不重复劳动了#includestdio.h#includesignal.hintsiginterrupt(intsig,intflag){intstatus;structsigactionact;statussigaction(sig,NULL,act);if(status-1)return-1;if(flag)act.sa_flags~SA_RESTART;elseact.sa_flags|SA_RESTART;returnsigaction(sig,act,NULL);}