可以通過設置屏蔽SIGALRM的方法來控制程序執行邏輯,但無論如何設置,程序都有可能在“解除信號屏蔽”與“掛起等待信號”這個兩個操作間隙失去cpu資源。除非將這兩步驟合并成一個“原子操作”。sigsuspend函數具備這個功能。在對時序要求嚴格的場合下都應該使用sigsuspend替換pause。信號被解除屏蔽和進程掛起等待(sigsuspend函數執行)是同時進行的,為原子操作,不可分割。
int sigsuspend(const sigset_t *mask);??? //其作用與返回值都與pause函數一樣(-1,EINTR)。
sigsuspend函數調用期間,進程信號屏蔽字由其參數mask指定,該mask代替了PCB中的信號屏蔽字,當函數執行完(即返回-1)后,信號屏蔽字才會恢復作用。
可將某個信號(如SIGALRM)從臨時信號屏蔽字mask中刪除,這樣在調用sigsuspend時將解除對該信號的屏蔽,然后掛起等待,當sigsuspend返回時,進程的信號屏蔽字恢復為原來的值。如果原來對該信號是屏蔽態,sigsuspend函數返回后仍然屏蔽該信號。
?在單核處理器中,一條指令能完成的操作是原子的(read、write、printf對應N多條匯編指令自然不是原子操作),因為中斷是發生指令之間的。多條指令之間基本都是通過lock來實現原子操作,將這多條指令綁定為一個整體,不可分割,即原語。原語就是由若干條指令組成的,用于完成一定功能的一個過程,原語在執行過程中不允許被中斷(類似一條指令)。原語在系統態下執行,常駐內存。在內核中有許多原語,如進程創建原語creat、掛起原語suspend、激活原語active、阻塞原語block和喚醒原語wakeup等。sigsuspend函數是一個系統調用,系統調用都是一個原子操作,sigsuspend系統調用實現進程的掛起和信號屏蔽的解除。
//mysleep函數的改進
#include <unistd.h>
#include <signal.h>
#include <stdio.h>void sig_alrm(int signo)
{/* nothing to do */
}unsigned int mysleep(unsigned int nsecs)
{struct sigaction newact, oldact;sigset_t newmask, oldmask, suspmask;unsigned int unslept;//1.為SIGALRM設置捕捉函數,一個空函數newact.sa_handler = sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags = 0;sigaction(SIGALRM, &newact, &oldact);//2.設置阻塞信號集,阻塞SIGALRM信號sigemptyset(&newmask);sigaddset(&newmask, SIGALRM);sigprocmask(SIG_BLOCK, &newmask, &oldmask); //信號屏蔽字mask//3.定時n秒,到時后可以產生SIGALRM信號alarm(nsecs);/*4.構造一個調用sigsuspend臨時有效的阻塞信號集,* 在臨時阻塞信號集里解除SIGALRM的阻塞*/suspmask = oldmask;sigdelset(&suspmask, SIGALRM);/*5.sigsuspend調用期間,采用臨時阻塞信號集suspmask替換原有阻塞信號集* 這個信號集中不包含SIGALRM信號,同時掛起等待,* 當sigsuspend被信號喚醒返回時,恢復原有的阻塞信號集*/sigsuspend(&suspmask);unslept = alarm(0);//6.恢復SIGALRM原有的處理動作,呼應前面注釋1(思想)sigaction(SIGALRM, &oldact, NULL);//7.解除對SIGALRM的阻塞,呼應前面注釋2(思想)sigprocmask(SIG_SETMASK, &oldmask, NULL);return(unslept);
}int main(void)
{while(1){mysleep(2);printf("Two seconds passed\n");}return 0;
}
時序競態總結
競態條件,跟系統負載有很緊密的關系,體現出信號的不可靠性。系統負載越嚴重,信號不可靠性越強。不可靠由其實現原理所致。信號是通過軟件方式實現(跟內核調度高度依賴,延時性強),每次系統調用結束后,或中斷處理處理結束后,需通過掃描PCB中的未決信號集,來判斷是否應處理某個信號。當系統負載過重時,會出現時序混亂。
其本質原因就是進程隨時都有可能會失去CPU,失去CPU時間太長導致信號的處理與進程中主控程序的執行順序發生了改變,從而產生了不符合預期的結果,出現錯誤。如上面的pause函數本應該在信號處理之前進行,但是延時太長,導致pause函數執行時,信號提前被處理了。這都是因為信號的處理與主控程序的執行時序不一樣產生了不一樣的結果。這種錯誤只有程序員提前遇見,主動規避。
這種意外情況只能在編寫程序過程中,提早預見,主動規避,而無法通過gdb程序調試等其他手段彌補。且由于該錯誤不具規律性,后期捕捉和重現十分困難。