可重入函数
一个函数被多个执行流调用,有可能在第一次调用还没返回时就再次进入函数,称为重入。
例如上面的insert函数,在执行第一步时收到信号产生硬件中断,转去信号处理函数,而信号处理函恰好调用了insert函数,由于两个函数操纵了同一个链表而产生意外的结果,所以这个函数是不可重入函数。如果这个函数只是操纵自己的局部变量就是可重入的。
另外调用IO库,或者使用malloc的函数也是不可重入的。
再看volatile
学c语言的时候知道volatile可以禁止编译器优化,始终从内存里拿变量的数据,在信号里它又有什么作用?
int flag = 0;
void handler()
{
flag = 1;
}
int main()
{
signal(SIGALRM, handler);
alarm(3);
while(1){
if(flag){
break;
}
printf(".\n");
sleep(1);
}
}
按理说睡三秒后,应该停止打点。但是如果加入优化,编译器在main控制流里看不到flag的改变,可能会从寄存器里拿flag,而捕捉信号后即使改变了flag,系统也不知道。
如果用volatile修饰flag,就能消除这种影响。
竟态条件
上一篇博文写了一个sleep的实现,但是有隐患,我是在单执行流环境下去写的代码,但若是放在多执行流环境呢?假如alarm刚设定了一个闹钟,还没来得及挂起进程就被中断了,等到回来时闹钟信号已经响了并且已经递达,这时再去挂起进程不就永久的睡下去了吗。
//设定闹钟
alarm(s);
pause();
这其实就要求着我们以新的视角去审视代码,这样的与代码时序紧密相关的错误称为竟态条件。
如果在挂起进程之前屏蔽ALRM信号能不能解决问题呢?
//屏蔽ALRM
alarm(s);
//解除屏蔽ALRM
pause();
看样子在pause之前不会递达ALRM信号了。其实解除屏蔽这一步和pause还是有竟态条件的问题,万一在解除屏蔽后被中断了。
真正的解决办法是把解除信号屏蔽和挂起设为原子操作!即使cpu中断也不能分离他俩。
int sigsuspend(const sigset_t *sigmask)
sigsuspend就是这么一个函数
sigset_t newmask, oldmask, suspmask;
//注册ALRM信号
//屏蔽ALRM信号
alarm(s);
//解除屏蔽并挂起
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);//确保ALRM没有被屏蔽
sigsuspend(&suspmask);//递达任意信号后唤醒
SIGCHLD信号
如果父进程在子进程之前终止,那么子进程变为孤儿进程,因此父进程需要wait来回收子进程,但是wait以后父进程阻塞,不能干别的事情,有没有办法让父进程干自己的事,子进程退出时自动回收?
有,子进程在退出时会给父进程发SIGCHLD信号,利用这个信号可以让父进程捕捉处理。
void handler(int signo)
{
pid_t id;
//有很多子进程同时发了SIGCHLD
while((id = waitpid(-1, NULL, WNOHANG)) > 0){
printf("wait child success %d", id);
}
}
int main()
{
//父进程都注册了SIGCHLD的处理函数
signal(SIGCHLD, handler);
pid_t cid;
if((cid=fork()) == 0){
printf("child");
sleep(3);
exit(1);
}
while(1){
printf("father");
sleep(1);
}
}
waitpid:
- 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;