亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Linux的Signal機制是什么

發布時間:2022-02-18 09:43:41 來源:億速云 閱讀:165 作者:iii 欄目:開發技術

這篇文章主要介紹了Linux的Signal機制是什么的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Linux的Signal機制是什么文章都會有所收獲,下面我們一起來看看吧。

Signal機制在Linux中是一個非常常用的進程間通信機制,很多人在使用的時候不會考慮該機制是具體如何實現的。signal機制可以被理解成進程的軟中斷,因此,在實時性方面還是相對比較高的。

Linux的Signal機制是什么
信號概述
  1. 信號的名字和編號: 每個信號都有一個名字和編號,這些名字都以“SIG”開頭,例如“SIGIO ”、“SIGCHLD”等等。 信號定義在signal.h頭文件中,信號名都定義為正整數。 具體的信號名稱可以使用kill -l來查看信號的名字以及序號,信號是從1開始編號的,不存在0號信號。kill對于信號0又特殊的應用。

    Linux的Signal機制是什么

    信號的名稱

  2. 信號的處理: 信號的處理有三種方法,分別是:忽略、捕捉和默認動作

  • 忽略信號,大多數信號可以使用這個方式來處理,但是有兩種信號不能被忽略(分別是 SIGKILLSIGSTOP)。因為他們向內核和超級用戶提供了進程終止和停止的可靠方法,如果忽略了,那么這個進程就變成了沒人能管理的的進程,顯然是內核設計者不希望看到的場景
  • 捕捉信號,需要告訴內核,用戶希望如何處理某一種信號,說白了就是寫一個信號處理函數,然后將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。
  • 系統默認動作,對于每個信號來說,系統都對應由默認的處理動作,當發生了該信號,系統會自動執行。不過,對系統來說,大部分的處理方式都比較粗暴,就是直接殺死該進程。 具體的信號默認動作可以使用man 7 signal來查看系統的具體定義。在此,我就不詳細展開了,需要查看的,可以自行查看。也可以參考 《UNIX 環境高級編程(第三部)》的 P251——P256中間對于每個信號有詳細的說明。

了解了信號的概述,那么,信號是如何來使用呢?

?

其實對于常用的 kill 命令就是一個發送信號的工具,kill 9 PID來殺死進程。比如,我在后臺運行了一個 top 工具,通過 ps 命令可以查看他的 PID,通過 kill 9 來發送了一個終止進程的信號來結束了 top 進程。如果查看信號編號和名稱,可以發現9對應的是 9) SIGKILL,正是殺死該進程的信號。而以下的執行過程實際也就是執行了9號信號的默認動作——殺死進程。

Linux的Signal機制是什么

kill 殺死進程

對于信號來說,最大的意義不是為了殺死信號,而是實現一些異步通訊的手段,那么如何來自定義信號的處理函數呢?

信號處理函數的注冊

信號處理函數的注冊不只一種方法,分為入門版和高級版

  1. 入門版:函數signal
  2. 高級版:函數sigaction
信號處理發送函數

信號發送函數也不止一個,同樣分為入門版和高級版 1.入門版:kill 2.高級版:sigqueue

信號注冊函數——入門版

在正式開始了解這兩個函數之前,可以先來思考一下,處理中斷都需要處理什么問題。 按照我們之前思路來看,可以發送的信號類型是多種多樣的,每種信號的處理可能不一定相同,那么,我們肯定需要知道到底發生了什么信號。 另外,雖然我們知道了系統發出來的是哪種信號,但是還有一點也很重要,就是系統產生了一個信號,是由誰來響應? 如果系統通過 ctrl+c 產生了一個 SIGINT(中斷信號),顯然不是所有程序同時結束,那么,信號一定需要有一個接收者。對于處理信號的程序來說,接收者就是自己。

開始的時候,先來看看入門版本的信號注冊函數,他的函數原型如下: signal 的函數原型

#include typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

根據函數原型可以看出由兩部分組成,一個是真實處理信號的函數,另一個是注冊函數了。 對于sighandler_t signal(int signum, sighandler_t handler);函數來說,signum 顯然是信號的編號,handler 是中斷函數的指針。 同樣,typedef void (*sighandler_t)(int);中斷函數的原型中,有一個參數是 int 類型,顯然也是信號產生的類型,方便使用一個函數來處理多個信號。我們先來看看簡單一個信號注冊的代碼示例吧。

#include#include#include //typedef void (*sighandler_t)(int);
void
handler(int signum)
{
   if(signum == SIGIO)
       printf("SIGIO   signal: %d\n", signum);
   else if(signum == SIGUSR1)
       printf("SIGUSR1   signal: %d\n", signum);
   else       printf("error\n");
}

int
main(void)
{
   //sighandler_t signal(int signum, sighandler_t handler);
   signal(SIGIO, handler);
   signal(SIGUSR1, handler);
   printf("%d  %d\n", SIGIO, SIGUSR1);
   for(;;)
   {
       sleep(10000);
   }
   return 0;
}

我們先使用 kill 命令發送信號給之前所寫的程序,關于這個命令,我們后面再談。

Linux的Signal機制是什么

通過 kill 命令發送信號

Linux的Signal機制是什么

程序接收到的信號的處理結果

簡單的總結一下,我們通過 signal 函數注冊一個信號處理函數,分別注冊了兩個信號(SIGIO 和 SIGUSER1);隨后主程序就一直“長眠”了。 通過 kill 命令發送信號之前,我們需要先查看到接收者,通過 ps 命令查看了之前所寫的程序的 PID,通過 kill 函數來發送。 對于已注冊的信號,使用 kill 發送都可以正常接收到,但是如果發送了未注冊的信號,則會使得應用程序終止進程。

那么,已經可以設置信號處理函數了,信號的處理還有兩種狀態,分別是默認處理和忽略,這兩種設置很簡單,只需要將 handler 設置為 SIG_IGN(忽略信號)或 SIG_DFL(默認動作)即可。

在此還有兩個問題需要說明一下:

  1. 當執行一個程序時,所有信號的狀態都是系統默認或者忽略狀態的。除非是 調用exec進程忽略了某些信號。exec 函數將原先設置為要捕捉的信號都更改為默認動作,其他信號的狀態則不會改變 。 2.當一個進程調動了 fork 函數,那么子進程會繼承父進程的信號處理方式。

入門版的信號注冊還是比較簡單的,只需要一句注冊和一個處理函數即可,那么,接下來看看,如何發送信號吧。

信號發送函數——入門版

kill 的函數原型

#include #include int kill(pid_t pid, int sig);

正如我之前所說的,信號的處理需要有接受者,顯然發送者必須要知道發給誰,根據 kill 函數的遠行可以看到,pid 就是接受者的 pid,sig 則是發送的信號的類型。從原型來看,發送信號要比接受信號還要簡單些,那么我們直接上代碼吧~~!Show me the code!!!

#include #include #include#include int main(int argc, char** argv)
{
   if(3 != argc)
   {
       printf("[Arguments ERROR!]\n");
       printf("\tUsage:\n");
       printf("\t\t%s  \n", argv[0]);
       return -1;
   }
   int pid = atoi(argv[1]);
   int sig = atoi(argv[2]);
   //int kill(pid_t pid, int sig);
   if(pid > 0 && sig > 0)
   {
       kill(pid, sig);
   }
   else   {
       printf("Target_PID or Signal_Number MUST bigger than 0!\n");
   }
   
   return 0;
}
Linux的Signal機制是什么
img

發送信號

Linux的Signal機制是什么

接收信號的結果

總結一下: 根據以上的結果可看到,基本可以實現了信號的發送,雖然不能直接發送信號名稱,但是通過信號的編號,可以正常的給程序發送信號了,也是初步實現了信號的發送流程。

關于 kill 函數,還有一點需要額外說明,上面的程序限定了 pid 必須為大于0的正整數,其實 kill 函數傳入的 pid 可以是小于等于0的整數。 pid > 0:將發送個該 pid 的進程 pid == 0:將會把信號發送給與發送進程屬于同一進程組的所有進程,并且發送進程具有權限想這些進程發送信號。 pid

關于信號,還有更多的話題,比如,信號是否都能夠準確的送達到目標進程呢?答案其實是不一定,那么這就有了可靠信號和不可靠信號

可靠信號和不可靠信號

不可靠信號:信號可能會丟失,一旦信號丟失了,進程并不能知道信號丟失 可靠信號:也是阻塞信號,當發送了一個阻塞信號,并且該信號的動作時系統默認動作或捕捉該信號,如果信號從發出以后會一直保持未決的狀態,直到該進程對此信號解除了阻塞,或將對此信號的動作更改為忽略。 對于信號來說,信號編號小于等于31的信號都是不可靠信號,之后的信號為可卡信號,系統會根據有信號隊列,將信號在遞達之前進行阻塞。

信號的阻塞和未決是通過信號的狀態字來管理的,該狀態字是按位來管理信號的狀態。每個信號都有獨立的阻塞字,規定了當前要阻塞地達到該進程的信號集。

信號阻塞狀態字(block),1代表阻塞、0代表不阻塞;信號未決狀態字(pending)的1代表未決,0代表信號可以抵達了;它們都是每一個bit代表一個信號

  • 阻塞和未決是如何工作的? 比如向進程發送SIGINT信號,內核首先會判斷該進程的信號阻塞狀態字是否阻塞狀態,如果該信號被設置為阻塞的狀態,也就是阻塞狀態字對應位為1,那么信號未決狀態字(pending)相應位會被內核設置為1;如果該信號阻塞解除了,也就是阻塞狀態字設置為了0,那么信號未決狀態字(pending)相應位會被內核設置為0,表示信號此時可以抵達了,也就是可以接收該信號了。 阻塞狀態字用戶可以讀寫,未決狀態字用戶只能讀,是由內核來設置表示信號遞達狀態的。 PS:這里額外說明以下,只有支持了 POSIX.1實時擴展的系統才支持排隊的功能(也就阻塞狀態下多次同一信號發送給某一進程可以得到多次,而不是一次)。
  • 關于進程關于信號的阻塞狀態字的設置 可以通過int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);函數來獲取或者設置。

該函數管理信號,是通過信號集的數據結構來進行管理的,信號集可以通過以下的函數進行管理。 信號集操作函數(狀態字表示)

#include       int sigemptyset(sigset_t *set);  //初始化 set 中傳入的信號集,清空其中所有信號
      int sigfillset(sigset_t *set);  //把信號集填1,讓 set 包含所有的信號
      int sigaddset(sigset_t *set, int signum);//把信號集對應位置為1
      int sigdelset(sigset_t *set, int signum);//吧信號集對應位置為0
      int sigismember(const sigset_t *set, int signum);//判斷signal是否在信號集

對于信號集分配好內存空間,需要使用初始化函數來初始化。初始化完成后,可以在該集合中添加、刪除特定的信號。 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 其中 how 變量決定了是如何操作該狀態字。 SIG_BLOCK:set包含了我們希望添加到當前信號阻塞字的信號,相當于mask=mask|set SIG_UNBLOCK:set包含了我們希望從當前信號阻塞字中解除阻塞的信號,相當于mask=mask&~set SIG_SETMASK:設置當前信號阻塞字為set所指的值,相當于mask=set

pending是由內核來根據block設置的,只可以讀取其中的數據,來段判斷信號是否會遞達。通過設置block可以將希望阻塞的信號進行阻塞,對應的pending會由內核來設置

設置信號阻塞、未達的步驟:

  1. 分配內存空間sigset sigset bset;
  2. 置空sigemptyset(&bset);
  3. 添加信號sigaddset(&bset, SIGINT);
  4. 添加其他需要管理的信號….
  5. 設置信號集中的信號處理方案(此處為解除阻塞)sigprocmask(SIG_UNBLOCK, &bset, NULL);
  • 簡化版設置阻塞狀態字
#include int sigpending(sigset_t *set);

這個函數使用很簡單,對于調用他的進程來說,其中信號集中的信號是阻塞不能遞送的,那么,也就一定會是當前未決的。

  • 原子操作的信號阻塞字的恢復并進入休眠狀態
#include int sigsuspend(const sigset_t *mask);

為何會出現原子性的解除阻塞的函數呢? 因為,當信號被阻塞的時候,產生了信號,那么該信號的遞送就要推遲到這個信號被解除了阻塞為止。如果此時,應用程序正好處在,解除 SIGINT 的阻塞和 pause 之間,那么此時,會產生問題,可能永遠 pause 不能夠等到SIGINT 信號來打斷他,造成程序永久阻塞在 pause 處。 為了解決這個問題,,需要在一個原子性的操作來恢復信號的屏蔽字,然后才能讓進程進入休眠狀態,以保證不會出現上述的問題。

進程的信號屏蔽字設置為

信號注冊函數——高級版

我們已經成功完成了信號的收發,那么為什么會有高級版出現呢?其實之前的信號存在一個問題就是,雖然發送和接收到了信號,可是總感覺少些什么,既然都已經把信號發送過去了,為何不能再攜帶一些數據呢? 正是如此,我們需要另外的函數來通過信號傳遞的過程中,攜帶一些數據。咱么先來看看發送的函數吧。

sigaction 的函數原型

#include int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
  void       (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 為忽略,SIG_DFL 為默認動作
  void       (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用
  sigset_t   sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。
  int        sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數據
};
//回調函數句柄sa_handler、sa_sigaction只能任選其一

這個函數的原版幫助信息,可以通過man sigaction來查看。

sigaction 是一個系統調用,根據這個函數原型,我們不難看出,在函數原型中,第一個參數signum應該就是注冊的信號的編號;第二個參數act如果不為空說明需要對該信號有新的配置;第三個參數oldact如果不為空,那么可以對之前的信號配置進行備份,以方便之后進行恢復。

在這里額外說一下struct sigaction結構體中的 sa_mask 成員,設置在其的信號集中的信號,會在捕捉函數調用前設置為阻塞,并在捕捉函數返回時恢復默認原有設置。這樣的目的是,在調用信號處理函數時,就可以阻塞默寫信號了。在信號處理函數被調用時,操作系統會建立新的信號阻塞字,包括正在被遞送的信號。因此,可以保證在處理一個給定信號時,如果這個種信號再次發生,那么他會被阻塞到對之前一個信號的處理結束為止。

sigaction 的時效性:當對某一個信號設置了指定的動作的時候,那么,直到再次顯式調用 sigaction并改變動作之前都會一直有效。

關于結構體中的 flag 屬性的詳細配置,在此不做詳細的說明了,只說明其中一點。如果設置為 SA_SIGINFO 屬性時,說明了信號處理程序帶有附加信息,也就是會調用 sa_sigaction 這個函數指針所指向的信號處理函數。否則,系統會默認使用 sa_handler 所指向的信號處理函數。在此,還要特別說明一下,sa_sigaction 和 sa_handler 使用的是同一塊內存空間,相當于 union,所以只能設置其中的一個,不能兩個都同時設置。

關于void (*sa_sigaction)(int, siginfo_t *, void *);處理函數來說還需要有一些說明。void* 是接收到信號所攜帶的額外數據;而struct siginfo這個結構體主要適用于記錄接收信號的一些相關信息。

siginfo_t {
              int      si_signo;    /* Signal number */
              int      si_errno;    /* An errno value */
              int      si_code;     /* Signal code */
              int      si_trapno;   /* Trap number that caused
                                       hardware-generated signal
                                       (unused on most architectures) */
              pid_t    si_pid;      /* Sending process ID */
              uid_t    si_uid;      /* Real user ID of sending process */
              int      si_status;   /* Exit value or signal */
              clock_t  si_utime;    /* User time consumed */
              clock_t  si_stime;    /* System time consumed */
              sigval_t si_value;    /* Signal value */
              int      si_int;      /* POSIX.1b signal */
              void    *si_ptr;      /* POSIX.1b signal */
              int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
              int      si_timerid;  /* Timer ID; POSIX.1b timers */
              void    *si_addr;     /* Memory location which caused fault */
              int      si_band;     /* Band event */
              int      si_fd;       /* File descriptor */
}

其中的成員很多,si_signo 和 si_code 是必須實現的兩個成員。可以通過這個結構體獲取到信號的相關信息。 關于發送過來的數據是存在兩個地方的,sigval_t si_value這個成員中有保存了發送過來的信息;同時,在si_int或者si_ptr成員中也保存了對應的數據。

那么,kill 函數發送的信號是無法攜帶數據的,我們現在還無法驗證發送收的部分,那么,我們先來看看發送信號的高級用法后,我們再來看看如何通過信號來攜帶數據吧。

信號發送函數——高級版
#include int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
  int   sival_int;
  void *sival_ptr;
};

使用這個函數之前,必須要有幾個操作需要完成

  1. 使用 sigaction 函數安裝信號處理程序時,制定了 SA_SIGINFO 的標志。
  2. sigaction 結構體中的 sa_sigaction 成員提供了信號捕捉函數。如果實現的時 sa_handler 成員,那么將無法獲取額外攜帶的數據。

sigqueue 函數只能把信號發送給單個進程,可以使用 value 參數向信號處理程序傳遞整數值或者指針值。

sigqueue 函數不但可以發送額外的數據,還可以讓信號進行排隊(操作系統必須實現了 POSIX.1的實時擴展),對于設置了阻塞的信號,使用 sigqueue 發送多個同一信號,在解除阻塞時,接受者會接收到發送的信號隊列中的信號,而不是直接收到一次。

但是,信號不能無限的排隊,信號排隊的最大值受到SIGQUEUE_MAX的限制,達到最大限制后,sigqueue 會失敗,errno 會被設置為 EAGAIN。

那么我們來嘗試一下,發送一個攜帶有額外數據的信號吧。 Show me the code!! 接收端

#include#include#include //void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t * info, void * context)
{
   if(signum == SIGIO)
       printf("SIGIO   signal: %d\n", signum);
   else if(signum == SIGUSR1)
       printf("SIGUSR1   signal: %d\n", signum);
   else       printf("error\n");
   
   if(context)
   {
       printf("content: %d\n", info->si_int);
       printf("content: %d\n", info->si_value.sival_int);
   }
}

int main(void)
{
   //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
   struct sigaction act;
   
   /*
    struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    };
    */
   act.sa_sigaction = handler;
   act.sa_flags = SA_SIGINFO;
   
   sigaction(SIGIO, &act, NULL);
   sigaction(SIGUSR1, &act, NULL);
   for(;;)
   {
       sleep(10000);
   }
   return 0;
}

發送端

#include #include #include#include int main(int argc, char** argv)
{
   if(4 != argc)
   {
       printf("[Arguments ERROR!]\n");
       printf("\tUsage:\n");
       printf("\t\t%s   \n", argv[0]);
       return -1;
   }
   int pid = atoi(argv[1]);
   int sig = atoi(argv[2]);

   if(pid > 0 && sig > 0)
   {
       //int sigqueue(pid_t pid, int sig, const union sigval value);
       union sigval val;
       val.sival_int = atoi(argv[3]);
       printf("send: %d\n", atoi(argv[3]));
       sigqueue(pid, sig, val);
   }
   else   {
       printf("Target_PID or Signal_Number MUST bigger than 0!\n");
   }
   
   return 0;
}
Linux的Signal機制是什么

接收到的信號和信息

Linux的Signal機制是什么

發送的信號和數據

關于“Linux的Signal機制是什么”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Linux的Signal機制是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

易门县| 西畴县| 盖州市| 西华县| 麻城市| 青阳县| 铁力市| 镇安县| 齐河县| 隆德县| 皋兰县| 肥东县| 丹巴县| 千阳县| 精河县| 苏尼特右旗| 蒲江县| 吴旗县| 建水县| 贵阳市| 平乐县| 巴南区| 大足县| 卫辉市| 犍为县| 新邵县| 柳河县| 连云港市| 锡林郭勒盟| 孟津县| 闵行区| 天津市| 墨竹工卡县| 巫溪县| 西青区| 临颍县| 米林县| 临汾市| 明溪县| 台湾省| 长沙市|