第8章 信号(4)_中断的系统调用和函数可重入性
3.2 中断的系统调用
(1)进程调用“慢”系统调用时,如果发生了信号,内核会重启系统调用(即“慢”系统调用被信号中断后,当信号处理完毕后,该系统调用会被重新从头开始执行,而不是从中断的地方继续执行!注意,这是重启的方式。除此之处,还有设置SA_RESTART和忽略信号等方式)
(2)慢系统调用
①可能会永远阻塞的系统调用
②从终端设备、管道或网络设备上的文件读取。
③向上述文件写入
④某些设备上的文件打开
⑤pause和wait系统调用
⑥一些设备的ioctl操作
⑦一些进程间的通信函数
(3)如何处理被中断的系统调用
①重启被中断的系统调用:如accept、read、write、select、wait、waitpid和open等。(是重启系统调用还是让系统调用失败?在早期的Unix是让系统调用失败,并返回-1,同时设置errno,如pause被中断唤醒而不是重启)
②安装信号时设置SA_RESTART属性(使用sigaction安装信号,该方法对有的系统调用无效)
③忽略信号,让系统不产生信号中断。
【编程实验】中断系统调用
//signal_syscall.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void sig_handler(int signo)
{
//ctrl-z信号
if(signo == SIGTSTP){
printf("SIGTSTP occured\n");
}
}
int main(void)
{
char buffer[512];
ssize_t size;
//注册信号处理函数
if(signal(SIGTSTP, sig_handler) == SIG_ERR){
perror("signal sigtstp error");
}
printf("begin running and waiting for signal\n");
//由于read会阻塞等待输入,如果此时先输入一些内容,然后按ctrl-z
//这里由于read被信号中断,会重启read函数,表现出来的是重新生成
//提示符等待用户输入,之前的输入全部清空。
size = read(STDIN_FILENO, buffer, sizeof(buffer)); //慢系统调用
if(size < 0){
perror("read error");
}
printf("reading finished\n");
if(write(STDOUT_FILENO, buffer, size) != size){ //慢系统调用
perror("write error");
}
printf("end running\n");
return 0;
}
/*输出结果:
[root@localhost]# bin/signal_syscall
begin running and waiting for signal
abcd //输完内容后,按回车正常结束一行的输入
reading finished
abcd
end running
[root@localhost]# bin/signal_syscall
begin running and waiting for signal
abcaksdfj ^ZSIGTSTP occured //输入一些内容后,按ctrl-z产生信号,中断read
mnk //这里会重启read,原内容被清空,重新生成提示符
reading finished
mnk
end running
*/
【编程实验】中断用户自定义的函数
//signal_usercall.c
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void sig_handler(int signo)
{
if(signo == SIGTSTP){
printf("SIGTSTP occured\n");
}
}
//当用户函数被信号中断后,信号处理完之后,会从中断的地方
//继续执行,而不会重启整个函数。这是与慢系统调用不同的。
void call_fun(void)
{
printf("begin running call_fun\n");
sleep(5); //演示在这期间产生信号中断现象
printf("end running call_fun\n");
}
int main(void)
{
if(signal(SIGTSTP, sig_handler) == SIG_ERR){
perror("signal sigtstp error");
}
printf("begin running main\n");
call_fun();
printf("end running main\n");
return 0;
}
/*
[root@localhost]# bin/signal_usercall
begin running main //正常流程
begin running call_fun
end running call_fun
end running main
[root@localhost]# bin/signal_usercall
begin running main //在call_fun执行过程中按ctrl-z中断
begin running call_fun
^ZSIGTSTP occured //发生信号,当信号处理完毕后,从中
end running call_fun //断的地方开始继续执行,而不是重启整个函数
end running main
*/
3.3 函数的可重入性
(1)在调用某个函数过程中出现信号,且该信号处理函数中再次调用该函数,就可能产生可重放入性问题。
(2)访问全局或静态变量的函数是不可重入函数
(3)程序片断
//可重入函数
int double(int a){
return a * 2;
}
//不可重入函数
void foo(){
static int array[28]={0};
static int index = 0;
if(index > 19) return;
array[index] = 9;
index++;
}
【编程实验】函数的可重入性
//signal_reentry.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int g_v[10]; //全局数组
int* h_v; //堆数组
void set(int val)
{
int a_v[10]; //局部数组
int i = 0;
for(; i<10; i++){
a_v[i] = val;
g_v[i] = val;
h_v[i] = val;
sleep(1);
}
printf("g_v:");
for(i=0; i<10; i++){
if(i != 0)
printf(",%d", g_v[i]);
else
printf("%d", g_v[i]);
}
printf("\n");
printf("h_v:");
for(i=0; i<10; i++){
if(i != 0)
printf(",%d", h_v[i]);
else
printf("%d", h_v[i]);
}
printf("\n");
printf("a_v:");
for(i=0; i<10; i++){
if(i != 0)
printf(",%d", a_v[i]);
else
printf("%d", a_v[i]);
}
printf("\n");
}
//信号处理函数
void sig_handler(int signo)
{
if(signo == SIGTSTP){
printf("SIGTSTP occured\n");
set(20); //信号处理函数内部再次调用set,以验证函数的
//可重入性。注意,传入20
printf("end SIGTSTP\n");
}
}
int main(void)
{
if(signal(SIGTSTP, sig_handler) == SIG_ERR){
perror("signal sigtstp error");
}
h_v = (int*)calloc(10, sizeof(int));
printf("begin running main\n");
set(10); //传入10
printf("end running main\n");
free(h_v);
}
/*
[root@localhost]# bin/signal_reentry
begin running main //正常执行,不会触发信号中断
g_v:10,10,10,10,10,10,10,10,10,10
h_v:10,10,10,10,10,10,10,10,10,10
a_v:10,10,10,10,10,10,10,10,10,10
end running main
[root@localhost]# bin/signal_reentry
begin running main //运行2-3秒后,按ctrl-z触发信号中断
^ZSIGTSTP occured //在信号处理函数中调用set赋值
g_v:20,20,20,20,20,20,20,20,20,20
h_v:20,20,20,20,20,20,20,20,20,20
a_v:20,20,20,20,20,20,20,20,20,20
end SIGTSTP //信号处理完,由原中断处继续执行,这里从第4个元素继续赋值(10)
g_v:20,20,20,10,10,10,10,10,10,10
h_v:20,20,20,10,10,10,10,10,10,10
a_v:10,10,10,10,10,10,10,10,10,10
end running main
*/
3.4 信号的特点
(1)信号的发生是随机的,但信号在何种条件下发生是可预测的(如按ctrl-z将发送SIGTSTP信号)。
(2)进程刚开始启动时所有信号的处理方式要么默认,要么忽略,忽略的是SIGUSR1和SIGUSR2两个信号,其它都采取默认的方式(大多数是终止进程)
(3)进程在调用exec函数后,原有信号的捕捉函数失效。
(4)子进程的诞生总是继承父进程的信号处理方式。
(5)在系统层面上,信号的发生是可靠的,在linux中的可靠性只保证一次,进程在处理信号期间若发生同类型的信号不会丢失(内核会保留),但会被延迟处理,但同类型信号的多次发生只会保留一次(即被处理一次)。若不同类型的信号发生也会被内核保留直接被处理,处理完后再处理原有信号。
(6)用户层面可靠性,依赖于信号而执行的用户代码放置在信号处理程序内部执行,否则不一定可靠。
(7)在信号发生时,慢系统调用可以被中断并在信号处理后系统调用会被重启。
(8)在信号发生时,用户函数可以被中断但不能被重启,沿着中断点继续执行(在用户函数中要保证数据一致性,即可重入性不要去访问全局变量和静态变量,堆中的变量若在用户函数内部分配没有关系,否则会出现不可重入性)

浙公网安备 33010602011771号