linux內(nèi)核之異步IO

一、前言

在嵌入式linux中,除了前面講到的輪詢式IO還有異步IO。異步IO可以在驅(qū)動或者文件在處理某一件事情后再想用戶空間發(fā)出信號,使得應用程序可以不用阻塞或者輪序去做其他事情,知道信號發(fā)生后再來處理。這樣可以使得我們的應用程序更加靈活,它與輪詢IO互為補充。本文著重講一下異步IO信號的原理,結(jié)合簡單的應用程序及驅(qū)動程序來講解

二、信號

2.1、可靠信號與不可靠信號

異步IO 有一種常用的實現(xiàn)方式,就是信號。在linux操作系統(tǒng)中,信號一共有 30 個。在PC端的linux中,有些發(fā)行版的信號有 64 個,并且分為可靠信號與不可靠信號。其中小于 SIGRTMIN=32 的為不可靠信號,而大于SIGRTMIN并且小于 SIGRTMAX=63 的為可靠信號。

我們可以使用下面的命令來查看操作系統(tǒng)支持的信號,如果所示

kill -l

操作系統(tǒng)信號

那么什么是 **可靠信號 **與 不可靠信號 ?
在執(zhí)行 信號處理程序 時,linux默認不再接收當前正在處理的信號。所以此時如果內(nèi)核再次發(fā)出信號時,那么會被應用程序忽略掉。這樣的信號我們稱之為不可靠信號,因為造成了信號丟失。可靠信號則不會丟失,因為可靠信號會被加入信號隊列,在系統(tǒng)處理完信號之后再次發(fā)出,每一次都會被接收到,不造成信號丟失的現(xiàn)象

關(guān)于可靠信號不可靠信號 的詳情,請各位讀者從其他文章資料獲取

2.2、信號應用

2.2.1、信號常用接口

我們在應用層一般情況下有 2 種使用信號的方法,分別是:

  • sighandler_t signal(int signum, sighandler_t handler)
  • int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

前者的操作比較簡單,只是為某一個信號注冊 處理函數(shù)。而后者可用于改變進程接收到信號后的行為,但其使用復雜度也比前者高一些,其中 struct sigaction 結(jié)構(gòu)體如下,我們后面還會再說到該結(jié)構(gòu)體。

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

在設(shè)置信號后,我們還需要使用 fcntl 系統(tǒng)調(diào)用會相關(guān)的驅(qū)動或者文件進行一些操作,常用的有

  • F_SETOWN:一般是使用語句 fcntl(common_fd, F_SETOWN, getpid()),對于某些多進程共用的文件描述符,比如標準輸入輸出,我們要讓操作系統(tǒng)知道這些信號要發(fā)往哪個進程。因為每一個進程都有標準輸入輸出,所以我們需要讓操作系統(tǒng)知道當前的標準輸入輸出屬于哪個進程,從而可以對進程發(fā)送信號

  • F_SETFL:一般是使用語句 fcntl(your_fd, F_SETFL, old_flags | FASYNC),該語句讓對應的驅(qū)動或者文件啟動異步通知機制

  • F_SETSIG:該標志可以設(shè)置用戶的 自定義信號 來替換掉原本的信號 SIGIO。一般的使用場景是多線程下,如果多個線程使用 SIGIO 作為觸發(fā)信號且每個線程對該信號的處理函數(shù)都不相同,那么這樣會造成 SIGIO 處理函數(shù)的覆蓋,最終只有一個處理函數(shù)會被執(zhí)行。從中可以看出,信號是 進程 屬性,也就是在進程范圍內(nèi),一個信號只有一個處理函數(shù)。那么我們可以調(diào)用該接口為每個線程指定一個自定義信號來替代 SIGIO,并為自定義信號安裝處理函數(shù)。那么當驅(qū)動或者文件觸發(fā) SIGIO 信號時,從線程角度來看則是觸發(fā)了每個線程自己的自定義信號,那么此時就會執(zhí)行每個線程自己的處理函數(shù)。使用該標志時需要加入編譯選項** -D_GNU_SOURCE**,不然會出現(xiàn) F_SETSIG undeclared 錯誤

  • F_SETOWN_EX:當我們想要讓驅(qū)動或文件的觸發(fā)信號只發(fā)送到某一 指定線程 。那么我們就可以使用該標志來設(shè)置信號的接收線程。它與 F_SETOWN 的區(qū)別在 F_SETOWN_EX 更加細致,可以指定只發(fā)送給某個線程;而 F_SETOWN 優(yōu)先發(fā)送給線程,如果接收線程被阻塞,則選擇同一進程中的其他線程接收。

以上就是筆者總結(jié)出來的 應用層信號 使用方法,信號還有很多其他的高級用法,這里筆者暫時未做深入研究,有興趣的讀者可以自行查閱其他資料,后續(xù)筆者有機會再把坑給填上

下面的筆者應用層樣例代碼,有需要的讀者可以借鑒。每個人的內(nèi)核驅(qū)動都不同,讀者們可以自行實現(xiàn)內(nèi)核驅(qū)動后來使用該樣例代碼驗證

#include <stdio.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>

#define SIGTEST (SIGRTMIN+1)
void test_sigacftionHandle(int signum , siginfo_t* siginfo, void* NULL_ptr)
{
    /* 在非實時信號下 si_code一直等于128,只有在實時信號下才是內(nèi)核發(fā)送出來的值 */
    printf("si_code = %d, si_band = %ld\n", siginfo->si_code, siginfo->si_band);
}
void test_signalHandle(int signum)
{
    printf("signum = %ld\n", signum);
}
int main()
{
    /* 非實時信號的正常sigaction流程 */
    int fd = 0;
    int old_flags = 0;
    struct sigaction sig_act = {0};
    fd = open("/dev/gpio_device", O_NONBLOCK);  
    fcntl(fd, F_SETOWN, getpid());
    old_flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, old_flags | FASYNC);
    sig_act.sa_flags = SA_SIGINFO;
    sig_act.sa_sigaction = test_sigacftionHandle;
    sigaction(SIGIO, &sig_act, NULL);//這樣寫會提示Real-time signal 1
    while(1)
        sleep(1);
    return 0;

    /* 非實時信號的正常signal流程 */
    int fd = 0;
    int old_flags = 0;
    fd = open("/dev/gpio_device", O_NONBLOCK);  
    fcntl(fd, F_SETOWN, getpid());
    old_flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, old_flags | FASYNC);
    signal(SIGIO, test_sigHandle);
    while(1)
        sleep(1);
    return 0;

    /* 使用signal安裝sa_sigaction類型的函數(shù)會編譯錯誤 */
    int fd = 0;
    int old_flags = 0;
    fd = open("/dev/gpio_device", O_NONBLOCK);  
    fcntl(fd, F_SETOWN, getpid());
    old_flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, old_flags | FASYNC);
    signal(SIGIO, test_sigacftionHandle);
    while(1)
        sleep(1);
    return 0;

    /* 設(shè)置實時信號后,為SIGIO安裝處理函數(shù)。當信號發(fā)生是會出現(xiàn) Real-time signal 1 ,并退出程序*/
    int fd = 0;
    int old_flags = 0;
    struct sigaction sig_act = {0};
    fd = open("/dev/gpio_device", O_NONBLOCK);  
    fcntl(fd, F_SETISG, SIGTEST);
    fcntl(fd, F_SETOWN, getpid());
    old_flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, old_flags | FASYNC);
    sig_act.sa_flags = SA_SIGINFO;
    sig_act.sa_sigaction = test_sigacftionHandle;
    sigaction(SIGIO, &sig_act, NULL);//這樣寫會提示
    while(1)
        sleep(1);
    return 0;

    /* 實時信號的正常signal流程 */
    int fd = 0;
    int old_flags = 0;
    struct sigaction sig_act = {0};
    fd = open("/dev/gpio_device", O_NONBLOCK);  
    fcntl(fd, F_SETISG, SIGTEST);
    fcntl(fd, F_SETOWN, getpid());
    old_flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, old_flags | FASYNC);
    sig_act.sa_flags = SA_SIGINFO;
    sig_act.sa_sigaction = test_sigacftionHandle;
    sigaction(SIGTEST, &sig_act, NULL);//正常
    while(1)
        sleep(1);
    return 0;

}

2.3、驅(qū)動層信號

應用程序 是接收信號的,那么發(fā)送信號的則是 內(nèi)核驅(qū)動。在驅(qū)動層面,linux提供了 2 個接口來實現(xiàn)信號的發(fā)送,分別是:

  • int fasync_helper(int fd, struct file* filp, int on, struct fasync_struct **fapp)
  • void kill_fasync(struct fasync_struct **fp, int sig, int band)

2.3.1 fasync_helper

fasync_helper
    ->fasync_remove_entry or fasync_add_entry

下面是 fasync_helper 相關(guān)源碼解析部分,其中已經(jīng)把部分代碼給省略去,以簡化講解思路。代碼 注釋 就是講解內(nèi)容

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
    if (!on)
        return fasync_remove_entry(filp, fapp);
    return fasync_add_entry(fd, filp, fapp);
}
int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp)
{
    struct fasync_struct *fa, **fp;
    int result = 0;
    ....
    for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
        if (fa->fa_file != filp)
            continue;//一直循環(huán),直到找到fapp所指向相應的struct fasync_struct結(jié)構(gòu)
        ....
        *fp = fa->fa_next;//將fapp前后的元素連接起來,其中fa指向當前的元素,fp是個雙重指針,指向了一個元素的next成員的地址
        call_rcu(&fa->fa_rcu, fasync_free_rcu);//釋放當前的struct fasync_struct結(jié)構(gòu)
        result = 1;
        break;
    }
    spin_unlock(&fasync_lock);
    spin_unlock(&filp->f_lock);
    return result;
}
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
    struct fasync_struct *new;

    new = fasync_alloc();//為新結(jié)構(gòu)開辟內(nèi)存
    if (!new)
        return -ENOMEM;

    if (fasync_insert_entry(fd, filp, fapp, new)) {//將新結(jié)構(gòu)加入鏈表
        fasync_free(new);//如果新結(jié)構(gòu)加入隊列失敗則釋放掉
        return 0;
    }

    return 1;
}
struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{
    struct fasync_struct *fa, **fp;

    ....
    for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {//查找鏈表是否存在當前文件的struct fasync_struct結(jié)構(gòu)體
        if (fa->fa_file != filp)//如果當前不是則繼續(xù)遍歷下一個
            continue;

        spin_lock_irq(&fa->fa_lock);//找到當前的struct fasync_struct結(jié)構(gòu)體,更換文件描述符
        fa->fa_fd = fd;
        spin_unlock_irq(&fa->fa_lock);
        goto out;
    }
    //跳出循環(huán)則說明鏈表沒有當前文件的struct fasync_struct結(jié)構(gòu)體,將新開辟的struct fasync_struct結(jié)構(gòu)體加入鏈表中,并設(shè)置標志位FASYNC
    spin_lock_init(&new->fa_lock);
    new->magic = FASYNC_MAGIC;
    new->fa_file = filp;
    new->fa_fd = fd;
    new->fa_next = *fapp;
    rcu_assign_pointer(*fapp, new);
    filp->f_flags |= FASYNC;

out:
    spin_unlock(&fasync_lock);
    spin_unlock(&filp->f_lock);
    return fa;
}
  1. fasync_helper
    我們看首先看到 fasync_helper 這個函數(shù),該函數(shù)功能就是讓當前進程進入或離開struct fasync_struct 結(jié)構(gòu)體隊列為了方面下面將struct fasync_struct 結(jié)構(gòu)體簡稱為fa結(jié)構(gòu)體)。而第三個參數(shù) on 就是決定進程是 進入鏈表 還是 離開鏈表。而我們傳給該函數(shù)只需要一個指針,該指針就是 鏈表頭
  2. fasync_add_entry
    該函數(shù)先使用 fasync_alloc 開辟了一個 fa結(jié)構(gòu)體,然后講該結(jié)構(gòu)體指針傳入 fasync_insert_entry,注意該函數(shù)的參數(shù)struct fasync_struct **fapp,它是個二級指針,指向了我們傳入給 fasync_helperfa結(jié)構(gòu)體指針,先在鏈表上進行一次遍歷,如果找到鏈表上有當前進程傳入的 fa結(jié)構(gòu)體。如果沒有遍歷到后就跳出循環(huán),將參數(shù) fapp 賦值為新開辟的結(jié)構(gòu)體指針,這樣我們完成了結(jié)構(gòu)體入鏈的過程了,而我們傳入的二級指針也指向了一個 fa結(jié)構(gòu)體
  3. fasync_remove_entry
    該函數(shù)是讓當前進程的 fa結(jié)構(gòu)體 離開鏈表,同理也是對鏈表進行遍歷,如果發(fā)現(xiàn)當前進程有 fa結(jié)構(gòu)體 鏈表中,就將結(jié)構(gòu)體出鏈并釋放。這里我們要注意到 變量fp 也是一個二級指針,該指針指向了我們傳入的 fp指針 或者 fa結(jié)構(gòu)體的fa_next成員 。變量fa指針 指向當前遍歷到的節(jié)點,節(jié)點使用fa結(jié)構(gòu)體指針來表示,我們從代碼中可以看到 fa*fp 都指向了同一個內(nèi)存地址,但是我們也要注意到 fp 這個指針指向了上一個節(jié)點的fa_next成員,所以這里其實就是將上一個節(jié)點的fa_next成員 指向 下一個節(jié)點的地址,這樣就實現(xiàn)了節(jié)點出鏈。這里的上一個節(jié)點和下一個節(jié)點都是相對當前節(jié)點而言。這里的邏輯可能比較繞,需要各位讀者仔細觀察思考

2.3.1 kill_fasync

kill_fasync 稍顯復雜,我們先看一下調(diào)用關(guān)系和閱讀相關(guān)源碼,然后再往下看一下講解。同理,這里筆者也省略了部分代碼以簡化講解思路

kill_fasync
    ->send_sigio
        ->send_sigio_to_task
            ->do_send_sig_info
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
    /* First a quick test without locking: usually
     * the list is empty.
     */
    if (*fp) {
        rcu_read_lock();
        kill_fasync_rcu(rcu_dereference(*fp), sig, band);
        rcu_read_unlock();
    }
}
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
    while (fa) {//查看struct fasync_struct結(jié)構(gòu)體是否有效
        struct fown_struct *fown;
        unsigned long flags;

        ....
        spin_lock_irqsave(&fa->fa_lock, flags);
        if (fa->fa_file) {
            fown = &fa->fa_file->f_owner;
            if (!(sig == SIGURG && fown->signum == 0))//sig并沒有繼續(xù)往下傳遞,只是在這里作為判斷用
                send_sigio(fown, fa->fa_fd, band);//向應用空間發(fā)送信號
        }
        spin_unlock_irqrestore(&fa->fa_lock, flags);
        fa = rcu_dereference(fa->fa_next);//遍歷下一個struct fasync_struct結(jié)構(gòu)體,這樣就把所謂在這個鏈表上的進程都遍歷了一遍,對每一個使用了該設(shè)備異步通知方法的進程都發(fā)送了信號
    }
}
void send_sigio(struct fown_struct *fown, int fd, int band)
{
    struct task_struct *p;
    enum pid_type type;
    struct pid *pid;
    int group = 1;
    
    ....
    pid = fown->pid;
    if (!pid)//如果pid為空則不進行發(fā)送,所以要發(fā)送信號必須在應用層使用F_SETOWN
        goto out_unlock_fown;

    do_each_pid_task(pid, type, p) {//這里按筆者 的理解是對該進程的所有線程都發(fā)送信號
        send_sigio_to_task(p, fown, fd, band, group);
    } while_each_pid_task(pid, type, p);
    ....
out_unlock_fown:
    read_unlock(&fown->lock);
}
static void send_sigio_to_task(struct task_struct *p,
                   struct fown_struct *fown,
                   int fd, int reason, int group)
{

    int signum = ACCESS_ONCE(fown->signum);

    if (!sigio_perm(p, fown, signum))
        return;

    switch (signum) {
        siginfo_t si;
        default:
            /* Queue a rt signal with the appropriate fd as its
               value.  We use SI_SIGIO as the source, not 
               SI_KERNEL, since kernel signals always get 
               delivered even if we can't queue.  Failure to
               queue in this case _should_ be reported; we fall
               back to SIGIO in that case. --sct */
          /*這里是意思是說如果一個實時信號(信號值大于32)無法進隊信號隊里,
            那么我們需要報告這件事情,那么報告就需要發(fā)送信號,這個信號就是SIGIO*/

            si.si_signo = signum;
            si.si_errno = 0;
                si.si_code  = reason;
            /*
             * Posix definies POLL_IN and friends to be signal
             * specific si_codes for SIG_POLL.  Linux extended
             * these si_codes to other signals in a way that is
             * ambiguous if other signals also have signal
             * specific si_codes.  In that case use SI_SIGIO instead
             * to remove the ambiguity.
             */
            //如果發(fā)送的信號不是SIGPOLL且有指定的si_code時,此時si_code會被指定為SI_SIGIO,一般信號不會有指定的si_code
            if ((signum != SIGPOLL) && sig_specific_sicodes(signum))
                si.si_code = SI_SIGIO;

            /* Make sure we are called with one of the POLL_*
               reasons, otherwise we could leak kernel stack into
               userspace.  */
            BUG_ON((reason < POLL_IN) || ((reason - POLL_IN) >= NSIGPOLL));
            if (reason - POLL_IN >= NSIGPOLL)
                si.si_band  = ~0L;
            else
                si.si_band = band_table[reason - POLL_IN];
            si.si_fd    = fd;
            if (!do_send_sig_info(signum, &si, p, group))//當發(fā)送信號失敗時,我們就不進行break,而是跳到了case 0去執(zhí)行,從而達到了失敗就發(fā)送SIGIO的目的
                break;
        /* fall-through: fall back on the old plain SIGIO signal */
        case 0:
            do_send_sig_info(SIGIO, SEND_SIG_PRIV, p, group);//通過發(fā)送SIGIO,讓用戶程序知道實時信號入隊失敗
    }
}
  1. kill_fasync
    我們先看看該函數(shù)的參數(shù),除了 fa結(jié)構(gòu)體 之外。還有 sigband,sig 我們知道就是信號值,其實該值并不是我們發(fā)送到應用層的值,它的作用只是做一個檢查而已,我們在后面會再看到。但我們要注意這個 band ,我們在后面會提起他的作用
  2. kill_fasync_rcu
    該函數(shù)是一個 while 循環(huán),可以從循環(huán)中看出每一次都會判斷 fa結(jié)構(gòu)體是否有效,且在循環(huán)完成后會遍歷一 一個 fa結(jié)構(gòu)體,從而達到發(fā)送信號給每一個掛在 fa結(jié)構(gòu)體鏈表 上的進程。它主要就是做一些邏輯判斷,很明顯,該接口不允許發(fā)送 SIGURG 信號。然后直接調(diào)用 send_sigio。注意這里傳入了參數(shù) band,但是參數(shù) **sig 并沒有往下繼續(xù)傳遞,進而還是用于邏輯判斷
  3. send_sigio
    該函數(shù)更加簡單,就是一個 for_each 的循環(huán)。按照筆者的理解,該循環(huán)是對進程上的每一個線程都執(zhí)行一次 send_sigio_to_task。這里還需要注意,這里回判斷 fown->pid 是否為空,非空情況下才發(fā)送,不然則跳過循環(huán)直接退出,如果要設(shè)置在成員,則必須在用戶空間使用F_SETOWN。循環(huán)部分不是本文的主要內(nèi)容,有興趣的讀者可以翻閱代碼
    ?。?!這里需要注意到,每一個進程打開同一個設(shè)備文件時,都會生成不同的struct file結(jié)構(gòu)體
  4. send_sigio_to_task
    該函數(shù)是本節(jié)的 主要內(nèi)容。其中參數(shù) reason 就是我們前面說的參數(shù) band。那么這里筆者需要先說到另外的知識點,也就是第一小節(jié)應用層信號提到的可靠信號和不可靠信號以及F_SETSIG標志。那么可靠信號的范圍是SIGRTMIN < sig_value < SIGRTMAX。按照筆者的理解,可靠信號也稱為實時信號
    4.1. case 0分支
    筆者為什么要提到這個呢?我們在前面說參數(shù) sig 并沒有往下傳遞,那么我們往應用層發(fā)送的信號值從哪里來?代碼很明顯給我們答案了,其實他就是來自fown結(jié)構(gòu)體signum 成員,該成員就是我們使用F_SETSIG標志設(shè)置的信號值,如果我們沒有使用該標志進行設(shè)置,那么默認發(fā)送SIGIO信號,也就是執(zhí)行 case 0 的分支。
    4.2. default分支
    那么如果我們在應用層使用了 F_SETSIG標志 標志設(shè)置了信號值,該信號值的范圍一般是SIGRTMIN < sig_value < SIGRTMAX,也就是實時信號。那么 fown->signum 就會變成我們指定的信號值。那么此時函數(shù)會執(zhí)行 default分支。在該分支中,如果發(fā)送的信號不是 SIGPOLL 且有指定的si_code時,此時si_code會被指定為SI_SIGIO(一般信號不會有指定的si_code),那么參數(shù) reason 會被賦值給 siginfo_t結(jié)構(gòu)體si_code成員 ,而且該siginfo_t結(jié)構(gòu)體也會被我們發(fā)往應用層,讓應用程序接收。后面筆者會說一下如何在應用層接收該結(jié)構(gòu)體。那么我們的參數(shù) band 就這樣被發(fā)送往應用程了,這樣我們就可以在應用層讀取該值,知道驅(qū)動發(fā)生的異步事件是哪個種類的事件,比如是讀事件 還是 寫事件。這樣我們在應用層編程就更加靈活。當然了,僅限于使用了F_SETSIG標志進程線程 設(shè)置了實時信號。
    4.3. 從default分支到case 0分支
    我們在看看 default分支break語句,會發(fā)現(xiàn)該語句有條件才會觸發(fā)的,也就是函數(shù) do_send_sig_info 返回 0 才觸發(fā)。在 linux 中,返回 0 一般是表示執(zhí)行成功。那么如果是返回非 0 值,表示不成功,那么按照C語言的語法,這個時候會往下執(zhí)行,也就是執(zhí)行 case 0分支發(fā)送 SIGIO 信號。這是為什呢?按照筆者的理解,并不是所有實時信號都能夠成功發(fā)送,當內(nèi)核信號隊列滿了,那么信號就有可能入隊不成功,也就無法送往應用層。那么應用層此時需要知道信號到底有沒有發(fā)送成功,那么我們就是通過使用 SIGIO 來通知應用層實時信號發(fā)送失敗。那么這個邏輯的應用場景筆者目前沒有遇到,但我們知道了這樣的事情,在我們遇到特殊場景的時候也許會有用

按照筆者的理解,講到這里應該可以理解異步信號機制的大部分了吧。那么關(guān)于驅(qū)動層面就講得差不多了,有興趣的讀者可以繼續(xù)往下閱讀代碼。

2.3、接收siginfo_t結(jié)構(gòu)體

typedef struct siginfo_t{ 
    int si_signo;//信號編號 
    int si_errno;//如果為非零值則錯誤代碼與之關(guān)聯(lián) 
    int si_code;//說明進程如何接收信號以及從何處收到 
    pid_t si_pid;//適用于SIGCHLD,代表被終止進程的PID 
    pid_t si_uid;//適用于SIGCHLD,代表被終止進程所擁有進程的UID 
    int si_status;//適用于SIGCHLD,代表被終止進程的狀態(tài) 
    clock_t si_utime;//適用于SIGCHLD,代表被終止進程所消耗的用戶時間 
    clock_t si_stime;//適用于SIGCHLD,代表被終止進程所消耗系統(tǒng)的時間 
    sigval_t si_value; 
    int si_int; 
    void * si_ptr; 
    void* si_addr; 
    int si_band; 
    int si_fd; 
};
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

在我們使用 sigaction 接口的時候,我們需要傳入相應信號的 struct sigaction 結(jié)構(gòu)體,其成員說明如下:

  • _sa_handler處理函數(shù)只有一個參數(shù),即信號值,所以信號不能傳遞除信號值之外的任何信息

  • _sa_sigaction處理函數(shù)帶有三個參數(shù),是為實時信號而設(shè)的(當然同樣支持非實時信號),它指定一個3參數(shù)信號處理函數(shù)。第一個參數(shù)為信號值,第二個參數(shù)是指向siginfo_t結(jié)構(gòu)的指針,結(jié)構(gòu)中包含信號攜帶的數(shù)據(jù)值,第三個參數(shù)沒有使用(posix沒有規(guī)范使用該參數(shù)的標準)

  • sa_mask 指定在信號處理程序的 執(zhí)行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發(fā)送。除非指定 SA_NODEFER 或者 SA_NOMASK標志位,那么在處理程序執(zhí)行完后,被阻塞的信號開始執(zhí)行。

  • sa_flags 中包含了許多標志位,包括剛剛提到的 SA_NODEFERSA_NOMASK 標志位。另一個重要的標志位是 SA_SIGINFO。當設(shè)定了該標志位時,表示信號附帶的參數(shù)可以被傳遞到信號處理函數(shù)中,也就是 siginfo_t結(jié)構(gòu)體 會被傳入。因此,如果此時設(shè)置了 SA_SIGINFO標志,那么應該為sa_sigaction函數(shù)指針賦值。如果 不設(shè)置SA_SIGINFO,信號處理函數(shù)同樣不能得到信號傳遞過來的數(shù)據(jù),在信號處理函數(shù)中對這些信息的訪問都將 導致段錯誤(Segmentation fault)。

那么到了這里,各位讀者應該知道獲取 siginfo_t結(jié)構(gòu)體 了。希望通過此文,可以讓各位讀者對于linux的異步信號機制有了更深一層的了解

三、參考鏈接

信號發(fā)送函數(shù)sigqueue和信號安裝函數(shù)sigaction: https://www.cnblogs.com/mickole/p/3191804.html
異步信號SIGIO為什么會被截胡?https://www.cnblogs.com/arnoldlu/p/10185126.html
可靠信號與不可靠信號https://www.cnblogs.com/wsw-seu/p/8383737.html
應用層獲得SIGIO信號如何區(qū)分是kill_fasync的第3個參數(shù)https://bbs.csdn.net/topics/392292366
IO多路復用、信號驅(qū)動IO以及epollhttps://www.cnblogs.com/arnoldlu/p/10264350.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 對于 Linux來說,實際信號是軟中斷,許多重要的程序都需要處理信號。信號,為 Linux 提供了一種處理異步事件...
    故事狗閱讀 86,256評論 2 63
  • IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作。那么我們對與外部設(shè)備的操作都可以看做對文件進行操...
    消失er閱讀 2,056評論 0 5
  • Linux異步通知 fasync 我們知道,驅(qū)動程序運行在內(nèi)核空間中,應用程序運行在用戶空間中,兩者是不能直接通信...
    小葉大孟閱讀 5,922評論 0 3
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作。那么我們對與外部設(shè)...
    VD2012閱讀 1,065評論 0 2
  • 信號本質(zhì) 軟中斷信號(signal,又簡稱為信號)用來通知進程發(fā)生了異步事件。在軟件層次上是對中斷機制的一種模擬,...
    飛揚code閱讀 841評論 0 2

友情鏈接更多精彩內(nèi)容