之前我們探究過mysleep的簡單用法,我們實現的代碼是這樣的:
#include<stdio.h>
#include<signal.h>void myhandler(int sig)
{}unsigned int mysleep(unsigned int timeout)
{struct sigaction act,oact;act.sa_handler = myhandler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGALRM,&act,&oact); alarm(timeout); pause(); unsigned int ret = alarm(0); sigaction(SIGALRM,&oact,NULL);return ret;
}int main()
{printf("ready sleeping!\n");mysleep(3);printf("i am waking!\n");return 0;
}
我們首先注冊了捕捉信號的函數,信號為SIGALRM,然后調用了alarm函數來設置鬧鐘,此時pause來掛起等待,然后內核切換到別的進程運行,在timeout秒后鬧鐘產生SIGALRM信號,從內核態到用戶態的過程中,收到用戶自定義的處理函數,就在用戶態處理函數,進入處理函數myhandler時,SIGALRM信號會被自動屏蔽,當處理完函數后,自動解除屏蔽,進入到內核態執行系統調用,最后切換到用戶態執行主函數控制邏輯。
這里存在一個問題,比如剛剛說的alarm函數調用完成后,pause掛起等待,當把所有的邏輯都處理完成alarm返回時,進入用戶態繼續pause,是不是沒有任何意義呢。還有一點,我們不知道pause掛起等待是在alarm函數之內還是執行后調用的,這就出現了異步情況,我們稱這種現象為競態條件。
我們如何解決這類問題呢。。我們試著將SIGALRM信號屏蔽起來,當執行完alarm函數后再自動解除屏蔽。這樣就保證是在alarm函數執行到時間后掛起的狀態。但是還有一種可能是當解除屏蔽之后還有可能使SIGALRM遞達,因此這種方法還是有缺陷的。我們只能用原子性的操作來避免這種問題的出現。
這時又引出了一個函數:sigsuspend包含了pause的掛起等待功能,同時解決了競態條件的問題(與exec和pause一樣,沒有成功的返回值)
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
調用sigsuspend時,進程的信號屏蔽字由sigmask參數指定,可以通過指定sigmask來臨時解除對某個信號的屏蔽,然后掛起等待,當sigsuspend返回時,進程的信號屏蔽字恢復為原來的值,如果原來對該信號是屏蔽的,從sigsuspend返回后仍然是屏蔽的。
實現代碼如下:
#include<stdio.h>
#include<signal.h>void myhandler(int sig)
{}unsigned int mysleep(unsigned int timeout)
{struct sigaction act,oact;sigset_t newmask,oldmask,suspmask;act.sa_handler = myhandler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigemptyset(&newmask);sigaddset(&newmask,SIGALRM);sigprocmask(SIG_BLOCK,&newmask,&oldmask);sigaction(SIGALRM,&act,&oact); alarm(timeout); suspmask = oldmask;sigdelset(&suspmask,SIGALRM);sigsuspend(&suspmask);unsigned int ret = alarm(0); sigaction(SIGALRM,&oact,NULL);sigprocmask(SIG_SETMASK,&oldmask,NULL);return ret;
}int main()
{printf("ready sleeping!\n");mysleep(3);printf("i am waking!\n");return 0;
}
運行結果:

3秒之后:
