APUE第10章 信號

第10章 信號

signal

10.1 引言

信號是軟件中斷枯冈。很多比較重要的應(yīng)用程序都需處理信號。信號提 供了一種處理異步事件的方法昆咽,例如驾凶,終端用戶鍵入中斷鍵,會通過信 號機制停止一個程序掷酗,或及早終止管道中的下一個程序调违。

UNIX系統(tǒng)的早期版本就已經(jīng)提供信號機制,但是這些系統(tǒng)(如 V7)所提供的信號模型并不可靠泻轰。信號可能丟失技肩,而且在執(zhí)行臨界區(qū)代 碼時,進程很難關(guān)閉所選擇的信號浮声。4.3BSD 和 SVR3對信號模型都做了 更改虚婿,增加了可靠信號機制。但是Berkeley和AT&T所做的更改之間并不 兼容泳挥。幸運的是雳锋,POSIX.1對可靠信號例程進行了標準化,這正是本章 所要說明的羡洁。

本章先對信號機制進行綜述玷过,并說明每種信號的一般用法。然后分 析早期實現(xiàn)的問題筑煮。在分析存在的問題之后再說明解決這些問題的方 法辛蚊,這種安排有助于加深對改進機制的理解。本章也包含了很多并非完 全正確的實例真仲,這樣做的目的是為了對其不足之處進行討論袋马。

10.2 信號概念

首先,每個信號都有一個名字秸应。這些名字都以3個字符SIG開頭虑凛。例 如碑宴,SIGABRT是夭折信號,當進程調(diào)用abort函數(shù)時產(chǎn)生這種信號桑谍。 SIGALRM是鬧鐘信號延柠,由alarm函數(shù)設(shè)置的定時器超時后將產(chǎn)生此信 號。V7 有 15 種不同的信號锣披,SVR4 和 4.4BSD 均有 31 種不同的信號贞间。 FreeBSD 8.0支持32種信號,Mac OS X 10.6.8以及Linux 3.2.0都支持31種信 號雹仿,而Solaris 10支持40種信號增热。但是,F(xiàn)reeBSD胧辽、Linux和Solaris作為實時 擴展都支持另外的應(yīng)用程序定義的信號峻仇。雖然本書不包括POSIX實時擴 展(有關(guān)信息請參閱Gallmeister[1995]),但是SUSv4已經(jīng)把實時信號接 口移至基礎(chǔ)規(guī)范說明中邑商。

在頭文件<signal.h>中摄咆,信號名都被定義為正整數(shù)常量(信號編 號)。
實際上奠骄,實現(xiàn)將各信號定義在另一個頭文件中,但是該頭文件又包 括在<signal.h>中番刊。內(nèi)核包括對用戶級應(yīng)用程序有意義的頭文件含鳞,這被 認為是一種不好的形式,所以如若應(yīng)用程序和內(nèi)核兩者都需使用同一定 義芹务,那么就將有關(guān)信息放置在內(nèi)核頭文件中蝉绷,然后用戶級頭文件再包括 該內(nèi)核頭文件。于是枣抱,F(xiàn)reeBSD 8.0和Mac OS X 10.6.8將信號定義在 <sys/signal.h>中熔吗,Linux 3.2.0將信號定義在<bits/signum.h>中, Solaris 10將信號定義在<sys/iso/signal_iso.h>中佳晶。

不存在編號為 0 的信號桅狠。在 10.9 節(jié)中將會看到,kill 函數(shù)對信號編 號 0 有特殊的應(yīng)用轿秧。POSIX.1將此種信號編號值稱為空信號中跌。
很多條件可以產(chǎn)生信號。 ?當用戶按某些終端鍵時菇篡,引發(fā)終端產(chǎn)生的信號漩符。在終端上按 Delete

鍵(或者很多系統(tǒng)中的Ctrl+C鍵)通常產(chǎn)生中斷信號(SIGINT)。這是 停止一個已失去控制程序的方法驱还。(第18章將說明此信號可被映射為終 端上的任一字符嗜暴。)

?硬件異常產(chǎn)生信號:除數(shù)為0凸克、無效的內(nèi)存引用等。這些條件通常 由硬件檢測到闷沥,并通知內(nèi)核萎战。然后內(nèi)核為該條件發(fā)生時正在運行的進程 產(chǎn)生適當?shù)男盘枴@绾模瑢?zhí)行一個無效內(nèi)存引用的進程產(chǎn)生SIGSEGV 信號撞鹉。
?進程調(diào)用kill(2)函數(shù)可將任意信號發(fā)送給另一個進程或進程組。自 然颖侄,對此有所限制:接收信號進程和發(fā)送信號進程的所有者必須相同鸟雏, 或發(fā)送信號進程的所有者必須是超級用戶。

?用戶可用kill(1)命令將信號發(fā)送給其他進程览祖。此命令只是kill函數(shù)的 接口孝鹊。常用此命令終止一個失控的后臺進程。
?當檢測到某種軟件條件已經(jīng)發(fā)生展蒂,并應(yīng)將其通知有關(guān)進程時也產(chǎn) 生信號又活。這里指的不是硬件產(chǎn)生條件(如除以 0),而是軟件條件锰悼。例 如 SIGURG(在網(wǎng)絡(luò)連接上傳來帶外的數(shù)據(jù))柳骄、SIGPIPE(在管道的讀 進程已終止后,一個進程寫此管道)以及 SIGALRM(進程所設(shè)置的定 時器已經(jīng)超時)箕般。
信號是異步事件的經(jīng)典實例耐薯。產(chǎn)生信號的事件對進程而言是隨機出 現(xiàn)的。進程不能簡單地測試一個變量(如errno)來判斷是否發(fā)生了一個 信號丝里,而是必須告訴內(nèi)核“在此信號發(fā)生時曲初,請執(zhí)行下列操作”。
在某個信號出現(xiàn)時杯聚,可以告訴內(nèi)核按下列3種方式之一進行處理臼婆, 我們稱之為信號的處理或與信號相關(guān)的動作。

(2)捕捉信號幌绍。為了做到這一點颁褂,要通知內(nèi)核在某種信號發(fā)生 時,調(diào)用一個用戶函數(shù)傀广。在用戶函數(shù)中痢虹,可執(zhí)行用戶希望對這種事件進 行的處理。例如主儡,若正在編寫一個命令解釋器奖唯,它將用戶的輸入解釋為 命令并執(zhí)行之,當用戶用鍵盤產(chǎn)生中斷信號時糜值,很可能希望該命令解釋 器返回到主循環(huán)丰捷,終止正在為該用戶執(zhí)行的命令坯墨。如果捕捉到 SIGCHLD 信號,則表示一個子進程已經(jīng)終止病往,所以此信號的捕捉函數(shù) 可以調(diào)用waitpid以取得該子進程的進程ID以及它的終止狀態(tài)捣染。又例如, 如果進程創(chuàng)建了臨時文件停巷,那么可能要為 SIGTERM 信號編寫一個信號 捕捉函數(shù)以清除臨時文件(SIGTERM 是終止信號耍攘,kill 命令傳送的系統(tǒng) 默認信號是終止信號)。注意畔勤,不能捕捉SIGKILL和SIGSTOP信號蕾各。

SIGABRT 調(diào)用abort函數(shù)時(見10.17節(jié))產(chǎn)生此信號。進程異常終 止庆揪。
SIGALRM 當用alarm函數(shù)設(shè)置的定時器超時時式曲,產(chǎn)生此信號。詳細 情況見10.10節(jié)缸榛。若由setitimer(2)函數(shù)設(shè)置的間隔時間已經(jīng)超時時吝羞,也產(chǎn) 生此信號。
SIGBUS 指示一個實現(xiàn)定義的硬件故障内颗。當出現(xiàn)某些類型的內(nèi)存故
障時(如 14.8 節(jié)中說明的)钧排,實現(xiàn)常常產(chǎn)生此種信號。
SIGCANCEL 這是Solaris線程庫內(nèi)部使用的信號均澳。它不適用于一般
應(yīng)用恨溜。
SIGCHLD 在一個進程終止或停止時,SIGCHLD信號被送給其父進
程负懦。按系統(tǒng)默認筒捺,將忽略此信號柏腻。如果父進程希望被告知其子進程的這 種狀態(tài)改變纸厉,則應(yīng)捕捉此信號。信號捕捉函數(shù)中通常要調(diào)用一種wait函 數(shù)以取得子進程ID和其終止狀態(tài)五嫂。System V的早期版本有一個名為 SIGCLD(無H)的類似信號颗品。這一信號具有與其他信號不同的語義, SVR2的手冊頁警告在新的程序中盡量不要使用這種信號沃缘。(令人奇怪 的是躯枢,在SVR3和SVR4版的手冊頁中,該警告消失了槐臀。)應(yīng)用程序應(yīng)當 使用標準的SIGCHLD信號锄蹂,但應(yīng)了解,為了向后兼容水慨,很多系統(tǒng)定義 了與SIGCHLD等同的SIGCLD得糜。如果有使用SIGCLD的軟件敬扛,需要查閱 系統(tǒng)手冊,了解它具體的語義朝抖。10.7節(jié)將討論這兩個信號啥箭。
SIGCONT 此作業(yè)控制信號發(fā)送給需要繼續(xù)運行,但當前處于停止 狀態(tài)的進程治宣。如果接收到此信號的進程處于停止狀態(tài)急侥,則系統(tǒng)默認動作 是使該進程繼續(xù)運行;否則默認動作是忽略此信號。例如侮邀,全屏編輯程 序在捕捉到此信號后坏怪,使用信號處理程序發(fā)出重新繪制終端屏幕的通 知。關(guān)于進一步的情況見10.21節(jié)豌拙。
SIGEMT 指示一個實現(xiàn)定義的硬件故障陕悬。

SIGFPE 此信號表示一個算術(shù)運算異常,如除以0按傅、浮點溢出等捉超。
SIGFREEZE 此信號僅由Solaris定義。它用于通知進程在凍結(jié)系統(tǒng) 狀態(tài)之前需要采取特定動作唯绍,例如當系統(tǒng)進入休眠或掛起狀態(tài)時可能需 要做這種處理拼岳。
SIGHUP 如果終端接口檢測到一個連接斷開,則將此信號送給與該
終端相關(guān)的控制進程(會話首進程)况芒。見圖9-13惜纸,此信號被送給session 結(jié)構(gòu)中s_leader字段所指向的進程。僅當終端的CLOCAL標志沒有設(shè)置 時绝骚,在上述條件下才產(chǎn)生此信號耐版。(如果所連接的終端是本地的,則設(shè) 置該終端的CLOCAL標志压汪。它告訴終端驅(qū)動程序忽略所有調(diào)制解調(diào)器的 狀態(tài)行粪牲。第18章將說明如何設(shè)置此標志。)
SIGILL 此信號表示進程已執(zhí)行一條非法硬件指令止剖。
SIGINFO 這是一種BSD信號腺阳,當用戶按狀態(tài)鍵(一般采用Ctrl+T) 時,終端驅(qū)動程序產(chǎn)生此信號并發(fā)送至前臺進程組中的每一個進程(見 圖 9-9)穿香。此信號通常造成在終端上顯示前臺進程組中各進程的狀態(tài)信 息亭引。

SIGINT 當用戶按中斷鍵(一般采用 Delete 或 Ctrl+C)時,終端驅(qū) 動程序產(chǎn)生此信號并發(fā)送至前臺進程組中的每一個進程(見圖9-9)皮获。 當一個進程在運行時失控焙蚓,特別是它正在屏幕上產(chǎn)生大量不需要的輸出 時,常用此信號終止它。

SIGKILL 這是兩個不能被捕捉或忽略信號中的一個购公。它向系統(tǒng)管理
員提供了一種可以殺死任一進程的可靠方法赵哲。

SIGQUIT 當用戶在終端上按退出鍵(一般采用Ctrl+)時,中斷驅(qū) 動程序產(chǎn)生此信號君丁,并發(fā)送給前臺進程組中的所有進程(見圖9-9)枫夺。 此信號不僅終止前臺進程組(如SIGINT所做的那樣),同時產(chǎn)生一個 core文件绘闷。
SIGSEGV 指示進程進行了一次無效的內(nèi)存引用(通常說明程序有 錯橡庞,比如訪問了一個未經(jīng)初始化的指針)。

SIGSTOP 這是一個作業(yè)控制信號印蔗,它停止一個進程扒最。它類似于交 互停止信號(SIGTSTP),但是SIGSTOP不能被捕捉或忽略华嘹。

SIGKILL 和SIGSTOP 不能被忽略和捕捉

SIGTERM 這是由kill(1)命令發(fā)送的系統(tǒng)默認終止信號吧趣。由于該信號 是由應(yīng)用程序捕獲的,使用SIGTERM也讓程序有機會在退出之前做好 清理工作耙厚,從而優(yōu)雅地終止(相對于SIGKILL而言强挫。SIGKILL不能被捕 捉或者忽略)。

10.3 signal函數(shù)

函數(shù)signal

UNIX系統(tǒng)信號機制最簡單的接口是signal函數(shù)薛躬。

#include <signal.h>
    void (*signal(int signo, void (*func)(int)))(int);

返回值:若成功俯渤,返回以前的信號處理配置;若出錯,返回SIG_ERR

signal函數(shù)由ISO C定義型宝。因為ISO C不涉及多進程八匠、進程組以及終 端I/O等,所以它對信號的定義非常含糊趴酣,以致于對UNIX系統(tǒng)而言幾乎 毫無用處梨树。
從UNIX System V派生的實現(xiàn)支持signal函數(shù),但該函數(shù)提供舊的 不可靠信號語義(10.4節(jié)將說明這些舊的語義)岖寞。提供此函數(shù)主要是為 了向后兼容要求此舊語義的應(yīng)用程序抡四,新應(yīng)用程序不應(yīng)使用這些不可靠 信號。
4.4BSD 也提供 signal 函數(shù)慎璧,但它是按照 sigaction 函數(shù)定義
所以在 4.4BSD 之下使用它提供 新的可靠信號語義床嫌。目前大多數(shù)系統(tǒng)遵循這種策略跨释,但Solaris 10沿用 System V signal函數(shù)的語義胸私。

signo參數(shù)是圖10-1中的信號名。func的值是常量SIG_IGN鳖谈、常量 SIG_DFL或當接到此信號后要調(diào)用的函數(shù)的地址岁疼。如果指定SIG_IGN, 則向內(nèi)核表示忽略此信號(記住有兩個信號SIGKILL和SIGSTOP不能忽略

當指定函數(shù)地址時,則在信號發(fā)生時捷绒,調(diào) 用該函數(shù)瑰排,我們稱這種處理為捕捉該信號,稱此函數(shù)為信號處理程序 (signal handler)或信號捕捉函數(shù)(signal-catching function)暖侨。

#include <iostream>
#include <stdio.h>
#include <sys/signal.h>
#include <unistd.h>

void add(int x) {
    std::cout << "use signal slover" << std::endl;
    exit(0);
}

int main() {

    /*----------------------------------- test signal ----------------------------------------*/
    void (*f)(int x) = add;
    signal(SIGINT, f);

    for (int i = 0; i < 10; ++i) {
        std::cout << "hello" << std::endl;
        sleep(1);
    }

    return 0;
}

我們可以捕捉信號進行處理椭住,或者忽略信號
捕捉信號SIGTSTP

#include <iostream>
#include <stdio.h>
#include <sys/signal.h>
#include <unistd.h>

void add(int x) {
    std::cout << "use signal slover" << std::endl;
    std::cout << "catch SIGTSTP" << std::endl;
    exit(0);
}

int main() {

    /*----------------------------------- test signal ----------------------------------------*/
    void (*f)(int x) = add;
    signal(SIGTSTP, f);

    for (int i = 0; i < 10; ++i) {
        std::cout << "hello" << std::endl;
        sleep(1);
    }

    return 0;
}

運行時像進程kill -TSTP pid或者輸入ctrl + z即可捕捉到

或者忽略指定信號(SIGKILL 和SIGSTOP不能忽略)

10.5 中斷的系統(tǒng)調(diào)用

早期UNIX系統(tǒng)的一個特性是:如果進程在執(zhí)行一個低速系統(tǒng)調(diào)用 而阻塞期間捕捉到一個信號,則該系統(tǒng)調(diào)用就被中斷不再繼續(xù)執(zhí)行字逗。該 系統(tǒng)調(diào)用返回出錯京郑,其errno設(shè)置為EINTR。這樣處理是因為一個信號發(fā) 生了葫掉,進程捕捉到它些举,這意味著已經(jīng)發(fā)生了某種事情,所以是個好機會 應(yīng)當喚醒阻塞的系統(tǒng)調(diào)用俭厚。
在這里户魏,我們必須區(qū)分系統(tǒng)調(diào)用和函數(shù)。當捕捉到某個信號時挪挤,被 中斷的是內(nèi)核中執(zhí)行的系統(tǒng)調(diào)用叼丑。
為了支持這種特性,將系統(tǒng)調(diào)用分成兩類:低速系統(tǒng)調(diào)用和其他系 統(tǒng)調(diào)用扛门。低速系統(tǒng)調(diào)用是可能會使進程永遠阻塞的一類系統(tǒng)調(diào)用幢码,包 括:

?如果某些類型文件(如讀管道、終端設(shè)備和網(wǎng)絡(luò)設(shè)備)的數(shù)據(jù)不 存在尖飞,則讀操作可能會使調(diào)用者永遠阻塞;
?如果這些數(shù)據(jù)不能被相同的類型文件立即接受症副,則寫操作可能會 使調(diào)用者永遠阻塞;
?在某種條件發(fā)生之前打開某些類型文件,可能會發(fā)生阻塞(例如 要打開一個終端設(shè)備政基,需要先等待與之連接的調(diào)制解調(diào)器應(yīng)答);
?pause函數(shù)(按照定義贞铣,它使調(diào)用進程休眠直至捕捉到一個信號) 和wait函數(shù);
?某些ioctl操作;
?某些進程間通信函數(shù)

在這些低速系統(tǒng)調(diào)用中,一個值得注意的例外是與磁盤I/O有關(guān)的
系統(tǒng)調(diào)用沮明。雖然讀辕坝、寫一個磁盤文件可能暫時阻塞調(diào)用者(在磁盤驅(qū)動

程序?qū)⒄埱笈湃腙犃校缓笤谶m當時間執(zhí)行請求期間)荐健,但是除非發(fā)生 硬件錯誤酱畅,I/O操作總會很快返回,并使調(diào)用者不再處于阻塞狀態(tài)江场。
可以用中斷系統(tǒng)調(diào)用這種方法來處理的一個例子是:一個進程啟動 了讀終端操作纺酸,而使用該終端設(shè)備的用戶卻離開該終端很長時間。在這 種情況下址否,進程可能處于阻塞狀態(tài)幾個小時甚至數(shù)天餐蔬,除非系統(tǒng)停機, 否則一直如此。

對于中斷的read樊诺、write系統(tǒng)調(diào)用仗考,POSIX.1的語義在該標準的2001 版有所改變。對于如何處理已 read词爬、write 部分數(shù)據(jù)量的相應(yīng)系統(tǒng)調(diào) 用秃嗜,早期版本允許實現(xiàn)自行選擇。如若 read系統(tǒng)調(diào)用已接收并傳送數(shù) 據(jù)至應(yīng)用程序緩沖區(qū)顿膨,但尚未接收到應(yīng)用程序請求的全部數(shù)據(jù)痪寻,此時被 中斷,操作系統(tǒng)可以認為該系統(tǒng)調(diào)用失敗虽惭,并將 errno 設(shè)置為 EINTR;另一種處理方式是允許該系統(tǒng)調(diào)用成功返回橡类,返回值是已接收 到的數(shù)據(jù)量。與此類似芽唇,如若write巳傳輸了應(yīng)用程序緩沖區(qū)中的部分 數(shù)據(jù)顾画,然后被中斷,操作系統(tǒng)可以認為該系統(tǒng)調(diào)用失敗,并將errno設(shè) 置為EINTR;另一種處理方式是允許該系統(tǒng)調(diào)用成功返回,返回值是已 寫部分的數(shù)據(jù)量惋戏。歷史上,從System V派生的實現(xiàn)將這種系統(tǒng)調(diào)用視為 失敗庶诡,而 BSD 派生的實現(xiàn)則處理為部分成功返回。2001 版 POSIX.1標 準采用BSD風(fēng)格的語義咆课。

與被中斷的系統(tǒng)調(diào)用相關(guān)的問題是必須顯式地處理出錯返回末誓。典型 的代碼序列(假定進行一個讀操作,它被中斷书蚪,我們希望重新啟動它) 如下:

    again:
      if ((n = read(fd, buf, BUFFSIZE)) < 0) {
        if (errno == EINTR)
          goto again; /* just an interrupted system call */
        /* handle other errors */
      }

為了幫助應(yīng)用程序使其不必處理被中斷的系統(tǒng)調(diào)用喇澡,4.2BSD引進了 某些被中斷系統(tǒng)調(diào)用的自動重啟動。自動重啟動的系統(tǒng)調(diào)用包括: ioctl殊校、read晴玖、readv、write为流、writev呕屎、wait 和waitpid。如前所述敬察,其中前5個 函數(shù)只有對低速設(shè)備進行操作時才會被信號中斷秀睛。而wait和waitpid 在捕 捉到信號時總是被中斷。因為這種自動重啟動的處理方式也會帶來問 題静汤,某些應(yīng)用程序并不希望這些函數(shù)被中斷后重啟動琅催。為此4.3BSD允許 進程基于每個信號禁用此功能。
POSIX.1 要求只有中斷信號的SA_RESTART標志有效時虫给,實現(xiàn)才重啟 動系統(tǒng)調(diào)用藤抡。在10.14節(jié)將看到,sigaction函數(shù)使用這個標志允許應(yīng)用 程序請求重啟動被中斷的系統(tǒng)調(diào)用抹估。
歷史上缠黍,使用signal函數(shù)建立信號處理程序時,對于如何處理被中 斷的系統(tǒng)調(diào)用药蜻,各種實現(xiàn)的做法各不相同瓷式。System V的默認工作方式是 從不重啟動系統(tǒng)調(diào)用。而BSD則重啟動被信號中斷的系統(tǒng)調(diào)用语泽。FreeBSD 8.0贸典、Linux 3.2.0和Mac OS X 10.6.8中,當信號處理程序是用signal 函數(shù)時踱卵,被中斷的系統(tǒng)調(diào)用會重啟動廊驼。但 Solaris 10 的默認方式是出 錯返回,將 errno 設(shè)置為EINTR惋砂。使用用戶自己實現(xiàn)的signal函數(shù)(見 圖10-18)可以避免必須處理這些差異的麻煩妒挎。
4.2BSD引入自動重啟動功能的一個理由是:有時用戶并不知道所使 用的輸入、輸出設(shè)備是否是低速設(shè)備西饵。如果我們編寫的程序可以用交互 方式運行酝掩,則它可能讀、寫終端低速設(shè)備眷柔。如果在程序中捕捉信號期虾,而 且系統(tǒng)并不提供重啟動功能,則對每次讀驯嘱、寫系統(tǒng)調(diào)用就要進行是否出 錯返回的測試彻消,如果是被中斷的,則再調(diào)用讀宙拉、寫系統(tǒng)調(diào)用宾尚。
圖10-3列出了幾種實現(xiàn)所提供的與信號有關(guān)的函數(shù)及它們的語義。

應(yīng)當了解谢澈,其他廠商提供的UNIX系統(tǒng)可能不同于圖10-3中所示的 情況煌贴。例如,SunOS 4.1.2中的sigaction默認方式是重啟動被中斷的系統(tǒng) 調(diào)用锥忿,這與列在圖10-3中的各平臺不同牛郑。
在圖10-18中,提供了我們自己的signal函數(shù)版本敬鬓,它自動地嘗試重 啟動被中斷的系統(tǒng)調(diào)用(除 SIGALRM信號外)淹朋。在圖10-19中則提供了 另一個函數(shù)signal_intr笙各,它不進行重啟動。
在14.4節(jié)說明select和poll函數(shù)時础芍,還將更多涉及被中斷的系統(tǒng)調(diào) 用杈抢。

進程捕捉到信號并對其進行處理時,進程正在執(zhí)行的正常指令序列 就被信號處理程序臨時中斷仑性,它首先執(zhí)行該信號處理程序中的指令惶楼。如 果從信號處理程序返回(例如沒有調(diào)用 exit 或longjmp),則繼續(xù)執(zhí)行在 捕捉到信號時進程正在執(zhí)行的正常指令序列(這類似于發(fā)生硬件中斷時 所做的)诊杆。但在信號處理程序中歼捐,不能判斷捕捉到信號時進程執(zhí)行到何 處。如果進程正在執(zhí)行malloc晨汹,在其堆中分配另外的存儲空間豹储,而此時 由于捕捉到信號而插入執(zhí)行該信號處理程序,其中又調(diào)用malloc淘这,這時 會發(fā)生什么?又例如颂翼,若進程正在執(zhí)行g(shù)etpwnam(見6.2節(jié))這種將其結(jié) 果存放在靜態(tài)存儲單元中的函數(shù),其間插入執(zhí)行信號處理程序慨灭,它又調(diào) 用這樣的函數(shù)朦乏,這時又會發(fā)生什么呢?在malloc例子中,可能會對進程 造成破壞氧骤,因為malloc通常為它所分配的存儲區(qū)維護一個鏈表呻疹,而插入 執(zhí)行信號處理程序時,進程可能正在更改此鏈表筹陵。在getpwnam的例子 中刽锤,返回給正常調(diào)用者的信息可能會被返回給信號處理程序的信息覆 蓋。

image.png

Single UNIX Specification說明了在信號處理程序中保證調(diào)用安全的 函數(shù)朦佩。這些函數(shù)是可重入的并被稱為是異步信號安全的(async-signal safe)并思。除了可重入以外,在信號處理操作期間语稠,它會阻塞任何會引起 不一致的信號發(fā)送宋彼。圖10-4列出了這些異步信號安全的函數(shù)。沒有列入 圖10-4中的大多數(shù)函數(shù)是不可重入的仙畦,因為(a)已知它們使用靜態(tài)數(shù)據(jù) 結(jié)構(gòu);(b)它們調(diào)用 malloc 或free;(c)它們是標準I/O函數(shù)输涕。標準 I/O庫的很多實現(xiàn)都以不可重入方式使用全局數(shù)據(jù)結(jié)構(gòu)。注意慨畸,雖然在 本書的某些實例中莱坎,信號處理程序也調(diào)用了printf函數(shù),但這并不保證產(chǎn) 生所期望的結(jié)果寸士,信號處理程序可能中斷主程序中的printf函數(shù)調(diào)用檐什。

應(yīng)當了解碴卧,即使信號處理程序調(diào)用的是圖10-4中的函數(shù),但是由于
每個線程只有一個errno變量(回憶1.7節(jié)對errno和線程的討論)乃正,所以 信號處理程序可能會修改其原先值住册。考慮一個信號處理程序烫葬,它恰好在 main剛設(shè)置errno之后被調(diào)用界弧。如果該信號處理程序調(diào)用read這類函數(shù)凡蜻, 則它可能更改errno的值搭综,從而取代了剛由main設(shè)置的值。因此划栓,作為一 個通用的規(guī)則兑巾,當在信號處理程序中調(diào)用圖10-4中的函數(shù)時,應(yīng)當在調(diào) 用前保存errno忠荞,在調(diào)用后恢復(fù)errno蒋歌。(應(yīng)當了解,經(jīng)常被捕捉到的信 號是SIGCHLD委煤,其信號處理程序通常要調(diào)用一種wait函數(shù)堂油,而各種wait 函數(shù)都能改變errno。)
注意碧绞,圖10-4沒有包括longjmp(7.10節(jié))和siglongjmp(10.15節(jié))府框。 這是因為主例程以非可重入方式正在更新一個數(shù)據(jù)結(jié)構(gòu)時可能產(chǎn)生信 號。如果不是從信號處理程序返回而是調(diào)用siglongjmp讥邻,那么該數(shù)據(jù)結(jié) 構(gòu)可能是部分更新的迫靖。如果應(yīng)用程序?qū)⒁龈氯謹?shù)據(jù)結(jié)構(gòu)這樣的事 情,而同時要捕捉某些信號兴使,而這些信號的處理程序又會引起執(zhí)行 siglongjmp系宜,則在更新這種數(shù)據(jù)結(jié)構(gòu)時要阻塞此類信號。

10.7 SIGCLD語義

SIGCLD和SIGCHLD這兩個信號很容易被混淆发魄。SIGCLD(沒有 H)是System V的一個信號名盹牧,其語義與名為SIGCHLD的BSD信號不 同。POSIX.1采用BSD的SIGCHLD信號励幼。
BSD的SIGCHLD信號語義與其他信號的語義相類似欢策。子進程狀態(tài) 改變后產(chǎn)生此信號,父進程需要調(diào)用一個wait函數(shù)以檢測發(fā)生了什么赏淌。
System V處理SIGCLD信號的方式不同于其他信號踩寇。如果用signal或 sigset(早期設(shè)置信號配置的,與SRV3兼容的函數(shù))設(shè)置信號配置六水,則 基于SVR4的系統(tǒng)繼承了這一具有問題色彩的傳統(tǒng)(即兼容性限制)俺孙。 對于SIGCLD的早期處理方式是:
(1)如果進程明確地將該信號的配置設(shè)置為SIG_IGN辣卒,則調(diào)用進 程的子進程將不產(chǎn)生僵死進程。注意睛榄,這與其默認動作 (SIG_DFL)“忽略”(見圖10-1)不同荣茫。子進程在終止時,將其狀態(tài) 丟棄场靴。如果調(diào)用進程隨后調(diào)用一個wait函數(shù)啡莉,那么它將阻塞直到所有子 進程都終止,然后該wait會返回?1旨剥,并將其errno設(shè)置為ECHILD咧欣。(此 信號的默認配置是忽略,但這不會使上述語義起作用轨帜。必須將其配置明 確指定為SIG_IGN才可以魄咕。)

我們需要先定義一些在討論信號時會用到的術(shù)語。首先蚌父,當造成信 號的事件發(fā)生時哮兰,為進程產(chǎn)生一個信號(或向一個進程發(fā)送一個信 號)。事件可以是硬件異常(如除以 0)苟弛、軟件條件(如alarm 定時器超 時)喝滞、終端產(chǎn)生的信號或調(diào)用kill 函數(shù)。當一個信號產(chǎn)生時膏秫,內(nèi)核通常 在進程表中以某種形式設(shè)置一個標志右遭。
當對信號采取了這種動作時,我們說向進程遞送了一個信號荔睹。在信 號產(chǎn)生(generation)和遞送(delivery)之間的時間間隔內(nèi)狸演,稱信號是未 決的(pending)。
進程可以選用“阻塞信號遞送”僻他。如果為進程產(chǎn)生了一個阻塞的信 號宵距,而且對該信號的動作是系統(tǒng)默認動作或捕捉該信號,則為該進程將 此信號保持為未決狀態(tài)吨拗,直到該進程對此信號解除了阻塞满哪,或者將對此 信號的動作更改為忽略。內(nèi)核在遞送一個原來被阻塞的信號給進程時 (而不是在產(chǎn)生該信號時)劝篷,才決定對它的處理方式哨鸭。于是進程在信號 遞送給它之前仍可改變對該信號的動作。進程調(diào)用sigpending函數(shù)(見 10.13節(jié))來判定哪些信號是設(shè)置為阻塞并處于未決狀態(tài)的娇妓。
如果在進程解除對某個信號的阻塞之前像鸡,這種信號發(fā)生了多次,那 么將如何呢?POSIX.1允許系統(tǒng)遞送該信號一次或多次哈恰。如果遞送該信 號多次只估,則稱這些信號進行了排隊志群。但是除非支持POSIX.1實時擴展, 否則大多數(shù)UNIX并不對信號排隊蛔钙,而是只遞送這種信號一次锌云。

10.9 函數(shù)kill和raise

kill函數(shù)將信號發(fā)送給進程或進程組。raise函數(shù)則允許進程向自身發(fā) 送信號吁脱。
raise最初是由ISO C定義的桑涎。后來,為了與ISO C標準保持一致兼贡, POSIX.1也包括了該函數(shù)攻冷。但是POSIX.1擴展了raise的規(guī)范,使其可處 理線程(12.8中討論線程如何與信號交互)紧显。
因為ISO C并不涉及多進程讲衫,所以它不能定義以進程ID作為其參數(shù) (如kill函數(shù))的函數(shù)缕棵。

#include <signal.h>
int kill(pid_t pid, int signo); int raise(int signo);

調(diào)用raise(signo);
等價于調(diào)用兩個函數(shù)返回值:若成功孵班,返回0;若出錯,返回?1

kill(getpid(), signo);
kill的pid參數(shù)有以下4種不同的情況招驴。

pid > 0 將該信號發(fā)送給進程ID為pid的進程篙程。
pid == 0 將該信號發(fā)送給與發(fā)送進程屬于同一進程組的所有進程(這些進程的進程組 ID等于發(fā)送進程的進程組 ID),而且發(fā)送進程具 有權(quán)限向這些進程發(fā)送信號别厘。這里用的術(shù)語“所有進程”不包括實現(xiàn)定 義的系統(tǒng)進程集虱饿。對于大多數(shù)UNIX系統(tǒng),系統(tǒng)進程集包括內(nèi)核進程和 init(pid為1)触趴。
pid < 0 將該信號發(fā)送給其進程組ID等于pid絕對值氮发,而且發(fā)送進程 具有權(quán)限向其發(fā)送信號的所有進程。如前所述冗懦,所有進程并不包括系統(tǒng) 進程集中的進程爽冕。
pid == ?1 將該信號發(fā)送給發(fā)送進程有權(quán)限向它們發(fā)送信號的所有 進程。如前所述披蕉,所有進程不包括系統(tǒng)進程集中的進程颈畸。

如前所述,進程將信號發(fā)送給其他進程需要權(quán)限没讲。超級用戶可將信 號發(fā)送給任一進程眯娱。對于非超級用戶,其基本規(guī)則是發(fā)送者的實際用戶 ID 或有效用戶 ID 必須等于接收者的實際用戶 ID或有效用戶ID爬凑。如果 實現(xiàn)支持_POSIX_SAVED_IDS(如POSIX.1現(xiàn)在要求的那樣)徙缴,則檢查 接收者的保存設(shè)置用戶ID(而不是有效用戶ID)。在對權(quán)限進行測試 時也有一個特例:如果被發(fā)送的信號是SIGCONT嘁信,則進程可將它發(fā)送 給屬于同一會話的任一其他進程于样。

10.11 信號集

我們需要有一個能表示多個信號——信號集(signal set)的數(shù)據(jù)類 型迁霎。我們將在sigprocmask (下一節(jié)中說明)類函數(shù)中使用這種數(shù)據(jù)類 型,以便告訴內(nèi)核不允許發(fā)生該信號集中的信號百宇。如前所述考廉,不同的信 號的編號可能超過一個整型量所包含的位數(shù),所以一般而言携御,不能用整 型量中的一位代表一種信號昌粤,也就是不能用一個整型量表示信號集。 POSIX.1定義數(shù)據(jù)類型sigset_t以包含一個信號集啄刹,并且定義了下列5個處 理信號集的函數(shù)涮坐。

include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo);
4個函數(shù)返回值:若成功,返回0;若出錯誓军,返回?1
int sigismember(const sigset_t *set, int signo);
返回值:若真袱讹,返回1;若假,返回0 函數(shù)sigemptyset初始化由set指向的信號集昵时,清除其中所有信號捷雕。函
數(shù)sigfillset初始化由set指向的信號集,使其包括所有信號壹甥。所有應(yīng)用程序 在使用信號集前救巷,要對該信號集調(diào)用sigemptyset或sigfillset一次。這是因 為C編譯程序?qū)⒉毁x初值的外部變量和靜態(tài)變量都初始化為0句柠,而這是否 與給定系統(tǒng)上信號集的實現(xiàn)相對應(yīng)卻并不清楚浦译。

一旦已經(jīng)初始化了一個信號集,以后就可在該信號集中增溯职、刪特定 的信號精盅。函數(shù) sigaddset將一個信號添加到已有的信號集中,sigdelset 則從信號集中刪除一個信號谜酒。對所有以信號集作為參數(shù)的函數(shù)叹俏,總是以信 號集地址作為向其傳送的參數(shù)。

如果實現(xiàn)的信號數(shù)目少于一個整型量所包含的位數(shù)甚带,則可用一位代 表一個信號的方法實現(xiàn)信號集她肯。例如,本書的后續(xù)部分都假定一種實現(xiàn) 有31種信號和32位整型鹰贵。sigemptyset函數(shù)將整型設(shè)置為0晴氨, sigfillset函數(shù) 則將整型中的各位都設(shè)置為1。這兩個函數(shù)可以在<signal.h>頭文件中實 現(xiàn)為宏:

#define sigemptyset(ptr) (*(ptr) = 0)
#define sigfillset(ptr) (*(ptr) = ~(sigset_t)0, 0)

注意碉输,除了設(shè)置信號集中各位為1外籽前,sigfillset必須返回0,所以使用
C語言的逗號算符,它將逗號算符后的值作為表達式的值返回枝哄。 使用這種實現(xiàn)肄梨,sigaddset 開啟一位(將該位設(shè)置為 1),sigdelset 則
關(guān)閉一位(將該位設(shè)置為0);sigismember測試一個指定的位挠锥。因為沒 有信號編號為0众羡,所以從信號編號中減1以得到要處理位的位編號數(shù)。圖 10-12給出了這些函數(shù)的實現(xiàn)蓖租。

10.12 函數(shù)sigprocmask

10.8節(jié)曾提及一個進程的信號屏蔽字規(guī)定了當前阻塞而不能遞送給 該進程的信號集粱侣。調(diào)用函數(shù)sigprocmask可以檢測或更改,或同時進行檢 測和更改進程的信號屏蔽字蓖宦。

#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

返回值:若成功齐婴,返回0;若出錯,返回?1 首先稠茂,若oset是非空指針柠偶,那么進程的當前信號屏蔽字通過oset返回。
其次睬关,若set是一個非空指針诱担,則參數(shù)how指示如何修改當前信號屏
蔽字。圖10-13說明了how可選的值共螺。SIG_BLOCK是或操作该肴,而 SIG_SETMASK則是賦值操作情竹。注意藐不,不能阻塞SIGKILL和SIGSTOP信 號。


image.png

10.13 函數(shù)sigpending

sigpending函數(shù)返回一信號集秦效,對于調(diào)用進程而言雏蛮,其中的各信號是 阻塞不能遞送的,因而也一定是當前未決的阱州。該信號集通過set參數(shù)返 回挑秉。

#include <signal.h>
int sigpending(sigset_t *set);

返回值:若成功,返回0;若出錯苔货,返回?1

#include <iostream>
#include <stdio.h>
#include <sys/signal.h>
#include <signal.h>
#include <unistd.h>

static void sig_quit(int signo) {
    std::cout << "sigprocess, sigpending" << std::endl;
}

int main() {
    sigset_t newmask, oldmask, pendmask;

    if(signal(SIGTSTP, sig_quit) == SIG_ERR) {
        printf("signal err");
    }

    /* Block SIGQUIT. */
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGTSTP);

    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
        printf("SIG Block error\n");
    }
    sleep(5);
    
    if(sigpending(&pendmask) < 0) {
        printf("sigpending error");
    }

    if(sigismember(&pendmask, SIGTSTP)) {
        printf("sigpending : SIGTSTP");
    }
}

10.14 函數(shù)sigaction

sigaction函數(shù)的功能是檢查或修改(或檢查并修改)與指定信號相 關(guān)聯(lián)的處理動作犀概。此函數(shù)取代了UNIX早期版本使用的signal函數(shù)。在本 節(jié)末尾用sigaction函數(shù)實現(xiàn)了signal夜惭。

#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act,
          struct sigaction *restrict oact);

返回值:若成功姻灶,返回0;若出錯,返回?1 其中诈茧,參數(shù)signo是要檢測或修改其具體動作的信號編號产喉。若act指針
非空,則要修改其動作。如果oact指針非空曾沈,則系統(tǒng)經(jīng)由oact指針返回 該信號的上一個動作这嚣。此函數(shù)使用下列結(jié)構(gòu):

struct sigaction {
void   (*sa_handler)(int); /* addr of signal handler,or SIG_IGN, or SIG_DFL */
sigset_t sa_mask  */block */
int   sa_flags;      /* signal options, Figure 10.16 */
/* alternate handler */ 
void  (*sa_sigaction)(int, siginfo_t *, void *);}; 

當更改信號動作時,如果 sa_handler 字段包含一個信號捕捉函數(shù)的 地址(不是常量SIG_IGN或SIG_DFL)塞俱,則sa_mask字段說明了一個信 號集姐帚,在調(diào)用該信號捕捉函數(shù)之前,這一信號集要加到進程的信號屏蔽 字中障涯。僅當從信號捕捉函數(shù)返回時再將進程的信號屏蔽字恢復(fù)為原先 值卧土。這樣,在調(diào)用信號處理程序時就能阻塞某些信號像樊。在信號處理程序 被調(diào)用時尤莺,操作系統(tǒng)建立的新信號屏蔽字包括正被遞送的信號。因此保 證了在處理一個給定的信號時生棍,如果這種信號再次發(fā)生颤霎,那么它會被阻 塞到對前一個信號的處理結(jié)束為止⊥康危回憶10.8節(jié)友酱,若同一種信號多次發(fā) 生,通常并不將它們加入隊列柔纵,所以如果在某種信號被阻塞時缔杉,它發(fā)生 了5次,那么對這種信號解除阻塞后搁料,其信號處理函數(shù)通常只會被調(diào)用 一次(上一個例子已經(jīng)說明了這種特性)或详。
一旦對給定的信號設(shè)置了一個動作,那么在調(diào)用sigaction顯式地改 變它之前郭计,該設(shè)置就一直有效霸琴。這種處理方式與早期的不可靠信號機制 不同,符合POSIX.1在這方面的要求昭伸。
act結(jié)構(gòu)的sa_flags字段指定對信號進行處理的各個選項梧乘。圖10-16詳 細列出了這些選項的意義。若該標志已定義在基本 POSIX.1 標準中庐杨,那 么 SUS 列包含“?”;若該標志定義在基本POSIX.1標準的XSI擴展中选调, 那么該列包含“XSI”。

image.png

sa_sigaction字段是一個替代的信號處理程序灵份,在sigaction結(jié)構(gòu)中使用 了SA_SIGINFO標志時仁堪,使用該信號處理程序。對于sa_sigaction字段和 sa_handler字段兩者各吨,實現(xiàn)可能使用同一存儲區(qū)枝笨,所以應(yīng)用只能一次使用 這兩個字段中的一個袁铐。
通常,按下列方式調(diào)用信號處理程序:
void handler(int signo);
但是横浑,如果設(shè)置了SA_SIGINFO標志剔桨,那么按下列方式調(diào)用信號處
理程序:
void handler(int signo, siginfo_t *info, void context);
siginfo結(jié)構(gòu)包含了信號產(chǎn)生原因的有關(guān)信息。該結(jié)構(gòu)的大致樣式如 下所示徙融。符合POSIX.1的所有實現(xiàn)必須至少包括si_signo和si_code成員洒缀。 另外,符合XSI的實現(xiàn)至少應(yīng)包含下列字段:
struct siginfo {
int si_signo; /
signal number /
int si_errno; /
if nonzero, errno value from
<errno.h> /
int si_code; /
additional info (depends on
signal) /
pid_t si_pid; /
sending process ID / uid_t si_uid; / sending process real user
ID */
void si_addr; / address that caused the
fault /
int si_status; /
exit value or signal number
/
union sigval si_value; /
application-specific value / / possibly other fields also */
};
sigval聯(lián)合包含下列字段:
int sival_int;
void *sival_ptr; 應(yīng)用程序在遞送信號時欺冀,在si_value.sival_int中傳遞一個整型數(shù)或者
在si_value.sival_ptr中傳遞一個指針值树绩。 圖10-17示出了對于各種信號的si_code值,這些信號是由Single

UNIX Specification定義的隐轩。注意饺饭,實現(xiàn)可定義附加的代碼值。 若信號是SIGCHLD职车,則將設(shè)置si_pid瘫俊、si_status和si_uid字段。若信
號是SIGBUS悴灵、SIGILL扛芽、SIGFPE或SIGSEGV,則si_addr包含造成故障的 根源地址积瞒,該地址可能并不準確川尖。si_errno字段包含錯誤編號,它對應(yīng)于 造成信號產(chǎn)生的條件茫孔,并由實現(xiàn)定義叮喳。
信號處理程序的context參數(shù)是無類型指針,它可被強制類型轉(zhuǎn)換為 ucontext_t結(jié)構(gòu)類型银酬,該結(jié)構(gòu)標識信號傳遞時進程的上下文嘲更。該結(jié)構(gòu)至少 包含下列字段:

ucontext_t *uc_link;    /* pointer to context resumed when */
sigset_t  uc_sigmask;  /* signals blocked when this context */
stack_t   uc_stack;   /* stack used by this context */
/* this context returns */
/* is active */
mcontext_t uc_mcontext; /* machine-specific representation
of */
/* saved context */ uc_stack字段描述了當前上下文使用的棧,至少包括下列成員: void *ss_sp;       /* stack base or pointer */ size_t ss_size;      /* stack size */ int  ss_flags;      /* flags */ 

當實現(xiàn)支持實時信號擴展時揩瞪,用SA_SIGINFO標志建立的信號處理程序?qū)⒃斐尚盘柨煽康嘏抨牎R恍┍A粜盘柨捎蓪崟r應(yīng)用使用篓冲。如果信號 由sigqueue函數(shù)產(chǎn)生李破,那么siginfo結(jié)構(gòu)能包含應(yīng)用特有的數(shù)據(jù)(參見 10.20節(jié))。

10.16sigsuspend

10.16 函數(shù)sigsuspend
上面已經(jīng)說明壹将,更改進程的信號屏蔽字可以阻塞所選擇的信號嗤攻,或 解除對它們的阻塞。使用這種技術(shù)可以保護不希望由信號中斷的代碼臨 界區(qū)诽俯。如果希望對一個信號解除阻塞妇菱,然后pause以等待以前被阻塞的信 號發(fā)生,則又將如何呢?假定信號是SIGINT,實現(xiàn)這一點的一種不正 確的方法是:
sigset_t newmask, oldmask; sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* block SIGINT and save current signal mask /
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
/
critical region of code /
/
restore signal mask, which unblocks SIGINT / if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/
window is open /
pause(); /
wait for signal to occur /
/
continue processing */ 如果在信號阻塞時闯团,產(chǎn)生了信號辛臊,那么該信號的傳遞就被推遲直到
對它解除了阻塞。對應(yīng)用程序而言房交,該信號好像發(fā)生在解除對SIGINT 的阻塞和pause之間(取決于內(nèi)核如何實現(xiàn)信號)。如果發(fā)生了這種情 況,或者如果在解除阻塞時刻和 pause 之間確實發(fā)生了信號莫辨,那么就會

產(chǎn)生問題洪灯。因為可能不會再見到該信號,所以從這種意義上講白群,在此時 間窗口中發(fā)生的信號丟失了尚胞,這樣就使得pause永遠阻塞。這是早期的不 可靠信號機制的另一個問題帜慢。
為了糾正此問題辐真,需要在一個原子操作中先恢復(fù)信號屏蔽字,然后 使進程休眠崖堤。這種功能是由sigsuspend函數(shù)所提供的侍咱。

include <signal.h>

int sigsuspend(const sigset_t *sigmask);
返回值:?1,并將errno設(shè)置為EINTR 進程的信號屏蔽字設(shè)置為由sigmask指向的值密幔。在捕捉到一個信號或 發(fā)生了一個會終止該進程的信號之前楔脯,該進程被掛起。如果捕捉到一個
信號而且從該信號處理程序返回胯甩,則sigsuspend返回昧廷,并且該進程的信號 屏蔽字設(shè)置為調(diào)用sigsuspend之前的值。
注意偎箫,此函數(shù)沒有成功返回值木柬。如果它返回到調(diào)用者,則總是返回 ?1淹办,并將 errno 設(shè)置為EINTR(表示一個被中斷的系統(tǒng)調(diào)用)眉枕。
實例
圖10-22顯示了保護代碼臨界區(qū),使其不被特定信號中斷的正確方 法怜森。

圖10-22 保護臨界區(qū)不被信號中斷 注意速挑,當sigsuspend返回時,它將信號屏蔽字設(shè)置為調(diào)用它之前的

值副硅。在本例中姥宝,SIGINT信號將被阻塞。因此將信號屏蔽恢復(fù)為之前保 存的值(oldmask)恐疲。
運行圖10-22中的程序得到下面的輸出:

$ ./a.out
program start:
in critical region: SIGINT
^C                鍵入中斷字符
in sig_int: SIGINT SIGUSR1
after return from sigsuspend: SIGINT
program exit:

在調(diào)用sigsuspend時腊满,將SIGUSRI信號加到了進程信號屏蔽字中套么,所以當運行該信號處理程序時,我們得知信號屏蔽字已經(jīng)改變了碳蛋。從中可 見胚泌,在 sigsuspend 返回時,它將信號屏蔽字恢復(fù)為調(diào)用它之前的值疮蹦。
實例
sigsuspend的另一種應(yīng)用是等待一個信號處理程序設(shè)置一個全局變 量诸迟。圖10-23中的程序用于捕捉中斷信號和退出信號,但是希望僅當捕捉 到退出信號時愕乎,才喚醒主例程阵苇。

10.17 abort

10.17 函數(shù)abort
前面已提及abort函數(shù)的功能是使程序異常終止。

#include <stdlib.h> 
void abort(void);

此函數(shù)不返回值 此函數(shù)將SIGABRT信號發(fā)送給調(diào)用進程(進程不應(yīng)忽略此信號)感论。
ISO C規(guī)定绅项,調(diào)用abort將向主機環(huán)境遞送一個未成功終止的通知,其方 法是調(diào)用raise(SIGABRT)函數(shù)比肄。

10.18 system 函數(shù)

10.19 sleep nanosleep和clock_nanosleep

在本書的很多例子中都已使用了sheep函數(shù)快耿,在圖10-7程序和圖10-8 程序中有兩個sleep的實現(xiàn),但它們都是有缺陷的芳绩。

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

返回值:0或未休眠完的秒數(shù) 此函數(shù)使調(diào)用進程被掛起直到滿足下面兩個條件之一掀亥。
(1)已經(jīng)過了seconds所指定的墻上時鐘時間。 (2)調(diào)用進程捕捉到一個信號并從信號處理程序返回妥色。

#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);

返回值:若休眠到要求的時間搪花,返回0;若出錯,返回?1 這個函數(shù)掛起調(diào)用進程嘹害,直到要求的時間已經(jīng)超時或者某個信號中 斷了該函數(shù)撮竿。reqtp參數(shù)用秒和納秒指定了需要休眠的時間長度。如果某
個信號中斷了休眠間隔笔呀,進程并沒有終止幢踏,remtp參數(shù)指向的 timespec 結(jié)構(gòu)就會被設(shè)置為未休眠完的時間長度。如果對未休眠完的時間并不感 興趣许师,可以把該參數(shù)置為NULL房蝉。
如果系統(tǒng)并不支持納秒這一精度,要求的時間就會取整枯跑。因為 nanosleep函數(shù)并不涉及產(chǎn)生任何信號惨驶,所以不需要擔心與其他函數(shù)的交 互。

隨著多個系統(tǒng)時鐘的引入(回憶 6.10 節(jié))敛助,需要使用相對于特定時 鐘的延遲時間來掛起調(diào)用線程。clock_nanosleep函數(shù)提供了這種功能屋确。

#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *reqtp, struct timespec *remtp);

返回值:若休眠要求的時間纳击,返回0;若出錯续扔,返回錯誤碼 clock_id參數(shù)指定了計算延遲時間基于的時鐘。時鐘標識符列于圖6-
8中焕数。flags參數(shù)用于控制延遲是相對的還是絕對的纱昧。flags為0時表示休眠 時間是相對的(例如,希望休眠的時間長度)堡赔,如果flags值設(shè)置為 TIMER_ABSTIME识脆,表示休眠時間是絕對的(例如,希望休眠到時鐘到 達某個特定的時間)善已。
其他的參數(shù)reqtp和remtp灼捂,與nanosleep函數(shù)中的相同。但是换团,使用
絕對時間時悉稠,remtp參數(shù)未使用,因為沒有必要艘包。在時鐘到達指定的絕 對時間值以前的猛,可以為其他的clock_nanosleep調(diào)用復(fù)用reqtp參數(shù)相同的 值。
注意想虎,除了出錯返回卦尊,調(diào)用
clock_nanosleep(CLOCK_REALTIME, 0, reqtp, remtp);
和調(diào)用
nanosleep(reqtp, remtp);
的效果是相同的。使用相對休眠的問題是有些應(yīng)用對休眠長度有精 度要求舌厨,相對休眠時間會導(dǎo)致實際休眠時間比要求的長岂却。例如,某個應(yīng) 用程序希望按固定的時間間隔執(zhí)行任務(wù)邓线,就必須獲取當前時間淌友,計算下 次執(zhí)行任務(wù)的時間,然后調(diào)用nanosleep骇陈。在獲取當前時間和調(diào)用 nanosleep之間震庭,處理器調(diào)度和搶占可能會導(dǎo)致相對休眠時間超過實際需 要的時間間隔。即便分時進程調(diào)度程序?qū)π菝邥r間結(jié)束后是否會馬上執(zhí) 行用戶任務(wù)并沒有給出保證你雌,使用絕對時間還是改善了精度器联。

10.20 函數(shù)sigqueue

在10.8節(jié)中,我們介紹了大部分UNIX系統(tǒng)不對信號排隊婿崭。在 POSIX.1的實時擴展中拨拓,有些系統(tǒng)開始增加對信號排隊的支持。在SUSv4 中氓栈,排隊信號功能已從實時擴展部分移至基礎(chǔ)說明部分渣磷。
通常一個信號帶有一個位信息:信號本身。除了對信號排隊以外授瘦, 這些擴展允許應(yīng)用程序在遞交信號時傳遞更多的信息(回憶10.14節(jié))醋界。 這些信息嵌入在siginfo結(jié)構(gòu)中竟宋。除了系統(tǒng)提供的信息,應(yīng)用程序還可以 向信號處理程序傳遞整數(shù)或者指向包含更多信息的緩沖區(qū)指針形纺。
使用排隊信號必須做以下幾個操作丘侠。
(1)使用sigaction函數(shù)安裝信號處理程序時指定SA_SIGINFO標 志。如果沒有給出這個標志逐样,信號會延遲蜗字,但信號是否進入隊列要取決 于具體實現(xiàn)。
(2)在sigaction結(jié)構(gòu)的sa_sigaction成員中(而不是通常的sa_handler 字段)提供信號處理程序脂新。實現(xiàn)可能允許用戶使用sa_handler字段挪捕,但不 能獲取sigqueue函數(shù)發(fā)送出來的額外信息。
(3)使用sigqueue函數(shù)發(fā)送信號戏羽。

include <signal.h>

int sigqueue(pid_t pid, int signo, const union sigval value);
返回值:若成功担神,返回0;若出錯,返回?1 sigqueue函數(shù)只能把信號發(fā)送給單個進程始花,可以使用value參數(shù)向信
號處理程序傳遞整數(shù)和指針值妄讯,除此之外,sigqueue函數(shù)與kill函數(shù)類 似酷宵。

信號不能被無限排隊亥贸。回憶圖2-9和圖2-11中的SIGQUEUE_MAX限 制浇垦。到達相應(yīng)的限制以后炕置,sigqueue就會失敗,將errno設(shè)為EAGAIN男韧。
隨著實時信號的增強朴摊,引入了用于應(yīng)用程序的獨立信號集。這些信 號的編號在SIGRTMIN~SIGRTMAX之間此虑,包括這兩個限制值甚纲。注意, 這些信號的默認行為是終止進程朦前。
圖10-30總結(jié)了排隊信號在本書不同的實現(xiàn)中的行為上的差異介杆。
Mac OS X 10.6.8并不支持sigqueue或者實時信號。在Solaris 10 中韭寸,sigqueue在實時庫librt中春哨。
圖10-30 不同平臺上排隊信號的行為


image.png

10.21 作業(yè)控制信號

在圖10-1所示的信號中,POSIX.1認為有以下6個與作業(yè)控制有關(guān)恩伺。 SIGCHLD 子進程已停止或終止赴背。
SIGCONT 如果進程已停止,則使其繼續(xù)運行。
SIGSTOP 停止信號(不能被捕捉或忽略)癞尚。
SIGTSTP 交互式停止信號耸三。
SIGTTIN 后臺進程組成員讀控制終端乱陡。
SIGTTOU 后臺進程組成員寫控制終端浇揩。 除SIGCHLD以外,大多數(shù)應(yīng)用程序并不處理這些信號憨颠,交互式shell
則通常會處理這些信號的所有工作胳徽。當鍵入掛起字符(通常是Ctrl+Z) 時,SIGTSTP被送至前臺進程組的所有進程爽彤。當我們通知shell在前臺或 后臺恢復(fù)運行一個作業(yè)時养盗,shell向該作業(yè)中的所有進程發(fā)送SIGCONT信 號。與此類似适篙,如果向一個進程遞送了SIGTTIN或SIGTTOU信號往核,則 根據(jù)系統(tǒng)默認的方式,停止此進程嚷节,作業(yè)控制shell了解到這一點后就通 知我們聂儒。
一個例外是管理終端的進程,例如硫痰,vi(1)編輯器衩婚。當用戶要掛起它 時,它需要能了解到這一點效斑,這樣就能將終端狀態(tài)恢復(fù)到 vi 啟動時的情 況非春。另外,當在前臺恢復(fù)它時缓屠,它需要將終端狀態(tài)設(shè)置回它所希望的狀 態(tài)奇昙,并需要重新繪制終端屏幕〉型辏可以在下面的例子中觀察到與 vi 類似的 程序是如何處理這種情況的储耐。
在作業(yè)控制信號間有某些交互。當對一個進程產(chǎn)生 4 種停止信號 (SIGTSTP蠢挡、SIGSTOP弧岳、SIGTTIN或SIGTTOU)中的任意一種時,對該 進程的任一未決SIGCONT信號就被丟棄业踏。與此類似禽炬,當對一個進程產(chǎn)生SIGCONT信號時,對同一進程的任一未決停止信號被丟棄勤家。 注意腹尖,如果進程是停止的,則SIGCONT的默認動作是繼續(xù)該進程;否則忽略此信號伐脖。通常热幔,對該信號無需做任何事情乐设。當對一個停止 的進程產(chǎn)生一個 SIGCONT 信號時,該進程就繼續(xù)绎巨,即使該信號是被阻 塞或忽略的也是如此近尚。

10.22 信號名和編號

本節(jié)介紹如何在信號編號和信號名之間進行映射。某些系統(tǒng)提供數(shù)

extern char *sys_siglist[];

數(shù)組下標是信號編號场勤,數(shù)組中的元素是指向信號名符串的指針戈锻。 FreeBSD 8.0、Linux 3.2.0和Mac OS X 10.6.8都提供這種信號名
數(shù)組和媳。Solaris 10也提供信號名數(shù)組格遭,但該數(shù)組名是_sys_siglist。
可以使用psignal函數(shù)可移植地打印與信號編號對應(yīng)的字符串留瞳。

#include <signal.h>
void psignal(int signo, const char *msg); 

字符串msg(通常是程序名)輸出到標準錯誤文件拒迅,后面跟隨一個
冒號和一個空格,再后面對該信號的說明她倘,最后是一個換行符璧微。如果 msg為NULL,只有信號說明部分輸出到標準錯誤文件帝牡,該函數(shù)類似于 perror(1.7節(jié))往毡。
如果在sigaction信號處理程序中有siginfo結(jié)構(gòu),可以使用psiginfo函數(shù) 打印信號信息靶溜。

#include <signal.h>
void psiginfo(const siginfo_t *info, const char *msg);

它的工作方式與 psignal 函數(shù)類似开瞭。雖然這個函數(shù)訪問除信號編號以
外的更多信息,但不同的平臺輸出的這些額外信息可能有所不同罩息。 如果只需要信號的字符描述部分嗤详,也不需要把它寫到標準錯誤文件
中(如可以寫到日志文件中),可以使用strsignal函數(shù)瓷炮,它類似于 strerror(另見1.7節(jié))葱色。

#include <string.h>
char *strsignal(int signo);

返回值:指向描述該信號的字符串的指針 給出一個信號編號,strsignal 將返回描述該信號的字符串娘香。應(yīng)用程
序可用該字符串打印關(guān)于接收到信號的出錯消息苍狰。
本書討論的所有平臺都提供psignal和strsignal函數(shù),但相互之間 有些差別烘绽。在Solaris 10中淋昭,若信號編號無效,strsignal將返回一個 空指針安接,而FreeBSD 8.0翔忽、Linux 3.2.0和Mac OS X 10.6.8則返回一個 字符串,它指出信號編號是不可識別的。
只有Linux 3.2.0和Solaris 10支持psiginfo函數(shù)歇式。
Solaris提供一對函數(shù)驶悟,一個函數(shù)將信號編號映射為信號名,另一個 則反之材失。

#include <signal.h>
int sig2str(int signo, char *str);
int str2sig(const char *str, int *signop);

兩個函數(shù)的返回值:若成功痕鳍,返回0;若出錯,返回?1 在編寫交互式程序豺憔,其中需接收和打印信號名和信號編號時额获,這兩
個函數(shù)是有用的。 sig2str函數(shù)將給定信號編號翻譯成字符串恭应,并將結(jié)果存放在str指向
的存儲區(qū)。調(diào)用者必須保證該存儲區(qū)足夠大耘眨,可以保存最長字符串昼榛,包 括終止 null 字節(jié)。Solaris 在<signal.h>中包含了常量SIG2STR_MAX剔难,它 定義了最大字符串長度胆屿。該字符串包括不帶“SIG”前綴的信號名。例 如偶宫,SIGKILL被翻譯為字符串“KILL”非迹,并存放在str指向的存儲緩沖區(qū) 中。
str2sig 函數(shù)將給出的信號名翻譯成信號編號纯趋。該信號編號存放在 signop指向的整型中憎兽。名字要么是不帶“SIG”前綴的信號名,要么是表 示十進制信號編號的字符串(如“9”)吵冒。

注意纯命,sig2str和str2sig與常用的函數(shù)做法不同,當它們失敗時痹栖,并不 設(shè)置errno亿汞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市揪阿,隨后出現(xiàn)的幾起案子疗我,更是在濱河造成了極大的恐慌,老刑警劉巖南捂,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吴裤,死亡現(xiàn)場離奇詭異,居然都是意外死亡黑毅,警方通過查閱死者的電腦和手機嚼摩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人枕面,你說我怎么就攤上這事愿卒。” “怎么了潮秘?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵琼开,是天一觀的道長。 經(jīng)常有香客問我枕荞,道長柜候,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任躏精,我火速辦了婚禮渣刷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矗烛。我一直安慰自己辅柴,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布瞭吃。 她就那樣靜靜地躺著碌嘀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歪架。 梳的紋絲不亂的頭發(fā)上股冗,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音和蚪,去河邊找鬼止状。 笑死,一個胖子當著我的面吹牛惠呼,可吹牛的內(nèi)容都是我干的导俘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼剔蹋,長吁一口氣:“原來是場噩夢啊……” “哼旅薄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泣崩,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤少梁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后矫付,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凯沪,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年买优,在試婚紗的時候發(fā)現(xiàn)自己被綠了妨马。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挺举。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烘跺,靈堂內(nèi)的尸體忽然破棺而出湘纵,到底是詐尸還是另有隱情,我是刑警寧澤滤淳,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布梧喷,位于F島的核電站,受9級特大地震影響脖咐,放射性物質(zhì)發(fā)生泄漏铺敌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一屁擅、第九天 我趴在偏房一處隱蔽的房頂上張望偿凭。 院中可真熱鬧,春花似錦煤蹭、人聲如沸笔喉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至作谭,卻和暖如春稽物,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背折欠。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工贝或, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锐秦。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓咪奖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酱床。 傳聞我的和親對象是個殘疾皇子羊赵,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容