您好,登錄后才能下訂單哦!
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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。