www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 嵌入式微處理器
[導(dǎo)讀]人們很容易高估某個(gè)決定性時(shí)刻的重要性,也很容易低估每天進(jìn)行微小改進(jìn)的價(jià)值。以前我也以為大規(guī)模的成功需要大規(guī)模的行動(dòng),現(xiàn)在我不這么認(rèn)為了。長(zhǎng)期來(lái)看,由于復(fù)利效果,一點(diǎn)小小的改進(jìn)就能產(chǎn)生驚人的變化。 還有一點(diǎn)值得注意的情況,大多數(shù)人有了家庭和子

人們很容易高估某個(gè)決定性時(shí)刻的重要性,也很容易低估每天進(jìn)行微小改進(jìn)的價(jià)值。以前我也以為大規(guī)模的成功需要大規(guī)模的行動(dòng),現(xiàn)在我不這么認(rèn)為了。長(zhǎng)期來(lái)看,由于復(fù)利效果,一點(diǎn)小小的改進(jìn)就能產(chǎn)生驚人的變化。

還有一點(diǎn)值得注意的情況,大多數(shù)人有了家庭和子女后,并且現(xiàn)在國(guó)內(nèi)盛行加班文化,很難再集中精力能抽出大塊的時(shí)間進(jìn)行學(xué)習(xí)了,部分還能堅(jiān)持學(xué)習(xí)的人幾乎都是以犧牲睡眠時(shí)間為代價(jià)的,我個(gè)人不太認(rèn)為這種做法,我始終認(rèn)為有更合理健康的方法能形成一個(gè)工作、生活、學(xué)習(xí)、娛樂(lè)的有效循環(huán),或許認(rèn)識(shí)到 微進(jìn)步 的重要性就是一個(gè)很好的開(kāi)始吧。

本文就是我的微進(jìn)步,歡迎閱讀。

一、概述

信號(hào)有時(shí)被稱(chēng)為提供處理異步事件機(jī)制的軟件中斷,與硬件中斷的相似之處在于打斷了程序執(zhí)行的正常流程,很多比較重要的應(yīng)用程序都需處理信號(hào)。事件可以來(lái)自于系統(tǒng)外部,例如用戶(hù)按下 Ctrl+C,或者來(lái)自程序或者內(nèi)核的某些操作。作為一種進(jìn)程間通信 (IPC) 的基本形式,進(jìn)行可以給另一個(gè)進(jìn)程發(fā)送信號(hào)。

信號(hào)很早就是 Unix 的一部分。隨著時(shí)間的推移,信號(hào)有了很大的改進(jìn)。比如在可靠性方面,之前的信號(hào)可能會(huì)出現(xiàn)丟失的情況。在功能方面,現(xiàn)在信號(hào)可以攜帶用戶(hù)定義的附加信息。最初,不同的 Unix 系統(tǒng)對(duì)信號(hào)的修改,后來(lái),POSIX 標(biāo)準(zhǔn)的到來(lái)挽救并且標(biāo)準(zhǔn)化了信號(hào)機(jī)制。

  • 用術(shù)語(yǔ) raise 表示一個(gè)信號(hào)的產(chǎn)生,catch 表示接收到一個(gè)信號(hào)。

  • 事件的發(fā)生是異步的,程序?qū)π盘?hào)的處理也是異步的。

  • 信號(hào)可以被生成、捕獲、響應(yīng)或忽略。有兩種信號(hào)不能被忽略:SIGKILL 和 SIGSTOP。不能被忽略的原因是:它們向內(nèi)核和超級(jí)用戶(hù)提供了使進(jìn)程終止或停止的可靠方法。

1. 簡(jiǎn)單概念

信號(hào)類(lèi)型:

$ man 7 signal
DESCRIPTION
   Standard signals
          First the signals described in the original POSIX.1-1990 standard.

       Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated

       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at terminal
       SIGTTIN   21,21,26    Stop    Terminal input for background process
       SIGTTOU   22,22,27    Stop    Terminal output for background process

       The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

       Next the signals not in the POSIX.1-1990 standard but described in SUSv2 and POSIX.1-2001.

       Signal       Value     Action   Comment
       ────────────────────────────────────────────────────────────────────
       SIGBUS      10,7,10     Core    Bus error (bad memory access)
       SIGPOLL                 Term    Pollable event (Sys V).
                                       Synonym for SIGIO
       SIGPROF     27,27,29    Term    Profiling timer expired
       SIGSYS      12,31,12    Core    Bad argument to routine (SVr4)
       SIGTRAP        5        Core    Trace/breakpoint trap
       SIGURG      16,23,21    Ign     Urgent condition on socket (4.2BSD)
       SIGVTALRM   26,26,28    Term    Virtual alarm clock (4.2BSD)
       SIGXCPU     24,24,30    Core    CPU time limit exceeded (4.2BSD)
       SIGXFSZ     25,25,31    Core    File size limit exceeded (4.2BSD)

        ...

       Next various other signals.

       Signal       Value     Action   Comment
       ────────────────────────────────────────────────────────────────────
       SIGIOT         6        Core    IOT trap. A synonym for SIGABRT
       SIGEMT       7,-,7      Term
       SIGSTKFLT    -,16,-     Term    Stack fault on coprocessor (unused)
       SIGIO       23,29,22    Term    I/O now possible (4.2BSD)
       SIGCLD       -,-,18     Ign     A synonym for SIGCHLD
       SIGPWR      29,30,19    Term    Power failure (System V)
       SIGINFO      29,-,-             A synonym for SIGPWR
       SIGLOST      -,-,-      Term    File lock lost (unused)
       SIGWINCH    28,28,20    Ign     Window resize signal (4.3BSD, Sun)
       SIGUNUSED    -,31,-     Core    Synonymous with SIGSYS

       (Signal 29 is SIGINFO / SIGPWR on an alpha but SIGLOST on a sparc.)

發(fā)送信號(hào):

  • 如果想發(fā)送一個(gè)信號(hào)給進(jìn)程,而該進(jìn)程并不是當(dāng)前的前臺(tái)進(jìn)程,就需要使用kill 命令。

  • kill 命令有一個(gè)有用的變體叫 killall,它可以給運(yùn)行著某一命令的所有進(jìn)程發(fā)送信號(hào)。

處理信號(hào):
Unix 系統(tǒng)提供了兩種方法來(lái)改變信號(hào)處置:signal() 和 sigaction()。signal()系統(tǒng)調(diào)用是設(shè)置信號(hào)處置的原始 API,所提供的接口比sigaction() 簡(jiǎn)單。另一方面,sigaction() 提供了 signal() 所不具備的功能。進(jìn)一步而言,signal() 的行為在不同 Unix 實(shí)現(xiàn)間存在差異,這意味著對(duì)可移植性有所追求的程序絕不能使用此調(diào)用來(lái)建立信號(hào)處理函數(shù) (signal handler)。故此,sigaction()是建立信號(hào)處理器的首選API。

由于可能會(huì)在許多老程序中看到 signal() 的應(yīng)用,我們先了解如何用 signal() 函數(shù)來(lái)處理信號(hào)。

signal() 的定義:

$ man 2 signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 參數(shù)1 signum 指定希望修改 handler 的信號(hào)編號(hào),參數(shù)2 handler,則指定信號(hào)抵達(dá)時(shí)所調(diào)用的 signal handler 函數(shù)的地址。

  • 成功,返回以前的信號(hào)處理函數(shù);出錯(cuò),返回 SIG_ERR;

2. 入門(mén)實(shí)驗(yàn)

簡(jiǎn)單試用 signal()。

分解代碼:

static void ouch(int sig) {
    printf("OUCH! - I got signal %d\n", sig);
    (void) signal(SIGINT, SIG_DFL);
}
int main() {
    (void) signal(SIGINT, ouch);

    while(1) {
        printf("Hello World!\n");
        sleep(1);
    }
}

運(yùn)行效果:

$ ./ctrlc1 
Hello World!
Hello World!
^COUCH! - I got signal 2
Hello World!
Hello World!

相關(guān)要點(diǎn):

  • 在信號(hào)處理函數(shù)中,調(diào)用如 printf 這樣的函數(shù)是不安全的。一般的做法是:在信號(hào)處理函數(shù)中設(shè)置一個(gè)標(biāo)志,然后在主程序中檢查該標(biāo)志,如需要就打印一條消息。

  • 如果想保留信號(hào)處理函數(shù),讓它繼續(xù)響應(yīng)用戶(hù)的 Ctrl+C 組合鍵,我們就需要再次調(diào)用 signal 函數(shù)來(lái)重新建立它。這會(huì)使信號(hào)在一段時(shí)間內(nèi)無(wú)法得到處理,這段時(shí)間從調(diào)用中斷函數(shù)開(kāi)始,到信號(hào)處理函數(shù)的重建為止。如果在這段時(shí)間內(nèi)程序接收到第二個(gè)信號(hào),它就會(huì)違背我們的意愿終止程序的運(yùn)行。

  • 不推薦使用 signal 接口。之所以介紹它,是因?yàn)榭赡軙?huì)在許多老程序中看到它的應(yīng)用。更清晰、執(zhí)行更可靠的函數(shù): sigaction(),在所有的新程序中都應(yīng)該使用這個(gè)函數(shù),暫不做深入介紹。

二、發(fā)送信號(hào)

1. 如何發(fā)送信號(hào)

進(jìn)程可以通過(guò)調(diào)用 kill 函數(shù)向包括它本身在內(nèi)的其他進(jìn)程發(fā)送一個(gè)信號(hào)。

kill():

$ man 2 kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

把參數(shù) sig 給指定的信號(hào)發(fā)送給由參數(shù) pid 指定的進(jìn)程號(hào)所指定的進(jìn)程。

kill 調(diào)用會(huì)在失敗時(shí)返回 -1 并設(shè)置 errno 變量,失敗的原因:

  • 給定的信號(hào)無(wú)效(errno設(shè)置為EINVAL);

  • 發(fā)送進(jìn)程權(quán)限不夠(errno設(shè)置為EPERM);

  • 目標(biāo)進(jìn)程不存在(errno設(shè)置為ESRCH);

關(guān)于權(quán)限:
要想發(fā)送一個(gè)信號(hào),發(fā)送進(jìn)程必須擁有相應(yīng)的權(quán)限,包括2種情況:

  • 兩個(gè)進(jìn)程必須擁有相同的用戶(hù) ID,即你只能發(fā)送信號(hào)給屬于自己的進(jìn)程;

  • 超級(jí)用戶(hù)可以發(fā)送信號(hào)給任何進(jìn)程;

2. 鬧鐘功能

進(jìn)程可以通過(guò)調(diào)用 alarm() 函數(shù)在經(jīng)過(guò)預(yù)定時(shí)間后發(fā)送一個(gè) SIGALRM 信號(hào)。

alarm():

$ man 2 alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • 在 seconds 秒之后發(fā)送一個(gè) SIGALRM 信號(hào)。

  • 返回值是以前設(shè)置的鬧鐘時(shí)間的余留秒數(shù),如果調(diào)用失敗則返回 -1。

相關(guān)要點(diǎn):

  • 由于處理的延時(shí)和時(shí)間調(diào)度的不確定性,實(shí)際鬧鐘時(shí)間將比預(yù)先安排的要稍微拖后一點(diǎn)兒。

  • 把參數(shù) seconds 設(shè)置為 0 將取消所有已設(shè)置的鬧鐘請(qǐng)求。

  • 如果在接收到 SIGALRM 信號(hào)之前再次調(diào)用 alarm() 函數(shù),則鬧鐘重新開(kāi)始計(jì)時(shí)

  • 每個(gè)進(jìn)程只能有一個(gè)鬧鐘時(shí)間。

3. 入門(mén)實(shí)驗(yàn)

用 kill() 模擬鬧鐘。

分解代碼:
設(shè)置 signal handler:

int main()
{
    pid_t pid;

    printf("alarm application starting\n");

    pid = fork();
    switch(pid) {
    case -1:
      /* Failure */
      perror("fork failed");
      exit(1);
    case 0:
      /* child */
        sleep(5);
        kill(getppid(), SIGALRM);
        exit(0);
    }

    /* parent */
    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);

    pause();
    if (alarm_fired)
        printf("Ding!\n");

    printf("done\n");
    exit(0);
}

定義 signal handler:

static int alarm_fired = 0;
static void ding(int sig)
{
    alarm_fired = 1;
}

通過(guò) fork 調(diào)用啟動(dòng)新的進(jìn)程:子進(jìn)程休眠 5 秒后向其父進(jìn)程發(fā)送一個(gè) SIGALRM 信號(hào)。父進(jìn)程在安排好捕獲 SIGALRM 信號(hào)后暫停運(yùn)行,直到接收到一個(gè)信號(hào)為止。

運(yùn)行效果:

$ ./alarm 
alarm application starting
waiting for alarm to go off
<等待5 秒鐘>
Ding!
done

相關(guān)要點(diǎn):

  • pause() 把程序的執(zhí)行掛起直到有一個(gè)信號(hào)出現(xiàn)為止。使用信號(hào)并掛起程序的執(zhí)行是 Unix 程序設(shè)計(jì)中的一個(gè)重要部分。

    $ man 2 pause
    #include <unistd.h>
    int pause(void);
  • 當(dāng)它被一個(gè)信號(hào)中斷時(shí),將返回 -1(如果下一個(gè)接收到的信號(hào)沒(méi)有導(dǎo)致程序終止的話(huà))并把 errno 設(shè)置為 EINTR。

  • 更常見(jiàn)的方法是使用 sigsuspend() 函數(shù),暫不做介紹。

  • 在信號(hào)處理函數(shù)中沒(méi)有調(diào)用 printf,而是通過(guò)設(shè)置標(biāo)志,然后在main函數(shù)中檢查該標(biāo)志來(lái)完成消息的輸出。

  • 如果信號(hào)出現(xiàn)在系統(tǒng)調(diào)用的執(zhí)行過(guò)程中會(huì)怎么樣?

    • 一般只需要考慮“慢”系統(tǒng)調(diào)用,例如從終端讀數(shù)據(jù),如果在這個(gè)系統(tǒng)調(diào)用等待數(shù)據(jù)時(shí)出現(xiàn)一個(gè)信號(hào),它就會(huì)返回錯(cuò)誤 EINTR。
      $ man 3 errno
      EINTR
      Interrupted function call (POSIX.1); see signal(7).
  • 如果你開(kāi)始在自己的程序中使用信號(hào),就需要注意一些系統(tǒng)調(diào)用會(huì)因?yàn)榻邮盏搅艘粋€(gè)信號(hào)而失敗。

  • 我們需要更健壯的信號(hào)接口:

    • 在編寫(xiě)程序中處理信號(hào)部分的代碼時(shí)必須非常小心,因?yàn)樵谑褂眯盘?hào)的程序中會(huì)出現(xiàn)各種各樣的“競(jìng)態(tài)條件”。例如,如果想調(diào)用pause等待一個(gè)信號(hào),可信號(hào)卻出現(xiàn)在調(diào)用 pause() 之前,就會(huì)使程序無(wú)限期地等待一個(gè)不會(huì)發(fā)生的事件。

    • POSIX 標(biāo)準(zhǔn)推薦了一個(gè)更新和更健壯的信號(hào)編程接口:sigaction。

三、信號(hào)集 (Signal Set)

多個(gè)信號(hào)可使用一個(gè)稱(chēng)之為信號(hào)集的數(shù)據(jù)結(jié)構(gòu)來(lái)表示,POSIX.1 定義了數(shù)據(jù)類(lèi)型 sigset_t 以表示一個(gè)信號(hào)集,并且定義了下列 5 個(gè)處理信號(hào)集的函數(shù):

$ man 3 sigemptyset
NAME
       sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set operations

SYNOPSIS
       #include <signal.h>

       int sigemptyset(sigset_t *set);
       int sigfillset(sigset_t *set);
       int sigaddset(sigset_t *setint signum);
       int sigdelset(sigset_t *setint signum);
       int sigismember(const sigset_t *setint signum);
  • 函數(shù) sigemptyset() 初始化由參數(shù) set 指向的信號(hào)集,清除其中所有信號(hào)。

  • 函數(shù) sigfillset() 初始化由參數(shù) set 指向的信號(hào)集,使其包括所有信號(hào)。

  • 必須使用 sigemptyset() 或者 sigfillset() 來(lái)初始化信號(hào)集。這是因?yàn)?C 語(yǔ)言不會(huì)對(duì)自動(dòng)變量進(jìn)行初始化,并且,借助于將靜態(tài)變量初始化為 0 的機(jī)制來(lái)表示空信號(hào)集的作法在可移植性上存在問(wèn)題,因?yàn)橛锌赡苁褂梦谎诖a之外的結(jié)構(gòu)來(lái)實(shí)現(xiàn)信號(hào)集。

  • 函數(shù) sigaddset() 將一個(gè)信號(hào)添加到已有的信號(hào)集中,sigdelset() 則從信號(hào)集中刪除一個(gè)信號(hào)。

  • sigismember() 函數(shù)用來(lái)測(cè)試信號(hào) sig 是否是信號(hào)集 set 的成員。

四、信號(hào)屏蔽字 (Signal Mask)

4.1 基礎(chǔ)概念

每個(gè)進(jìn)程都有一個(gè)信號(hào)屏蔽字(或稱(chēng)信號(hào)掩碼,signal mask),它規(guī)定了當(dāng)前要阻塞遞送到該進(jìn)程的信號(hào)集。對(duì)于每種信號(hào),屏蔽字中都有一位與之對(duì)應(yīng)。對(duì)于某種信號(hào),若其對(duì)應(yīng)位被設(shè)置,則它當(dāng)前是被阻塞的。進(jìn)程可以調(diào)用 sigprocmask() 檢測(cè)或更改,或同時(shí)進(jìn)行檢測(cè)和更改進(jìn)程的信號(hào)屏蔽字。

向信號(hào)屏蔽字中添加信號(hào)的3種方式:

  • 當(dāng)調(diào)用信號(hào)處理器 (signal handler) 時(shí),可能會(huì)引發(fā)信號(hào)自動(dòng)添加到信號(hào)屏蔽字中的行為,暫不作深入介紹。

  • 使用 sigaction() 函數(shù)建立信號(hào)處理器時(shí),可以指定一組信號(hào)集,當(dāng)調(diào)用該處理器時(shí)會(huì)將該信號(hào)集里的信號(hào)阻塞,暫不作深入介紹。

  • 使用sigprocmask()系統(tǒng)調(diào)用,可以隨時(shí)顯式地向信號(hào)屏蔽字中添加或移除信號(hào)。

先來(lái)了解 sigprocmask():

$ man 2 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

相關(guān)知識(shí)點(diǎn):

  • sigprocmask() 既可用于修改 進(jìn)程的信號(hào)屏蔽字,也可用于獲取現(xiàn)有的屏蔽字,或者同時(shí)執(zhí)行這2個(gè)操作。

  • 參數(shù) how 指定了 sigprocmask() 該如何操作信號(hào)屏蔽字。

    • SIG_BLOCK: 將參數(shù) set 信號(hào)集內(nèi)的信號(hào)添加到信號(hào)屏蔽字中;
    • SIG_UNBLOCK: 將參數(shù) set 信號(hào)集內(nèi)的信號(hào)從信號(hào)屏蔽字中移除;
    • SIG_SETMASK: 將參數(shù) set 信號(hào)集賦給信號(hào)屏蔽字。
  • 若 set 參數(shù)不為空,則其指向一個(gè) sigset_t 緩沖區(qū),用于返回之前的信號(hào)屏蔽字。

  • SUSv3 規(guī)定,如果有任何正在等待的信號(hào) (pending signals) 因調(diào)用了 sigprocmask() 解除了鎖定,那么在此調(diào)用返回前至少會(huì)傳遞一次這些信號(hào)。

  • 系統(tǒng)將忽略試圖阻塞 SIGKILL 和 SIGSTOP 信號(hào)的請(qǐng)求。如果試圖阻塞這些信號(hào),sigprocmask() 既不會(huì)予以關(guān)注,也不會(huì)產(chǎn)生錯(cuò)誤。

  • 常見(jiàn)的使用方法:

sigset_t blockSet, prevMask;
sigemptyset(&blockSet);

/* 1. Block SIGINT, save previous signal mask */
sigaddset(&blockSet, SIGINT);
if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
    errExit("sigprocmask1");

/* 2. Code that should not be interrupted by SIGINT */

/* 3. Restore previous signal mask, unblocking SIGINT */
if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
    errExit("sigprocmask2");

4.2 實(shí)驗(yàn) demo

main() 函數(shù):

1> 為所有信號(hào)注冊(cè)同一個(gè)信號(hào)處理函數(shù),用于驗(yàn)證信號(hào)集是否被成功屏蔽:

static void handler(int sig)
{
    if (sig == SIGINT)
        gotSigint = 1;
    else
        sigCnt[sig]++;
}

int main(int argc, char *argv[])
{
    int n, numSecs;
    sigset_t fullMask, emptyMask;

    printf("%s: PID is %ld\n", argv[0], (long) getpid());

    for (n = 1; n < NSIG; n++)
        (void) signal(n, handler); // UNSAFE
    ...
}

注意:siganl() 是不可靠的,這里為了簡(jiǎn)化程序而采用該接口。

2> 初始化信號(hào)集,然后屏蔽所有信號(hào):

sigfillset(&fullMask);
if (sigprocmask(SIG_SETMASK, &fullMask, NULL) == -1) {
    perror("sigprocmask");
    exit(EXIT_FAILURE);
}

printf("%s: sleeping for %d seconds\n", argv[0], numSecs);
sleep(numSecs);

先屏蔽所有的信號(hào),然后睡眠。睡眠期間,進(jìn)程無(wú)法響應(yīng)除 SIGSTOP 和 SIGKILL 之外的任何信號(hào)。

3> 睡眠結(jié)束后,用空信號(hào)集來(lái)解除所有的信號(hào)屏蔽:

sigemptyset(&emptyMask);   /* Unblock all signals */
if (sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1) {
    perror("sigprocmask");
    exit(EXIT_FAILURE);
}

while (!gotSigint)  /* Loop until SIGINT caught */
        continue;

for (n = 1; n < NSIG; n++)
    if (sigCnt[n] != 0)
        printf("%s: signal %d caught %d time%s\n", argv[0], n,
                sigCnt[n], (sigCnt[n] == 1) ? "" : "s");

exit(EXIT_SUCCESS);
}

解除了對(duì)某個(gè)等待信號(hào)的屏蔽后,系統(tǒng)會(huì)立刻將該信號(hào)傳遞一次給進(jìn)程。

打印信號(hào)集 printSigset():

void printSigset(FILE *of, const char *prefix, const sigset_t *sigset)
{
    int sig, cnt;

    cnt = 0;
    for (sig = 1; sig < NSIG; sig++) {
        if (sigismember(sigset, sig)) {
            cnt++;
            fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
        }
    }

    if (cnt == 0)
        fprintf(of, "%s<empty signal set>\n", prefix);
}

3. 運(yùn)行效果:
屏蔽期間多次按下 ctrl + c (發(fā)送 SIGINT):

$ ./signal_set 5
./signal_set: PID is 18375
blocked:1 (Hangup)
blocked:2 (Interrupt)
blocked:3 (Quit)
...
blocked:64 (Real-time signal 30)
./signal_set: sleeping for 5 seconds
^C^C^Cblocked:<empty signal set>
./signal_set: signal 2 caught 1 time

在信號(hào)被屏蔽的 5 秒期間,連續(xù)按下 3 次 ctrl + c,所有信號(hào)都不會(huì)被處理。當(dāng)過(guò)了 5 秒后,解除信號(hào)屏蔽,僅僅有一次 SIGINT 信號(hào)被成功地傳遞并處理。

五、等待中的信號(hào) (Pending Signals)

如果某進(jìn)程接受了一個(gè)該進(jìn)程正在阻塞的信號(hào),那么會(huì)將該信號(hào)填加到進(jìn)程的等待信號(hào)集中。當(dāng)解除對(duì)該信號(hào)的鎖定時(shí),會(huì)隨之將信號(hào)傳遞給此進(jìn)程。為了確定進(jìn)程中處于等待狀態(tài)的是哪些信號(hào),可以使用 sigpending()。

$ man 2 sigpending
NAME
       sigpending, rt_sigpending - examine pending signals
SYNOPSIS
       #include <signal.h>

       int sigpending(sigset_t *set);
DESCRIPTION
       sigpending() returns the set of signals that are pending for delivery to the calling thread (i.e., the signals
       which have been raised while blocked)
.  The mask of pending signals is returned in set.

sigpending() 為調(diào)用進(jìn)程返回處于等待狀態(tài)的信號(hào)集,并將其置于 set 指向的sigset_t 中。

相關(guān)知識(shí)點(diǎn):

  • 如果修改了對(duì)等待信號(hào)的處置 (術(shù)語(yǔ)disposition),那么當(dāng)后來(lái)解除對(duì)信號(hào)的鎖定時(shí),將根據(jù)新的處置來(lái)處理信號(hào)。

六、待處理的信號(hào) (Pending Signals)

如果某進(jìn)程接受了一個(gè)該進(jìn)程正在阻塞 (blocking) 的信號(hào),那么會(huì)將該信號(hào)填加到進(jìn)程的 等待信號(hào)集 (set of pending signals) 中。當(dāng)解除對(duì)該信號(hào)的阻塞時(shí),會(huì)隨之將信號(hào)傳遞給此進(jìn)程??梢允褂?sigpending() 確定進(jìn)程中處于等待狀態(tài)的是哪些信號(hào)。

$ man 2 sigpending
    #include <signal.h>

    int sigpending(sigset_t *set);

sigpending() 為調(diào)用進(jìn)程返回處于等待狀態(tài)的信號(hào)集,并將其置于參數(shù) set 指向的 sigset_t 中。

1. 一個(gè)簡(jiǎn)單的例子 (sig_pending.c)

1) 分解代碼:
1> main():

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

 if (signal(SIGQUIT, sig_quit) == SIG_ERR)
  err_sys("can't catch SIGQUIT");

 /* Block SIGQUIT and save current signal mask. */
 sigemptyset(&newmask);
 sigaddset(&newmask, SIGQUIT);
 if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
  err_sys("SIG_BLOCK error");

    /* SIGQUIT here will remain pending */
 sleep(5);

 if (sigpending(&pendmask) < 0)
  err_sys("sigpending error");
 if (sigismember(&pendmask, SIGQUIT))
  printf("\nSIGQUIT pending\n");

 /* Restore signal mask which unblocks SIGQUIT. */
 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
  err_sys("SIG_SETMASK error");
 printf("SIGQUIT unblocked\n");

    /* SIGQUIT here will terminate with core file */
 sleep(5);

 exit(0);
}

main() 做了 5 件事:

  • 設(shè)置 SIGQUIT 的信號(hào)處理函數(shù);
  • 屏蔽 SIGQUIT;
  • 睡眠 5 秒,用于等待 SIGQUIT 信號(hào);
  • 睡眠結(jié)束,檢測(cè) SIGQUIT 是否處于 pending;
  • 解除屏蔽 SIGQUIT;

注意:在設(shè)置 SIGQUIT 為阻塞時(shí),我們保存了老的屏蔽字。為了解除對(duì)該信號(hào)的阻塞,用老的屏蔽字重新設(shè)置了進(jìn)程信號(hào)屏蔽字。另一種方法是用 SIG_UNBLOCK 使阻塞的信號(hào)不再阻塞。如果編寫(xiě)一個(gè)可能由其他人使用的函數(shù),而且需要在函數(shù)中阻塞一個(gè)信號(hào),則不能用 SIG_UNBLOCK 簡(jiǎn)單地解除對(duì)此信號(hào)的阻塞,這是因?yàn)榇撕瘮?shù)的調(diào)用者在調(diào)用本函數(shù)之前可能也阻塞了此信號(hào)。

2> 信號(hào)處理函數(shù) sig_quit():

static void sig_quit(int signo)
{
 printf("caught SIGQUIT\n");
 if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
  err_sys("can't reset SIGQUIT");
}

2) 運(yùn)行效果:

$ ./sig_pending 
^\                      // 按下 1 次 ctrl + \ (在5s之內(nèi))
SIGQUIT pending         // 從 sleep(5) 返回后
caught SIGQUIT          // 在信號(hào)處理程序中
SIGQUIT unblocked       // 從sigprocmask() 返回
^\Quit (core dumped)

2 個(gè)值得注意的點(diǎn):

  • 信號(hào)處理函數(shù)是在 sigprocmask() unblock 信號(hào)返回之前被調(diào)用;

  • 用 signal() 設(shè)置信號(hào)處理函數(shù),信號(hào)被處理時(shí),會(huì)將信號(hào)處置重置為其默認(rèn)行為。要想在同一信號(hào)“再度光臨”時(shí)再次調(diào)用該信號(hào)處理器函數(shù),程序員必須在信號(hào)處理器內(nèi)部調(diào)用signal(),以顯式重建處理器函數(shù),但是這種處理方式是不安全的,真實(shí)的項(xiàng)目里應(yīng)使用 sigaction(),后續(xù)的文章會(huì)舉例講解。

、不對(duì)待處理的信號(hào)進(jìn)行排隊(duì)處理

等待信號(hào)集只是一個(gè)掩碼,僅表明一個(gè)信號(hào)是否發(fā)生,而未表明其發(fā)生的次數(shù)。換言之,如果同一信號(hào)在阻塞狀態(tài)下產(chǎn)生多次,那么會(huì)將該信號(hào)記錄在等待信號(hào)集中,并在稍后僅傳遞一次。后面會(huì)介紹實(shí)時(shí)信號(hào),對(duì)實(shí)時(shí)信號(hào)所采取的是隊(duì)列化管理。如果將某一實(shí)時(shí)信號(hào)的多個(gè)實(shí)例發(fā)送給一進(jìn)程,那么將會(huì)多次傳遞該實(shí)時(shí)信號(hào),暫不做深入介紹。

1. 仍是那個(gè)簡(jiǎn)單的例子 (sig_pending.c)

為了降低學(xué)習(xí)難度,跟前面的 Pending Signals 章節(jié)使用同一個(gè)例子,修改一下測(cè)試步驟:

$ ./sig_pending 
^\^\^\                  // 按下 3 次 ctrl + \ (在5s之內(nèi))
SIGQUIT pending         // 從 sleep(5) 返回后
caught SIGQUIT          // 只調(diào)用了一次信號(hào)處理程序
SIGQUIT unblocked       // 從sigprocmask() 返回
^\Quit (core dumped)

第二次運(yùn)行該程序時(shí),在進(jìn)程休眠期間產(chǎn)生了 3 次 SIGQUIT 信號(hào),但是取消對(duì)該信號(hào)的阻塞后,系統(tǒng)只向進(jìn)程傳送了一次 SIGQUIT,從中可以看出在 Linux 系統(tǒng)上沒(méi)有對(duì)信號(hào)進(jìn)行排隊(duì)處理。

2. 查看 Linux 內(nèi)核里 Signal Pending 相關(guān)的實(shí)現(xiàn) (非重點(diǎn))

1) 相關(guān)數(shù)據(jù)結(jié)構(gòu)
內(nèi)核用 struct task_struct 來(lái)描述一個(gè)進(jìn)程,struct task_struct 中信號(hào)相關(guān)的成員 (Linux-4.14):

<sched.h>
struct task_struct {
...
 /* Signal handlers: */
 struct signal_struct  *signal;
 struct sighand_struct  *sighand;
 sigset_t   blocked;
 sigset_t   real_blocked;
 /* Restored if set_restore_sigmask() was used: */
 sigset_t   saved_sigmask;
 struct sigpending  pending;
 unsigned long   sas_ss_sp;
 size_t    sas_ss_size;
 unsigned int   sas_ss_flags;
...
};

我們將注意力集中中 struct sigpending pending 上。struct sigpending pending 建立了一個(gè)鏈表,該鏈表包含了所有已經(jīng)產(chǎn)生、且有待內(nèi)核處理的信號(hào),其定義如下:

struct sigpending {
 struct list_head list;
 sigset_t signal;
};
  • 成員 struct list_head list 通過(guò)雙向鏈表管理所有待處理信號(hào),每一種待處理的信號(hào)對(duì)應(yīng)雙向鏈表中的 1 個(gè) struct sigqueue 節(jié)點(diǎn)。

  • 成員 sigset_t signal 是位圖 (bit mask,或稱(chēng)位掩碼),它指定了仍然有待處理的所有信號(hào)的編號(hào)。某 1 bit = 1 表示該 bit 對(duì)應(yīng)的信號(hào)待處理。sigset_t 所包含的比特位數(shù)目要 >= 所支持的信號(hào)數(shù)目。因此,內(nèi)核使用了 unsigned long 數(shù)組來(lái)定義該數(shù)據(jù)類(lèi)型:

typedef struct {
 unsigned long sig[_NSIG_WORDS];
sigset_t;
  • struct sigqueue 的定義如下:
struct sigqueue {
 struct list_head list;
 int flags;
 siginfo_t info;
 ...
};
  • siginfo_t 用于保存信號(hào)的額外信息,暫時(shí)不用關(guān)心。

注意:在 struct sigpending 鏈表中,struct sigqueue 對(duì)應(yīng)的是一種類(lèi)型的待處理信號(hào),而不是某一個(gè)具體的信號(hào)。

示意圖:

2) 信號(hào)的產(chǎn)生
當(dāng)給進(jìn)程發(fā)送一個(gè)信號(hào)時(shí),這個(gè)信號(hào)可能來(lái)自?xún)?nèi)核,也可能來(lái)自另外一個(gè)進(jìn)程。

內(nèi)核里有多個(gè) API 能產(chǎn)生信號(hào),這些 API 最終都會(huì)調(diào)用 send_signal()。我們重點(diǎn)關(guān)注信號(hào)是何時(shí)被設(shè)置為 pending 狀態(tài)的。

linux/kernel/signal.c:

send_signal()
 __send_signal()
  struct sigqueue *q = __sigqueue_alloc();
  list_add_tail(&q->list, &pending->list); // 將待處理信號(hào)添加到 pending 鏈表中
  sigaddset(&pending->signal, sig); // 在位圖中將信號(hào)對(duì)應(yīng)的 bit 置 1
  complete_signal(sig, t, group);
   signal_wake_up();

send_signal() 會(huì)分配一個(gè)新的 struct sigqueue 實(shí)例,然后為其填充信號(hào)的額外信息,并添加到目標(biāo)進(jìn)程的 sigpending 鏈表且設(shè)置位圖。

如果信號(hào)成功發(fā)送,沒(méi)有被阻塞,就可以用 signal_wake_up() 喚醒目標(biāo)進(jìn)程,使得調(diào)度器可以選擇目標(biāo)進(jìn)程運(yùn)行。

3) 信號(hào)的傳遞:
這些知識(shí)放在這篇文章里已經(jīng)完全超綱了,如果將所有的細(xì)節(jié)都暴露出來(lái)會(huì)讓初學(xué)者感到極度的困惑。

所以,我們只邁出一小步,將僅剩的一點(diǎn)注意力集中在內(nèi)核在執(zhí)行信號(hào)處理函數(shù)前是如何處理 pending 信號(hào)的。

在每次由內(nèi)核態(tài)切換到用戶(hù)態(tài)時(shí),內(nèi)核都會(huì)進(jìn)行信號(hào)處理,最終的效果就是調(diào)用 do_signal() 函數(shù)。

linux/kernel/signal.c:

do_signal()
 get_signal()
  dequeue_signal(current, &current->blocked, &ksig->info);
    handle_signal()
  signal_setup_done();
   signal_delivered();
  • dequeue_signal() 是關(guān)鍵點(diǎn):
dequeue_signal()
 int sig = next_signal(pending, mask);
 collect_signal(sig, pending, info, resched_timer);
  sigdelset(&list->signal, sig); // 取消信號(hào)的 pending 狀態(tài)
  list_del_init(&first->list); // 刪除 pending 鏈表中的 struct sigqueue 節(jié)點(diǎn)
  copy_siginfo(info, &first->info);
  • handle_signal() 會(huì)操作進(jìn)程在用戶(hù)態(tài)下的棧,使得在從內(nèi)核態(tài)切換到用戶(hù)態(tài)之后運(yùn)行信號(hào)處理程序,而不是正常的程序代碼。

  • do_signal() 返回時(shí),信號(hào)處理函數(shù)就會(huì)被執(zhí)行。

、相關(guān)參考

  • 《Unix 環(huán)境高級(jí)編程-第10章 信號(hào)》
  • 《Linux/Unix 系統(tǒng)編程手冊(cè)-第20章 信號(hào):基本概念》
  • 《Linux 系統(tǒng)編程-第10章 信號(hào)》
  • 《Linux 程序設(shè)計(jì)-第11章 進(jìn)程和信號(hào)》
  • 《深入理解 Linux 內(nèi)核 第11章 信號(hào)》
  • 《深入 Linux 內(nèi)核架構(gòu) 5.4.1信號(hào)》
  • 《Linux 內(nèi)核源代碼情景分析 6.4信號(hào)

你和我各有一個(gè)蘋(píng)果,如果我們交換蘋(píng)果的話(huà),我們還是只有一個(gè)蘋(píng)果。但當(dāng)你和我各有一個(gè)想法,我們交換想法的話(huà),我們就都有兩個(gè)想法了。如果你也對(duì) 嵌入式系統(tǒng)和開(kāi)源軟件 感興趣,并且想和更多人互相交流學(xué)習(xí)的話(huà),請(qǐng)關(guān)注我的公眾號(hào):嵌入式系統(tǒng)磚家,一起來(lái)學(xué)習(xí)吧,無(wú)論是 關(guān)注或轉(zhuǎn)發(fā) ,還是賞賜,都是對(duì)作者莫大的支持,謝謝 各位的大拇指 ,祝工作順利,家庭和睦~


本文授權(quán)轉(zhuǎn)載自公眾號(hào)“嵌入式Hacker” ,作者吳偉東Jack


-END-




推薦閱讀



【01】為什么要使用二級(jí)指針?
【02】指針和引用有什么區(qū)別?分別什么時(shí)候引用?
【03】“懸空指針”和“野指針”究竟是什么意思?標(biāo)準(zhǔn)答案來(lái)了
【04】用指針實(shí)現(xiàn)高低位倒序,瘋了吧?
【05】再談指針:大佬給你撥開(kāi) C指針 的云霧


免責(zé)聲明:整理文章為傳播相關(guān)技術(shù),版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系刪除

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

嵌入式ARM

掃描二維碼,關(guān)注更多精彩內(nèi)容

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車(chē)的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車(chē)技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車(chē)工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車(chē)。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車(chē) 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶(hù)希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱(chēng),數(shù)字世界的話(huà)語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱(chēng)"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉