什么是信號
信號是一種軟件中斷,用于通知進程系統中發生了某種特定事件。它是操作系統與進程之間,以及進程與進程之間進行異步通信的一種方式。在 Linux 系統中,信號是一種比較簡單的進程間通信機制。當一個信號產生時,內核會通過某種方式通知相應的進程,進程接收到信號后,會根據信號的類型以及自身對該信號的處理方式來做出響應。例如,用戶按下 Ctrl + C 組合鍵,系統會向當前前臺進程發送一個 SIGINT 信號,通常情況下,進程接收到這個信號后會終止運行。
常見的信號
SIGINT
SIGINT:中斷信號,通常由用戶按下 Ctrl + C 組合鍵產生。它用于通知進程需要立即停止當前操作并退出。許多交互式程序,如命令行工具,在接收到 SIGINT 信號時會停止運行,清理資源并退出。?
SIGTERM:終止信號。這是一個通用的終止信號,系統或其他進程可以發送給目標進程,請求其正常終止。與 SIGKILL 不同,SIGTERM 允許進程有機會在終止前進行清理操作,例如關閉打開的文件、釋放內存等。許多服務器程序在接收到 SIGTERM 信號時,會停止接受新的連接,并逐步關閉當前正在處理的連接,然后安全退出。?
SIGKILL
SIGKILL:強制終止信號。一旦進程接收到 SIGKILL 信號,內核會立即終止該進程,進程沒有機會進行任何清理操作。這個信號主要用于處理那些陷入死鎖或無法響應其他正常終止信號的進程。但由于它不允許進程進行清理,可能會導致資源沒有正確釋放等問題,所以一般作為最后的手段使用。?
SIGALRM
SIGALRM:鬧鐘信號。進程可以使用 alarm 函數設置一個定時器,當定時器超時后,內核會向該進程發送 SIGALRM 信號。常用于實現定時任務,比如在一個網絡請求中設置超時時間,如果在規定時間內沒有得到響應,進程接收到 SIGALRM 信號后可以進行相應的錯誤處理。?
SIGCHLD
SIGCHLD:子進程狀態改變信號。當一個進程的子進程終止、暫停或繼續運行時,內核會向該父進程發送 SIGCHLD 信號。父進程可以通過處理這個信號來回收子進程的資源,避免產生僵尸進程。
進程怎么處理信號
默認處理
默認處理:每個信號都有系統默認的處理方式。例如,對于 SIGINT 信號,默認處理方式是終止進程;對于 SIGQUIT 信號,默認處理方式是終止進程并生成核心轉儲文件(如果允許的話)。進程在沒有對某個信號進行自定義處理時,就會按照系統默認方式來響應信號。
忽略信號?
忽略信號:進程可以選擇忽略某些信號,即接收到信號后不進行任何操作。但并不是所有信號都可以被忽略,例如 SIGKILL 和 SIGSTOP 信號不能被忽略,這是為了保證系統能夠在必要時強制終止或暫停進程。一般情況下,進程可以通過調用 signal 函數來設置對某個信號的處理方式為忽略,例如signal(SIGINT, SIG_IGN); 這行代碼會使進程忽略 SIGINT 信號,當用戶按下 Ctrl + C 時,進程不會終止。?
捕獲信號并自定義處理
捕獲信號并自定義處理:進程可以定義一個信號處理函數,當接收到特定信號時,內核會調用該函數,進程在函數中可以執行自定義的操作。首先需要定義信號處理函數,
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>int fd;
int* ptr;void sigint_handler(int signum) {printf("Received SIGINT. Cleaning up...\n");if (fd != -1) {close(fd);printf("File test.txt closed.\n");}if (ptr != NULL) {free(ptr);printf("Memory freed.\n");}exit(0);
}int main() {signal(SIGINT, sigint_handler);fd = open("test.txt", O_RDONLY);ptr = (int*)malloc(sizeof(int));while(1) {printf("Main process is working...\n");sleep(1);}return 0;
}
stdio.h:提供標準輸入輸出庫函數,如 printf 用于輸出信息。
signal.h:用于信號處理相關的函數和宏定義,這里會用到 signal 函數來設置信號處理程序。
unistd.h:包含了許多 Unix 標準庫函數,例如 close 用于關閉文件描述符,sleep 用于使程序暫停執行。
fcntl.h:提供文件控制操作的函數和宏定義,代碼中使用 open 函數來打開文件。
stdlib.h:包含通用工具函數,例如 malloc 用于動態分配內存,free 用于釋放動態分配的內存,exit 用于終止程序。
fd:用于存儲文件描述符,后續會通過 open 函數打開文件并將返回的文件描述符賦值給它。
ptr:是一個指向 int 類型的指針,用于存儲動態分配的內存地址。
sigint_handler:這是一個信號處理函數,當進程接收到 SIGINT 信號時會被調用。
signum:是傳遞給信號處理函數的信號編號,在這個函數中,signum 的值為 SIGINT。
函數內部操作:
打印一條消息,表示接收到了 SIGINT 信號,開始進行清理工作。
檢查 fd 是否有效(不等于 -1),如果有效則關閉文件并打印關閉文件的信息。
檢查 ptr 是否為空指針,如果不為空則釋放動態分配的內存并打印釋放內存的信息。
調用 exit(0) 終止程序,返回狀態碼 0 表示正常退出。
signal(SIGINT, sigint_handler):將 SIGINT 信號的處理函數設置為 sigint_handler,這樣當進程接收到 SIGINT 信號(通常是用戶按下 Ctrl+C)時,會調用 sigint_handler 函數進行處理。
fd = open(“test.txt”, O_RDONLY);:以只讀模式打開 test.txt 文件,并將返回的文件描述符賦值給 fd。
ptr = (int*)malloc(sizeof(int));:動態分配一個 int 類型大小的內存空間,并將其地址賦值給 ptr。
while(1):創建一個無限循環,模擬主程序持續執行任務。
printf(“Main process is working…\n”);:每秒打印一條消息,表示主程序正在運行。
sleep(1);:使程序暫停執行 1 秒,避免消息打印過快。
代碼通過設置信號處理函數,確保在程序運行過程中接收到 SIGINT 信號時,能夠正確地關閉打開的文件并釋放動態分配的內存,然后正常終止程序,避免資源泄漏。
alarm 和pause函數
alarm 函數:alarm 函數用于設置一個定時器,在指定的秒數后,內核會向調用該函數的進程發送 SIGALRM 信號。函數原型為unsigned int alarm(unsigned int seconds);,參數 seconds 表示定時器的超時時間,單位為秒。如果在調用 alarm 函數時,之前已經設置過定時器且尚未超時,那么之前設置的定時器將被新的定時器覆蓋,并且返回值是之前定時器剩余的時間。如果 seconds 為 0,則取消之前設置的定時器,并且返回值為之前定時器剩余的時間(如果之前沒有設置定時器,則返回 0)。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void sigalrm_handler(int signum) {printf("Time's up!\n");
}int main() {signal(SIGALRM, sigalrm_handler);alarm(5); // 設置5秒后發送SIGALRM信號while(1) {// 主程序執行其他任務sleep(1);printf("Working...\n");}return 0;
}
設置了一個 5 秒的定時器,5 秒后進程接收到 SIGALRM 信號,會調用 sigalrm_handler 函數輸出 “Time’s up!”。
pause 函數:pause 函數用于使調用它的進程暫停執行,直到該進程接收到一個信號。函數原型為int pause(void);。如果進程接收到的信號的處理方式是默認處理或忽略,那么 pause 函數返回后,進程會繼續執行 pause 函數后面的代碼;如果進程接收到的信號的處理方式是捕獲信號并執行自定義處理函數,那么在自定義處理函數執行完畢后,pause 函數返回,進程繼續執行 pause 函數后面的代碼。pause 函數的返回值總是 - 1,并且會設置 errno 為 EINTR,表示被信號中斷。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void sigalrm_handler(int signum) {printf("Time's up!\n");
}int main() {signal(SIGALRM, sigalrm_handler);alarm(5); // 設置5秒后發送SIGALRM信號while(1) {// 主程序執行其他任務sleep(1);printf("Working...\n");}return 0;
}
進程執行到 pause 函數時會暫停,當用戶按下 Ctrl + C 發送 SIGINT 信號后,進程調用 sigint_handler 函數,然后 pause 函數返回,進程繼續執行輸出 “After pause”。