全局变量异步 I/O
分析如下父子进程交替 数数 程序。当捕捉函数里面的 sleep 取消,程序即会出现问题。请分析原因。
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>intn=0,flag=0; void sys_err(char* str)
{ perror(str); exit(1);
}
void do_sig_child (int num)
{printf("I am child %d\t%d\n",getpid(),n);n+=2;flag=1; sleep(1);
}
void do_sig_parent (int num)
{printf("I am parent%d\t%d\n",getpid(),n);n+=2;flag=1;sleep(1);
}
int main(void) {pid_tpid;
struct sigaction act;
if((pid=fork())<0)sys_err("fork");else if(pid>0){n=1;sleep(1);act.sa_handler = do_sig_parent; sigemptyset(&act.sa_mask); act.sa_flags=0;sigaction(SIGUSR2,&act,NULL); //注册自己的信号捕捉函数 父使用 SIGUSR2 信号 do_sig_parent(0); while(1){ /*waitforsignal*/;if(flag==1){ //父进程数数完成 kill(pid,SIGUSR1);flag=0; //标志已经给子进程发送完信号 } } }else if(pid==0){ n=2;act.sa_handler = do_sig_child; sigemptyset(&act.sa_mask); act.sa_flags=0; sigaction(SIGUSR1,&act,NULL);while(1){ /*waitingforasignal*/;if (flag==1) {kill(getppid(),SIGUSR2); flag=0; } }}return0;
}
示例中,通过 flag 变量标记程序实行进度。flag 置 1 表示数数完成。flag 置 0 表示给对方发送信号完成。 问题出现的位置,在父子进程 kill 函数之后需要紧接着调用 flag,将其置 0,标记信号已经发送。但,在这期 间很有可能被 kernel 调度,失去执行权利,而对方获取了执行时间,通过发送信号回调捕捉函数,从而修改了全局 的 flag。
如何解决该问题呢?可以使用后续课程讲到的“锁”机制。当操作全局变量的时候,通过加锁、解锁来解决该 问题。
现阶段,我们在编程期间如若使用全局变量,应在主观上注意全局变量的异步 IO 可能造成的问题。
有两个进程对同一个变量进行操作。
可/不可重入函数
一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称之为“重入”。根据函数实现的方 法可分为“可重入函数”和“不可重入函数”两种
例如:
显然,insert 函数是不可重入函数,重入调用,会导致意外结果呈现。究其原因,是该函数内部实现使用了全 局变量。
注意事项
- 定义可重入函数,函数内不能含有全局变量及 static 变量,不能使用 malloc、free
- 信号捕捉函数应设计为可重入函数
- 信号处理程序可以调用的可重入函数可参阅 man7signal
- 没有包含在上述列表中的函数大多是不可重入的,其原因为:
a) 使用静态数据结构
b) 调用了 malloc 或 free
c) 是标准 I/O 函数