建站上市公司成人技術(shù)培訓(xùn)班有哪些種類
并發(fā)
并發(fā)是指兩個(gè)或多個(gè)同時(shí)獨(dú)立進(jìn)行的活動(dòng)。在計(jì)算機(jī)系統(tǒng)中,并發(fā)指的是同一個(gè)系統(tǒng)中多個(gè)獨(dú)立活動(dòng)同時(shí)進(jìn)行,而非依次進(jìn)行。
并發(fā)在計(jì)算機(jī)系統(tǒng)中的表現(xiàn):
一個(gè)時(shí)間段中有幾個(gè)程序都處于已啟動(dòng)運(yùn)行到運(yùn)行完畢之間,且這幾個(gè)程序都是在同一個(gè)處理機(jī)上運(yùn)行,但任一個(gè)時(shí)刻點(diǎn)上只有一個(gè)程序在處理機(jī)上運(yùn)行。
雖然每個(gè)任務(wù)的部分操作在時(shí)間上重疊,但從微觀上看,這些任務(wù)是交替執(zhí)行的。
任務(wù)切換對(duì)使用者和應(yīng)用軟件自身都制造出并發(fā)的表象。
總之,并發(fā)是一種同時(shí)進(jìn)行的概念,可以存在于計(jì)算機(jī)系統(tǒng)的不同層面。
補(bǔ)充同步和異步的概念:
同步和異步是兩種不同的處理方式,它們?cè)谟?jì)算機(jī)系統(tǒng)中有著不同的含義和應(yīng)用。
1、同步(Synchronous)
同步是指發(fā)送方發(fā)出數(shù)據(jù)后,等接收方發(fā)回響應(yīng)以后才發(fā)下一個(gè)數(shù)據(jù)包的通訊方式。在同步通信中,發(fā)送方和接收方之間的數(shù)據(jù)傳輸是按照一定的時(shí)間順序進(jìn)行的。發(fā)送方會(huì)等待接收方對(duì)每個(gè)請(qǐng)求進(jìn)行響應(yīng),然后才能繼續(xù)發(fā)送下一個(gè)請(qǐng)求。因此,同步通信具有可靠性和順序性的特點(diǎn)。
在同步通信中,由于發(fā)送方需要等待接收方的響應(yīng),因此數(shù)據(jù)的傳輸速度相對(duì)較慢。此外,如果接收方在處理請(qǐng)求時(shí)出現(xiàn)錯(cuò)誤,發(fā)送方可能需要重新發(fā)送請(qǐng)求。因此,同步通信適用于對(duì)可靠性要求較高的場景,如文件傳輸、郵件通信等。
2、異步(Asynchronous)
異步是指發(fā)送方發(fā)出數(shù)據(jù)后,不等接收方發(fā)回響應(yīng),接著發(fā)送下個(gè)數(shù)據(jù)包的通訊方式。在異步通信中,發(fā)送方和接收方之間的數(shù)據(jù)傳輸是獨(dú)立進(jìn)行的,不需要按照時(shí)間順序進(jìn)行。發(fā)送方在發(fā)送請(qǐng)求后可以繼續(xù)執(zhí)行其他操作,而不需要等待接收方的響應(yīng)。當(dāng)接收方處理完請(qǐng)求后,會(huì)通知發(fā)送方并返回結(jié)果。
異步通信具有高效性和靈活性的特點(diǎn)。由于發(fā)送方不需要等待接收方的響應(yīng),因此數(shù)據(jù)的傳輸速度相對(duì)較快。此外,異步通信適用于對(duì)實(shí)時(shí)性要求較高的場景,如網(wǎng)絡(luò)通信、事件驅(qū)動(dòng)的系統(tǒng)等。在異步通信中,需要注意處理并發(fā)訪問和數(shù)據(jù)競爭等問題。
總之,同步和異步是兩種不同的處理方式,它們?cè)谟?jì)算機(jī)系統(tǒng)中有著不同的含義和應(yīng)用。根據(jù)不同的需求和場景,可以選擇適合的處理方式來提高系統(tǒng)的性能和可靠性。
對(duì)于異步處理方式我們有兩種常用的方法:查詢法(輪詢法) 和 通知法(條件成熟再來進(jìn)行操作),后面會(huì)再詳細(xì)展開。
對(duì)于并發(fā)主要有兩種方式的并發(fā),分別是信號(hào)實(shí)現(xiàn)并發(fā)和多線程實(shí)現(xiàn)并發(fā)。
信號(hào)
信號(hào)概念
信號(hào)是軟件層面的中斷,更確切的說是信號(hào)的響應(yīng)依賴于中斷(硬件層面的中斷)。
在Linux中,信號(hào)是一種進(jìn)程間通信的方式,用于通知某個(gè)進(jìn)程發(fā)生了某個(gè)事件。信號(hào)是一種異步的通知機(jī)制,當(dāng)一個(gè)事件發(fā)生時(shí),操作系統(tǒng)會(huì)向進(jìn)程發(fā)送一個(gè)信號(hào),中斷該進(jìn)程的正??刂屏鞒?。
進(jìn)程可以定義信號(hào)的處理函數(shù),當(dāng)接收到信號(hào)時(shí),執(zhí)行相應(yīng)的處理操作。如果沒有定義處理函數(shù),系統(tǒng)會(huì)默認(rèn)處理信號(hào),如終止進(jìn)程或執(zhí)行特定的系統(tǒng)操作。
在Linux系統(tǒng)中,有多種類型的信號(hào),例如:
SIGINT:當(dāng)用戶按下中斷鍵(如CTRL+C)時(shí),會(huì)向當(dāng)前進(jìn)程發(fā)送SIGINT信號(hào),用于終止進(jìn)程。
SIGTERM:當(dāng)使用kill命令向進(jìn)程發(fā)送信號(hào)時(shí),默認(rèn)發(fā)送的是SIGTERM信號(hào),用于請(qǐng)求進(jìn)程正常終止。
SIGKILL:當(dāng)需要立即終止進(jìn)程時(shí),可以向進(jìn)程發(fā)送SIGKILL信號(hào),它會(huì)無條件地終止進(jìn)程。
SIGSTOP:當(dāng)需要暫停進(jìn)程時(shí),可以向進(jìn)程發(fā)送SIGSTOP信號(hào),進(jìn)程會(huì)停止執(zhí)行。
SIGCONT:當(dāng)需要恢復(fù)暫停的進(jìn)程時(shí),可以向進(jìn)程發(fā)送SIGCONT信號(hào),進(jìn)程會(huì)繼續(xù)執(zhí)行。
除了用戶觸發(fā)的信號(hào)外,內(nèi)核也可以因?yàn)閮?nèi)部事件而向進(jìn)程發(fā)送信號(hào),通知進(jìn)程發(fā)生了某個(gè)事件。例如,當(dāng)進(jìn)程執(zhí)行非法操作時(shí),內(nèi)核會(huì)向進(jìn)程發(fā)送SIGSEGV信號(hào),表示發(fā)生了段錯(cuò)誤。
總之,信號(hào)是Linux系統(tǒng)中一種重要的進(jìn)程間通信方式,用于通知進(jìn)程發(fā)生了某個(gè)事件,并且可以定義相應(yīng)的處理函數(shù)來處理信號(hào)。
我們可以使用 kill -l 的命令來看一下信號(hào):
其中編號(hào)1 到 31的信號(hào)叫做標(biāo)準(zhǔn)信號(hào);從 34 到 64 稱為實(shí)時(shí)信號(hào),這些信號(hào)名字中的RT表示real time,實(shí)時(shí)的意思。
signal()系統(tǒng)調(diào)用
在Linux中,signal系統(tǒng)調(diào)用是一種用于處理信號(hào)、注冊(cè)信號(hào)的方式。它允許進(jìn)程捕獲、忽略或改變特定信號(hào)的默認(rèn)處理行為。
signal系統(tǒng)調(diào)用的第一個(gè)參數(shù) signum 是信號(hào)行為,第二個(gè)參數(shù)是對(duì)于指定的 signum信號(hào)行為 所要采取的措施 handler,然后返回值指的是這個(gè)信號(hào)之前的行為,這個(gè)行為是由void (*sighandler_t) (int) 函數(shù)指定的。
其原型還可以寫成下面這種形式,下面這種形式才是我們最常寫的形式,其實(shí)就是把signal這個(gè)函數(shù)直接放到了 sighandler_t 的位置嘛,這樣寫的好處是可以防止命名空間沖突:
void (*signal(int sig, void (*func)(int)))(int);
這個(gè)原型定義了一個(gè)函數(shù)指針,該函數(shù)指針指向一個(gè)處理信號(hào)的函數(shù)。參數(shù)sig指定要處理的信號(hào),而func是一個(gè)指向處理函數(shù)的指針。如果func為SIG_IGN,則忽略信號(hào);如果func為SIG_DFL,則使用默認(rèn)處理行為。
當(dāng)進(jìn)程接收到信號(hào)時(shí),會(huì)執(zhí)行相應(yīng)的處理函數(shù)。處理函數(shù)可以是一個(gè)自定義的函數(shù),也可以是系統(tǒng)定義的一些特殊值,如SIG_IGN或SIG_DFL。
signal系統(tǒng)調(diào)用返回之前為指定信號(hào)設(shè)置的處理函數(shù)的指針。如果成功,它返回之前的信號(hào)處理函數(shù)指針;否則,返回SIG_ERR并設(shè)置errno以表示錯(cuò)誤原因。
信號(hào)機(jī)制是一種異步通知機(jī)制,進(jìn)程不必等待信號(hào)的到來。當(dāng)進(jìn)程接收到信號(hào)時(shí),會(huì)立即執(zhí)行相應(yīng)的處理函數(shù)。進(jìn)程可以捕獲、忽略或改變特定信號(hào)的處理行為,以便在接收到信號(hào)時(shí)執(zhí)行自定義的操作。
在Linux中,有多種類型的信號(hào),例如SIGINT、SIGTERM、SIGKILL、SIGSTOP和SIGCONT等。這些信號(hào)有不同的含義和默認(rèn)處理行為,進(jìn)程可以根據(jù)需要捕獲和處理這些信號(hào)。
總之,signal系統(tǒng)調(diào)用是Linux中用于處理信號(hào)的一種方式,它允許進(jìn)程自定義信號(hào)的處理函數(shù),以便在接收到信號(hào)時(shí)執(zhí)行相應(yīng)的操作。
來寫個(gè)小例子:
這個(gè)程序的作用是讓其在終端輸出設(shè)備上打印十個(gè)*:
此時(shí)我們?cè)谒蛴〉倪^程中直接使用 Ctrl + C 給中斷了,其實(shí)這就是在發(fā)送信號(hào),Ctrl+C是信號(hào) SIGINT (INT是Interrupt的縮寫)的快捷方式。
SIGINT是終端中斷符,其默認(rèn)功能是終止一個(gè)進(jìn)程。
接下來我們來驗(yàn)證該程序確實(shí)是接收到了一個(gè) SIGINT 才被結(jié)束的:
可以看到此時(shí)我們發(fā)送的SIGINT信號(hào)就不再起作用了。
繼續(xù)進(jìn)行驗(yàn)證:
可以看見我們的信號(hào)處理函數(shù)起了作用,它輸出了感嘆號(hào)。
我們來試一下按住ctrl+c不放:
但其實(shí)靜態(tài)的圖片顯示不了這個(gè)操作的效果,因?yàn)檫@樣按住不放的話,原本十秒才能打印完的 *,瞬間就會(huì)全部打印完,這意味著:
信號(hào)會(huì)打斷阻塞的系統(tǒng)調(diào)用!
這意味著我們之前寫的所有程序可能都是錯(cuò)誤的,因?yàn)槲覀兌际窃跊]用涉及信號(hào)的前提下寫的,如果涉及了信號(hào)那么就可能都會(huì)出錯(cuò)。
一個(gè)最簡單的例子,比如read系統(tǒng)調(diào)用,其出錯(cuò)時(shí)可能是代碼邏輯導(dǎo)致的,也可能是信號(hào)導(dǎo)致的,如果是信號(hào)導(dǎo)致的,比如最經(jīng)典的:
上圖中的EINTR,在讀到任何內(nèi)容之前就被信號(hào)打斷了,但此時(shí)讀操作只是被阻塞了因?yàn)檫€沒有讀到內(nèi)容,若有信號(hào)到來就會(huì)打斷從而返回了形式上的錯(cuò)誤信息(比如讀到0字節(jié),真實(shí)含義確實(shí)是讀失敗,但是實(shí)際上是還沒開始讀就被打斷了),這就是信號(hào)打斷了阻塞的系統(tǒng)調(diào)用,很明顯這是一個(gè)假的錯(cuò)誤,因?yàn)椴⒉皇亲x不到而是還沒開始讀被打斷了,那么我們就可以做如下的改進(jìn):
如果是假錯(cuò)誤,那么就再給其一次機(jī)會(huì),直到正常運(yùn)行結(jié)束為止。
信號(hào)的不可靠性
標(biāo)準(zhǔn)信號(hào)其實(shí)有個(gè)特點(diǎn)就是一定會(huì)丟失,但其不屬于信號(hào)的不可靠性, 而實(shí)時(shí)信號(hào)是不會(huì)丟失的。
這里所說的信號(hào)不可靠性,指的是信號(hào)的行為不可靠。
其意思是一個(gè)信號(hào)在處理這個(gè)信號(hào)行為的同時(shí),又來了另外一個(gè)相同的信號(hào),那么由于這個(gè)執(zhí)行現(xiàn)場是由內(nèi)核來幫忙布置的(如果是我們自己程序中寫的調(diào)用函數(shù)的話,那么就是OS來幫我們布置的,所以我們函數(shù)a調(diào)用函數(shù)b調(diào)用函數(shù)c都一點(diǎn)問題沒用,但信號(hào)行為處理函數(shù)的執(zhí)行線程則是內(nèi)核來幫忙布置的),就很有可能不會(huì)是在同一個(gè)位置,那么第二次的執(zhí)行現(xiàn)場就把第一次的給覆蓋掉了,這就是信號(hào)行為的不可靠性。
可重入函數(shù)
什么是可重入?
就是第一次調(diào)用還沒完成緊接著又發(fā)生了第二次調(diào)用,這樣就可能導(dǎo)致第一次調(diào)用發(fā)生一些不可預(yù)料的問題。
所有的系統(tǒng)調(diào)用都是可重入的,一部分庫函數(shù)也是可重入的,如 memcpy。
舉例:
比如上面這個(gè)函數(shù),其有一個(gè)對(duì)應(yīng)的 _r 版本,這就是一個(gè)可重入的版本;如果一個(gè)函數(shù)有其對(duì)應(yīng)的 _r 可重入版本,那么就說明該函數(shù)則一定不能用在信號(hào)處理函數(shù)當(dāng)中(既無 _r 不可重入的版本)。
另外 _r 的函數(shù)版本是線程安全的:
在Linux下,asctime函數(shù)有一個(gè)asctime_r版本的原因是為了提供線程安全。
asctime函數(shù)是將結(jié)構(gòu)化時(shí)間轉(zhuǎn)換為字符串的函數(shù),它將一個(gè)struct tm結(jié)構(gòu)體轉(zhuǎn)換為一個(gè)格式化的字符串,表示為"Day Mon 2 Hour:Minute:Second YYYY\n"的形式。
然而,asctime函數(shù)有一個(gè)問題,它是非線程安全的。這意味著在多線程環(huán)境下,多個(gè)線程可能會(huì)同時(shí)調(diào)用asctime函數(shù),導(dǎo)致競爭條件和未定義的行為。
為了解決這個(gè)問題,Linux提供了asctime_r函數(shù)。asctime_r函數(shù)是一個(gè)線程安全的版本,它接受一個(gè)額外的參數(shù)struct tm結(jié)構(gòu)體和一個(gè)字符數(shù)組,將結(jié)構(gòu)體轉(zhuǎn)換為一個(gè)格式化的字符串,并將結(jié)果存儲(chǔ)在字符數(shù)組中。這樣,每個(gè)線程都可以使用自己的輸入和輸出緩沖區(qū),避免了競爭條件。
因此,使用asctime_r函數(shù)可以確保在多線程環(huán)境下安全地使用asctime函數(shù)的功能。
注意區(qū)分二者的概念:
可重入函數(shù)一定是線程安全的,但線程安全的函數(shù)不一定是可重入的。
可重入函數(shù)的特點(diǎn)在于它們被多個(gè)線程調(diào)用時(shí),不會(huì)引用任何共享數(shù)據(jù)。而線程安全函數(shù)要解決的問題是,多個(gè)線程調(diào)用函數(shù)時(shí)訪問資源沖突。
如果一個(gè)函數(shù)中有全局變量,那么這個(gè)函數(shù)既不是線程安全也不是可重入的。如果將對(duì)臨界資源的訪問加上鎖,則這個(gè)函數(shù)是線程安全的,但如果這個(gè)重入函數(shù)若鎖還未釋放則會(huì)產(chǎn)生死鎖,因此是不可重入的。
總的來說,可重入和線程安全是兩個(gè)不同的概念,它們之間有區(qū)別也有聯(lián)系。
信號(hào)的響應(yīng)過程
線程與進(jìn)程的響應(yīng)過程有一點(diǎn)區(qū)別,這里先說進(jìn)程:
內(nèi)核為每個(gè)進(jìn)程維護(hù)了最少兩個(gè)位圖,一個(gè)是Mask信號(hào)屏蔽字,一個(gè)是pending,理論上說二者都是 32 位的。
還記得我們之前寫的star.c的程序嗎,就是每一秒鐘打印一個(gè)*號(hào),連續(xù)打印十個(gè),然后遇到ctrl+c就被打斷打印一個(gè)!:
上圖的左側(cè)是我們的main函數(shù),另外一個(gè)就是我們的信號(hào)處理函數(shù)。
mask位圖是我們的信號(hào)屏蔽字,它用來表示當(dāng)前信號(hào)的狀態(tài),而pending位圖用來記錄當(dāng)前這個(gè)進(jìn)程接收到了哪些信號(hào)。
而mask位圖的值一般情況下全部都為1,我們可以改的:
而pending位圖在進(jìn)程一開始時(shí)則全部為0:
在每次進(jìn)程被中斷(不是信號(hào)中斷,而是其它類型的中斷比如時(shí)間片用完的中斷)時(shí),進(jìn)程會(huì)攜帶著自己的現(xiàn)場扎進(jìn)內(nèi)核態(tài)中,然后等待著下一次被調(diào)度時(shí)回到用戶態(tài)重新恢復(fù)現(xiàn)場繼續(xù)運(yùn)行,從內(nèi)核切換到用戶態(tài)這個(gè)時(shí)間點(diǎn)進(jìn)程會(huì)執(zhí)行一個(gè)表達(dá)式:mask 按位與上 pending,通過上圖的初識(shí)狀態(tài)我們得知按位與的結(jié)果為 0,說明沒有收到任何信號(hào),所以就正常運(yùn)行即可。
注意,信號(hào)并非是一發(fā)送進(jìn)程就能夠收到的,信號(hào)從收到到響應(yīng)有一個(gè)不可避免的延遲。
思考問題:如何忽略掉一個(gè)信號(hào)的?標(biāo)準(zhǔn)信號(hào)為什么要丟失?
剛剛說了沒有信號(hào)的情況,那么再來看有信號(hào)的情況。
假如說某個(gè)時(shí)刻有一信號(hào)(比如interrupt)到來,該信號(hào)反應(yīng)到pending位圖上某一個(gè)位上,那么該位就被置為1:
此時(shí)進(jìn)程收到了信號(hào)但卻還不知道(因?yàn)樯形丛趦?nèi)核態(tài)中,進(jìn)程還在用戶態(tài)呢),什么時(shí)候才會(huì)知道?一定要等到有中斷來對(duì)進(jìn)程執(zhí)行中斷之后,進(jìn)程帶著自己的上下文現(xiàn)場扎進(jìn)內(nèi)核態(tài)中的就緒隊(duì)列中,等待下一次被調(diào)度時(shí)又帶著自己的現(xiàn)場從內(nèi)核態(tài)回到用戶態(tài)的時(shí)候進(jìn)行了之前說的表達(dá)式:mask 按位與上 pending,此時(shí)可以發(fā)現(xiàn)按位與后的結(jié)果值中的 interrupt 信號(hào)的那一位變成 1 了,此時(shí)進(jìn)程才真正知道了自己收到的信號(hào)是什么,即信號(hào)從收到到響應(yīng)有一個(gè)不可避免的延遲。
即收到的時(shí)候就pending位圖對(duì)應(yīng)的信號(hào)位值變?yōu)?1 的時(shí)候,而響應(yīng)則是在進(jìn)程從內(nèi)核態(tài)回到用戶態(tài)時(shí)通過mask與pending進(jìn)行了按位與之后才能進(jìn)行響應(yīng)。
收到這個(gè)信號(hào)之后,進(jìn)程會(huì)將這個(gè)mask位置成0,pending位也置成0,然后進(jìn)程的上下文現(xiàn)場中肯定有一個(gè)地址值(不然它怎么返回用戶態(tài)對(duì)吧?),此時(shí)這個(gè)地址值會(huì)被改變成信號(hào)處理函數(shù)init_handler的地址而非原來的main函數(shù)地址:
響應(yīng)完信號(hào)處理函數(shù)之后,還要返回內(nèi)核態(tài),將地址值又改回原來的main函數(shù)繼續(xù)完成剩下的任務(wù)(對(duì)于我們的star.c程序來說就是打印完信號(hào)處理函數(shù)中的 !之后還要返回繼續(xù)打印main函數(shù)中的*號(hào)),除此之外還要將mask剛剛被改為0的位置重新置為 1。
而pending是0是1不知道不用管,此時(shí)進(jìn)程從內(nèi)核態(tài)又回到用戶態(tài)時(shí)又會(huì)進(jìn)行mask與pending的按位與操作,此時(shí)就能知道剛才的信號(hào)已經(jīng)被響應(yīng)掉了,然后進(jìn)程正常返回到main函數(shù)繼續(xù)完成后續(xù)操作。
這就是一個(gè)信號(hào)的響應(yīng)過程。
而標(biāo)準(zhǔn)信號(hào)是有缺陷的,因?yàn)樵谑盏蕉鄠€(gè)標(biāo)準(zhǔn)信號(hào)的時(shí)候,此時(shí)先響應(yīng)哪個(gè)其實(shí)是不知道的,即標(biāo)準(zhǔn)信號(hào)的響應(yīng)沒有嚴(yán)格的順序。
所以對(duì)于剛剛的思考問題我們應(yīng)該有了答案,如何忽略掉一個(gè)信號(hào)?
其實(shí)就是把mask位圖對(duì)應(yīng)于pending信號(hào)位圖上所對(duì)應(yīng)的那個(gè)位改成0,這樣當(dāng)有信號(hào)到來時(shí)哪怕是pending上該信號(hào)對(duì)應(yīng)的位為變?yōu)榱?1 ,那么進(jìn)行按位與的時(shí)候該信號(hào)的按位與結(jié)果永遠(yuǎn)為 0 那我們自然就不會(huì)響應(yīng)該信號(hào)啦。這也就意味著其實(shí)我們無法阻止信號(hào)的到來,但是我們可以決定是否響應(yīng)。
還有一個(gè)問題,為什么標(biāo)準(zhǔn)信號(hào)會(huì)丟失?
很簡單,因?yàn)檫@是位圖,位圖意味著無法疊加,比如有十萬個(gè)相同的信號(hào)來臨時(shí),因?yàn)閺膬?nèi)核態(tài)返回用戶態(tài)時(shí)會(huì)進(jìn)行mask與pending的按位與嘛,按位與后響應(yīng)信號(hào)最后會(huì)將mask和pending置為0,但此時(shí)又來一個(gè)相同信號(hào)的話這個(gè)pending又會(huì)被置為1,但此時(shí)mask是為0的呀,0和1與上結(jié)果為0所以這一次的結(jié)果就不會(huì)為1了,也就意味著該次信號(hào)不會(huì)被響應(yīng),也就造成了標(biāo)準(zhǔn)信號(hào)丟失的原因。
信號(hào)相關(guān)的常用函數(shù):kill()、raise()、alarm()、pause()、abort()、system()、sleep()
kill()
在Linux操作系統(tǒng)中,kill是一種系統(tǒng)調(diào)用(system call),它是應(yīng)用程序與操作系統(tǒng)內(nèi)核交互的一種方式。通過kill系統(tǒng)調(diào)用,應(yīng)用程序可以向操作系統(tǒng)發(fā)送信號(hào)(signal),以請(qǐng)求操作系統(tǒng)終止一個(gè)進(jìn)程、重新啟動(dòng)等。
在C語言中,你可以使用kill系統(tǒng)調(diào)用來發(fā)送信號(hào)。它的函數(shù)原型如下:
int kill(pid_t pid, int sig);
其中,pid是進(jìn)程ID,sig是要發(fā)送的信號(hào)。
kill系統(tǒng)調(diào)用的作用是向指定的進(jìn)程發(fā)送指定的信號(hào)。如果進(jìn)程無法被終止,你可以使用SIGKILL信號(hào)(編號(hào)為9)強(qiáng)制終止進(jìn)程。另外,如果你不指定信號(hào),kill系統(tǒng)調(diào)用將默認(rèn)發(fā)送SIGTERM信號(hào)(編號(hào)為15),請(qǐng)求進(jìn)程終止。
需要注意的是,只有具有足夠權(quán)限的用戶(通常是root用戶)才能向其他用戶的進(jìn)程發(fā)送信號(hào)。在非root用戶的情況下,你只能向自己的進(jìn)程發(fā)送信號(hào)。
raise()
在Linux中,raise函數(shù)是用于發(fā)送一個(gè)信號(hào)給當(dāng)前進(jìn)程的。它允許當(dāng)前進(jìn)程主動(dòng)終止自己或者請(qǐng)求操作系統(tǒng)的注意。
raise函數(shù)的函數(shù)原型通常為:
int raise(int sig);
其中,sig是要發(fā)送的信號(hào)的標(biāo)識(shí)符。
raise函數(shù)的作用是向當(dāng)前進(jìn)程發(fā)送一個(gè)信號(hào)。如果成功,函數(shù)返回0;如果失敗,返回-1。
在Linux中,有許多不同的信號(hào)可以使用,包括:
SIGTERM(信號(hào)編號(hào)15):這是默認(rèn)的終止信號(hào)。如果進(jìn)程不捕獲此信號(hào),那么它將被終止。
SIGINT(信號(hào)編號(hào)2):這是鍵盤中斷信號(hào),通常由用戶按下Ctrl+C產(chǎn)生。
SIGKILL(信號(hào)編號(hào)9):這是一個(gè)強(qiáng)制終止信號(hào),無論進(jìn)程是否捕獲它,都將導(dǎo)致進(jìn)程終止。然而,進(jìn)程可以捕獲這個(gè)信號(hào)并執(zhí)行一些清理操作,然后再退出。
使用raise函數(shù)可以主動(dòng)發(fā)送這些信號(hào)給自己,這樣就可以請(qǐng)求操作系統(tǒng)終止自己或者其他操作。例如,你可以使用raise(SIGTERM)來請(qǐng)求操作系統(tǒng)終止當(dāng)前進(jìn)程。
需要注意的是,不是所有的信號(hào)都可以被捕獲和處理。例如,SIGKILL就不能被捕獲,所以使用raise(SIGKILL)將立即終止當(dāng)前進(jìn)程,而無法執(zhí)行任何清理操作。
在編寫程序時(shí),使用raise函數(shù)可以提供一種優(yōu)雅地結(jié)束進(jìn)程的方式,例如在需要釋放資源或者執(zhí)行一些清理工作時(shí)。
alarm()
Linux系統(tǒng)中的alarm系統(tǒng)調(diào)用是一種在指定時(shí)間后發(fā)送一個(gè)SIGALRM信號(hào)給當(dāng)前進(jìn)程的方式。它通常用于在進(jìn)程完成某項(xiàng)任務(wù)后觸發(fā)一個(gè)定時(shí)器,以便在特定時(shí)間點(diǎn)執(zhí)行其他操作。
alarm系統(tǒng)調(diào)用的原型如下:
#include <unistd.h> int alarm(unsigned int seconds);
這里的seconds參數(shù)指定了定時(shí)器的持續(xù)時(shí)間,單位為秒。當(dāng)指定的時(shí)間過去后,系統(tǒng)會(huì)自動(dòng)向當(dāng)前進(jìn)程發(fā)送一個(gè)SIGALRM信號(hào)。
alarm系統(tǒng)調(diào)用可以用于多種情況,例如:
超時(shí)處理:當(dāng)進(jìn)程需要在特定時(shí)間內(nèi)完成某項(xiàng)任務(wù)時(shí),可以使用alarm設(shè)置一個(gè)定時(shí)器。如果在定時(shí)器觸發(fā)之前進(jìn)程沒有完成任務(wù),那么系統(tǒng)會(huì)發(fā)送一個(gè)SIGALRM信號(hào)給進(jìn)程,進(jìn)程可以捕獲該信號(hào)并進(jìn)行相應(yīng)的處理,例如超時(shí)處理或重新嘗試任務(wù)。
定期任務(wù):進(jìn)程可以使用alarm系統(tǒng)調(diào)用設(shè)置一個(gè)定期觸發(fā)的時(shí)間點(diǎn)。在每個(gè)觸發(fā)時(shí)間點(diǎn)上,系統(tǒng)會(huì)發(fā)送一個(gè)SIGALRM信號(hào)給進(jìn)程,進(jìn)程可以捕獲該信號(hào)并執(zhí)行相應(yīng)的操作。這種方式可以用于實(shí)現(xiàn)定期任務(wù),例如每隔一段時(shí)間檢查文件更新、統(tǒng)計(jì)系統(tǒng)資源使用情況等。
喚醒休眠的進(jìn)程:當(dāng)進(jìn)程進(jìn)入休眠狀態(tài)時(shí),可以使用alarm系統(tǒng)調(diào)用設(shè)置一個(gè)喚醒時(shí)間。當(dāng)休眠的進(jìn)程到達(dá)指定的喚醒時(shí)間時(shí),系統(tǒng)會(huì)發(fā)送一個(gè)SIGALRM信號(hào)將其喚醒。
需要注意的是,如果指定的時(shí)間設(shè)置為0,則alarm系統(tǒng)調(diào)用會(huì)立即返回而不發(fā)送任何信號(hào)。此外,如果進(jìn)程沒有捕獲SIGALRM信號(hào)并對(duì)其做出處理,那么默認(rèn)情況下進(jìn)程將被終止。因此,在使用alarm系統(tǒng)調(diào)用時(shí),通常需要編寫代碼來捕獲和處理SIGALRM信號(hào)。
我們可以寫一個(gè)小例子:
五秒鐘之后該進(jìn)程就會(huì)收到一個(gè)sigalarm信號(hào),然后打印上面的輸出。
注意alarm系統(tǒng)調(diào)用是無法實(shí)現(xiàn)多任務(wù)計(jì)時(shí)器的效果的,只能一個(gè)任務(wù)一個(gè)計(jì)數(shù)器,比如剛剛的程序,如果有三個(gè)alarm:
實(shí)際上只會(huì)執(zhí)行第三個(gè)alarm,一秒鐘就打印了alarm clock,和秒數(shù)無關(guān),只會(huì)執(zhí)行最下面的那個(gè)alarm。
這就意味著如果程序當(dāng)中出現(xiàn)多個(gè)alarm時(shí)可能就會(huì)出錯(cuò),這里涉及到下一個(gè)系統(tǒng)調(diào)用的使用,pause();
pause()
Linux系統(tǒng)中的pause系統(tǒng)調(diào)用是一種讓當(dāng)前進(jìn)程進(jìn)入等待狀態(tài),直到接收到某種信號(hào)為止的機(jī)制。它的函數(shù)原型如下:
#include <unistd.h> int pause(void);
pause`系統(tǒng)調(diào)用會(huì)使當(dāng)前進(jìn)程進(jìn)入等待狀態(tài),直到收到一個(gè)信號(hào)為止。收到信號(hào)后,進(jìn)程會(huì)返回-1,并設(shè)置errno為收到信號(hào)的編號(hào)。
pause系統(tǒng)調(diào)用的主要作用是讓進(jìn)程暫停執(zhí)行,等待接收信號(hào)。它通常用于實(shí)現(xiàn)進(jìn)程間的同步、延時(shí)操作或等待某個(gè)條件滿足等場景。
下面是一些使用pause系統(tǒng)調(diào)用的示例:
進(jìn)程間同步:如果有多個(gè)進(jìn)程需要按照一定的順序執(zhí)行,可以使用pause系統(tǒng)調(diào)用來實(shí)現(xiàn)同步。例如,一個(gè)進(jìn)程在完成某項(xiàng)任務(wù)之前,先調(diào)用pause等待另一個(gè)進(jìn)程完成其他任務(wù)。當(dāng)另一個(gè)進(jìn)程完成任務(wù)后,發(fā)送一個(gè)信號(hào)給第一個(gè)進(jìn)程,第一個(gè)進(jìn)程收到信號(hào)后繼續(xù)執(zhí)行后續(xù)任務(wù)。
延時(shí)操作:pause系統(tǒng)調(diào)用也可以用于實(shí)現(xiàn)簡單的延時(shí)操作。通過調(diào)用pause函數(shù),進(jìn)程會(huì)進(jìn)入等待狀態(tài)直到接收到信號(hào)。通過設(shè)置等待的時(shí)間,可以實(shí)現(xiàn)一定時(shí)間間隔的延時(shí)效果。
等待某個(gè)條件滿足:當(dāng)進(jìn)程需要等待某個(gè)條件滿足才能繼續(xù)執(zhí)行時(shí),可以使用pause系統(tǒng)調(diào)用。例如,進(jìn)程在執(zhí)行某個(gè)操作之前需要等待文件就緒,此時(shí)可以先調(diào)用pause等待文件就緒。當(dāng)文件就緒后,其他進(jìn)程可以發(fā)送一個(gè)信號(hào)給該進(jìn)程,該進(jìn)程收到信號(hào)后繼續(xù)執(zhí)行后續(xù)操作。
需要注意的是,在使用pause系統(tǒng)調(diào)用時(shí)需要謹(jǐn)慎處理信號(hào)的處理方式。如果進(jìn)程沒有捕獲信號(hào)并對(duì)其做出處理,那么默認(rèn)情況下進(jìn)程將被終止。因此,通常需要編寫代碼來捕獲信號(hào)并進(jìn)行相應(yīng)的處理,例如忽略信號(hào)、處理信號(hào)或者進(jìn)行其他操作。
有了pause系統(tǒng)調(diào)用,我們就可以解決剛剛程序中的CPU忙等問題啦:
該程序的執(zhí)行順序?yàn)?#xff1a;先執(zhí)行alarm,此時(shí)會(huì)有一個(gè)計(jì)時(shí)器在計(jì)時(shí),然后程序繼續(xù)向下執(zhí)行打印hahah字符串,然后就會(huì)進(jìn)入while(1)死循環(huán)中(之前CPU會(huì)卡在這里一直忙等直到alarm時(shí)間到發(fā)送中斷信號(hào)而終止程序),但現(xiàn)在有了pause之后則會(huì)直接讓進(jìn)程進(jìn)入等待阻塞的狀態(tài)而不會(huì)讓CPU一直跑這個(gè)循環(huán),直到alarm計(jì)時(shí)器到了之后發(fā)送sigalarm信號(hào)給該程序,該程序就被終止了。pause所起到的作用也就是釋放了CPU。
另外之前提過sleep函數(shù)有缺陷最好別用,這是因?yàn)楦鱾€(gè)系統(tǒng)上的sleep函數(shù)的底層實(shí)現(xiàn)有可能不一樣,這就會(huì)導(dǎo)致意外的錯(cuò)誤,比如有些系統(tǒng)的sleep就是使用 alarm + pause 進(jìn)行封裝的,那假如我們的程序中也有alarm,那剛剛才說過alarm是不支持多任務(wù)計(jì)時(shí)的,這程序邏輯不就出錯(cuò)了嗎?所以考慮到移植問題,我們最好別輕易使用sleep函數(shù)。
接下來我們來寫幾個(gè)例子。
例子1,定時(shí)循環(huán),即讓一個(gè)數(shù)瘋狂的自增五秒鐘,復(fù)習(xí)一個(gè)之前學(xué)習(xí)過的time函數(shù):
在Linux中,time函數(shù)是C語言中的一個(gè)標(biāo)準(zhǔn)庫函數(shù),它用于獲取當(dāng)前的系統(tǒng)時(shí)間。
time函數(shù)的原型如下:
time_t time(time_t *tloc);
它接受一個(gè)指向 time_t 類型的指針作為參數(shù),并將當(dāng)前的系統(tǒng)時(shí)間(以從1970年1月1日00:00:00 UTC開始的秒數(shù))存儲(chǔ)在該指針指向的位置上。如果參數(shù)tloc為NULL,則time函數(shù)只返回當(dāng)前時(shí)間的秒數(shù),而不保存到任何變量中。
補(bǔ)充這個(gè)函數(shù)是因?yàn)榈谝粋€(gè)例子我們先不用信號(hào)來處理:
接下來用信號(hào)來處理一遍:
可以看見使用信號(hào)來進(jìn)行處理會(huì)更加的精確,另外這一塊內(nèi)容可以從匯編的角度來看,這個(gè)具體就參考一些其它的內(nèi)容吧,我就再補(bǔ)充一個(gè)關(guān)鍵字volatile的用法:
在Linux C中,volatile關(guān)鍵字用于告訴編譯器不要對(duì)變量進(jìn)行優(yōu)化,即使這個(gè)變量沒有被修改。
在C語言中,編譯器通常會(huì)對(duì)代碼進(jìn)行優(yōu)化,以提升程序的運(yùn)行效率。這種優(yōu)化可能會(huì)導(dǎo)致某些變量的值被緩存或者寄存器中,而不是直接從內(nèi)存中讀取。這對(duì)于一些循環(huán)或者條件語句中使用的變量很常見。
然而,有些情況下,我們希望編譯器始終從內(nèi)存中讀取變量的值,而不是使用緩存的值。這種情況下,我們就可以使用volatile關(guān)鍵字來告訴編譯器不要對(duì)這個(gè)變量進(jìn)行優(yōu)化。
volatile關(guān)鍵字告訴編譯器這個(gè)變量可能會(huì)被意外地修改,因此不能使用緩存的值。例如,在多線程編程中,一個(gè)線程修改了一個(gè)變量的值,而另一個(gè)線程需要讀取這個(gè)變量的值。如果編譯器對(duì)這個(gè)變量進(jìn)行了優(yōu)化,那么另一個(gè)線程可能無法獲取到修改后的值。因此,在這種情況下,我們需要將這個(gè)變量聲明為volatile。
需要注意的是,volatile關(guān)鍵字只能保證編譯器不會(huì)對(duì)變量進(jìn)行優(yōu)化,但并不能保證變量的值不會(huì)被修改。因此,在多線程編程中,我們還需要使用其他的同步機(jī)制來確保變量的值不會(huì)被意外地修改。
abort()
在Linux中,abort函數(shù)是一個(gè)系統(tǒng)調(diào)用,用于終止當(dāng)前進(jìn)程并生成一個(gè)異常。它的原型如下:
#include <stdlib.h> void abort(void);
abort`函數(shù)的作用是立即終止當(dāng)前進(jìn)程,并產(chǎn)生一個(gè)異常信號(hào)。默認(rèn)情況下,該信號(hào)將被進(jìn)程的信號(hào)處理程序捕獲,并導(dǎo)致程序異常終止。如果進(jìn)程沒有安裝信號(hào)處理程序,或者信號(hào)處理程序未能終止進(jìn)程,則進(jìn)程可能會(huì)繼續(xù)執(zhí)行,但這并不是一個(gè)推薦的做法。
當(dāng)調(diào)用abort函數(shù)時(shí),會(huì)執(zhí)行以下操作:
1、生成一個(gè)異常信號(hào)(通常是SIGABRT)。
2、如果進(jìn)程有未捕獲的異常信號(hào),則立即終止進(jìn)程。
3、如果進(jìn)程有已捕獲的異常信號(hào)處理程序,則執(zhí)行相應(yīng)的處理程序。
4、如果異常信號(hào)處理程序未能終止進(jìn)程,則進(jìn)程繼續(xù)執(zhí)行直到下一個(gè)異?;蛘=K止。
abort函數(shù)通常用于在程序中檢測到無法處理的錯(cuò)誤條件時(shí)終止進(jìn)程。它是一種快速終止進(jìn)程的方法,但需要注意的是,它不會(huì)執(zhí)行任何清理操作(如調(diào)用atexit函數(shù)注冊(cè)的函數(shù))。因此,在使用abort函數(shù)時(shí),需要確保程序能夠正確地處理異常情況,并盡可能進(jìn)行必要的清理工作。
信號(hào)集
信號(hào)集相關(guān)的函數(shù)如下:
上述函數(shù)族都涉及一個(gè)信號(hào)集類型:sigset_t 。
sigemptyset函數(shù)及其相關(guān)函數(shù)是Linux下信號(hào)處理(signal handling)中的重要組成部分。它們用于對(duì)信號(hào)進(jìn)行操作,包括添加、移除和檢查信號(hào)的處理器。這些函數(shù)主要用于處理系統(tǒng)發(fā)出的不同類型的信號(hào),如中斷、異常等。
以下是對(duì)這些函數(shù)的詳細(xì)解釋:
sigemptyset:此函數(shù)用于清空信號(hào)集。它接受一個(gè)sigset_t類型的參數(shù),該參數(shù)是一個(gè)信號(hào)集,所有信號(hào)都被移除。
例如:
#include <signal.h>
sigset_t signal_set;
sigemptyset(&signal_set);
sigfillset:此函數(shù)與sigemptyset相反,它接受一個(gè)sigset_t類型的參數(shù),并將其所有信號(hào)設(shè)置為已添加到該集。
例如:
#include <signal.h>
sigset_t signal_set;
sigfillset(&signal_set);
sigaddset:此函數(shù)將指定的信號(hào)添加到給定的信號(hào)集中。它接受兩個(gè)參數(shù):一個(gè)是sigset_t類型的信號(hào)集,另一個(gè)是要添加的信號(hào)。
例如:
#include <signal.h>
sigset_t signal_set;
sigemptyset(&signal_set); // 清空信號(hào)集
sigaddset(&signal_set, SIGINT); // 添加SIGINT信號(hào)到信號(hào)集中
sigdelset:此函數(shù)從給定的信號(hào)集中刪除指定的信號(hào)。它接受兩個(gè)參數(shù):一個(gè)是sigset_t類型的信號(hào)集,另一個(gè)是要從集中刪除的信號(hào)。
例如:
#include <signal.h>
sigset_t signal_set;
sigfillset(&signal_set); // 添加所有信號(hào)到信號(hào)集中
sigdelset(&signal_set, SIGINT); // 從信號(hào)集中刪除SIGINT信號(hào)
sigismember:此函數(shù)檢查指定的信號(hào)是否存在于給定的信號(hào)集中。它接受兩個(gè)參數(shù):一個(gè)是sigset_t類型的信號(hào)集,另一個(gè)是要檢查的信號(hào)。如果信號(hào)存在于集中,則返回1;否則返回0。
例如:
#include <signal.h>
sigset_t signal_set;
sigfillset(&signal_set); // 添加所有信號(hào)到信號(hào)集中
if (sigismember(&signal_set, SIGINT)) { // 檢查SIGINT信號(hào)是否在信號(hào)集中 // SIGINT信號(hào)在信號(hào)集中,執(zhí)行相應(yīng)的代碼
} else { // SIGINT信號(hào)不在信號(hào)集中,執(zhí)行相應(yīng)的代碼
}
信號(hào)屏蔽字 / pending集的處理
還記得之前說過的mask位圖嗎,我們可以使用 sigprocmask() 這個(gè)系統(tǒng)調(diào)用來進(jìn)行人為的對(duì)mask位圖的干涉。
sigprocmask是Linux系統(tǒng)中的一個(gè)系統(tǒng)調(diào)用,用于在進(jìn)程級(jí)別設(shè)置信號(hào)屏蔽字,即決定哪些信號(hào)可以被進(jìn)程接收和處理。它允許進(jìn)程更改當(dāng)前的內(nèi)核的阻塞信號(hào)集以阻止(屏蔽)或允許(取消屏蔽)特定的信號(hào)傳遞到進(jìn)程。
sigprocmask函數(shù)的原型如下:
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask`函數(shù)接受三個(gè)參數(shù):
how:指定如何修改內(nèi)核阻塞信號(hào)集,有以下幾種方式:
SIG_BLOCK:將內(nèi)核阻塞信號(hào)集與給定的自定義信號(hào)集合set進(jìn)行邏輯或操作,即將set中的信號(hào)添加到屏蔽字中。
SIG_UNBLOCK:將內(nèi)核阻塞信號(hào)集與給定的自定義信號(hào)集合set的補(bǔ)集進(jìn)行邏輯與操作,即從屏蔽字中移除set中的信號(hào)。
SIG_SETMASK:將內(nèi)核的阻塞信號(hào)集設(shè)置為給定的信號(hào)集合set。
set:指向一個(gè)信號(hào)集合的指針,表示需要修改的信號(hào)集合。
oldset:指向一個(gè)信號(hào)集合的指針,用于存儲(chǔ)修改前的內(nèi)核的阻塞信號(hào)集。如果不關(guān)心舊的內(nèi)核的阻塞信號(hào)集,可以傳遞NULL。
sigprocmask函數(shù)返回成功時(shí)返回0,出錯(cuò)時(shí)返回-1并設(shè)置errno。在調(diào)用sigprocmask之后,內(nèi)核的阻塞信號(hào)集將根據(jù)how參數(shù)的值進(jìn)行相應(yīng)的修改。被屏蔽的信號(hào)不會(huì)被進(jìn)程接收,而會(huì)被暫時(shí)掛起。當(dāng)信號(hào)再次變得可接收時(shí),這些信號(hào)將被傳遞給進(jìn)程。
我們來寫一個(gè)小例子來感受這個(gè)事情,這個(gè)程序是建立在之前的打印*號(hào)的程序基礎(chǔ)上改的:
編譯運(yùn)行就有了如下效果:
從輸出的第一行和第二行可以看到,我們先用ctrl+c發(fā)出了一個(gè)sigint信號(hào),但是直到第二行開始的時(shí)候該信號(hào)才被響應(yīng),這符合我們的預(yù)期。第七行和第八行,我們連續(xù)發(fā)送多個(gè)sigint信號(hào),可以看見該信號(hào)只會(huì)被響應(yīng)一次,這是因?yàn)榫退惆l(fā)送多次,因?yàn)樵撔盘?hào)的mask位已經(jīng)被置為0了,所以收到多少次其實(shí)都不會(huì)被響應(yīng),而最后mask位重新被置為1時(shí)也就只能響應(yīng)到一次了(具體可以看上面的信號(hào)的響應(yīng)過程那一章節(jié))。
而sigprocmask的第三個(gè)參數(shù)是用來恢復(fù)信號(hào)集狀態(tài)的,依然是改上面的程序:
運(yùn)行效果相同,這里不再贅述。
上面說了關(guān)于mask位圖的操作系統(tǒng)調(diào)用,對(duì)于pending位圖也是有的,叫sigpending():
sigpending函數(shù)是Linux中的一個(gè)系統(tǒng)調(diào)用,用于查詢當(dāng)前進(jìn)程的未決信號(hào)集合。未決信號(hào)集合是指那些已經(jīng)發(fā)送給進(jìn)程但尚未被處理的信號(hào)。這些信號(hào)可能因?yàn)楸黄帘味鵁o法立即傳遞給進(jìn)程。
sigpending函數(shù)的原型如下:
#include<signal.h>
int sigpending(sigset_t *set);
使用此函數(shù)需導(dǎo)入<signal.h>頭文件。
該函數(shù)將進(jìn)程的未決信號(hào)集合存儲(chǔ)在參數(shù)set指向的位置。如果成功,函數(shù)返回0,否則返回-1并設(shè)置errno以指示錯(cuò)誤。
在Linux中,可以通過修改內(nèi)核頭文件signal.h中的函數(shù)實(shí)現(xiàn)自定義的信號(hào)處理。例如,可以在自定義的信號(hào)處理函數(shù)中調(diào)用sigpending函數(shù)來獲取當(dāng)前進(jìn)程的未決信號(hào)集合。
需要注意的是,在多線程或多進(jìn)程環(huán)境下,由于多個(gè)線程或進(jìn)程可能同時(shí)處理相同的信號(hào),因此使用sigpending函數(shù)時(shí)需要考慮線程安全和進(jìn)程間同步的問題。同時(shí),在調(diào)用sigpending函數(shù)時(shí)也需要保證進(jìn)程處于正確的狀態(tài),例如不能在信號(hào)處理函數(shù)中調(diào)用sigpending函數(shù),因?yàn)檫@可能會(huì)導(dǎo)致競爭條件和不可預(yù)測的行為。
但是這個(gè)系統(tǒng)調(diào)用基本上用不到,就了解一下即可。
更好的信號(hào)相關(guān)的系統(tǒng)調(diào)用:sigsuspend()、sigaction()、setitimer()
sigsuspend()
這個(gè)系統(tǒng)調(diào)用可以用來幫我們寫信號(hào)驅(qū)動(dòng)程序,是一個(gè)非常強(qiáng)大的系統(tǒng)調(diào)用。
sigsuspend函數(shù)是Linux中的系統(tǒng)調(diào)用,用于掛起進(jìn)程并等待特定的信號(hào)。它可以看作是sigprocmask函數(shù)和pause函數(shù)的組合。
sigsuspend函數(shù)的原型如下:
#include<signal.h>
int sigsuspend(const sigset_t *mask);
使用此函數(shù)需導(dǎo)入<signal.h>頭文件。
sigsuspend函數(shù)會(huì)暫停進(jìn)程的執(zhí)行,直到收到指定的信號(hào)。它接受一個(gè)參數(shù)mask,該參數(shù)是一個(gè)信號(hào)集,用于指定需要屏蔽的信號(hào)。當(dāng)收到指定的信號(hào)時(shí),進(jìn)程會(huì)恢復(fù)執(zhí)行。
與pause函數(shù)相比,sigsuspend函數(shù)更加靈活。它可以指定需要屏蔽的信號(hào),而不是簡單地等待任何信號(hào)。同時(shí),sigsuspend函數(shù)也解決了競態(tài)條件的問題。當(dāng)調(diào)用sigsuspend函數(shù)時(shí),進(jìn)程的信號(hào)屏蔽字由mask參數(shù)指定,可以通過指定mask來臨時(shí)解除對(duì)某個(gè)信號(hào)的屏蔽,然后掛起等待。當(dāng)sigsuspend返回時(shí),進(jìn)程的信號(hào)屏蔽字恢復(fù)為原來的值,如果原來對(duì)該信號(hào)是屏蔽的,從sigsuspend返回后仍然是屏蔽的。
需要注意的是,在多線程或多進(jìn)程環(huán)境下,使用sigsuspend函數(shù)時(shí)需要考慮線程安全和進(jìn)程間同步的問題。同時(shí),在調(diào)用sigsuspend函數(shù)時(shí)也需要保證進(jìn)程處于正確的狀態(tài),例如不能在信號(hào)處理函數(shù)中調(diào)用sigsuspend函數(shù),因?yàn)檫@可能會(huì)導(dǎo)致競爭條件和不可預(yù)測的行為。
接下來我們使用剛剛的程序來實(shí)現(xiàn)一個(gè)例子,之前是阻塞信號(hào)處理函數(shù)到下一行開始的時(shí)候才執(zhí)行,現(xiàn)在改成阻塞住之后通過信號(hào)驅(qū)動(dòng)的方式再執(zhí)行信號(hào)處理函數(shù)進(jìn)行打印!,否則就一直阻塞住,其實(shí)這個(gè)使用我們之前說過的pause系統(tǒng)調(diào)用就能實(shí)現(xiàn),但是它有缺陷;
從第一行和第二行可以看到確實(shí)是達(dá)到了我們一開始想要的效果,但有個(gè)問題,就是我們連續(xù)發(fā)送sigint信號(hào)時(shí),信號(hào)是會(huì)打斷阻塞的系統(tǒng)調(diào)用的(也就是上圖中的sleep系統(tǒng)調(diào)用,都沒有被阻塞一秒就直接打印*號(hào)了)。
我們想要的效果是在每一行*打印期間,就算收到了信號(hào)但是也不做出響應(yīng),此時(shí)就得使用我們這里說的sigsuspend系統(tǒng)調(diào)用了:
這個(gè)例子舉的有點(diǎn)亂…看不懂就算了,以后經(jīng)驗(yàn)豐富起來了應(yīng)該就懂了。
sigaction()
sigaction函數(shù)是一個(gè)Linux系統(tǒng)調(diào)用,用于設(shè)置信號(hào)處理函數(shù),以指定在進(jìn)程收到信號(hào)時(shí)應(yīng)采取的操作。
函數(shù)原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
參數(shù):
signum:指定要捕獲的信號(hào)類型。
act:指向一個(gè)sigaction結(jié)構(gòu)體的指針,該結(jié)構(gòu)體包含新的信號(hào)處理方式。
oldact:指向一個(gè)sigaction結(jié)構(gòu)體的指針,用于輸出先前信號(hào)的處理方式(如果不為NULL的話)。
sigaction函數(shù)允許您同時(shí)檢查或修改與指定信號(hào)相關(guān)聯(lián)的處理動(dòng)作。如果您希望使用自定義的信號(hào)處理函數(shù),可以將act參數(shù)設(shè)置為指向您自定義函數(shù)的結(jié)構(gòu)體指針。如果oldact參數(shù)不為NULL,則該參數(shù)用于輸出先前信號(hào)的處理方式。
使用sigaction函數(shù)可以設(shè)置信號(hào)處理函數(shù)以捕獲各種類型的信號(hào),例如中斷(如SIGINT)、異常(如SIGSEGV)等。在信號(hào)處理函數(shù)中,您可以執(zhí)行特定的操作,例如清理和終止程序、跳轉(zhuǎn)到備用代碼等。
請(qǐng)注意,在使用sigaction函數(shù)時(shí),需要確保信號(hào)處理函數(shù)的可重入性,并避免在信號(hào)處理函數(shù)中調(diào)用其他可能導(dǎo)致不可預(yù)測行為的系統(tǒng)調(diào)用。
對(duì)于這個(gè)系統(tǒng)調(diào)用的第二個(gè)結(jié)構(gòu)體參數(shù)再進(jìn)行一個(gè)詳細(xì)的說明:
在Linux的sigaction系統(tǒng)調(diào)用中,第二個(gè)參數(shù)是一個(gè)指向struct sigaction結(jié)構(gòu)體的指針。struct sigaction結(jié)構(gòu)體是一個(gè)用于描述信號(hào)處理函數(shù)和相關(guān)屬性的數(shù)據(jù)結(jié)構(gòu)。
以下是 struct sigaction 結(jié)構(gòu)體的主要成員:
__sighandler_t sa_handler:這是一個(gè)函數(shù)指針,它指向在進(jìn)程接收到信號(hào)時(shí)被調(diào)用的信號(hào)處理函數(shù)。這個(gè)函數(shù)通常接受一個(gè)整數(shù)參數(shù),即信號(hào)的值。當(dāng)信號(hào)被捕獲時(shí),內(nèi)核將調(diào)用這個(gè)函數(shù)來處理信號(hào)。
sigset_t sa_mask:這是一個(gè)信號(hào)集,用于指定在信號(hào)處理函數(shù)執(zhí)行期間需要被屏蔽的信號(hào)。在信號(hào)處理函數(shù)的執(zhí)行期間,這些信號(hào)將被忽略。這樣可以防止在處理一個(gè)信號(hào)時(shí)被其他信號(hào)中斷。
unsigned long sa_flags:這是一組標(biāo)志位,用于指定信號(hào)處理函數(shù)的屬性。這些標(biāo)志位可以控制信號(hào)處理函數(shù)的執(zhí)行方式,例如是否在處理函數(shù)返回后自動(dòng)重新注冊(cè)處理函數(shù)等。
void (*sa_restorer)(void):這是一個(gè)可選的函數(shù)指針,用于恢復(fù)信號(hào)處理函數(shù)之前的狀態(tài)。在信號(hào)處理函數(shù)的執(zhí)行期間,內(nèi)核將使用這個(gè)函數(shù)來恢復(fù)處理函數(shù)的先前狀態(tài),以便在處理函數(shù)返回后可以繼續(xù)執(zhí)行。
使用sigaction系統(tǒng)調(diào)用時(shí),可以將struct sigaction結(jié)構(gòu)體中的sa_handler成員設(shè)置為自定義的信號(hào)處理函數(shù),以指定在接收到特定信號(hào)時(shí)應(yīng)該執(zhí)行的操作。同時(shí),還可以根據(jù)需要設(shè)置sa_mask和sa_flags等成員來進(jìn)一步控制信號(hào)處理的行為。這個(gè)結(jié)構(gòu)體中的最后一個(gè) sa_restorer 是已經(jīng)不怎么用了的,了解一下即可,主要是用其它的幾個(gè)。
我們可以使用這個(gè)系統(tǒng)調(diào)用來取代signal()系統(tǒng)調(diào)用,這是一種更好的做法,因?yàn)閟ignal系統(tǒng)調(diào)用自身存在一定缺陷。
我們改寫一個(gè)之前寫的守護(hù)進(jìn)程的那個(gè)例子來感受一下這個(gè)系統(tǒng)調(diào)用的用法:
setitimer()
setitimer系統(tǒng)調(diào)用是Linux系統(tǒng)中的一個(gè)功能,用于設(shè)置一個(gè)間隔性定時(shí)器。它可以設(shè)置一個(gè)進(jìn)程在特定時(shí)間間隔后接收一個(gè)信號(hào)(signal),通常用于限制程序或操作的執(zhí)行時(shí)間、定期執(zhí)行任務(wù)等。
setitimer系統(tǒng)調(diào)用的原型如下:
#include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
setitimer`函數(shù)接受三個(gè)參數(shù):
which:指定設(shè)置哪種類型的定時(shí)器,有以下三種類型:
ITIMER_REAL:以實(shí)時(shí)時(shí)間遞減,到期后發(fā)送SIGALRM信號(hào)。
ITIMER_VIRTUAL:只有在進(jìn)程執(zhí)行時(shí)才遞減,到期后發(fā)送SIGVTALRM信號(hào)。
ITIMER_PROF:在進(jìn)程被執(zhí)行和系統(tǒng)在代表該進(jìn)程執(zhí)行的時(shí)間都進(jìn)行計(jì)數(shù),到期后發(fā)送SIGPROF信號(hào)。
new_value:指向一個(gè)itimerval結(jié)構(gòu)體的指針,用于設(shè)置定時(shí)器的初始值和間隔時(shí)間。
old_value:指向一個(gè)itimerval結(jié)構(gòu)體的指針,用于返回定時(shí)器的原有值。
itimerval結(jié)構(gòu)體包含兩個(gè)成員:it_interval和it_value,分別表示定時(shí)器的間隔時(shí)間和初始值。這兩個(gè)成員都是以秒和微秒為單位進(jìn)行計(jì)時(shí)的。
當(dāng)定時(shí)器到期時(shí),系統(tǒng)會(huì)發(fā)送一個(gè)信號(hào)給進(jìn)程。進(jìn)程可以捕獲這個(gè)信號(hào)并執(zhí)行相應(yīng)的操作。一般來說,可以使用signal函數(shù)或sigaction函數(shù)來處理這些信號(hào)。
需要注意的是,一個(gè)進(jìn)程只能有一個(gè)setitimer定時(shí)器,并且不支持在同一進(jìn)程中同時(shí)使用多次以支持多個(gè)定時(shí)器。如果需要多個(gè)定時(shí)器,可以通過編程實(shí)現(xiàn)多個(gè)定時(shí)器的邏輯。
getitimer系統(tǒng)調(diào)用是Linux系統(tǒng)中的一個(gè)功能,用于獲取一個(gè)進(jìn)程的間隔性定時(shí)器的狀態(tài)。它可以用于查詢定時(shí)器的當(dāng)前值、剩余時(shí)間以及定時(shí)器的類型。
getitimer系統(tǒng)調(diào)用的原型如下:
#include <sys/time.h> int getitimer(int which, struct itimerval *value);
getitimer`函數(shù)接受兩個(gè)參數(shù):
which:指定要查詢哪種類型的定時(shí)器,和setitimer系統(tǒng)調(diào)用中的which參數(shù)相同。
value:指向一個(gè)itimerval結(jié)構(gòu)體的指針,用于保存定時(shí)器的當(dāng)前值和剩余時(shí)間。
當(dāng)調(diào)用getitimer系統(tǒng)調(diào)用時(shí),會(huì)返回該定時(shí)器的當(dāng)前值和剩余時(shí)間,以及定時(shí)器的類型(即ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF)。如果定時(shí)器沒有設(shè)置或者不存在,則返回-1并設(shè)置errno為ENOENT。
使用getitimer系統(tǒng)調(diào)用可以方便地獲取進(jìn)程的定時(shí)器狀態(tài),以便進(jìn)行監(jiān)控和管理。例如,可以通過查詢定時(shí)器的剩余時(shí)間來判斷是否需要進(jìn)行某些操作,或者通過獲取定時(shí)器的當(dāng)前值來判斷是否已經(jīng)超過了某個(gè)時(shí)間閾值。
我們可以使用這個(gè)系統(tǒng)調(diào)用來取代 alarm 系統(tǒng)調(diào)用。
實(shí)時(shí)信號(hào)
Linux下的實(shí)時(shí)信號(hào)是一種改進(jìn)的信號(hào)機(jī)制,相對(duì)于標(biāo)準(zhǔn)信號(hào)而言,它具有更高的可靠性和可控性。
標(biāo)準(zhǔn)信號(hào)是 Linux 系統(tǒng)中的一種異步通信方式,用于通知進(jìn)程發(fā)生了某種事件。標(biāo)準(zhǔn)信號(hào)的投遞順序未定義,且信號(hào)不排隊(duì)會(huì)丟失。當(dāng)一個(gè)進(jìn)程多次接收到相同的信號(hào)時(shí),它只會(huì)接收到一次信號(hào),而丟失的信號(hào)將無法被處理。此外,標(biāo)準(zhǔn)信號(hào)中用于自定義的只有SIGUSER1和SIGUSER2,而實(shí)時(shí)信號(hào)的信號(hào)范圍有所擴(kuò)大,可供應(yīng)用程序自定義的目的。
實(shí)時(shí)信號(hào)是一種改進(jìn)的信號(hào)機(jī)制,它解決了標(biāo)準(zhǔn)信號(hào)的缺陷。實(shí)時(shí)信號(hào)采取的是隊(duì)列化管理,如果某一個(gè)信號(hào)多次發(fā)送給一個(gè)進(jìn)程,該進(jìn)程會(huì)多次收到這個(gè)信號(hào)。此外,實(shí)時(shí)信號(hào)還具備以下優(yōu)勢:
傳遞順序有保障:不同的實(shí)時(shí)信號(hào)之間有傳遞順序的保障,信號(hào)的編號(hào)越小優(yōu)先級(jí)越高。
可伴隨數(shù)據(jù):實(shí)時(shí)信號(hào)可以伴隨數(shù)據(jù),供接收進(jìn)程的信號(hào)處理器使用。
綜上所述,實(shí)時(shí)信號(hào)和標(biāo)準(zhǔn)信號(hào)之間的區(qū)別在于:實(shí)時(shí)信號(hào)解決了標(biāo)準(zhǔn)信號(hào)的缺陷,具備更高的可靠性和可控性,同時(shí)具備傳遞順序保障和伴隨數(shù)據(jù)等優(yōu)勢。
標(biāo)準(zhǔn)信號(hào)是Unix系統(tǒng)早期定義的信號(hào)類型,被稱為傳統(tǒng)信號(hào)或標(biāo)準(zhǔn)信號(hào)。傳統(tǒng)信號(hào)的范圍是1到31,用整數(shù)方式表示,例如,SIGINT是2,SIGALRM是14。傳統(tǒng)信號(hào)的處理方式是異步的,即信號(hào)發(fā)送后立即觸發(fā)信號(hào)處理程序執(zhí)行,中斷當(dāng)前進(jìn)程的正常執(zhí)行流程。傳統(tǒng)信號(hào)的處理程序是函數(shù)指針,可以由進(jìn)程注冊(cè)自定義的信號(hào)處理函數(shù),用于在接收到信號(hào)時(shí)執(zhí)行特定的操作。如果進(jìn)程沒有為某個(gè)信號(hào)注冊(cè)處理函數(shù),操作系統(tǒng)將采用默認(rèn)的處理方式,例如終止進(jìn)程、忽略信號(hào)或者產(chǎn)生核心轉(zhuǎn)儲(chǔ)文件。
實(shí)時(shí)信號(hào)(Real-Time Signals)是在POSIX.1b標(biāo)準(zhǔn)中引入的一種更高級(jí)的信號(hào)機(jī)制。實(shí)時(shí)信號(hào)的范圍是從實(shí)時(shí)信號(hào)1(SIGRTMIN)到實(shí)時(shí)信號(hào)31(SIGRTMAX),共計(jì)64個(gè)信號(hào)。實(shí)時(shí)信號(hào)的處理方式可以是同步的或異步的,具體取決于進(jìn)程設(shè)置的信號(hào)發(fā)送和接收機(jī)制。實(shí)時(shí)信號(hào)的處理程序是函數(shù)指針,可以由進(jìn)程注冊(cè)自定義的信號(hào)處理函數(shù),用于在接收到信號(hào)時(shí)執(zhí)行特定的操作。實(shí)時(shí)信號(hào)的一個(gè)重要特點(diǎn)是可以傳遞一個(gè)整數(shù)值作為附加數(shù)據(jù),這使得實(shí)時(shí)信號(hào)在進(jìn)程間通信中非常有用。實(shí)時(shí)信號(hào)相對(duì)于傳統(tǒng)信號(hào)具有更高的靈活性和可靠性,且能夠提供更細(xì)粒度的信號(hào)處理。傳統(tǒng)信號(hào)在某些情況下可能會(huì)發(fā)生競爭條件或丟失信號(hào)的問題,而實(shí)時(shí)信號(hào)可以幫助解決這些問題。
實(shí)時(shí)信號(hào)并沒有相關(guān)的可以使用的系統(tǒng)調(diào)用或者函數(shù),實(shí)時(shí)信號(hào)的使用與標(biāo)準(zhǔn)信號(hào)的使用相比其實(shí)就是值的不同,用不同的值來區(qū)別系統(tǒng)調(diào)用使用的是標(biāo)準(zhǔn)信號(hào)還是實(shí)時(shí)信號(hào),比如下面要使用實(shí)時(shí)信號(hào) SIGRTMIN+6,那么我們可以宏定義一個(gè)值來代替它:
此時(shí)在程序中直接使用這個(gè)值就可以使用實(shí)時(shí)信號(hào)了: