您的位置:首页 > 其它

volatile关键字,竟态条件

2016-05-05 15:50 176 查看
volatile:防止编译器性能优化,与移植性有关。
#include<stdio.h>
#include<signal.h>
int done=0;
void handle(int sig)
{
printf("get sig %d\n",sig);
done=1;
}
int main()
{
signal(SIGINT,handle);
while(!done);
}
Makefile:

my_volatile:my_volatile.c
gcc -o $@ $^ -O3
.PHONY:clean
clean:
rm -f my_volatile
在while循环中:没有写入修改done的值,编译器会在读取变量的时候,将其从内存拿出存入寄存器并比较,在下次读取时,直接从寄存器中取该变量的值。
而在信号处理函数中,有修改done的值,需写回内存。
可指定编译器优化级别
编译器优化级别:
gcc -o my_volatile my_volatile.c -O0//不优化

gcc -o my_volatile my_volatile.c -O1//默认

gcc -o my_volatile my_volatile.c -O2

gcc -o my_volatile my_volatile.c -O3//优化级别最高

此时会发生内存级别的不一致:寄存器:0,内存:1.
运行结果:



要改变此程序显示预期效果,只需定义done为:volatile int done=0;//这样每次使用done时都会从内存中取done的值。
sig_atomic_t:由C语言提供。
虽然C代码只有一行,但是在32位机上对一个64位的long long变量赋值需要两条指令完成,因此不是原子操作。同样地,读取这个变量到寄存器需要两个32位寄存器才放得下,也需要两条指令, 不是原子操作。
为了解决这些平台相关的问题,C标准定义了一个类型sig_atomic_t,在不同平台的C语言库中取不同的类型,例如在32位机 上定义sig_atomic_t为int类型。
竟态条件:
由于异步事件在任何时候都有可能发生(这里的异步事件指出现更优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。

#include<stdio.h>
#include<signal.h>
#include<string.h>
void handler(int sig)
{
//do nothing
}
int my_sleep(int time)
{
struct sigaction act;
act.sa_handler=handler;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
struct sigaction old;
memset(&old,'\0',sizeof(old));
sigaction(SIGALRM,&act,&old);//注册信号处理函数
alarm(time);//time秒后让系统发SIGALRM信号
pause();//内核切换到别的进程运行
int ret=alarm(0);
sigaction(SIGALRM,&old,NULL);//恢复默认信号处理动作
return ret;
}
int main()
{
while(1)
{
printf("I am sleep...\n");
my_sleep(5);
}
return 0;
}


虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之 后的nsecs秒之内被调用。

在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. 解除对SIGALRM信号的屏蔽;
4. pause();
从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。

要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend
函数的功 能。 sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对
时序要求严格的场合下都应该调用sigsuspend不是pause。

注:调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时
解除对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原
来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

#include<stdio.h>
#include<signal.h>
void handle(int sig)
{
//do nothing
}
int sleep(int time)
{
struct sigaction oldact,newact;
sigset_t newmask,oldmask,suspmask;
newact.sa_handler=handle;
newact.sa_flags=0;
sigemptyset(&newact.sa_mask);
sigaction(SIGALRM,&newact,&oldact);//注册SIGALRM的信号处理函数
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);//屏蔽SIGALRM信号;
alarm(time);
suspmask=oldmask;
sigdelset(&suspmask,SIGALRM);//解除suspmask中SIGALRM信号的屏蔽;
sigsuspend(&suspmask);//用suspmask去替换PCB中的block表,从而解除对SIGALRM信号的阻塞
int ret=alarm(0);
sigaction(SIGALRM,&oldact,NULL);
sigprocmask(SIG_SETMASK,&oldmask,NULL);//恢复之前的系统默认处理信号方式
return ret;
}
int main()
{
while(1)
{
printf("I am sleep\n");
sleep(5);
}
return 0;
}
子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
优点:没花费时间在等待上,直到收到信号(异步信号)
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void my_sigchld(int sig)
{
int status=0;
pid_t ret=waitpid(-1,&status,0);
if(ret>0)
{
printf("sig: %d,code: %d\n",status&0xff,(status>>8)&0xff);
}
}
int main()
{
pid_t tid=fork();
if(tid<0)
{
perror("fork");
exit(1);
}
else if(tid==0)
{
sleep(10);//保证父进程已注册完信号处理函数,父,子进程谁先运行不确定
printf("child is quit!\n");
exit(1);
}
else
{
signal(SIGCHLD,my_sigchld);
while(1);
}
return 0;
}
但是,如果一个父进程有100个子进程,收到好多SIGCHLD信号,只会保存一份,只能wait一份,故应该修改代码防止此情况发生
void my_sigchld(int sig)
{
int status=0;
pid_t ret;
while((ret=waitpid(-1,&status,0))>0)
{
printf("sig: %d,code: %d\n",status&0xff,(status>>8)&0xff);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  竟态条件