原理就是先使用定時器定時,然后再使用pause
函數或者sigsuspend
函數主動阻塞掛起,最終恢復現場。
如果使用pause
函數的話,優點是使用簡單,缺點是有可能產生時序競態,導致進程一直阻塞下去:在定時和掛起之間有一個縫隙,有可能定時后因為其他原因沒有直接掛起,而是被動掛起或者處理其他信號,但這段時間時鐘還在繼續計時,當時間到了以后信號就被發送,等回來主動掛起的時候再也等不到那個信號了,因此進程就會被一直掛起。為了解決這個問題,我們在定時前先將SIGALRM
信號屏蔽,然后定時、掛起,在掛起的同時我們解除對SIGALRM
的屏蔽,這樣就不用擔心主動掛起前錯過信號了,最后恢復現場。
可以根據代碼理解一下,其實是一個很符合直覺的過程。需要注意的是pause
和sigsuspend
只有失敗返回值-1
,不過這個失敗的意思是掛起失敗,也就是恢復運行,從這個意義上來講應該是成功返回值,因此我們不要對-1
返回值做處理(我順手處理了,然后一直出錯檢查了半天)。
代碼如下:
Utils.h:里面是一些我封裝的函數,為了簡化代碼
//
// Created by edward on 2021/5/7.
//#ifndef LINUX_UTILS_H
#define LINUX_UTILS_H#include <string>
#include <initializer_list>
#include <signal.h>/*!* 檢查系統調用返回值* @param x 返回值* @param msg 錯誤提示語句* @param y 錯誤狀態,默認為-1*/
void check_error(int x, const std::string &msg = "error", int y = -1);
/*!* 清零mask,并將il中的信號加入到mask中* @param mask* @param il*/
void add2mask(sigset_t *mask, std::initializer_list<int> il);
/*!* 將il中的信號從mask中刪除* @param mask* @param il*/
void del2mask(sigset_t *mask, std::initializer_list<int> il);#endif //LINUX_UTILS_H
mysleep函數:
2021.05.11更新:修復了傳入參數為0或者負數的bug。如果傳入參數都是0的話將導致進程進入阻塞狀態無法被喚醒
struct itimerval my_sleep(int seconds, int microseconds) {if (seconds <= 0 && microseconds <= 0)return {0, 0}; //注冊SIGALRM信號捕捉函數struct sigaction act, oldact;act.sa_handler = alrm_handler;act.sa_flags = 0;sigset_t mask, oldmask,suspendmask;sigemptyset(&mask); //屏蔽鍵盤信號add2mask(&mask, {SIGINT, SIGQUIT, SIGTSTP});act.sa_mask = mask;check_error(sigaction(SIGALRM, &act, &oldact), "sigaction error");//屏蔽alarm信號add2mask(&mask, {SIGALRM});check_error(sigprocmask(SIG_BLOCK, &mask, &oldmask), "sigprocmask error");//設置定時器struct itimerval new_value, old_value;new_value.it_value = {seconds, microseconds};new_value.it_interval = {0, 0};check_error(setitimer(ITIMER_REAL, &new_value, &old_value), "setitimer error");//主動阻塞掛起等待被信號喚醒//pause(); //使用pause會產生競態,導致信號失效,最終導致進程無限制掛起//通過首先將信號屏蔽防止信號失效,然后再使用sigpending函數在掛起期間解除對ALRM信號的屏蔽,使得進程最終能夠被喚醒//在掛起時解除屏蔽alarm信號suspendmask = oldmask;del2mask(&suspendmask, {SIGALRM});sigsuspend(&suspendmask);//恢復現場//恢復SIGALRM信號捕獲函數check_error(sigaction(SIGALRM, &oldact, nullptr), "sigaction error");//重置定時器check_error(getitimer(ITIMER_REAL, &new_value)); //獲取剩余定時時間old_value.it_interval = {0, 0};old_value.it_value = {0, 0};check_error(setitimer(ITIMER_REAL, &old_value, nullptr), "setitimer error");//解除對ALRM信號的屏蔽add2mask(&mask, {SIGALRM});check_error(sigprocmask(SIG_UNBLOCK, &mask, nullptr), "sigprocmask error");return new_value; //返回剩余定時時間
}
Utils.cpp:工具類實現,非常簡單
//
// Created by edward on 2021/5/7.
//#include "utils.h"using std::string;void check_error(int x, const string &msg, int y) {if (x == y) {perror(msg.c_str());exit(1);}
}void add2mask(sigset_t *mask, std::initializer_list<int> il) {check_error(sigemptyset(mask), "sigemptyset error");for (auto signum : il) {check_error(sigaddset(mask, signum), "sigaddset error");}
}void del2mask(sigset_t *mask, std::initializer_list<int> il) {for (auto signum : il) {check_error(sigdelset(mask, signum), "sigdelset error");}
}