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); } }
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。