全局變量異步 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 函數