- 1. Linux环境高级编程
- 2. 第五讲 信号
- 3. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 4. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 5. 信号的概念基本概念
信号的产生
信号的处理方式
常见信号
信号与文字说明的转换5
- 6. 信号的概念当某个程序长时间运行,如何中断它的执行,让其退出?
示例5.8
Ctrl+c中断程序执行
是如何中断程序执行的呢?6
- 7. 信号的概念信号是软件中断。
例如,Ctrl + C,将产生SIGINT信号,中断程序执行
它可以作为进程间通信的一种方式,但更主要的是,信号总是中断一个进程的正常运行,它更多地被用于处理一些非正常情况
每个信号都有一个名字,以SIG开头。
SIGABRT:进程异常终止信号,abort产生
SIGALRM:闹钟信号,计时器超时后产生
Linux 2.4.22共有31种不同的信号
查看头文件/usr/include/bits/signum.h7
- 8. 信号的产生很多条件可以产生一个信号
当用户按某些终端键时,产生信号。例如在终端上按Ctrl+C键通常产生中断信号(SIGINT)。
硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。
进程用Kill函数可将信号发生给另一个进程或进程组;
用户用Kill命令将信号(终止信号SIGTERM)发送给其他进程
当检测到某种软件条件已经发生,并将其通知有关进程时产生信号。如SIGPIPE、SIGALRM
等等8
- 9. 信号的概念基本概念
信号的产生
信号的处理方式
常见信号
信号与文字说明的转换9
- 10. 进程对信号的处理方式进程对信号的处理方式可以有三种:
忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因时:它们向超级用户提供了一种使进程终止或停止的可靠方法;
捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。
执行系统默认动作。对大多数信号的系统默认动作是终止该进程。10
- 11. 常见的信号SIGABRT 处理异常信号。当前进程调用abort后发送该信号;
SIGALRM 报时时钟。当进程设置的时钟超时后,内核向进程发送一个时钟信号;
SIGCHLD 子进程被终止或停止。每当子进程被终止或停止时,内核都会给父进程发送一个信号SIGCHLD,该信号表示的是一个子进程的消亡。
SIGHUP 挂起信号。当终端连接断开时,内核向所有依附在此控制终端上的进程发送此信号;
SIGINT 中断信号。当用户按下中断键时,从内核向所有与终端会话有联系的进程发出信号。11
- 12. 常见的信号(续)SIGPIPE 写无接收方的管道或套接字信号。管道和套接字都是一种进程间通讯机制;
SIGQUIT 退出信号。与SIGINT信号非常类似,当用户在终端上敲下退出键时,内核会发出这个信号。与SIGINT不同点在于,该信号会导致进程的异常终止。
SIGTTIN/SIGTTOU 后台进程读/写信号。每当一个后台进程试图从控制终端读入/写数据时就会产生此信号,此信号的默认动作是终止进程的运行。
SIGURG 高带宽数据通知信号。该信号通知进程,网络连接中出现了紧急情况或者带外数据;
SIGUSR1和SIGUSR2 用户自定义信号。12
- 13. 信号的概念基本概念
信号的产生
信号的处理方式
常见信号
信号与文字说明的转换13
- 14. 信号值与文字说明的转换Linux提供了一个数组
extern char* sys_siglist[];
数组的下标是信号的编号,给出的字符串指针指向相应信号的说明文字
示例5.9
注意,有效的信号编号是从1到3114
- 15. psignal函数该函数类似于perror,根据信号值,输出相关的信息
函数原型
#include
void psignal(int signo, const char* msg);
该函数将字符串msg输出,后面接一个冒号和一个空格,再接着对该信号的说明,最后一个换行符
示例(5.10)15
- 16. strsignal函数该函数类似于strerror函数,将信号值翻译成可读的字符串
函数原型
#include
char* strsignal(int signo);
参数:signo,即信号编号
返回值:signo对应的说明文字
在Solaris 9中,若信号编号无效,则返回NULL,在FreeBSD、Linux中,则返回字符串,说明信号不可识别
示例(5.11)16
- 17. strsignal函数strsignal函数返回了一个字符串指针
该指针指向的内存区域需要释放吗?
该指针指向的内存区域可以被修改吗?
若能修改,修改后,再调用strsignal函数会有什么结果?
示例(5.12)17
- 18. strsignal函数为什么不需要释放内存?
可以修改strsignal返回的指针,所对应的内存区域
但是,再次调用strsignal后,并未修改成功
为什么?18
- 19. strsignal函数查看源文件strsignal.c
实际上,对于有效的信号编号,若修改对应的内存区域,将段错误
而对于实时信号和未知信号,则可以随意修改;
但每次调用strsignal后,修改的内容都不存在,原因即strsignal内部通过重新分配buffer,填充信号说明导致19
- 20. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 21. signal函数用于设置信号的处理方法
函数原型
void (*signal(int signo, void (*func)(int)))(int);
返回类型与参数
返回类型:void (*)(int)
第一个参数signo:信号名
第二个参数func:类型void (*)(int)
21
- 22. signal函数说明:
typedef void (*Sigfunc)(int);
Sigfunc *signal(int, Sigfunc *);
第二个参数func
SIG_IGN:内核忽略该信号(SIGKILL、SIGSTOP不能忽略)
SIG_DFL:接收到信号signo后,按照系统默认动作处理
用户自己提供的信号处理函数。当收到信号时,进程立即转移去执行信号处理函数
返回值:
成功,返回信号以前的处理方式
出错,返回SIG_ERR
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)()) 0
#define SIG_IGN (void (*)()) 1 22
- 23. signal函数程序演示(5.1后台启动)
$kill -USR1 进程ID
$kill -s 信号编号 进程ID 23
- 24. signal函数当一个进程调用fork时,其子进程继承父进程的信号处理方式(处理函数地址在子进程有意义?)
当执行一个程序时,所有信号的状态通常都是系统默认(除非调用exec的进程忽略某信号)
exec函数将原先设置为要捕捉的信号,改为它们的默认动作(为什么?),其他信号的状态则不变24
- 25. signal函数例子(5.8)
命令行中:cc main.c &
输入Ctrl+C,产生中断信号,为什么后台进程不退出?
Shell将后台进程对中断和退出信号的处理方式设置为忽略25
- 26. signal函数程序演示(5.2)(两个终端)
父进程忽略信号
父进程捕获信号26
- 27. signal的封装示例5.23
缺点:
CSignalDispatcher权责不清
需要显示调用signal,未封装SIGKILL和SIGSTOP的特殊性
需要显示删除具体的信号处理对象27
- 28. signal函数获取当前信号处理方式
SigFunc *sig_int;
sig_int = signal(SIGINT, SIG_IGN);
signal(SIGINT, sig_int);
必须两次调用signal(两次改变信号处理方式),才能获取当前信号处理方式
当改变信号处理方式之间,有信号达到,该如何?
28
- 29. 问题?signal函数存在的问题-不可靠在早期的UNIX系统中(如SVR4中),进程每次处理信号时,随即将信号动作复位为默认动作。因此需要重复安装信号函数,例如:
void sig_int( int signo );
…
signal( SIGINT, sig_int );
…
void sig_int( int signo )
{
signal( SIGINT, sig_int );
…
}29
- 30. signal函数存在的问题(续)上述解决方法存在的问题是:
在信号发生之后到信号处理程序中调用signal函数之间有一个时间窗口。在此期间,可能发生另一次信号,而第二个信号有可能造成执行默认动作。(信号丢失,即不可靠)
进程所能做的仅仅是捕捉或忽略信号,而不能让内核阻塞信号
不要忽略信号
但是在发生信号时,记住它,在进程做好准备的时候传递它30
- 31. 第二个问题的典型例子int sig_int_flag;
int main(void)
{
void sig_int();
...
signal(SIGINT,sig_int);
...
while(sig_int_flag == 0)
pause();
...
}
void sig_int()
{
signal(SIGINT,sig_int);
sig_int_flag = 1;
}问题:有可能在while检测变量之后,而在pause函数之前发生SIGINT信号,而且信号只发生此一次,由此,该程序将一直睡眠。
这种情况导致程序在大多数时间能正确运行。假设在此处发生信号?31
- 32. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 33. 不可靠信号与可靠信号不可靠信号:建立在早期机制上的信号。这些信号的值从1到31,已有预定义含义,如SIGINT
进程每次处理信号后,系统就将对信号的响应设置为默认动作(需两次调用signal)linux已解决
信号可能丢失
不可靠信号不支持排队(程序演示5.3,同一种信号,test后台启动)
在信号处理函数执行过程中,到来的所有相同信号,都被合并为一个信号33
- 34. 不可靠信号与可靠信号可靠信号:后期引入的,其信号值从34到64
可靠信号支持排队,不会丢失(程序演示5.4,同一种信号,test后台启动)
可靠信号也被称为实时信号
不可靠信号被称为非实时信号
信号:软中断(中断程序,执行信号处理程序)
可靠信号与不可靠信号:在信号处理程序中,收到与正处理信号不同的信号时,会中断当前信号的处理,去执行新到信号的处理函数(示例5.13)34
- 35. 示例5.13的启示当正在处理可靠信号A时,若收到另一可靠信号B,则中断信号A的执行,转去执行信号B的处理;
若此时又收到信号B,则该信号在B队列排队;若此时又收到信号A,则该信号在A队列排队
若信号B处理完毕,则检查B队列中有无信号尚需处理,处理完毕后,再返回中断点执行35
- 36. 可靠信号与不可靠信号早期的信号安装和发送机制
signal
kill
改进后的信号安装和发送机制
sigaction
sigqueue
信号可靠与否,只与信号值有关,与采用何种信号安装、发送函数无关36
- 37. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 38. 信号的发送接收机制kill函数、raise函数
alarm函数、pause函数
sleep函数及实现
具有超时功能的API38
- 39. 信号的发送接收机制kill函数、raise函数
alarm函数、pause函数
sleep函数及实现
具有超时功能的API39
- 40. kill函数该函数将信号发送给其他进程或者进程组
函数原型
#include
int kill(pid_t pid, int signo);
参数与返回值
signo:需要发送的信号编号
pid:发送的目的地
成功返回0,出错返回-140
- 41. kill函数pid的取值
pid > 0:将信号发送给进程ID为pid的进程
pid == 0:将信号发送给起进程组ID等于发送进程的进程组ID,而且发送进程有许可权向其发送信号的所有进程
pid < 0 :将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送信号的所有进程
pid == -1 将信号发送给除进程0以外的所有进程,但发送进程必须有许可权41
- 42. raise函数该函数将信号发送给自己
函数原型
#include
int raise(int signo);
参数与返回值
signo:需要发送的信号编号
成功返回0,出错返回-142
- 43. 发送信号的权限有发送信号许可权的基本规则是:1)超级用户可以将信号发送给任意进程;2)发送者的实际或有效用户ID,必须等于接收者实际或有效用户ID;
当signo为0时,则kill仍执行正常的错误检测,但不发送信号。这常被用来确定一个特定进程是否存在。如果向一个并不存在的进程发送空信号,则kill返回-1,errno则被设置为ESRCH。43
- 44. 信号的发送接收机制kill函数、raise函数
alarm函数、pause函数
sleep函数及实现
具有超时功能的API44
- 45. alarm函数该函数用于设置一个计时器,在将来某个指定的时间,该计时器将超时,产生SIGALRM信号
函数原型
#include
unsigned int alarm(unsigned int seconds);
参数与返回值
seconds:经过seconds秒后,产生SIGALRM
返回0或以前设置的计时器时间的余留秒数45
- 46. alarm函数注意:每个进程只有一个计时器
若在调用alarm时,以前已为该进程设置的计时器并未超时,则将该计时器的余留值作为本次alarm调用的值返回,以前登记的计时器,则被新计时器替代
取消定时器
若有以前为进城登记的尚未超时的计时器,而且本次调用seconds等于0,则取消计时器46
- 47. alarm函数是先调用alarm函数,再调用signal函数设置SIGALRM处理函数?
还是先调用signal函数设置SIGALRM处理函数,再调用alarm函数?47
- 48. alarm函数使用示例(5.14)48
- 49. pause函数该函数使调用进程挂起,直到捕捉到一个信号
函数原型
#include
int pause();
返回值
只有执行了一个信号处理程序并从其返回时,pause才返回
返回-1,errno被设置为EINTR49
- 50. 信号的发送接收机制kill函数、raise函数
alarm函数、pause函数
sleep函数及实现
具有超时功能的API50
- 51. sleep函数该函数用于让调用进程挂起,直到
已经过了指定的时间,或者
调用进程捕捉到一个信号,并从信号处理程序返回
函数原型
#include
unsigned int sleep(unsigned int seconds);51
- 52. sleep函数返回值:
若已经过了指定的时间,则返回0
若调用进程捕捉到一个信号,并从信号处理程序返回,则sleep提前返回,返回值是未睡够的秒数52
- 53. sleep函数的实现方式一:
Solaris 9使用alarm实现sleep函数
方式二:
FreeBSD、Linux使用nanosleep提供时间延迟53
- 54. sleep的实现方式一static void sig_alrm(int signo)
{
return; //nothing to do, just return to wake up the pause
}
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
}There is a race condition54
- 55. sleep1函数上述实现存在如下问题:
如果调用者此前曾设置了计时器,则它被sleep1函数中的第一次alarm调用擦去。(考虑解决方法)
该程序中修改了对SIGALRM的配置。如果编写一个函数供其他函数调用,则在该函数调用时要保存原配置,并在返回前恢复原配置;
在调用alarm和pause之间有一个竞争条件。(思考在什么情况下会发生?结果如何?)55
- 56. sleep1函数对于问题1
检查第一次调用alarm的返回值,若其小于本次调用alarm的参数值,则只应等到上次设置的计时器超时;
若上次设置的计时器超时时间晚于本次设置值,则在sleep1函数返回前,复位此计时器,使其在上次计时器的设定时间再次发生超时
56
- 57. sleep1函数对于问题2
保存signal函数的返回值,在返回前复位原配置
对于问题3
可以使用setjmp,或者
sigprocmask、sigsuspend
sleep的早期实现,更正了问题1和问题257
- 58. sleep2函数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 */
}58
- 59. sleep2函数这种实现解决了竞争条件,
即使pause从未执行,在发生SIGALRM时,sleep2也会返回
但却带来另一个难于察觉的问题:
它涉及到与其他信号的相互作用。如果SIGALRM中断了某个其他信号处理程序,则调用longjmp会提早终止该信号处理程序
下页PPT的例子59
- 60. unsigned int sleep2(unsigned int);
static void sig_int(int);
int main(void)
{
unsigned int unslept;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
unslept = sleep2(5);
printf("sleep2 returned: %u\n", unslept);
exit(0);
}static void sig_int(int signo)
{
int i;
volatile int j;
printf("\nsig_int starting\n");
for (i = 0; i < 2000000; i++)
j += i * i;
printf("sig_int finished\n");
return;
}Example use of sleep260
- 61. sleep2函数上述程序运行后,立即键入Ctrl+c
程序输出
Sig_int strarting
Sleep2 returned: 0
问题
SIGINT的处理并未结束61
- 62. sleep函数的实现方式一:
Solaris 9使用alarm实现sleep函数
方式二:
FreeBSD、Linux使用nanosleep提供时间延迟62
- 63. sleep的实现方式二Linux中并未提供系统调用sleep,sleep是在库函数中实现的
nanosleep则是Linux的系统调用,提供高分辨率的时间延迟,该函数可以使sleep的实现与信号无关63
- 64. nanosleep函数该函数挂起调用者线程,直到超时或者接收到信号
函数原型
#include
int nanosleep(const struct timespec* rqtp
struct timespec* rmtp);64
- 65. nanosleep函数timespec结构体
struct timespec {
time_t tv_sec; //秒数
long tv_nsec; //纳秒数
};65
- 66. nanosleep函数int nanosleep(const struct timespec* rqtp
struct timespec* rmtp);
参数与返回值
rqtp:指定需要等待的时间,秒数和纳秒数
rmtp:当nanosleep返回时,在rmtp指定的结构体中存储距离超时的时间;若不关心该时间,可设为NULL
返回值:超时返回0,被信号中断,则返回-1
示例5.15
66
- 67. sleep函数实现的问题使用alarm实现的sleep函数,与alarm函数之间存在交互的可能
例如:首先调用alarm(10)
过了3秒,又调用了sleep(5)
Sleep将在5秒后返回,但是否在2秒后又产生一个SIGALRM信号呢?
POSIX.1标准未对这些交互做任何说明,因此,考虑到移植性,不应对sleep的实现做任何假设67
- 68. 信号的发送接收机制kill函数、raise函数
alarm函数、pause函数
sleep函数及实现
具有超时功能的API68
- 69. 中断的系统调用早期的UNIX系统的一个特性是
如果进程在执行一个低速的系统调用(究竟什么是系统调用?与一般函数调用有何区别?)期间,捕捉到了一个信号,
则该系统调用就被中断不再继续执行
该系统调用返回出错,errno被设置为EINTR
这样做的理由:进程可能被这些低速系统调用永远阻塞,例如读网络
69
- 70. 低速系统调用常见的低速系统调用包括
读写(read、write)管道、终端设备、网络
打开(open)终端设备等
pause、wait函数
某些ioctl操作
某些进程间通信函数
注意:磁盘I/O并不属于这类低速系统调用。因为只要不是硬件错误,I/O操作总会返回。70
- 71. 被中断的系统调用的处理被中断的系统调用,必须显式地处理出错返回,典型的代码:
Again:
if((n=read(fd, buf, BUFFSIZE)) < 0) {
if(errno == EINTR)
goto Again;
……………….
}71
- 72. 中断的系统调用4.2BSD引入了被中断的系统调用的自动重启动
不同的系统对自动重启动设置不一
示例5.16
函数系统是否自动重启?signalsolaris绝不4.2BSD总是Linux默认sigactionPOSIX.1未说明Linux可选72
- 73. 带超时功能的read函数73
- 74. 带超时功能的read函数上述程序从标准输入读一行,然后将其写到标准输出上
存在的两个问题
第一次alarm调用和read调用之间存在竞争关系,即在调用read前,alarm超时已经发生
在Linux中,通过signal注册的信号处理机制,被信号中断的系统调用是可以自动重启动的。这种情况下,SIGALRM信号对read的时间限制无作用74
- 75. 带超时功能的read函数75
- 76. 带超时功能的read函数上述方案存在的问题
它涉及到与其他信号的相互作用。如果SIGALRM中断了某个其他信号处理程序,则调用longjmp会提早终止该信号处理程序76
- 77. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 78. 信号集和可靠信号机制基本概念
信号集及基本操作
sigprocmask函数,sigpending函数
sigaction函数
sigsuspend函数
可重入函数和线程安全的函数78
- 79. 可靠信号信号未决(pending):在信号产生和递送之间的时间间隔;
进程可以选用“信号递送阻塞”。如果为进程产生了一个选择为阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则该信号保持未决状态,直到下述条件之一发生:
对此信号解除了阻塞;
或者将对此信号的动作更改为忽略。
当递送一个原来被阻塞的信号给进程时,才决定其处理方式。即进程在信号递送之前仍可改变对它的动作。
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。79
- 80. 信号集信号集:能表示多个信号的数据类型,即信号的集合
例如:信号屏蔽字使用的就是信号集
信号集的类型即 sigset_t
在signal.h中,
typedef __sigset_t sigset_t;80
- 81. 信号集在bits/sigset.h中
#define _SIGSET_NWORDS \
(1024 / (8 * sizeof(unsigned long int)))
typedef struct {
unsigned long int __val[_SIGSET_NWORDS];
}__sigset_t;
sigset_t 是32个4字节81
- 82. 信号集的处理函数与信号集相关的函数有:
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,出错返回-182
- 83. sigemptyset函数int sigemptyset( sigset_t *set );
该函数初始化由set指向的信号集,并清除其中的所有信号
实现(bits/sigset.h)中的关键代码片断
#define __sigemptyset(set) \
int _cnt = _SIGSET_NWORDS; \
sigset_t *_set = (set); \
while (--_cnt >= 0) _set->__val[_cnt] = 0;83
- 84. sigfillset函数int sigfillset( sigset_t *set );
该函数初始化由set指向的信号集,使其包括所有信号
实现(bits/sigset.h)中的关键代码片断
#define __sigfillset(set) \
int _cnt = _SIGSET_NWORDS; \
sigset_t *_set = (set); \
while (--_cnt >= 0) _set->__val[_cnt] = ~0UL;
84
- 85. sigaddset函数int sigaddset( sigset_t *set, int signo);
该函数将一个信号添加到set中
查看bits/sigset.h中的108到120行
实现中的关键代码翻译
int __sigaddset(__sigset_t *__set, int __sig){
unsigned int __mask = 1 << ((__sig-1)%32);
unsigned int __word = (__sig-1)/32;
__set->__val[__word] |= __mask;
…………………..
}
85
- 86. sigaddset函数在sigset_t结构体中,第一个32比特,对应信号1到31,第二个32比特,对应可靠信号34到64
这一点与教材描述的结构不同
示例5.17
在gdb中,使用“x /12tb &st”查看地址空间。86
- 87. sigdelset函数int sigdelset( sigset_t *set, int signo);
该函数从set中删除信号signo
查看bits/sigset.h中的108到120行
实现中的关键代码翻译
int __sigdelset(__sigset_t *__set, int __sig){
unsigned int __mask = 1 << ((__sig-1)%32);
unsigned int __word = (__sig-1)/32;
__set->__val[__word] &= ~__mask;
…………………..
}
87
- 88. sigismember函数int sigismember(const sigset_t *set, int signo);
该函数判断信号signo是否在set中
查看bits/sigset.h中的108到120行
实现中的关键代码翻译
int __sigismember(__const __sigset_t *__set, int __sig){
unsigned int __mask = 1 << ((__sig-1)%32);
unsigned int __word = (__sig-1)/32;
(__set->__val[__word] & __mask) ? 1 : 0,
…………………..
}
88
- 89. 信号集和可靠信号机制基本概念
信号集及基本操作
sigprocmask函数,sigpending函数
sigaction函数
sigsuspend函数
可重入函数和线程安全的函数89
- 90. 信号阻塞当程序执行敏感任务时(比如更新数据库),不希望外界信号中断程序的运行。在这个阶段并不是简单地忽略信号,而是阻塞这些信号,当进程处理完关键任务后,还会处理这些信号。
sigprocmask函数可以检测或更改进程的信号屏蔽字90
- 91. sigprocmask函数该函数检测或更改进程需要阻塞的信号集合,或者同时执行这两个操作
函数原型
#include
int sigprocmask(int how, const sigset_t *set,
sigset_t *oset);
返回值
成功返回0
出错返回-191
- 92. sigprocmask函数参数
oset:进程的当前信号屏蔽字通过oset返回,若不关心当前信号屏蔽字,可以设为NULL
若set不为NULL,则
若how=SIG_BLOCK,则进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的并集,set包含了希望阻塞的附加信号
若how=SIG_UNBLOCK,则进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集补集的交集,set包含了希望解除阻塞的信号
若how=SIG_SETMASK,则进程新的信号屏蔽字将被set指向的信号集的值代替92
- 93. sigprocmask函数参数
若set为NULL,how也无意义
调用sigprocmask后,如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。
该函数仅为单线程的进程定义的,对于多线程的进程,有另一套函数
示例5.1893
- 94. sigpending函数该函数返回一个信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的
函数原型
#include
int sigpending ( sigset_t *set );
参数与返回值
set:sigpending函数填充该结构体,返回未决信号
成功返回0,出错返回-1
程序演示(5.5)94
- 95. sigprocmask if (sigismember(&pendmask, SIGQUIT))
{
cout << "SIGQUIT pending" << endl;
}
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
cout << "SIG_SETMASK error" << endl;
return 0;
}
cout << "SIGQUIT unblocked" << endl;
pause();能否使用:
SIG_UNBLOCK95
- 96. 信号集和可靠信号机制基本概念
信号集及基本操作
sigprocmask函数,sigpending函数
sigaction函数
sigsuspend函数
可重入函数和线程安全的函数96
- 97. sigaction函数该函数用于替换signal函数,即可以检查、修改信号的处理动作
函数原型
#include
int sigaction(int signo, const struct sigaction *act,
struct sigaction *oact);
返回值
成功返回0,出错返回-197
- 98. sigaction函数int sigaction(int signo, const struct sigaction *act,
struct sigaction *oact);
参数
signo:指定需要处理的特定信号。应该在该信号收到之前调用sigaction。除了SIGSTOP和SIGKILL之外,可以把signo设为任何类型的信号
若act非空,则要修改信号的处理动作
若oact非空,则系统经由oact指针返回该信号的上一个动作
act、oact可以为空98
- 99. sigaction结构体struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
sa_handler用于设置信号处理函数的地址,或SIG_IGN、SIG_DFL
sa_mask说明了一个信号集,在调用该信号处理函数之前,这一信号集要附加到进程的信号屏蔽字中;仅当从信号处理函数返回时,再将进程的信号屏蔽字复原
在信号处理函数被调用时,操作系统建立的新信号屏蔽字包括了正在被处理的信号。因此,保证了在处理一个给定信号时,同种信号会被阻塞(可靠信号与不可靠信号)99
- 100. 设置信号处理动作:sigactionsa_flags成员用来改变signo信号的行为属性。sa_flags的若干值可以用或运算组合。
比如把sa_flags设为SA_RESETHAND,使得在执行该信号处理函数时,信号处理函数重新设为SIG_DFL。
把sa_flags设为SA_RESTART,则如果该信号中断了系统调用,在处理完信号处理程序后,重新启动该系统调用。100
- 101. Linux中常用的sa_flags值SA_INTERRUPT(0x20000000)
由此信号中断的系统调用不会自动重启(示例5.19)
SA_NOCLDSTOP(1)
若signo是SIGCHLD,当子进程停止时,不产生此信号,但当其终止时,仍旧产生
SA_NOCLDWAIT(2)
若signo是SIGCHLD,则当调用进程的子进程终止时,不创建僵死进程;
若调用进程在后面调用了wait,则调用进程阻塞,直到其所有子进程都终止,此时返回-1,并将errno设置为ECHILD
(示例5.20)101
- 102. Linux中常用的sa_flags值SA_NODEFER(0x40000000)
在捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号
除非sa_mask包括了此信号
此种类型的操作对应于早期的不可靠信号
SA_RESETHAND(0x80000000)
在执行该信号处理函数时,信号处理函数重新设为SIG_DFL,并清除SA_SIGINFO标志102
- 103. Linux中常用的sa_flags值SA_RESTART(0x10000000)
由此信号中断的系统调用会自动重启动
SA_SIGINFO(4)
此选项对信号处理函数提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针103
- 104. sigaction结构体struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
sa_sigaction字段:用于设置一个替代的信号处理函数
若sa_flag指定了SA_SIGINFO标志,则使用由sa_sigaction字段指定的信号处理函数,而不是由sa_handler指定的信号处理函数
注意sa_sigaction和sa_handler可能使用同一个存储区域,因此只能使用其中之一104
- 105. 信号处理函数原来的信号处理函数原型:
void handler(int signo);
现在的信号处理函数原型:
void handler(int signo, siginfo_t *info,
void *context);
查看siginfo_t结构(程序5.6)
Context:可以转换为ucontext_t类型指针,用于标识信号传递时进程的上下文105
- 106. sigqueue函数用于向进程发送一个带参数的信号
函数原型:
int sigqueue(pid_t pid, int signo,
const union sigval val);
val:
typedef union sigval {
int sival_int; //该值可以传递给信号处理函数
void *sival_ptr;
}sigval_t106
- 107. 信号处理函数void handler(int signo, siginfo_t *info, void *context)
{
cout << info->si_value.sival_int << endl;
}
程序演示5.7107
- 108. 信号集和可靠信号机制基本概念
信号集及基本操作
sigprocmask函数,sigpending函数
sigaction函数
sigsuspend函数
可重入函数和线程安全的函数108
- 109. 保护一段代码使其不被某种信号中断信号首先被阻塞,若在此处被处理,pause可能永远睡眠109
- 110. sigsuspend函数针对上述问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠;该功能可由sigsuspend函数实现。
函数原型
#include
int sigsuspend(const sigset_t *sigmask);
将进程的信号屏蔽字设为sigmask指向的值,在捕捉到一个信号之前,该进程挂起;
从一个信号处理函数返回后,sigsuspend返回,并将信号屏蔽字恢复为调用sigsuspend之前的值;
函数总是返回-1,errno设置为EINTR110
- 111. sigsuspend函数该函数可以用于保护临界区代码,使其不被指定的信号中断。
该函数也可以用于等待一个信号处理程序设置一个全局变量111
- 112. sigsuspend保护临界区代码#include
#include
#include
static void sig_int(int);
int main(void)
{
sigset_t newmask, oldmask, zeromask;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");112
- 113. /* critical region of code */
pr_mask("in critical region: ");
/* allow all signals and pause */
if (sigsuspend(&zeromask) != -1)
err_sys("sigsuspend error");
pr_mask("after return from sigsuspend: ");
/* reset signal mask which unblocks SIGINT */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/* and continue processing ... */
exit(0);
}
static void sig_int(int signo)
{
pr_mask("\nin sig_int: ");
return;
}113
- 114. volatile sig_atomic_t quitflag; /* set nonzero by signal handler */
int main(void)
{
void sig_int(int);
sigset_t newmask, oldmask, zeromask;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if (signal(SIGQUIT, sig_int) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
pr_mask("Before Sigprocmask:");sigsuspend: wait for global variable114
- 115. /* block SIGQUIT and save current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
pr_mask("Before critical code:");
while (quitflag == 0)
sigsuspend(&zeromask);
pr_mask("After critical code:");
/* SIGQUIT has been caught and is now blocked; do whatever */
quitflag = 0;
/* reset signal mask which unblocks SIGQUIT */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
pr_mask("After Sigprocmask:");
exit(0);
}115
- 116. void sig_int(int signo)
{
if(signo == SIGINT)
printf(…..);
else
quitflag = 1;
}116
- 117. sleep的可靠实现(三)static void sig_alrm(void) {
return; /* nothing to do, just returning wakes up sigsuspend() */
}
unsigned int sleep(unsigned int nsecs) {
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;这个程序涉及到信号处理的方方面面,希望大家认真阅读。117
- 118. sigaction(SIGALRM, &newact, &oldact);
/* set our handler, save previous information */
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
/* block SIGALRM and save current signal mask */
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(nsecs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't blocked */
sigsuspend(&suspmask); /* wait for any signal to be caught */
/* some signal has been caught, SIGALRM is now blocked */118
- 119. unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL
/* reset signal mask, which unblocks SIGALRM */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return(unslept);
}119
- 120. 信号集和可靠信号机制基本概念
信号集及基本操作
sigprocmask函数,sigpending函数
sigaction函数
sigsuspend函数
可重入函数和线程安全的函数120
- 121. 可重入函数进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理函数临时中断
从信号处理函数返回后,则继续执行被中断的正常指令序列
假设进程正在调用malloc函数,信号将其中断,信号处理函数中也调用malloc函数,会有什么问题?121
- 122. 可重入函数可重入函数是指:
某个函数被信号中断执行,信号处理函数中又调用了该函数,而不会带来任何问题的函数
不可重入的原因
使用静态的数据结构
调用malloc或free
标准I/O函数,例如printf等122
- 123. 可重入函数每个线程只有一个errno。
当信号处理函数调用某个API后,errno可能被重新设置
这样,被信号中断的程序将看到被修改后的errno,而不是原来的值
所以,信号处理函数中,需要首先保存errno,在信号处理函数返回前,恢复errno123
- 124. 线程安全的函数对于同一个函数,若两个或两个以上的线程同时执行,均得到正确的结果,则称该函数是线程安全的函数
可重入的函数一定是线程安全的函数?
线程安全的函数,不一定是可重入的?
例如:互斥变量保护的临界区
例如malloc函数124
- 125. 信号集和可靠信号机制基本概念
信号集及基本操作
sigprocmask函数,sigpending函数
sigaction函数
sigsuspend函数
可重入函数和线程安全的函数125
- 126. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 127. sigsetjmp和siglongjmp函数P57在分析sleep2时,使用到了setjmp和longjmp
在信号处理中,当捕捉到信号并进入信号处理函数时,当前信号被自动加入到进程的信号屏蔽字中
当退出信号处理函数时,当前信号又会从进程信号屏蔽字中删除
当使用longjmp跳出信号处理函数时,信号屏蔽字会有何改动?(示例5.21)127
- 128. sigsetjmp和siglongjmp函数从示例5.21可知,longjmp并未恢复进程的信号屏蔽字
POSIX.1定义了sigsetjmp和siglongjmp,以解决上述问题。
调用sigsetjmp时,除了保存上下文外,还将保存当前的信号屏蔽字
调用siglongjmp执行跳转后,将恢复调用sigsetjmp时的信号屏蔽字128
- 129. sigsetjmp和siglongjmp函数函数原型
#include
int sigsetjmp(sigjmp_buf env, int savemask);
参数和返回值
若savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字
返回值:若直接调用则返回0,若从siglongjmp调用返回则返回siglongjmp的第二个参数129
- 130. sigsetjmp和siglongjmp函数函数原型
#include
void siglongjmp(sigjmp_buf env, int val);
参数
val作为sigsetjmp的返回值130
- 131. static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
int main(void)
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
pr_mask("starting main: "); /* {Prog prmask} 10.10*/
if (sigsetjmp(jmpbuf, 1)) {
pr_mask("ending main: ");
exit(0);
}
canjump = 1; /* now sigsetjmp() is OK */
for ( ; ; )
pause();
}131
- 132.
static void sig_usr1(int signo) {
time_t starttime;
if (canjump == 0)
return; /* unexpected signal, ignore */
pr_mask("starting sig_usr1: ");
alarm(3); /* SIGALRM in 3 seconds */
starttime = time(NULL);
for ( ; ; ) /* busy wait for 5 seconds */
if (time(NULL) > starttime + 5)
break;
pr_mask("finishing sig_usr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1); /* jump back to main, don't return */
……
}保护机制132
- 133. static void sig_alrm(int signo) {
pr_mask("in sig_alrm: ");
return;
}
133
- 134. 执行序列mainsignal()
signal()
pr_mask()
sigsetjmp()
pause()sig_usr1递送SIGUSR1pr_mask()
alarm()
time()
time()sig_alrmpr_mask()
return()递送SIGALRM从信号处理程序返回pr_mask()
siglongjmp()sigsetjmp()
pr_mask()
exit()134
- 135. If we take sigsetjmp with non-zero value for parameter savesigs135
- 136. If we take sigsetjmp with zero value for parameter savesigs136
- 137. 示例程序-几点注意该程序演示两种信号特性:
当执行信号处理程序时,自动阻塞该信号;
信号能嵌套发生。
sigsetjmp和siglongjmp的联合使用方式;
sigsetjmp和siglongjmp联合使用必须要有保护机制,即程序中的canjmp变量的判断;保护变量定义为sig_atomic_t是为了保证在写该变量时不会被中断。137
- 138. 示例程序示例程序中,信号有可能中断sigsetjmp的调用,即jmpbuf并未正确地保存现场;这样在信号处理函数中,调用siglongjmp将可能出错
针对这种情况,引入了canjump变量。只有当canjump变量被设为1后,信号处理函数才能调用siglongjmp138
- 139. 示例程序主程序中设置canjmp时,会被信号中断,信号处理函数读canjmp,而引起错误吗?
canjmp的类型是sig_atomic_t,在写这种类型的变量时,不会被中断
在具有虚拟存储器的系统上,这种变量不会垮页
可以用一条机器指令对其进行访问
为什么又使用volatile?139
- 140. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数
- 141. abort函数该函数可以使异常程序终止
函数原型
#include
void abort();
此函数将SIGABRT信号发送给调用进程
ISO C要求若捕捉到此信号而且相应信号处理程序返回,abort仍不会返回到其调用者(示例5.22)141
- 142. abort函数POSIX.1说明了abort并不理会进程对SIGABRT的阻塞和忽略
让进程捕捉该信号的意图
在进程终止前由其执行所需的清理操作
若信号处理函数不终止自己,abort终止该进程
142
- 143. 第五讲 信号信号的概念
signal函数
不可靠信号与可靠信号
信号的发送接收机制
信号集与可靠信号机制
sigsetjmp和siglongjmp函数
abort函数