Linux中進程和線程常用的API詳解

進程與線程基礎及 Linux 進程間通信(IPC)詳解

一、程序與進程

1. 程序(靜態文件)

程序是存儲在磁盤上的可執行文件,是靜態實體,不占用 CPU、內存等運行時資源,僅占用磁盤空間。不同操作系統的可執行文件格式不同:

  • Windows:.exe
  • Linux:ELF(Executable and Linkable Format)
  • Android:.apk(本質是包含 ELF 可執行文件的壓縮包)

2. 進程(動態執行)

進程是程序的動態執行過程,是操作系統進行資源分配和調度的基本單位,擁有獨立的生命周期和運行時資源(CPU、內存、文件描述符等)。

(1)ELF 文件解析工具

Linux 下通過以下工具查看和分析 ELF 文件:

  • file 命令:查看文件類型基本信息
    示例:file /bin/ls
    輸出:/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, ...

  • readelf 命令:解析 ELF 文件詳細結構(需掌握核心選項)

    選項功能描述示例
    -h查看 ELF 文件頭信息(位數、字節序等)readelf -h /bin/ls
    -S查看 ELF 節頭信息(代碼段、數據段等)readelf -S /bin/ls
(2)ELF 文件頭關鍵信息

通過 readelf -h 可獲取以下核心信息:

  • 文件位數:第 5 個字節(Magic 字段)標識,01 表示 32 位,02 表示 64 位。
  • 字節序:第 6 個字節標識,01 表示小端序,02 表示大端序。
    • 小端序(Little-Endian):低位字節存低地址,高位字節存高地址。例如 0x12345678 存儲為 78 56 34 12
    • 大端序(Big-Endian):高位字節存低地址,低位字節存高地址。例如 0x12345678 存儲為 12 34 56 78
(3)ELF 文件類型

ELF 格式包含 4 類核心文件,對應不同使用場景:

類型描述示例
可執行文件可直接運行,包含完整的代碼和數據,加載后可執行/bin/ls、自己編譯的 ./test
可重定位文件(.o)編譯器生成的中間文件,需鏈接后才能執行(單個源文件編譯產物)gcc -c test.c 生成的 test.o
共享目標文件(.so)動態共享庫,可被多個程序動態鏈接復用,節省內存/lib/x86_64-linux-gnu/libc.so.6
核心轉儲文件(core)程序崩潰時生成的內存快照,用于調試(默認關閉,需 ulimit -c unlimited 開啟)程序崩潰后生成的 core.12345

3. 進程控制塊(PCB)—— task_struct

當 ELF 程序被執行時,Linux 內核會創建一個 task_struct 結構體 來描述該進程,即進程控制塊(PCB)。它記錄了進程的所有運行時信息,包括:

  • 進程 ID(PID)、父進程 ID(PPID)
  • 內存資源(虛擬地址空間、頁表)
  • CPU 調度信息(優先級、狀態)
  • 文件描述符表、信號處理方式
  • 鎖資源、信號量等
查看 task_struct 定義

task_struct 定義在 Linux 內核頭文件中,路徑如下:
/usr/src/linux-headers-<版本號>/include/linux/sched.h
查看命令:

cd /usr/src/linux-headers-$(uname -r)/include/linux
vim sched.h

4. 進程查看命令

命令功能描述示例
pstree以樹狀圖展示進程間的父子關系pstree(查看所有進程樹)
ps -ef查看系統中所有進程的詳細信息(PID、PPID 等)`ps -ef

二、進程狀態

Linux 進程有 7 種核心狀態,可歸納為 5 大類,狀態轉換是進程調度的核心邏輯。

1. 進程的“誕生”—— fork() 系統調用

  • 觸發條件:父進程調用 fork() 系統調用。
  • 核心邏輯:內核復制父進程的上下文(PCB、內存空間等),創建一個幾乎完全相同的子進程(子進程 PID 唯一,PPID 為父進程 PID)。
  • 初始狀態:子進程創建后進入 就緒態(TASK_RUNNING),等待 CPU 調度。

2. 核心狀態解析

狀態分類內核標識含義與典型場景
就緒態TASK_RUNNING進程已準備好運行,等待 CPU 時間片(放在就緒隊列中)。
執行態TASK_RUNNING進程正在 CPU 上執行代碼(內核復用 TASK_RUNNING 標識,通過是否在 CPU 上區分就緒/執行)。
睡眠態(掛起態)TASK_INTERRUPTIBLE可中斷睡眠:等待非關鍵事件(如 sleep(10)、鍵盤輸入),可被信號(如 SIGINT)喚醒。
TASK_UNINTERRUPTIBLE不可中斷睡眠:等待關鍵硬件操作(如磁盤修復),僅事件完成后喚醒,ps 顯示為 D 狀態。
暫停態TASK_STOPPED進程被暫停信號(如 SIGSTOP、Ctrl+Z)暫停,可通過 SIGCONT 恢復。
TASK_TRACED進程被調試器(如 gdb)跟蹤,處于暫停調試狀態。
退出相關狀態EXIT_ZOMBIE(僵尸態)進程已終止,但父進程未讀取其退出狀態,保留 PCB(ps 顯示為 Z 狀態)。
EXIT_DEAD(死亡態)父進程調用 wait()/waitpid() 讀取退出狀態后,內核釋放所有資源(進程徹底消失)。

三、進程控制核心函數

1. fork()——創建子進程

函數原型
#include <unistd.h>
pid_t fork(void);
返回值規則
  • 父進程:返回子進程的 PID(正數)。
  • 子進程:返回 0。
  • 失敗:返回 -1(如內存不足)。
示例代碼(父子進程區分)
#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork error"); // 錯誤處理return -1;} else if (pid == 0) {// 子進程邏輯printf("我是子進程,PID:%d,PPID:%d\n", getpid(), getppid());} else {// 父進程邏輯printf("我是父進程,PID:%d,子進程PID:%d\n", getpid(), pid);pause(); // 暫停父進程,避免子進程先退出}return 0;
}
關鍵特性:寫時復制(Copy-On-Write)

父子進程初始共享同一份物理內存,但當任一進程修改數據(棧、堆、全局變量等)時,內核才為修改的頁分配新物理內存,避免不必要的復制開銷。示例如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_num = 123; // 全局變量int main() {int stack_num = 10; // 棧變量int *heap_num = malloc(4); // 堆變量*heap_num = 100;pid_t pid = fork();if (pid == 0) {// 子進程修改數據(觸發寫時復制)g_num++;stack_num++;(*heap_num)++;printf("子進程:g_num=%d, stack_num=%d, heap_num=%d\n", g_num, stack_num, *heap_num);free(heap_num);} else {sleep(1); // 等待子進程修改完成// 父進程數據未被修改printf("父進程:g_num=%d, stack_num=%d, heap_num=%d\n", g_num, stack_num, *heap_num);free(heap_num);}return 0;
}

輸出結果
子進程:g_num=124, stack_num=11, heap_num=101
父進程:g_num=123, stack_num=10, heap_num=100

2. exit()/_exit()——進程退出

函數區別
函數功能描述緩沖區處理
exit(int status)終止進程,執行退出清理(調用 atexit() 注冊的函數),刷新標準 I/O 緩沖區。刷新緩沖區
_exit(int status)直接終止進程,不執行清理,不刷新緩沖區(內核級退出)。不刷新緩沖區
退出碼規則
  • exit(0)/exit(EXIT_SUCCESS):正常退出。
  • exit(1)/exit(EXIT_FAILURE):異常退出(非 0 即可,通常用 1)。
  • 退出碼范圍:0~255,超出則取模 256。
示例代碼(atexit() 注冊退出函數)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 注冊的退出函數(棧式調用,反向執行)
void clean1() { printf("clean1: 退出清理1\n"); }
void clean2() { printf("clean2: 退出清理2\n"); }int main() {atexit(clean1); // 先注冊atexit(clean2); // 后注冊printf("程序執行中(未刷新緩沖區)"); // 無換行符,緩沖區未刷新
#ifdef USE__EXIT_exit(0); // 直接退出,不刷新緩沖區,不執行 clean1/clean2
#elseexit(0);  // 刷新緩沖區,執行 clean2 → clean1(反向)
#endifreturn 0;
}

編譯與運行

  • 正常退出(exit(0)):
    gcc test.c -o test && ./test
    輸出:程序執行中(未刷新緩沖區)clean2: 退出清理2 clean1: 退出清理1
  • 直接退出(_exit(0)):
    gcc test.c -o test -DUSE__EXIT && ./test
    輸出:程序執行中(未刷新緩沖區)(無清理函數執行)

3. wait()/waitpid()——回收子進程

父進程通過這兩個函數回收子進程的退出狀態,避免子進程成為僵尸進程。

函數原型
#include <sys/wait.h>
#include <sys/types.h>// 等待任意子進程退出,獲取退出狀態
pid_t wait(int *status);// 等待指定 PID 的子進程退出,可設置非阻塞
pid_t waitpid(pid_t pid, int *status, int options);
核心參數說明(waitpid()
  • pid:指定等待的子進程 PID(-1 表示等待任意子進程)。
  • status:存儲子進程退出狀態的指針(需通過宏解析)。
  • options:選項(0 表示阻塞,WNOHANG 表示非阻塞)。
退出狀態解析宏

通過 status 指針獲取子進程退出詳情,核心宏如下:

功能描述適用場景
WIFEXITED(status)判斷子進程是否正常退出(exit/_exit正常退出
WEXITSTATUS(status)提取正常退出的退出碼(需先通過 WIFEXITED 判斷)正常退出
WIFSIGNALED(status)判斷子進程是否被信號終止信號終止(如 SIGKILL、SIGSEGV)
WTERMSIG(status)提取終止子進程的信號編號(需先通過 WIFSIGNALED 判斷)信號終止
WIFSTOPPED(status)判斷子進程是否被暫停暫停狀態(如 SIGSTOP)
示例代碼(回收正常退出的子進程)
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid = fork();if (pid == 0) {// 子進程:正常退出,退出碼 42printf("子進程 PID:%d,即將退出\n", getpid());exit(42);} else {// 父進程:等待子進程退出int status;pid_t ret = waitpid(pid, &status, 0); // 阻塞等待if (ret == -1) {perror("waitpid error");return -1;}// 解析退出狀態if (WIFEXITED(status)) {printf("子進程 %d 正常退出,退出碼:%d\n", ret, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("子進程 %d 被信號 %d 終止\n", ret, WTERMSIG(status));}}return 0;
}

輸出結果
子進程 PID:1234,即將退出
子進程 1234 正常退出,退出碼:42

示例代碼(回收被信號終止的子進程)
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>int main() {pid_t pid = fork();if (pid == 0) {// 子進程:觸發段錯誤(SIGSEGV,信號 11)int *null_ptr = NULL;*null_ptr = 10; // 非法內存訪問exit(0); // 不會執行} else {// 父進程:等待子進程退出int status;pid_t ret = waitpid(pid, &status, 0);if (WIFSIGNALED(status)) {printf("子進程 %d 被信號 %d 終止(段錯誤)\n", ret, WTERMSIG(status));}}return 0;
}

輸出結果
子進程 1235 被信號 11 終止(段錯誤)

4. exec 系列函數——替換進程映像

exec 系列函數將當前進程的代碼段和數據段替換為新的可執行文件(如 /bin/ls),實現“進程復用”(PID 不變,僅映像替換)。

核心函數(常用 execl
#include <unistd.h>// 格式:路徑 +  argv[0] + 參數列表 + NULL
int execl(const char *path, const char *arg0, ..., (char *)NULL);
示例代碼(子進程執行 /bin/ls
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {// 子進程:替換為 ls 命令(顯示當前目錄下的文件)printf("子進程即將執行 ls 命令\n");execl("/bin/ls", "ls", "-l", NULL); // 路徑:/bin/ls,參數:-l// execl 成功則不返回,失敗才執行以下代碼perror("execl error");exit(1);} else {wait(NULL); // 等待子進程執行完成printf("父進程:子進程執行完畢\n");}return 0;
}

輸出結果
子進程即將執行 ls 命令
total 8
-rwxr-xr-x 1 user user 4568 Aug 10 15:30 test
父進程:子進程執行完畢

四、進程組、會話與終端

1. 進程組(Process Group)

  • 定義:由一個或多個進程組成的集合,用于統一管理(如向組內所有進程發送信號)。
  • 核心屬性
    • 進程組 ID(PGID):組內所有進程的 PGID 相同。
    • 組長進程:PGID 等于其 PID 的進程(組長可終止,但組內有進程則組存在)。

2. 會話(Session)

  • 定義:進程組的集合,由會話首進程(創建會話的進程)初始化。
  • 核心屬性
    • 會話分為 前臺進程組后臺進程組
    • 前臺進程組可接收終端輸入(如 Ctrl+C 發送 SIGINT),后臺進程組不可。
    • 會話首進程終止時,會話依然存在。

3. 終端(Terminal)

  • 定義:用戶與系統交互的接口(如 SSH 終端、本地終端)。
  • 關聯關系:一個控制終端對應一個會話,終端產生的信號(如 Ctrl+Z)發送給前臺進程組。

五、守護進程(Daemon)

1. 定義與特點

守護進程是 Linux 中 脫離終端、后臺長期運行 的進程,用于執行周期性任務(如日志收集、服務監聽)。核心特點:

  • 脫離終端:不依賴任何交互窗口,終端關閉不影響其運行。
  • 父進程為 init(或 systemd):啟動后斷開與原父進程的聯系,由系統初始化進程托管。
  • 后臺運行:ps 顯示為 daemon 或無終端關聯(TTY 為 ?)。

2. 守護進程編寫流程(簡化版)

  1. fork() 創建子進程,父進程退出(脫離原進程組)。
  2. 子進程調用 setsid() 創建新會話(脫離原終端)。
  3. fork() 創建孫子進程,子進程退出(避免成為會話首進程,無法再次脫離終端)。
  4. 切換工作目錄(如 /),關閉不需要的文件描述符,重定向標準 I/O 到 /dev/null
  5. 執行核心業務邏輯(如循環監聽端口)。

六、Linux 進程間通信(IPC)

Linux 提供多種 IPC 機制,適用于不同場景,以下是核心方式:

1. 管道(Pipe)—— 匿名管道

  • 定義:內核維護的字節流緩沖區,用于父子進程或兄弟進程間的單向通信。
  • 核心特點
    • 半雙工:數據只能單向流動(需兩個管道實現雙向通信)。
    • 無名稱:僅能在有親緣關系的進程間使用。
    • 基于文件描述符:pipe(fd) 創建兩個描述符,fd[0] 讀,fd[1] 寫。
示例代碼(父子進程管道通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>int main() {int fd[2];if (pipe(fd) == -1) { // 創建管道perror("pipe error");return -1;}pid_t pid = fork();if (pid == 0) {// 子進程:寫數據(關閉讀端)close(fd[0]);char msg[] = "你好,父進程!";write(fd[1], msg, strlen(msg));close(fd[1]);exit(0);} else {// 父進程:讀數據(關閉寫端)close(fd[1]);char buf[100] = {0};ssize_t n = read(fd[0], buf, sizeof(buf));if (n > 0) {printf("父進程收到:%s\n", buf);}close(fd[0]);wait(NULL);}return 0;
}

輸出結果
父進程收到:你好,父進程!

2. 有名管道(FIFO)

  • 定義:有文件系統路徑的管道(如 /tmp/myfifo),可在無親緣關系的進程間通信。
  • 核心特點
    • 有名稱:通過文件路徑標識,任意進程可通過路徑訪問。
    • 阻塞特性:open 時若管道未被另一端打開,會阻塞直到另一端連接。
示例代碼(兩個獨立進程 FIFO 通信)
發送端(Jack)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>#define FIFO_PATH "/tmp/jack_rose_fifo"int main() {// 若 FIFO 不存在則創建if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {perror("mkfifo error");return -1;}// 以只寫方式打開 FIFO(阻塞直到讀端打開)int fd = open(FIFO_PATH, O_WRONLY);if (fd == -1) {perror("open error");return -1;}// 循環發送消息char buf[100] = {0};while (1) {printf("Jack: ");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));}close(fd);return 0;
}
接收端(Rose)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>#define FIFO_PATH "/tmp/jack_rose_fifo"int main() {// 若 FIFO 不存在則創建if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {perror("mkfifo error");return -1;}// 以只讀方式打開 FIFO(阻塞直到寫端打開)int fd = open(FIFO_PATH, O_RDONLY);if (fd == -1) {perror("open error");return -1;}// 循環接收消息char buf[100] = {0};while (1) {memset(buf, 0, sizeof(buf));ssize_t n = read(fd, buf, sizeof(buf) - 1);if (n > 0) {printf("Rose 收到: %s", buf);}}close(fd);return 0;
}

運行方式

  1. 先啟動接收端:gcc rose.c -o rose && ./rose
  2. 再啟動發送端:gcc jack.c -o jack && ./jack
  3. 發送端輸入消息,接收端實時顯示。

3. 共享內存(Shared Memory)

  • 定義:內核創建的一塊內存區域,多個進程可將其映射到自身虛擬地址空間,實現高效數據共享(無需拷貝,直接訪問內存)。
  • 核心 API
    • shm_open():創建或打開共享內存對象(類似文件操作)。
    • ftruncate():設置共享內存大小。
    • mmap():將共享內存映射到進程虛擬地址空間。
    • munmap():解除映射。
    • shm_unlink():刪除共享內存對象(所有進程解除映射后釋放)。
示例代碼(父子進程共享內存通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/stat.h>#define SHM_NAME "/my_shared_mem"
#define SHM_SIZE 1024int main() {// 1. 創建/打開共享內存對象int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);if (shm_fd == -1) {perror("shm_open error");return -1;}// 2. 設置共享內存大小if (ftruncate(shm_fd, SHM_SIZE) == -1) {perror("ftruncate error");return -1;}// 3. 映射共享內存到虛擬地址空間char *shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shm_ptr == MAP_FAILED) {perror("mmap error");return -1;}// 關閉文件描述符(映射后不再需要)close(shm_fd);pid_t pid = fork();if (pid == 0) {// 子進程:寫入數據strcpy(shm_ptr, "共享內存通信成功!");printf("子進程寫入:%s\n", shm_ptr);exit(0);} else {// 父進程:讀取數據wait(NULL);printf("父進程讀取:%s\n", shm_ptr);// 4. 解除映射munmap(shm_ptr, SHM_SIZE);// 5. 刪除共享內存對象(所有進程解除映射后釋放)shm_unlink(SHM_NAME);}return 0;
}

編譯與運行
gcc shm_test.c -o shm_test -lrt && ./shm_test-lrt 鏈接實時庫)
輸出結果
子進程寫入:共享內存通信成功!
父進程讀取:共享內存通信成功!

4. 消息隊列(Message Queue)

  • 定義:內核維護的消息鏈表,進程可按優先級發送/接收消息(類似“郵箱”)。
  • 核心特點
    • 消息有序:按優先級或發送順序排隊。
    • 非阻塞選項:支持超時機制(mq_timedsend/mq_timedreceive)。
    • 基于描述符:通過 mq_open() 獲取消息隊列描述符。
核心 API 與結構體
  • mq_attr 結構體:描述消息隊列屬性(最大消息數、單條消息最大大小等)。
    struct mq_attr {long mq_flags;    // 標志(忽略)long mq_maxmsg;   // 最大消息數long mq_msgsize;  // 單條消息最大字節數long mq_curmsgs;  // 當前消息數(忽略)
    };
    
  • mq_open():創建或打開消息隊列。
  • mq_timedsend():發送消息(支持超時)。
  • mq_timedreceive():接收消息(支持超時)。
  • mq_unlink():刪除消息隊列。
示例代碼(父子進程消息隊列通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <time.h>
#include <sys/wait.h>
#include <stdlib.h>#define MQ_NAME "/father_son_mq"
#define MAX_MSG_NUM 10    // 最大消息數
#define MAX_MSG_SIZE 100  // 單條消息最大大小int main() {// 1. 初始化消息隊列屬性struct mq_attr attr;attr.mq_maxmsg = MAX_MSG_NUM;attr.mq_msgsize = MAX_MSG_SIZE;attr.mq_flags = 0;attr.mq_curmsgs = 0;// 2. 創建/打開消息隊列mqd_t mq_fd = mq_open(MQ_NAME, O_CREAT | O_RDWR, 0666, &attr);if (mq_fd == (mqd_t)-1) {perror("mq_open error");return -1;}pid_t pid = fork();if (pid == 0) {// 子進程:接收消息char buf[MAX_MSG_SIZE] = {0};struct timespec timeout;for (int i = 0; i < 5; i++) {// 設置超時時間(當前時間 + 15 秒)clock_gettime(CLOCK_REALTIME, &timeout);timeout.tv_sec += 15;// 接收消息(超時返回錯誤)ssize_t n = mq_timedreceive(mq_fd, buf, MAX_MSG_SIZE, NULL, &timeout);if (n == -1) {perror("mq_timedreceive error");break;}printf("子進程收到:%s\n", buf);}exit(0);} else {// 父進程:發送消息char buf[MAX_MSG_SIZE] = {0};struct timespec timeout;for (int i = 0; i < 5; i++) {// 構造消息sprintf(buf, "父進程的第 %d 條消息", i + 1);// 設置超時時間(當前時間 + 5 秒)clock_gettime(CLOCK_REALTIME, &timeout);timeout.tv_sec += 5;// 發送消息if (mq_timedsend(mq_fd, buf, strlen(buf), 0, &timeout) == -1) {perror("mq_timedsend error");break;}printf("父進程發送:%s\n", buf);sleep(1); // 間隔 1 秒發送}// 等待子進程結束wait(NULL);// 3. 關閉消息隊列描述符mq_close(mq_fd);// 4. 刪除消息隊列mq_unlink(MQ_NAME);}return 0;
}

編譯與運行
gcc mq_test.c -o mq_test -lrt && ./mq_test
輸出結果
父進程發送:父進程的第 1 條消息
子進程收到:父進程的第 1 條消息
父進程發送:父進程的第 2 條消息
子進程收到:父進程的第 2 條消息

5. 信號(Signal)

信號是 Linux 中輕量級的進程間通信機制,用于通知進程發生了某種事件(如中斷、錯誤)。

(1)常用信號與編號
信號名稱編號含義與默認行為
SIGINT2中斷(Ctrl+C),默認終止進程
SIGKILL9強制終止,不可被捕獲/阻塞
SIGSTOP19暫停(Ctrl+Z),不可被捕獲/阻塞
SIGSEGV11段錯誤(非法內存訪問),默認終止并生成 core 文件
SIGCONT18恢復暫停的進程
(2)信號發送函數—— kill()/sigqueue()
  • kill():發送信號給指定進程。
    原型:int kill(pid_t pid, int sig);
    示例:kill(1234, SIGINT);(給 PID 1234 的進程發送中斷信號)。

  • sigqueue():發送信號并攜帶數據(僅支持實時信號)。
    原型:int sigqueue(pid_t pid, int sig, const union sigval value);
    示例:

    union sigval val;
    val.sival_int = 1001; // 攜帶整數數據
    sigqueue(1234, SIGUSR1, val); // 發送自定義信號 SIGUSR1
    
(3)信號捕獲函數—— signal()/sigaction()
  • signal():簡單信號捕獲(不推薦,兼容性差)。
    原型:void (*signal(int sig, void (*handler)(int)))(int);
    示例:

    #include <stdio.h>
    #include <signal.h>void sigint_handler(int sig) {printf("捕獲到 SIGINT 信號(編號:%d),不終止進程!\n", sig);
    }int main() {signal(SIGINT, sigint_handler); // 捕獲 SIGINTwhile (1) { sleep(1); } // 循環等待信號return 0;
    }
    
  • sigaction():功能強大的信號捕獲(推薦,支持攜帶數據、信號掩碼等)。
    示例(捕獲信號并接收數據):

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>// 信號處理函數(支持接收數據)
    void sigusr1_handler(int sig, siginfo_t *info, void *arg) {printf("捕獲到信號:%d\n", sig);printf("攜帶的數據:%d\n", info->si_int); // 讀取 sigqueue 發送的數據
    }int main() {struct sigaction act;act.sa_sigaction = sigusr1_handler; // 設置處理函數act.sa_flags = SA_SIGINFO; // 啟用數據接收sigemptyset(&act.sa_mask); // 清空信號掩碼// 注冊 SIGUSR1 信號的處理函數sigaction(SIGUSR1, &act, NULL);printf("進程 PID:%d,等待 SIGUSR1 信號...\n", getpid());while (1) { sleep(1); }return 0;
    }
    
(4)信號阻塞—— sigprocmask()

通過信號掩碼(sigset_t)阻塞指定信號,阻塞期間信號會被掛起,解除阻塞后再處理。
示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int sig) {printf("捕獲到 SIGINT 信號\n");
}int main() {// 1. 注冊信號處理函數signal(SIGINT, sigint_handler);// 2. 初始化信號集,添加 SIGINTsigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT);// 3. 阻塞 SIGINT(期間按 Ctrl+C 不會觸發處理函數)printf("開始阻塞 SIGINT,持續 5 秒...\n");sigprocmask(SIG_BLOCK, &set, NULL);sleep(5);// 4. 解除阻塞(掛起的 SIGINT 會立即觸發處理函數)printf("解除阻塞 SIGINT\n");sigprocmask(SIG_UNBLOCK, &set, NULL);while (1) { sleep(1); }return 0;
}

七、線程(Thread)

1. 進程與線程的核心區別

對比維度進程(Process)線程(Thread)
資源分配單位系統資源分配的基本單位(獨立內存、文件描述符等)CPU 調度的基本單位(共享進程資源)
資源占用占用資源多,創建/銷毀開銷大僅需少量棧空間,創建/切換開銷小
獨立性進程間獨立,一個崩潰不影響其他進程線程依賴進程,一個線程崩潰可能導致整個進程崩潰
通信方式依賴 IPC 機制(管道、共享內存等)直接共享進程資源(全局變量、堆內存等)

2. 線程的共享與非共享資源

(1)共享資源(進程級資源)
  • 文件描述符表
  • 信號處理方式
  • 當前工作目錄
  • 用戶 ID 和組 ID
  • 內存地址空間(代碼段 .text、數據段 .data、堆 .heap、共享庫)
(2)非共享資源(線程級資源)
  • 線程 ID(TID)
  • 處理器現場(寄存器、程序計數器)
  • 獨立的用戶棧和內核棧
  • errno 變量(每個線程獨立)
  • 信號屏蔽字
  • 調度優先級

3. 線程核心 API(POSIX 線程庫 pthread

編譯時需鏈接線程庫:gcc test.c -o test -lpthread

(1)pthread_self()——獲取當前線程 ID
#include <stdio.h>
#include <pthread.h>int main() {// 主線程 ID(%lu 對應 unsigned long)printf("主線程 ID:%lu\n", pthread_self());return 0;
}
(2)pthread_create()——創建線程
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 線程函數(返回值和參數均為 void*,需強制類型轉換)
void *thread_func(void *arg) {char *msg = (char *)arg;printf("子線程 ID:%lu,收到參數:%s\n", pthread_self(), msg);sleep(3); // 模擬業務邏輯pthread_exit("子線程退出!"); // 線程退出并返回數據
}int main() {pthread_t tid; // 線程 IDchar *msg = "Hello, Thread!";// 創建線程(attr 為 NULL 表示默認屬性)int ret = pthread_create(&tid, NULL, thread_func, (void *)msg);if (ret != 0) {perror("pthread_create error");return -1;}printf("主線程 ID:%lu,子線程 ID:%lu\n", pthread_self(), tid);// 等待子線程退出并回收資源(類似 wait())void *exit_msg;pthread_join(tid, &exit_msg);printf("子線程返回:%s\n", (char *)exit_msg);return 0;
}

輸出結果
主線程 ID:140709376947008,子線程 ID:140709368554240
子線程 ID:140709368554240,收到參數:Hello, Thread!
子線程返回:子線程退出!

(3)pthread_join()——回收線程資源
  • 功能:阻塞等待指定線程退出,回收其資源,獲取退出狀態(類似進程的 waitpid())。
  • 注意:僅適用于 可接合屬性 的線程(默認屬性),若線程為 分離屬性pthread_join() 會直接返回失敗。
(4)線程屬性——分離屬性(Detached)

分離屬性的線程退出時會自動釋放資源,無需 pthread_join() 回收(避免僵尸線程)。
示例代碼:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>void *thread_func(void *arg) {printf("分離線程 ID:%lu\n", pthread_self());sleep(2);return NULL;
}int main() {pthread_t tid;pthread_attr_t attr;// 1. 初始化線程屬性pthread_attr_init(&attr);// 2. 設置分離屬性pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 3. 創建分離線程pthread_create(&tid, &attr, thread_func, NULL);// 4. 銷毀線程屬性(不再需要)pthread_attr_destroy(&attr);// 嘗試回收分離線程(會失敗)void *exit_msg;int ret = pthread_join(tid, &exit_msg);if (ret != 0) {printf("pthread_join 失敗:%s\n", strerror(ret));}sleep(3); // 等待分離線程執行完成return 0;
}

輸出結果
分離線程 ID:140709368554240
pthread_join 失敗:Invalid argument

4. 示例代碼(創建兩個線程并發執行)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 線程 1 函數
void *thread1_func(void *arg) {while (1) {printf("線程 1(%lu):%s\n", pthread_self(), (char *)arg);sleep(1);}
}// 線程 2 函數
void *thread2_func(void *arg) {while (1) {printf("線程 2(%lu):%s\n", pthread_self(), (char *)arg);sleep(1);}
}int main() {pthread_t tid1, tid2;// 創建兩個線程pthread_create(&tid1, NULL, thread1_func, "Hello World!");pthread_create(&tid2, NULL, thread2_func, "Hello Thread!");// 等待線程(避免主線程先退出)pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

輸出結果(兩個線程交替執行):
線程 1(140709368554240):Hello World!
線程 2(140709360161536):Hello Thread!
線程 1(140709368554240):Hello World!
線程 2(140709360161536):Hello Thread!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96722.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96722.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96722.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VS Code 插件開發教程

VS Code 插件開發教程 概述 Visual Studio Code&#xff08;簡稱 VS Code&#xff09;是一款由 Microsoft 開發的開源輕量級編輯器&#xff0c;支持跨平臺&#xff08;Windows、macOS、Linux&#xff09;。 其最大的優勢之一是強大的插件系統&#xff0c;開發者可以通過編寫擴…

Docker技術解析

1.Docker安裝 1.如果Ubuntu自帶的Docker版本太低&#xff0c;我們需要卸載舊版本并安裝新的 sudo apt-get remove docker docker-engine docker.io containerd runc2. 備份原有軟件源 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak3.選擇合適的鏡像源 # 或者使用…

TCP套接字的使用

Java中使?TCP協議通信,使用ServerSocket來建立鏈接,使用Socket進行通信.ServerSocketServerSocket是創建TCP服務端Socket的api,主要方法:方法簽名說明ServerSocket(int port)創建一個服務端流套接字Socket,并綁定指定端口Socket accpet()開始監聽指定端口,有客戶端鏈接后,返回…

linux執行systemctl enable xxxxx 報 Failed to execute operation: Bad message

linux執行systemctl enable redis.service 報 Failed to execute operation: Bad message 如果在執行 systemctl enable 命令時遇到 "Failed to execute operation: Bad message" 錯誤&#xff0c;可能是由于以下幾個原因導致的。你可以按照以下步驟進行排查和解決&a…

終端之外:解鎖Linux命令行的魔法與力量

Linux命令行的核心理念 在記憶具體的指令之前&#xff0c;先理解它的哲學&#xff1a; 一切皆文件 &#xff1a;硬件設施&#xff0c;進程&#xff0c;目錄…在Linux中幾乎所有資源都被抽象為文件&#xff0c;這意味著你可以通過同樣的指令&#xff08;如 ench ,cat&#xff…

CSS 動畫實戰:實現電商中“加入購物車”的拋物線效果

引言 在電商網站中&#xff0c;“加入購物車”動畫 是提升用戶體驗的經典交互之一。一個小小的商品圖標從頁面飄向購物車&#xff0c;不僅直觀地反饋了操作結果&#xff0c;還能增加趣味性與沉浸感。 實現這一效果的方式有很多&#xff0c;比如 JavaScript 計算路徑 動畫&…

深度學習之損失函數

深度神經網絡由多層網絡連接而成&#xff0c;網絡連接處防止線性直接相關&#xff0c;采用非線性函數進行逐層隔離&#xff0c;真正實現每層參數的獨立性&#xff0c;也就是只對本層提取到的特征緊密相關。因為如果是線性函數直接相連就成了一層中間網絡了&#xff0c;只不過參…

Oracle OCP認證考試題目詳解082系列第32題

考察知識點:Oracle profiles(配置文件) 英語題目 32.Which are two of the account management capabilities that can be configured using Oracle profiles? A.the number of days for which an account may be logged in to one or more sessions before it is locked…

Docker 部署 MongoDB:單節點與副本集的最佳實踐

Docker 部署 MongoDB&#xff1a;單節點與復制集的企業級最佳實踐引言&#xff1a;容器化有狀態服務的范式轉變第一部分&#xff1a;基礎概念與生產環境考量1.1 核心 Docker 概念深度解析1.2 Volume vs. Bind Mount&#xff1a;生產環境抉擇1.3 獲取與驗證官方鏡像官方鏡像默認…

公司本地服務器上搭建部署的辦公系統web項目網站,怎么讓外網訪問?有無公網IP下的2種通用方法教程

本地物理服務器計算機搭建部署應用包括網站等&#xff0c;然后在局域網內的訪問外&#xff0c;還需要提供外地的連接訪問&#xff0c;這是比較常見的跨網通信需求。如在家或在外訪問公司內部辦公系統網站&#xff0c;這就涉及內網IP和公網IP的轉換&#xff0c;或域名的解析使用…

整體設計 之 緒 思維導圖引擎 之 引 認知系統 之 引 認知系統 之 序 認知元架構 之6 拼句 之1 (豆包助手 之8)

摘要(AI生成)認知演進 中 交流句子所包含的 信息描述框架 < i , j > ( m , n )本體論基礎&#xff08;數學約束&#xff09;&#xff1a; n n元&#xff08;維度&#xff09;n次&#xff08;層次&#xff09;n個&#xff08;方程&#xff09;n場&#xff08;場景&am…

微軟的兩個調試器debugpy和python

在生成launch.json文件時&#xff0c;新版本的python擴展解釋器類型是debugpy&#xff0c;而不是就版本的type:python&#xff0c;那么兩者的區別在哪&#xff1f;1. 歷史演變背景&#xff08;1&#xff09;舊版&#xff08;Python擴展 < 2021.09&#xff09;使用 "typ…

【連載2】C# MVC 自定義錯誤頁設計:404/500 處理與 SEO 優化

在開發ASP.NET MVC 應用時&#xff0c;自定義錯誤頁是提升用戶體驗和 SEO 表現的重要環節。默認的錯誤頁不僅不美觀&#xff0c;還可能泄露技術細節&#xff0c;影響用戶體驗和搜索引擎排名。 實現自定義錯誤頁的完整代碼 配置 Web.config 自定義錯誤頁 在 ASP.NET 中&#…

mcp解讀——概述及整體架構

概念介紹 什么是模型上下文協議 &#xff08;MCP&#xff09; MCP&#xff08;模型上下文協議&#xff09;是一種用于將 AI 應用程序連接到外部系統的開源標準。 使用 MCP&#xff0c;Claude 或 ChatGPT 等人工智能應用程序可以連接到數據源&#xff08;例如本地文件、數據庫&a…

AI 賦能云端運維:基于 MCP 協議深度集成 Codebuddy CLI 與騰訊云 Lighthouse 的實戰全解

摘要 在云計算技術飛速演進的今天&#xff0c;服務器的管理與運維正經歷著從傳統手動操作、腳本自動化到智能化、對話式交互的深刻變革。本文將系統性地、全流程地展示如何將騰訊云 Lighthouse 輕量應用服務器與尖端的 AI 編程助手 Codebuddy CLI 進行深度集成。我們將從服務器…

【Proteus仿真】【51單片機】教室燈光控制器設計

文章目錄一、功能簡介二、軟件設計三、實驗現象聯系作者一、功能簡介 本項目使用Proteus8仿真51單片機控制器&#xff0c;使用LCD1602液晶、DS1302時鐘模塊、人體紅外感應模塊、開關LED指示燈、繼電器、PCF8591 ADC模塊、光敏傳感器、按鍵模塊等。 主要功能&#xff1a; 系統運…

成為一個年薪30W+的FPGA工程師是一種什么體驗?

FPGA&#xff08;Field-Programmable Gate Array&#xff09;是現場可編程門陣列&#xff0c;通過硬件描述語言設計電路&#xff0c;可實現并行計算&#xff0c;廣泛應用于通信、人工智能、工業控制等領域。FPGA工程師的工作包括RTL設計、仿真驗證、時序分析等。盡管并非所有公…

ZooKeeper Java客戶端與分布式應用實戰

1. ZooKeeper Java客戶端實戰 ZooKeeper應用開發主要通過Java客戶端API連接和操作ZooKeeper集群&#xff0c;有官方和第三方兩種客戶端選擇。 1.1 ZooKeeper原生Java客戶端 依賴引入 <dependency><groupId>org.apache.zookeeper</groupId><artifactId>…

0303 【軟考高項】項目管理概述 - 組織系統(項目型組織、職能型組織、矩陣型組織)

0303 【軟考高項】項目管理概述 - 組織系統&#xff08;項目型組織、職能型組織、矩陣型組織&#xff09; 目錄0303 【軟考高項】項目管理概述 - 組織系統&#xff08;項目型組織、職能型組織、矩陣型組織&#xff09;一、基本概念二、職能型組織二、項目型組織三、矩陣型組織3…

計算機視覺與模式識別前沿一覽:2025年8月arXiv 熱點研究趨勢解析

本推文分析了arXiv中Computer Vision and Patteren Recognition(計算機視覺與模式識別)領域2025年8月發布的近50篇論文的研究熱點&#xff0c;旨在幫助讀者快速了解近期領域內的前沿技術與研究方向。arXiv是全球最具影響力的開放電子預印本平臺之一&#xff0c;由美國國家科學基…