20191302第六章學習筆記
阿新 • • 發佈:2021-11-13
第六章 訊號與訊號處理
本章概述
- 本章講述了訊號和訊號處理;
- 介紹了訊號和中斷的統一處理,有助於從正確的角度看待訊號;將訊號視為程序中斷,將程序從正常執行轉移到訊號處理;
- 解釋了訊號的來源,包括來自硬體、異常和其他程序的訊號;
- 然後舉例說明了訊號在Unix/Linux 中的常見用法;
- 詳細解釋了Unix/Linux中的訊號處理,包括訊號型別、訊號向量位、訊號掩碼位、程序PROC結構體中的訊號處理程式以及訊號處理步驟;
- 用示例展示瞭如何安裝訊號捕捉器來處理程式異常,如使用者模式下的段錯誤;
- 還討論了將訊號用作程序間通訊(IPC)機制的適用性。讀者可藉助該程式設計專案,使用訊號和管道來實現用於程序交換資訊的程序間通訊機制。
訊號和中斷
- “中斷”是從I/O裝置或協處理器傳送到CPU的外部請求,它將CPU從正常執行轉移 到中斷處理。與傳送給CPU的中斷請求一樣,“訊號”是傳送給程序的請求,將程序從正常執行轉移到中斷處理。
- 程序:一個“程序”就是一系列活動。廣義的 “程序”包括:從事日常事務的人。在使用者模式或核心模式下執行的Unix/Linux程序。執行機器指令的CPU。
- “中斷”是傳送給“程序”的事件,它將“程序”從正常活動轉移到其他活動,稱為“中斷處理”。“程序”可在完成“中斷”處理後恢復正常活動。
- 根據來源,中斷可分為三類:
- 來自硬體的中斷;
- 來自其他人的中斷;
- 自己造成的中斷。
- 按照緊急程度,中斷可分為以下幾類:
- 不可遮蔽(NMI);
- 可遮蔽。
- 程序中斷
- 這類中斷是傳送給程序的中斷。當某程序正在執行時,可能會收到來自3個不同來源的中斷:
- 來自硬體的中斷:終端、間隔定時器的“Ctrl+C”組合鍵等。
- 來自其他程序的中斷:kill(pid,SIG#), death_of_child等。
- 自己造成的中斷:除以0、無效地址等。
- 每個程序中斷都被轉換為一個唯一ID號,傳送給程序。與多種類的人員中斷不同,我們始終可限制在一個程序中的中斷的數量。
- Unix/Linux中的程序中斷稱為訊號,編號為1到31。
- 程序的PROC結構體中有對應每個訊號的動作函式,程序可在收到訊號後執行該動作函式。
- 與人員類似,程序也可遮蔽某些型別的訊號,以推遲處理。必要時,程序還可能會修改訊號動作函式。
- 硬體中斷:
- 這類中斷是傳送給處理器或CPU的訊號。它們也有三個可能的來源:
- 來自硬體的中斷:定時器、I/O裝置等.
- 來自其他處理器的中斷:FFP. DMA、多處理器系統中的其他CPU。
- 自己造成的中斷:除以0、保護錯誤、INT指令。
- 這類中斷是傳送給處理器或CPU的訊號。它們也有三個可能的來源:
- 毎箇中斷都有唯一的中斷向量號。動作函式是中斷向量表中的中斷處理程式。
- 程序的陷阱錯誤
- 程序可能會自己造成中斷。這些中斷是由被CPU識別為異常的錯誤引起的,例如除以0、無效地址、非法指令、越權等。
- 當程序遇到異常時,它會陷入作業系統核心,將陷阱原因轉換為訊號編號,並將訊號傳送給自己。如果在使用者模式下發生異常,則程序的預設操作是終止,並使用一個可選的記憶體轉儲進行除錯。
Unix/Linux中的訊號處理
- Unix/Linux支援31種不同的訊號,每種訊號在 signal.h檔案中都有定義。
#define SIGHUP
#define SIGINT
#define SIGQUIT
#define SIGILL #define SIGTRAP
#define SIGABRT #define SIGIOT
#define SIGBUS
#define SIGFPE
#define SIGKILL
#define SIGUSR1
#define SIGSEGV
#define SIGUSR2
#define SIGPIPE #define SIGALRM
#define SIGTERM
#define SIGSTKFLT
#define SIGCHLD
#define SIGCONT
#define SIGSTOP
#define SIGTSTP
#define SIGTTIN
#define SIGTTOU
#define SIGURG
#define SIGXCPU
#define SIGXFSZ
#define SIGVTALRM
#define SIGPROF
#define SIGWINCH
#define SIGPOLL
#define SIGPWR
#define SIGSYS
Unix/Linux訊號示例
- (1)按“Ctrl+C”組合鍵通常會導致當前執行的程序終止。原因如下:
- “Ctr1+C”組合鍵會生成一個鍵盤硬體中斷。鍵盤中斷處理程式將“Ctrl+C”組合鍵轉換為SIGINT(2)訊號,傳送給終端上的所有程序,並喚醒等待鍵盤輸人的程序。在核心模式下,每個程序都要檢查和處理未完成的訊號。程序對大多數訊號的預設操作是呼叫核心的kexit(exitValue)函式來終止。在Linux中,exitValue的低位位元組是導致程序終止的訊號編號。
- (2)使用者可使用
nohup a.out
命令在後臺執行一個程式。即使在使用者退出後,程序仍將繼續執行。- nobup命令會使sh像往常一樣復刻子程序來執行程式,但是子程序會忽略SIGHuP(1)訊號。當用戶退出時,sh會向與終端有關的所有程序傳送一個SIGHUP訊號。後臺程序在接收到這一訊號後,會忽略它並繼續執行。為防止後臺程序使用終端進行I/O,後臺程序通常會斷開與終端的連線(通過將其檔案描述符0、1、2重定向到/dev/null),使其完全不受任何面向終端訊號的影響。
- (3) 使用者可以使用sh命令killpid(orkill-s9pia)殺死該程序。方法如下。
- 執行殺死的程序向pid標識的目標程序傳送一個SIGTERM ( 15 )訊號,請求它死亡。目標程序將會遵從請求並終止。如果程序選擇忽略SIGTERM訊號,它可能拒絕死亡。
訊號的來源
- 來自硬體中斷的訊號:在執行過程中,一些硬體中斷被轉換為訊號傳送給程序硬體訊號示例。
- 中斷鍵(Ctrl+C),它產生一個SIGINT(2)訊號。
- 間隔定時器,當他的時間到期時,會生成一個SIGALRM(14)、SIGTALRM(26)或SIGPROF(27)訊號。
- 其他硬體錯誤,如匯流排錯誤、IO陷進。
- 來自異常的訊號:常見的陷阱訊號有SIGFPE(8),表示浮點異常(除以0),最常見也是最可怕的時SIGSEGV(11),表示段錯誤。
- 來自其他程序的訊號:程序可以使用kill(pid,sig)系統呼叫向pid標識的目標程序傳送訊號。
訊號處理函式
- 每個程序PROC 都有一個訊號處理陣列 int sig[32]。Sig[32]陣列的每個條目都指定了如何處理相應的訊號,其中0表示 DEFault(預設).1表示 IGNore(忽略).其他非零值表示使用者模式下預先安裝的訊號捕捉(處理)函式。下圖給出了訊號位向量、遮蔽位向量和訊號處理函式。
實踐
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<setjmp.h>
#include<string.h>
jmp_buf env;
int count = 0;
void handler(int sig, siginfo_t *siginfo, void *context)
{
printf ("handler: sig=%d from PID=%d UID=%d count=%d\n",
sig, siginfo->si_pid, siginfo->si_uid, ++count);
if (count >= 4) // let it occur up to 4 times
longjmp(env, 1234);
}
int BAD()
{
int *ip = 0;
printf("in BAD(): try to dereference NULL pointer\n");
*ip = 123; // dereference a NULL pointer
printf("should not see this line\n");
}
int main (int argc, char *argv[])
{
int r;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = &handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV,&act,NULL);
if ((r = setjmp(env)) == 0)
BAD();
else
printf("proc %d survived SEGMENTATION FAULT: r=%d\n",getpid(), r);
printf("proc %d looping\n",getpid());
while(1);
}