簡體   English   中英

在 Linux 中接收 SIGINT 和異常句柄

[英]Receiving SIGINT and exception Handles in Linux

假設我們有一個使用 sleep() 函數的 C 程序

程序執行並進入休眠狀態。 然后我們鍵入Ctrl + C向進程發送 SIGINT 信號。

我們知道收到 SIGINT 后的默認操作是終止進程,我們也知道只要休眠進程收到信號,sleep() 函數就會恢復進程。

我的教科書說為了允許 sleep() 函數返回,我們必須像這樣安裝一個 SIGINT 處理程序:

void handler(int sig){
    return; /* Catch the signal and return */
}
...
int main(int argc, char **argv) {
   ...
   if (signal(SIGINT, handler) == SIG_ERR) /* Install SIGINT handler */
      unix_error("signal error\n");
   ...
   sleep(1000)
}

雖然代碼看起來很簡單,但如果我想深入挖掘,我仍然有疑問:

背景:當進程正在休眠時,我們鍵入Ctrl + C發送 SIGINT

Q1-我的理解是,內核通過更新掛起位向量中 SIGINT 的相應掛起位來向進程發送 SIGINT,我的理解是否正確?

Q2-處理器檢測到 SIGINT 的存在,但是由於我們覆蓋了處理程序使其返回而不是終止進程,所以我們的處理程序被執行,然后內核清除了 SIGINT 的相應掛起位,我的理解是否正確?

Q3- 既然SIGINT對應的pending位被清除了,那么sleep()函數如何返回呢? 我認為它應該仍然處於睡眠狀態,因為理論上,sleep() 函數無法知道 SIGINT 的存在(已被清除)

Q3- 既然SIGINT對應的pending位被清除了,那么sleep()函數如何返回呢?

將內核中的sleep()函數想象成一個函數:

  • 在某種“定時器事件”結構中分配和設置字段
  • 將“計時器事件”添加到計時器事件列表中,供計時器的 IRQ 處理程序稍后處理(當到期時間已過時)
  • 將任務從“RUNNING”狀態移動到“SLEEPING”狀態(因此調度程序知道不給任務 CPU 時間),導致調度程序執行任務切換到其他任務
  • 配置用戶空間的返回參數(剩余時間量,如果時間過期則為0
  • 弄清楚為什么調度程序再次給它 CPU 時間(時間是否到期或睡眠是否被信號中斷?)
  • 可能會稍微破壞堆棧(以便在sleep()被信號中斷時內核返回到信號處理程序,而不是返回到調用sleep()的代碼)
  • 返回用戶空間

還想象有第二個函數(我將無特殊原因調用wake() ):

  • 從定時器事件列表中刪除“定時器事件”(讓定時器的 IRQ 處理程序擔心)
  • 將任務從“SLEEPING”狀態移動到“READY TO RUN”狀態(因此調度程序知道可以再次為任務提供 CPU 時間)

當然,如果定時器的 IRQ 處理程序注意到“定時器事件”已經過期,那么定時器的 IRQ 處理程序將調用wake()函數再次喚醒任務。

現在假設有第三個函數(我將調用它send_signal() ),它可能會被其他函數調用(例如被kill()調用)。 這個函數可能會為應該接收信號的任務設置一個“掛起信號”標志,然后檢查接收任務處於什么狀態; 如果接收任務處於“睡眠”狀態,它會調用wake()函數將其喚醒(然后讓sleep()函數的后半部分擔心在調度程序感覺時將信號傳送回用戶空間比如稍后給任務 CPU 時間)。

  • Q1:內核檢查進程是否阻塞了接收到的信號,如果是,則更新進程入口中的掛起信號位(不可靠,在有可靠信號的系統上,這應該是一個計數器),以便調用信號處理程序當信號再次暢通時(見下文)。 如果沒有被阻塞,系統調用准備返回值和errno值並返回到用戶模式,並在程序的虛擬堆棧中安裝一個特殊代碼,使其在從通用syscall代碼返回之前調用信號處理程序(已經在用戶模式) . 系統調用的返回將-1賦給調用者代碼,並將errno變量設置為EINTR 這需要進程安裝信號處理程序,因為默認情況下操作是中止進程,因此它不會從正在等待的系統調用中返回。 認為當一個人說內核時,實際執行的代碼是在系統調用中被喚醒並通知特殊條件(收到信號)中斷的調用,檢測到要調用信號處理程序,並准備用戶堆棧跳轉在從syscall()包裝器返回之前到適當的位置(用戶代碼中的中斷處理程序)。

  • Q2: pending bit只是用來保存要調用一個pending signal handler,所以不是這樣的。 在進程的執行部分,unix 程序加載器安裝一些基本代碼以在從系統調用返回之前跳轉到信號處理程序。 這是因為信號處理程序必須在用戶模式下執行(而不是在內核模式下),所以一切都在系統調用終止時發生。 執行的信號處理程序是SIGINT ,但中斷的代碼是系統調用,在系統調用返回之前什么都不會發生(返回代碼和errno變量已經固定)

  • Q3:嗯,你的推理是基於一個錯誤的前提,即中斷掛起標志表示已經接收到中斷 該位表示未處理的中斷已被標記為在您解除阻塞后立即傳遞,並且這僅發生在另一個系統調用中(解除阻塞信號)。 一旦信號被解鎖, sigsetmask(2)系統調用的返回代碼將執行信號處理程序。 在這種情況下,信號將在計時器結束后立即傳遞給進程,系統調用將被中斷,如果您沒有為SIGALRM信號安裝信號處理程序(但sleep(2)實現會這樣做 - - 至少,舊的實現是這樣的)程序將被中止。

筆記

當我說程序被內核中止時,但在這兩種情況下,所涉及的信號( SIGINTSIGALRM )都不會轉儲核心文件。 該程序在未生成core的情況下中止。 這與發送SIGABRTabort()例程的行為不同,它使 de kernel 轉儲進程的核心文件。

你的理解是正確的。

想想看。 該進程在內核中被阻塞。 我們需要返回到用戶空間來運行處理程序。 我們如何才能在不中斷任何正在運行的阻塞內核調用的情況下做到這一點? 我們在這里只有一個進程/線程上下文可以使用。 該進程不能同時處於休眠狀態和運行信號處理程序。

順序是:

  1. 某些阻塞內核調用中的進程塊。
  2. 信號發送給它。
  3. 位已設置,進程已准備好運行。
  4. 進程在內核模式下恢復運行,檢查掛起的非阻塞信號。
  5. 調用信號調度程序。
  6. 進程上下文被修改為在恢復時執行信號處理程序。
  7. 進程在用戶空間恢復
  8. 信號處理程序運行。
  9. 信號處理程序返回。
  10. 內核在信號處理程序結束時被調用。
  11. 內核決定是恢復系統調用還是返回中斷錯誤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM