1. 程式人生 > 遊戲攻略 >《雲頂之弈》11.22版女槍保鏢狙玩法講解

《雲頂之弈》11.22版女槍保鏢狙玩法講解

訊號和訊號處理

摘要

本章講述了訊號和訊號處理;介紹了訊號和中斷的統一處理,有助於從正確的角度看待訊號;將訊號視為程序中斷,將程序從正常執行轉移到訊號處理;解釋了訊號的來源,包括來自硬體、異常和其他程序的訊號;然後舉例說明了訊號在Unix/Linux中的常見用法;詳細解釋了Unix/Linux中的訊號處理,包括訊號型別、訊號向量位、訊號掩碼位、程序PROC結構體中的訊號處理程式以及訊號處理步驟;用示例展示瞭如何安裝訊號捕捉器來處理程式異常,如使用者模式下的段錯誤;還討論了將訊號用作程序間通訊(IPC)機制的適用性。讀者可藉助該程式設計專案,使用訊號和管道來實現用於程序交換資訊的程序間通訊機制。

訊號和中斷

  • 中斷:是從I/0裝置或協處理器傳送到CPU的外部請求,它將CPU從正常執行轉移到中斷處理。與傳送給CPU的中斷請求一樣,“訊號”是傳送給程序的請求,將程序從正常執行轉移到中斷處理。
    (1)一個程序就是一系列活動。
    (2)中斷是傳送給程序的事件,它將程序從正常活動轉移到其他活動,稱 為“中斷處理”。程序可在完成中斷處理後恢復正常活動。
    (3)中斷可應用於任何程序,並不僅限於計算機中的CPU。

  • 程序中斷:這類中斷是傳送給程序的中斷。當某程序正在執行時,可能會收到三個不同來源的終端:

​ (1)來自硬體的中斷:終端、間隔定時器的“Ctrl+C”組合鍵等。

​ (2)來自其他程序的中斷。

​ (3)自己造成的中斷。

  • 硬體中斷:這類中斷時傳送給處理器或CPU的訊號。
  • 程序陷阱錯誤:程序可能會造成中斷。這些中斷是由被CPU識別為異常的錯誤引起的,例如除以0、無效地址、非法指令、越權等。當程序遇到異常時,他會陷入作業系統核心,將陷阱原因轉換為訊號編號,並將訊號發給自己。

​ (1)使用者模式下發生異常:則程序的預設操作是終止,並使用一個可選的記憶體儲存進行除錯。程序可以用訊號捕捉器代替預設動作函式,允許它在使用者模式下處理訊號;

​ (2)核心模式下發生陷阱:原因是硬體錯誤或者是核心程式碼中的漏洞,在這種情況下,核心無法處理。在Unix/Uinux中,核心只打印一條PANIC錯誤的訊息,然後停止,希望在下一個核心版本中可以跟蹤並修復這些問題。

Unix/Linux中的訊號處理

訊號型別

每種訊號都有一個符號名,如SIGHUP(1)、SIGEMT(2)、SIGKILL(9)、SIGSEGV(11)等。

訊號的來源

  • 來自硬體中斷的訊號:在執行過程中,一些硬體中斷被轉換為訊號傳送給程序硬體訊號示例

    (1)中斷鍵(Ctrl+C),它產生一個SIGINT(2)訊號。

    (2)間隔定時器,當他的時間到期時,會生成一個SIGALRM(14)、SIGTALRM(26)或SIGPROF(27)訊號。

    (3)其他硬體錯誤,如匯流排錯誤、IO陷進

  • 來自異常的訊號:常見的陷阱訊號有SIGFPE(8),表示浮點異常(除以0),最常見也是最可怕的時SIGSEGV(11),表示段錯誤

  • 來自其他程序的訊號:程序可以使用kill(pid,sig)系統呼叫向pid標識的目標程序傳送訊號。

程序PROC結構體中的訊號

每個程序PROC都有一個32位向量,用來記錄傳送給程序的訊號。在位向量中,每一位(0位除外)代表一個訊號編號。此外,他還有一個訊號MASK位向量,用來遮蔽相信的訊號。

訊號處理函式

每個程序PROC都有一個訊號處理陣列int sig[32]。sig[32]陣列的每個條目都指定了如何處理相應的訊號,其中0表示 DEFault(預設),1表示IGNore(忽略),其他非零值表示使用者模式下預先安裝的訊號捕捉(處理)函式。圖給出了訊號位向量、遮蔽位向量和訊號處理函式。

如果訊號位向量中的位1為1,則會生成一個訊號I或將其傳送給程序。如果遮蔽位向量的位1為1,則訊號會被阻塞或遮蔽。否則,訊號未被阻塞。只有當訊號存在並且未被阻塞時,訊號才會生效或傳遞給程序。當核心模式下的程序發現一個未阻塞訊號時,會將訊號位清除為0,並嘗試通過訊號處理陣列中的處理函式來處理該訊號。0表示 DEFault,1表示IGNore,其他數值表示使用者空間內預先安裝的捕捉函式。

安裝訊號捕捉函式

程序可以使用系統呼叫

int r = signal(int signal_number,voide *handler);

來修改選定訊號編號的處理函式,(19)和(9)除外,他們不能修改。

signal()系統呼叫在所有類Unix系統中均可用,但它有一些不理想的特點。
(1)在執行已安裝的訊號捕捉函式之前,通常將訊號處理函式重置為DEFault。為捕捉下次出現的相同訊號,必須重新安裝捕捉函式。這可能會導致下一個訊號和訊號處理函式重新安裝之間出現競態條件。相反,sigaction()在執行當前捕捉函式時會自動阻塞下一個訊號,因此不會出現競態條件。
( 2 ) signal()不能阻塞其他訊號。必要時,使用者必須使用sigprocmask()顯式地阻塞或解鎖其他訊號。相反,sigaction(可以指定要阻塞的其他訊號。
( 3 ) signal()只能向捕捉函式傳送一個訊號編號。sigaction()可以傳輸關於訊號的其他資訊。

signal()系統呼叫在所有類Unix系統中均可用,但它有一些不理想的特點。
(1)在執行已安裝的訊號捕捉函式之前,通常將訊號處理函式重置為DEFault。為捕捉下次出現的相同訊號,必須重新安裝捕捉函式。這可能會導致下一個訊號和訊號處理函式重新安裝之間出現競態條件。相反,sigaction()在執行當前捕捉函式時會自動阻塞下一個訊號,因此不會出現競態條件。
( 2 ) signal()不能阻塞其他訊號。必要時,使用者必須使用sigprocmask()顯式地阻塞或解鎖其他訊號。相反,sigaction(可以指定要阻塞的其他訊號。
( 3 ) signal()只能向捕捉函式傳送一個訊號編號。sigaction()可以傳輸關於訊號的其他資訊。

( 4 ) signal()可能不適用於多執行緒程式中的執行緒。sigaction()適用於執行緒。
(5)不同Unix版本的signal()可能會有所不同。sigaction()採用的是POISX標準,可移植性更好。

sigaction()系統呼叫

  • 原型:int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact);

  • sigaction()結構體的定義為:

struct sigaction{
	void (*sa_handler)(int);
	void (*sa_sigaction)(int,siginfo_t *,void *);
	sigset_t sa_mask;
	int sa_flags;
	void (*sa_restorer)(void);
}
  • 重要欄位:

​ sa_handler:指向處理函式的指標,與signal()處理函式有相同的原型;

​ sa_sigaction:執行訊號處理函式的另一種方法。它的訊號編號旁邊有兩個額外引數,其中siginfo t *提供關於所接收訊號的更多資訊;

​ sa_mask:可在處理函式執行期間設定要阻塞的訊號;

​ sa_flags:可修改訊號處理程序的行為。若要使用sa_sigaction處理函式,必須將sa_flags設定為SA_SIGINFO。

訊號處理步驟

  • 當某程序處於核心模式時,會檢查訊號並處理未完成的訊號。如果某訊號有使用者安裝的捕捉函式,該程序會先清除訊號,獲取捕捉函式地址,對於大多數陷阱訊號,則將已安裝的捕捉函式重置為DEFault。然後,它會在使用者模式下返回,以執行捅捉函式,以這種方式篡改返回路徑。當捕捉函式結束時,它會返回到最初的中斷點,即它最後進入核心模式的地方。因此,該程序會先遷回執行捕捉函式,然後再恢復正常執行。

  • 重置使用者安裝的訊號捕捉函式:使用者安裝的陷阱相關訊號捕捉函式用於處理使用者程式碼中的陷阱錯誤。由於捕捉函式也在使用者模式下執行,因此可能會再次出現同樣的錯誤。如果是這樣,該程序最終會陷入無限迴圈,一直在使用者模式和核心模式之間跳躍。為了防止這種情況,Unix核心通常會在允許程序執行捕捉函式之前先將處理函式重置為DEFault。這意味著使用者安裝的捕捉函式只對首次出現的訊號有效。若要捕捉再次出現的同一訊號,則必須重新安裝捕捉函式。但是,使用者安裝的訊號捕捉函式的處理方法並不都一樣,在不同 Unix版本中會有所不同。例如,在 BSD Unix中,訊號處理函式不會被重置,但是該訊號在執行訊號捕捉函式時會被阻塞。感興趣的讀者可參考關於Lioux訊號和 sigaction函式的手冊頁,以瞭解更多詳細資訊。

  • 訊號和喚醒:在Unix/Linux核心中有兩種SLEEP程序;深度休眠程序和淺度休眠程序。前一種程序不可中斷,而後一種程序可由訊號中斷。如果某程序處於不可中斷的SLEEP狀態,到達的訊號(必須來自硬體中斷或其他程序)不會喚醒程序。如果它處於可中斷的SLEEP狀態,到達的訊號將會喚醒它。例如,當某程序等待終端輸入時,它會以低優先順序休眠,這種休眠是可中斷的,SIGINT這類訊號即可喚醒它。

Linux中的IPC

IPC是指用於程序間通訊的機制。在Linux中,IPC包含以下部分

1)管道和FIFO

2)訊號

3)System V IPC

4)POSIX訊息佇列

5)執行緒同步機制

6)套接字

實踐:

程式碼:

#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);
}