Linux學習筆記:
https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482
前言:
前面我們已經將進程通信部分講完了,現在我們來講一個進程部分也非常重要的知識點——信號,信號也是進程間通信的一種,本篇主要講解信號的概念和信號的幾種產生方法及對應的場景
目錄
一、引言
二、信號的概念
2.1 什么是信號
2.2 信號的作用
2.3 信號的特點
2.4 常見信號列表
?編輯
三、信號的產生
3.1 前臺進程和后臺進程
3.2?用戶產生信號
3.3?系統產生信號
3.4?軟件產生信號
四、信號的處理
4.1 默認處理方式
4.2 自定義信號處理函數
五、總結
一、引言
在 Linux 操作系統中,信號(Signal)是一種進程間通信(IPC,Inter - Process Communication)的機制,它用于通知進程發生了某種異步事件。信號可以來自內核,也可以來自其他進程。進程接收到信號后,會根據信號的類型以及自身的處理方式做出相應的反應。理解信號對于編寫健壯的 Linux 程序以及深入理解 Linux 操作系統的運行機制至關重要。
二、信號的概念
2.1 什么是信號
信號是一種軟中斷,它是一種異步通知機制。當某個特定事件發生時,如用戶按下特定組合鍵、系統資源耗盡、進程異常終止等,系統會向相關進程發送一個信號。每個信號都有一個對應的編號和名稱,例如信號 1 表示 SIGHUP(掛起信號),信號 9 表示 SIGKILL(強制終止信號)。
2.2 信號的作用
信號的主要作用是讓進程能夠對異步事件做出響應。例如,當用戶在終端中按下 Ctrl + C 組合鍵時,系統會向當前前臺進程發送 SIGINT 信號,通常進程會接收到這個信號后停止當前正在執行的任務并退出。信號還可以用于進程間的通信,一個進程可以向另一個進程發送信號來通知其執行某些操作。
結合2.1和2.2我們來講解一個概念:信號是一種軟中斷,是什么意思呢?當我們往鍵盤中輸入內容時是如何告訴給內核的?ctrl+c又是如何被解釋為指令的呢?
我們先來看下面這張圖:
? ? ? ? 鍵盤實際上是通過中斷來讓操作系統知道自己要寫入內容的,鍵盤被按下時,就會觸發硬件中斷,不同的硬件對應著不同的中斷號,中斷單元就可以通過它們的中斷號將它們與CPU中不同的鍵位相連,從而使CPU中這個方向的寄存器(32位)特定位置產生電信號,操作系統中有一個叫中斷向量表的類似于函數指針結構體的結構,里面保存著訪問各種外設的方法,操作系統通過CPU產生的電信號就辨別出要獲取哪種硬件的信息,從而通過中斷向量表中的方法,將硬件中的信息拷貝到操作系統的文件緩沖區中(操作系統下一切皆文件,且每一個文件都有自己的文件緩沖中區),然后再拷貝到用戶緩沖區
? ? ? ?同時比如鍵盤等外鍵,操作系統在獲取鍵盤上的信息時會先進行識別,會對數據進行判斷,如果是控制進程的比如ctrl+c等組合鍵就不會往緩沖區中拷貝,我們可以發現我們學習的信號與上面的中斷過程很像,其實信號,就是用軟件方式,模擬的對講程的硬件中斷,所以信號也被叫做軟中斷
2.3 信號的特點
- 異步性:信號的產生是異步的,與進程的執行順序無關。進程在運行過程中可能隨時收到信號。
- 簡單性:信號機制相對簡單,只需要一個信號編號就可以標識不同的信號。
- 有限性:Linux 系統中定義的信號數量是有限的,不同的系統可能略有差異,但通常在幾十種左右。
2.4 常見信號列表
信號編號 | 信號名稱 | 含義 | 默認處理方式 |
1 | SIGHUP | 掛起信號,通常在終端關閉時發送給相關進程 | 終止進程 |
2 | SIGINT | 中斷信號,由用戶按下 Ctrl + C 組合鍵產生 | 終止進程 |
3 | SIGQUIT | 退出信號,由用戶按下 Ctrl + \ 組合鍵產生 | 終止進程并生成核心轉儲文件 |
9 | SIGKILL | 強制終止信號,不能被捕獲、阻塞或忽略 | 立即終止進程 |
15 | SIGTERM | 終止信號,通常用于正常終止進程 | 終止進程 |
18 | SIGCONT | 繼續信號,用于恢復被暫停的進程 | 繼續執行進程 |
19 | SIGSTOP | 停止信號,用于暫停進程,不能被捕獲、阻塞或忽略 | 暫停進程 |
可以通過kill -l指令查看所有信號
kill -l
三、信號的產生
3.1 前臺進程和后臺進程
先來科普一個小知識點:前臺進程和后臺進程,來看下面一個程序
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{while(true){cout<<"I am a crazy process"<<endl;sleep(1);}return 0;
}
我們進行編譯后會得到一個可執行程序
./myfile
我們這樣執行時我們會發現在程序運行的時候,我們輸入其它指令比如Is,pwd等都不會有結果,進程還在繼續運行,除非用ctrl+c終止掉進程,這樣的進程稱為前臺進程
./myfile &
這種的后面加上地址符的叫做后臺進程,后臺進程可以被其它進程命令臨時打斷并執行這個命令,比如我們輸入ls指令,進程就會暫停并且輸出Is的結果,但是最后需要自己把進程結束掉
Linux中,一次登陸中, 一個終端,一般會配上一個bash,每一個登陸,只允許一個進程是前臺進程,可以允許多個進程是后臺進程
當./process運行時,輸入指令之所以不能運行就是因為此時的前臺進程由bash轉變為了process
- 終端占用情況
- 前臺進程:會獨占終端,直到進程執行完成或者被掛起,在這期間終端無法接受其他命令輸入,用戶只能與該進程進行交互。
- 后臺進程:不會占用終端,終端可以繼續接受用戶輸入的其他命令,用戶可以在同一個終端中同時啟動多個后臺進程,并隨時切換到其他任務。
- 運行特性
- 前臺進程:其執行過程會受到用戶操作的直接影響,比如用戶可以通過鍵盤輸入來中斷或暫停進程。如果終端關閉,前臺進程通常會被終止,除非進行了特殊的設置。
- 后臺進程:通常是長時間運行的,不受終端關閉的影響,除非明確地對其進行停止或重啟操作。它按照自身的邏輯和任務需求在后臺持續運行,不會因為用戶的一些常規操作而中斷。
3.2?用戶產生信號
- 鍵盤輸入:用戶可以通過在終端中按下特定的組合鍵來產生信號。例如:
- Ctrl + C:產生 SIGINT 信號,用于中斷當前正在運行的進程。比如,我們在終端中運行一個長時間運行的命令while true; do echo "Hello"; sleep 1; done,按下 Ctrl + C 后,該命令對應的進程會接收到 SIGINT 信號并終止。
- Ctrl + \:產生 SIGQUIT 信號,不僅會終止進程,還會生成核心轉儲文件(如果系統配置允許,一般在云服務器上是默認關閉的,虛擬機上可能是開啟的)。例如,運行一個簡單的 C 程序#include <stdio.h> int main() { while(1); return 0; },編譯運行后,按下 Ctrl + \,進程會終止并生成核心轉儲文件(在當前目錄下,文件名為 core,具體名稱和位置可能因系統配置而異)。(了解即可,這個生成core文件的內容與進程退出部分也有聯系,有想了解的可以單獨去搜索一下)
- 使用 kill 命令:用戶可以使用 kill 命令向指定進程發送信號。kill 命令的基本語法是kill [信號編號] 進程ID。例如,要向進程 ID 為 1234 的進程發送 SIGTERM 信號(信號編號為 15),可以在終端中輸入kill -15 1234,也可以使用信號名稱kill -SIGTERM 1234。如果省略信號編號或名稱,默認發送 SIGTERM 信號。
3.3?系統產生信號
- 進程異常:當進程發生異常時,如段錯誤(訪問非法內存地址)、除零錯誤等,系統會向該進程發送相應的信號。
-
- 段錯誤(Segmentation Fault):當進程訪問了不屬于它的內存區域時,會產生段錯誤,一般都是野指針問題,系統會向該進程發送 SIGSEGV 信號。例如,下面的 C 代碼會導致段錯誤:
#include <stdio.h>int main() {int *ptr = NULL;*ptr = 10; // 試圖向空指針指向的地址寫入數據,會引發段錯誤return 0;}
編譯運行這段代碼,程序會崩潰,并提示 “Segmentation fault”,這是因為進程接收到了 SIGSEGV 信號。
- 除零錯誤(Division by Zero):當進程執行除法運算時,如果除數為零,會產生除零錯誤,系統會向該進程發送 SIGFPE 信號。例如:
#include <stdio.h>int main()
{int a = 10;int b = 0;int c = a / b; // 除零操作,會引發除零錯誤return 0;
}
運行這段代碼,程序會崩潰,并提示 “Floating point exception”,這是因為進程接收到了 SIGFPE 信號。
2. 系統資源相關:當系統資源達到一定閾值時,也可能產生信號。例如,當進程使用的內存超過了系統限制時,系統可能會發送 SIGKILL 信號來終止該進程,以防止系統內存耗盡。不過,這種情況通常需要系統進行相關的配置和監控。
3.4?軟件產生信號
- 使用 kill 函數:在 C 語言編程中,可以使用 kill 函數向指定進程發送信號。kill 函數的原型可以用man手冊查看,如下:
man 2 kill
其中,pid 是目標進程的 ID,sig 是要發送的信號編號。例如,下面的代碼演示了如何使用 kill 函數向另一個進程發送 SIGTERM 信號:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{pid_t target_pid = 1234; // 假設目標進程ID為1234int result = kill(target_pid, SIGTERM);if (result == -1){perror("kill failed");}else{printf("SIGTERM sent to process %d\n", target_pid);}return 0;
}
在實際使用中,需要將target_pid替換為真實的目標進程 ID。
2. 使用 raise 函數:進程可以使用 raise 函數向自身發送信號。raise 函數的原型也可以通過man手冊來查看,如下:
man raise
其中,sig 是要發送的信號編號。例如,下面的代碼演示了如何使用 raise 函數向自身發送 SIGINT 信號:
#include <stdio.h>
#include <signal.h>
int main()
{int result = raise(SIGINT);if (result != 0){perror("raise failed");}else{printf("SIGINT sent to self\n");}return 0;
}
運行這段代碼,進程會接收到自己發送的 SIGINT 信號并終止。
四、信號的處理
4.1 默認處理方式
每個信號都有一個默認的處理方式,常見的默認處理方式包括:
- 終止進程:如 SIGINT、SIGTERM 等信號的默認處理方式是終止進程。
- 生成核心轉儲文件并終止進程:例如 SIGQUIT 信號,在終止進程的同時會生成核心轉儲文件,該文件包含了進程在收到信號時的內存狀態等信息,可用于調試程序。
- 忽略信號:有些信號(如 SIGCHLD,子進程狀態改變時發送給父進程的信號)的默認處理方式是忽略。
4.2 自定義信號處理函數
進程可以通過調用 signal 函數或 sigaction 函數來設置自定義的信號處理函數。
- signal 函數:signal 函數的原型如下:
man signal
其中,signum 是信號編號,handler 是指向信號處理函數的指針。例如,下面的代碼演示了如何使用 signal 函數設置 SIGINT 信號的自定義處理函數:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signum)
{printf("Received SIGINT. Cleaning up...\n");// 在這里進行一些清理工作,如關閉文件、釋放資源等_exit(0); // 退出進程
}
int main()
{signal(SIGINT, signal_handler);while (1){printf("Running...\n");sleep(1);}return 0;
}
在這個例子中,當進程接收到 SIGINT 信號時,會調用signal_handler函數,而不是默認的終止進程操作。
2. sigaction 函數:sigaction 函數比 signal 函數提供了更豐富的功能,它可以設置信號處理函數、處理信號時的掩碼、信號的標志等。sigaction 函數的原型如下:
#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);};
其中,signum 是信號編號,act 是指向新的信號處理動作的結構體指針,oldact 是指向舊的信號處理動作的結構體指針(如果不需要獲取舊的處理動作,可以設為 NULL)。例如,下面的代碼演示了如何使用 sigaction 函數設置 SIGINT 信號的自定義處理函數:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signum)
{printf("Received SIGINT. Cleaning up...\n");// 在這里進行一些清理工作,如關閉文件、釋放資源等_exit(0); // 退出進程
}
int main()
{struct sigaction new_action, old_action;new_action.sa_handler = signal_handler;sigemptyset(&new_action.sa_mask);new_action.sa_flags = 0;sigaction(SIGINT, &new_action, &old_action);while (1){printf("Running...\n");sleep(1);}return 0;
}
這段代碼與使用 signal 函數的例子功能類似,但使用 sigaction 函數可以更靈活地配置信號處理方式。
五、總結
信號是 Linux 系統中一種重要的進程間通信和異步事件通知機制。通過本文,我們詳細了解了信號的概念,信號的產生和部分信號的處理工作,后面我們還會講解信號的捕捉等處理工作,學習信號可以幫助我們更好的實現進程通信和異步處理等諸多操作
本篇筆記:
感謝各位大佬觀看,創作不易,還請各位大佬點贊支持!!!