《APUE》笔记-第十章-信号
来源:互联网 发布:无主之地前传改枪软件 编辑:程序博客网 时间:2024/05/16 04:17
重点:信号意义、几种常见信号、signal、信号的阻塞和未决、sigprocmask、sigpending、sigaction、sigsuspend、竞争条件
1.信号
信号是软件中断,信号提供了一种处理异步事件的方法:产生信号的事件是随机出现的,需要告诉内核当什么信号发生时该执行什么操作。
定义在<signal.h>里(本机实际位置:/usr/include/bits/signum.h),形式:“#define 信号名 信号编号” ,如下图,不存在编号为0的信号。
信号处理动作:1.忽略;2.捕捉;3.执行系统默认动作(大多数是终止该进程)
几种常见信号及其产生原因:
SIGKILL和SIGSTOP两种信号不能被忽略也不能被捕捉
发送给其父进程SIGCONT发送给需要继续运行,
但出于停止状态的进程SIGHUP终端检测到连接断开,
则将此信号发送给控制
进程SIGINT按中断键 Ctrl + CSIGKILL杀死任一进程SIGQUIT按退出键 Ctrl + \SIGSTOP停止一个进程SIGTERMkill命令发送SIGTSTP停止信号,发送给前台
进程组的所有进程SIGUSR1用户定义的信号,可用
于应用程序SIGUSR2同SIGUSR1SIGTTIN后台进程组的进程试图
读控制终端SIGTTOUT后台进程组的进程试图
写控制终端
2.signal
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
成功:返回以前的信号配置;出错,返回SIG_ERR
第二个参数:SIG_IGN、SIG_DFL、或信号捕捉函数的函数名。
从函数定义可看出,对signal来说,不改变信号处理方式就不能确定信号的当前处理方式,sigaction可对此作出改善。
因为子进程在开始时复制了父进程的内存映像,所以子进程继承父进程的信号处理方式。
练习程序:
#include <stdio.h>#include <signal.h>static void sig_usr(int);int main(){ if (signal(SIGUSR1, sig_usr) == SIG_ERR) printf("can't catch signal SIGUSR1\n"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) printf("can't catch signal SIGUSR2\n"); for (; ;) pause();//在接到信号前,一直挂起 return 0;}static void sig_usr(int signo){ if (signo == SIGUSR1) printf("received SIGUSR1\n"); if (signo == SIGUSR2) printf("received SIGUSR2\n");}结果:
分析:
1.pause函数会导致进程在接到一个信号前,会一直处于挂起状态。
2.调用不带参数的kill,默认会发送SIGTERM信号,而对该信号的默认处理方式是终止该进程。
3.不可靠信号
不可靠指的是:
信号可能会丢失:一个信号发生了,但进程可能会不知道。
对信号控制能力差:无法阻塞信号
进程每次接收到信号对其进行处理时,随即将该信号的动作重置为默认值
不能关闭信号
4.中断的系统调用
低速系统调用和其他系统调用
低速系统调用:可能会使进程永远阻塞的一类系统调用
早起Unix:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno设置为EINTR
为使应用程序不必处理被中断的系统调用,BSD引进了某些被中断系统调用的自动重启动。自动重启动的系统调用有:ioctl、read、readv、write、writev、wait、waitpid
自动重启动可能会引起问题,所以允许禁用此功能。
引入自动重启动理由是:不必每次对读、写系统调用进行是否出错的测试,如果是被中断的,则再调用读、写系统调用。
5.可重入函数
进程捕捉到信号并对其进行处理,进程正在执行的指令序列就被信号处理程序临时中断,首先执行该信号处理程序中的指令,如果从信号处理程序返回,则继续在捕捉到信号时进程正在执行的正常指令中返回。在信号处理程序中,不能判断捕捉到信号时进程在何处执行,这样不能保证在中断处理结束后能够正确返回到进程的执行指令中。为了保证进程在处理完中断后能够正确返回,需要保证调用的是可重入的函数。
不可重入函数包括:(1)使用静态数据结构,(2)调用malloc或free,(3)标准I/O函数。
信号处理程序中调用一个不可重入的函数,则结果是不可预测的。例如getpwnam函数是个不可重入的,因为其结果存放在静态存储单元中,信号处理程序调用后,返回给正常调用者的信息可能是被返回给信号处理程序的信息覆盖。
6.kill、 raise
kill向进程或进程组发送信号,raise向自身发送信号
#include <signal.h>
int kill(pid_t pid, int signo)
int raise(int signo)
返回值:成功,返回0;出错,返回-1
raise(signo)<=>kill(getpid(), signo)
kill的pid:
pid>0:发送给pid
pid<0:发送给|pid|
pid=0:发送给同一进程组所有进程
pid=-1:发送给有权限发送的所有进程
练习程序:
#include <signal.h>#include <unistd.h>#include <stdio.h>static void sig_usr1(int signo){ printf("received signal SIGUSR1\n");}static void sig_chld(int signo){ printf("received signal SIGCHLD\n");}int main(){ pid_t pid; if ((pid = fork()) < 0) { printf("fork() error\n"); exit(-1); } if (pid == 0) { signal(SIGUSR1, sig_usr1); raise(SIGUSR1);//子进程向自身发送信号 pause();//等待父进程发送信号 } else { sleep(1);//等待子进程执行 signal(SIGCHLD, sig_chld); kill(pid, SIGKILL);//杀死子进程 sleep(2);//等待子进程终止,捕捉信号SIGCHLD } exit(0);}结果:
分析都在代码注释里
7.alarm、 pause
#include <unistd.h>
//设置一个定时器
unsigned int alarm(unsigned int seconds);
返回值:0或以前设置的闹钟时间的余留秒数
若seconds=0,则取消以前设置的闹钟时间,余留秒数仍作为返回值
//使调用进程挂起,直到捕捉到一个信号
int pause(void);
返回值:-1,errno设置为EINTR
程序练习:
#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>#define ERR_EXIT(m) \ {\ perror(m);\ exit(EXIT_FAILURE);\ }void sig_alrm(int signo){ printf("received SIGALRM\n");}int main(){ signal(SIGALRM, sig_alrm); int time_origin = alarm(0); alarm(2); int time_now = alarm(5); printf("origin time is: %d\n", time_origin); printf("now time is: %d\n", time_now); pause(); printf("after pause()\n"); exit(0);}
结果:
8.信号集
信号集是一个能表示多个信号的数据类型,用sigset_t定义一个信号集
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(sigset_t *set, int signo);
//若真,返回1;若假,返回0
所有应用程序在使用信号集之前,要对该信号集调用sigemptyset或sigfillset一次
程序练习:
#include <stdio.h>#include <signal.h>void printsig(const sigset_t *set){ //未列出全部信号 if (sigismember(set, SIGINT)) printf("SIGINT\n"); if (sigismember(set, SIGQUIT)) printf("SIGQUIT\n"); if (sigismember(set, SIGALRM)) printf("SIGALRM\n"); if (sigismember(set, SIGUSR1)) printf("SIGUSR1\n"); int i; for (i = 1; i < NSIG; i++)//NSIG:最大信号编号 { if (sigismember(set, i)) putchar('1'); else putchar('0'); } printf("\n");}int main(){ sigset_t set; sigemptyset(&set); printsig(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGUSR1); printsig(&set); sigdelset(&set, SIGINT); printsig(&set); exit(0);}结果:
9.信号的阻塞和未决
参考文章:信号的阻塞和未决(写的很好)
实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号,SIGKILL 和 SIGSTOP 不能被阻塞。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
概括如下:
若sigprocmask将一个信号集设置为信号屏蔽字时,当该信号集中的信号发生后,在递送到信号处理程序过程中,因为被阻塞所以出于未决状态。此时,通过sigpending就可取出处于未决状态的信号集。
10.sigprocmask、 sigpending
//将信号集中的信号设为信号屏蔽字
int sigprocmask(int how, sigset_t *restrict set, sigset_t *restrict oset);
how:
SIG_BLOCK:set包含我们希望加到当前信号屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCK:set包含我们希望从当前信号屏蔽字删除的信号,相当于mask=mask& ~set
SIG_SETMASK:设置当前信号屏蔽字为set,相当于mask=set
oset为非空指针,则当前信号屏蔽字通过oset返回
//取出处于未决状态的信号集
int sigpending(sigset_t *set);
成功,返回0;出错,返回-1
程序练习:
#include <stdio.h>#include <signal.h>#include <stdlib.h>#define ERR_EXIT(m)\ {\ perror(m);\ exit(EXIT_FAILURE);\ }//打印全部信号编号static void print_sig(const sigset_t *set){ int i; for (i = 1; i < NSIG; i++) { if (sigismember(set, i)) putchar('1'); else putchar('0'); } printf("\n");}//信号处理程序static void sig_quit(int signo){ printf("caught SIGQUIT\n"); if (signal(SIGQUIT, SIG_DFL) == SIG_ERR) ERR_EXIT("signal error");}int main(){ if (signal(SIGQUIT, sig_quit) == SIG_ERR) ERR_EXIT("signal error"); sigset_t newmask; sigset_t oldmask; sigset_t pendmask; sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); sigprocmask(SIG_BLOCK, &newmask, &oldmask); sigpending(&pendmask); print_sig(&pendmask); sleep(5);//此期间产生退出信号 sigpending(&pendmask); print_sig(&pendmask); if (sigismember(&pendmask, SIGQUIT)) printf("SIGQUIT pending\n"); sigprocmask(SIG_SETMASK, &oldmask, NULL); sleep(5);//再次产生退出信号 exit(0);}结果:
分析:
通过第一次调用sigprocmask后就调用sigpending来获取信号集并打印,可知,sigpending返回的是处于未决状态的信号集,而不是信号屏蔽字。只有一个信号产生了并被阻塞处于未决状态时,sigpending才返回该信号集
另:系统不会对信号进行排队,即阻塞期间产生多次SIGQUIT,但只向进程递送一次SIGQUIT
11.sigaction
//功能:检查或修改(或检查并修改)与指定信号相关联的处理动作,取代早期的signal函数
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
struct sigaction
{
void (*sa_handler)(int); //信号处理函数
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
sa_handler:信号捕捉函数的地址;
sa_mask:调用信号捕捉函数前,该信号集被加到信号屏蔽字中,从而在调用信号捕捉函数时,能阻塞某些信号。
sa_flags:SA_INTERRUPT:中断的系统调用不自动重启动;SA_RESTART:中断的系统调用自动重启动;......
sa_sigaction:替代的信号处理程序,一次只能使用sa_handler和sa_sigaction中的一个。
说明:同一信号多次发生,并不将它们加入队列;如:某种信号阻塞时发生了5次,解除阻塞后,信号处理函数只调用一次。
linux下默认是不自动重启动的。
用sigaction实现signal,程序如下:
#include <stdio.h>#include <signal.h>typedef void Sigfunc(int);Sigfunc *signal(int signo, Sigfunc *func){ printf("now in my signal\n"); struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif sigaction(signo, &act, &oact); return(oact.sa_handler);}void sig_func(int signo){ printf("catched signal\n");}int main(){ signal(SIGALRM, sig_func); alarm(1); pause(); exit(0);}结果:
12.sigsetjmp、 siglongjmp
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);
若savemask非0,则sigsetjmp在env中保存了进程当前信号屏蔽字。当调用siglongjmp时,如果带非0的sigsetjmp已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
调用信号处理函数时,再次产生一个信号,并通过siglongjmp返回,程序如下:
#include <stdio.h>#include <signal.h>#include <setjmp.h>#include <time.h>#include <errno.h>static void sig_usr1(int);static void sig_alarm(int);static sigjmp_buf jmpbuf;static volatile sig_atomic_t canjump;void pr_mask(const char* str);int main(){ signal(SIGUSR1, sig_usr1); signal(SIGALRM, sig_alarm); pr_mask("starting main:"); if (sigsetjmp(jmpbuf,1)) { pr_mask("ending main:"); exit(0); } canjump = 1; for (; ;) pause();}static void sig_usr1(int signo){ time_t starttime; if (canjump == 0) return; pr_mask("starting sig_usr1: "); alarm(3); sleep(5); pr_mask("finishing sig_usr1: "); canjump = 0; siglongjmp(jmpbuf, 1);}static void sig_alarm(int signo){ pr_mask("in sig_alrm: ");}void pr_mask(const char *str){ sigset_t sigmask; sigprocmask(0, NULL, &sigmask); printf("%s", str); if (sigismember(&sigmask, SIGINT)) printf(" SIGINT"); if (sigismember(&sigmask, SIGALRM)) printf(" SIGALRM"); if (sigismember(&sigmask, SIGUSR1)) printf(" SIGUSR1"); if (sigismember(&sigmask, SIGQUIT)) rintf(" SIGQUIT"); printf("\n");}结果:
分析:
1.打印信号屏蔽字pr_mask("starting main:"),无
2.调用sigsetjmp,在jmpbuf中保存进程当前的信号屏蔽字
3.发送SIGUSR1信号给进程
4.接收到信号,进入信号处理程序,打印此时的信号屏蔽字,pr_mask("starting sig_usr1");
注:当调用一个信号处理程序时,被捕捉到的信号加到进程的当前信号屏蔽字中。 SIGUSR1
5. 3秒后,产生信号SIGALRM,进入另一个信号处理程序,打印此时信号屏蔽字pr_mask("in sig_alrm:");
注:当调用一个信号处理程序时,被捕捉到的信号加到进程的当前信号屏蔽字中。 SIGUSR1 SIGALRM
6.返回上一层信号处理程序,打印信号屏蔽字,pr_mask("ending sig_usr1");
注:当从信号处理程序返回时,恢复原来的信号屏蔽字 SIGUSR1
7.调用siglongjmp,返回sigsetjmp位置,siglongjmp从jmpbuf中恢复之前的信号屏蔽字。打印此时信号屏蔽字 pr_mask("ending main:"),无
注:当从信号处理程序返回时,恢复原来的信号屏蔽字 无
本程序还利用了canjump为1从而确保sigsetjmp已经将当前的信号屏蔽字保存到jmpbuf中了;否则不执行信号处理程序,直接返回。
13.sigsuspend
sigsuspend将解除信号屏蔽字和使进程挂起结合成一个原子操作。这样可以解决竞争条件,见第14节。在原子操作中,先恢复信号屏蔽字,然后使进程休眠。进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。
int sigsuspend(const sigset_t *sigmask);
返回值:-1,并将errno设置为EINTR
程序如下:
#include <stdio.h>#include <signal.h>static void pr_mask(const char *str);static void sig_int(int signo);int main(){ signal(SIGINT, sig_int); pr_mask("starting main: "); sigset_t newmask, oldmask, waitmask; sigemptyset(&newmask); sigemptyset(&waitmask); sigaddset(&newmask, SIGINT); sigaddset(&waitmask, SIGUSR1); sigprocmask(SIG_BLOCK, &newmask, &oldmask); pr_mask("after newmask: "); sigsuspend(&waitmask);//恢复之前的信号屏蔽字,现在的信号屏蔽字设置为SIGUSR1,进程休眠。调用sig_int后返回 pr_mask("after return from sigsuspend(): "); sigprocmask(SIG_SETMASK, &oldmask, NULL); pr_mask("ending main: "); return 0;}static void sig_int(int signo){ pr_mask("in sig_int: ");}static void pr_mask(const char *str){ sigset_t sigmask; sigprocmask(0, NULL, &sigmask); printf("%s", str); if (sigismember(&sigmask, SIGINT)) printf(" SIGINT"); if (sigismember(&sigmask, SIGALRM)) printf(" SIGALRM"); if (sigismember(&sigmask, SIGUSR1)) printf(" SIGUSR1"); if (sigismember(&sigmask, SIGQUIT)) printf(" SIGQUIT"); printf("\n");}结果:
分析:
调用sigsuspend(&waitmask)后,进程原先的信号屏蔽字(SIGINT)被恢复,现在的信号屏蔽字被设置为waitmask(SIGUSR1);
sigsuspend捕捉到一个信号并从信号处理程序返回时,sigsuspend返回。所以中断键引起sig_int被调用,sig_int返回时,sigsuspend返回,并将信号屏蔽字设置为之前的信号屏蔽字(SIGINT)
14.竞争条件和sleep函数
使用alarm和pause函数可实现sleep函数
程序如下:
#include <stdio.h>#include <signal.h>static void sig_alrm(int signo){}unsigned int sleep1(unsigned int seconds){ if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(seconds); alarm(seconds); pause(); return(alarm(0));}int main(){ printf("now sleep 3 seconds\n"); sleep1(3); printf("end.\n"); exit(0);}该简单实现有3个问题:
1.如果在调用sleep1之前,调用者已设置了闹钟,则它被sleep1函数中的第一次alarm调用擦除
2.改程序中修改了对SIGALRM的配置
3.在第一次调用alarm和pause之间有一个竞争条件。在一个繁忙的系统中, 可能alarm在调用pause之前超时,并调用了信号处理程序。如果发生了这种情况,则在调用pause后,如果没有捕捉到其他信号,调用者将永远被挂起。虽然alarm的下一行就是pause,但无法保证pause在调用alarm后的seconds秒后一定会被调用,由于时序而导致的错误,叫做竞争条件
sleep:
unsigned int sleep(unsigned int seconds);
返回值:0或未休眠完的秒数
此函数使调用进程挂起直到满足下面两个条件之一:
(1)已经过了seconds所指定的墙上时钟时间
(2)调用进程捕捉到一个信号并从信号处理程序返回
使用alarm实现的sleep函数,该函数可靠的处理信号,避免了竞争条件,程序如下:
#include <stdio.h>#include <signal.h>static void sig_alrm(int signo){}unsigned int sleep(unsigned int seconds){ struct sigaction newact, oldact; sigset_t newmask, oldmask, suspmask; unsigned int unslept; newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); sigemptyset(&newmask); sigaddset(&newmask, SIGALRM); sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(seconds); suspmask = oldmask; sigdelset(&suspmask, SIGALRM); sigsuspend(&suspmask); unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); sigprocmask(SIG_SETMASK, &oldmask, NULL); return unslept;}int main(){ printf("sleep 5 seconds:\n"); int i; for (i = 0; i < 5; i++) { sleep(1); printf("%d\n", i+1); } printf("end\n"); return 0;}结果:
分析:
因为sigprocmask先将SIGALRM设置为信号屏蔽字,则就算调用alarm后内核转而去执行其他进程而超时,但由于被屏蔽,所以无法执行信号处理程序。而sigsuspend将解除信号屏蔽和挂起等待合为一个原子操作,则不可能存在解除信号后又去执行其他进程,而原来的进程一直处于等待的情况。所以能够确保解除信号后执行信号处理程序,然后一定会返回,不会使调用进程一直处于阻塞状态。sigprocmask和sigsuspend一起使用就解决了原来的sleep的pause后可能一直阻塞的问题。
- apue-第十章 信号 笔记
- 《APUE》笔记-第十章-信号
- apue学习笔记(第十章 信号)
- [APUE]第十章 信号
- APUE(第十章)信号
- APUE第十章 信号
- APUE读书笔记-第十章 信号
- APUE读书笔记-第十章-信号
- APUE学习笔记——第十章 信号
- APUE读书笔记-第十章 信号 (二)
- APUE第十章学习笔记
- apue学习第十六天——信号(第十章)
- 《APUE》读书笔记—第十章信号(上)
- APUE 信号学习笔记
- APUE函数笔记八: 信号
- apue第10章 信号
- APUE 第10章 信号
- APUE学习笔记(17)-线程和信号
- 图的遍历(dfs、bfs、最短路、最小生成树、拓扑排序)
- js实现window 弹窗小案例
- 【bzoj 1616】[Usaco2008 Mar]Cow Travelling游荡的奶牛(dfs|dp)
- Android四大组件之Service学习
- CodeForces 733D Kostya the Sculptor
- 《APUE》笔记-第十章-信号
- 连续输入多个字符出现的问题解决方法
- 1016.11.06 连续第四天总结
- Ubuntu下开启root登录
- SLES 11 SP2 安装 Nagios
- *20161106*关于Linux的那点事
- 位运算与小数的二进制表示
- Maven介绍,包括作用、核心概念、用法、常用命令、扩展及配置
- 位运算