在 Linux 系統中,程序 是靜態的二進制可執行文件,佔據磁碟空間但不消耗運行時資源(CPU、記憶體、文件描述符等)。而進程 是程序的動態執行實例,擁有獨立的虛擬地址空間、PCB(進程控制塊)、打開的文件表等內核資源。同一個程序可被加載為多個互不干擾的進程——如同時開啟多個終端,每個終端運行獨立的 bash 進程,擁有不同的 PID。
並發 是指在一段時間內,多個進程處於「已啟動至運行完畢」的狀態,但在任一時刻點上,單核 CPU 僅執行一個進程。其核心機制是分時復用 CPU:由時鐘中斷強制調度,使進程輪流佔用 CPU 時間片。現代 CPU 每秒執行數十億條指令,遠超人眼毫秒級感知,形成「宏觀並行、微觀串行」的假象。
早期的單道程序設計 導致 CPU 空閒等待 I/O,資源利用率極低。多道程序設計 將多個程序同時載入記憶體,配合時鐘中斷與調度演算法,實現 CPU 高效利用,推動了多媒體時代的到來。

Linux 內核用 task_struct 結構體描述進程,定義於 /usr/src/linux-headers-.../include/linux/sched.h。關鍵字段包括:
pid_t,非負整數)mm_struct)file 結構體)rlimit)


環境變量以 名=值 字符串形式存儲於用戶空間高地址(棧上方),以 NULL 結尾的 char **environ 數組訪問。常見變量:
PATH:可執行文件搜索路徑,冒號分隔SHELL:當前 Shell 路徑(通常 /bin/bash)TERM:終端類型(如 xterm)LANG:語言與 locale 設置HOME:用戶主目錄操作函數:getenv() 讀取、setenv() 設置(overwrite 控制覆蓋)、unsetenv() 刪除。
#include <stdio.h>
extern char **environ;
int main() {
for (int i = 0; environ[i]; i++)
printf("%s\n", environ[i]);
return 0;
}
pid_t fork(void) 創建子進程。返回值區分父子:父進程獲取子進程 PID,子進程返回 0,失敗返回 -1。父子進程共享代碼段(.text)、數據段(.data)、堆、棧、環境變量、文件描述符表等,但擁有獨立的 PID、返回值、父進程 ID、運行時間、定時器、未決信號集。
核心優化:讀時共享、寫時復制(COW)。fork 時不複製物理頁面,而是將父進程頁表標記為只讀,共享物理內存。當任一方嘗試寫入,頁面缺失中斷觸發內核複製該頁,實現真正的隔離,極大節省內存。
#include <stdio.h> #include <unistd.h> #include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
else if (pid == 0) printf("Child: pid=%d, ppid=%d\n", getpid(), getppid());
else { printf("Parent: pid=%d, child=%d\n", getpid(), pid); sleep(1); }
return 0;
}
循環創建 N 個子進程 需在子進程分支 break,否則會產生 2^N 個進程(二叉樹擴展)。

GDB 預設跟蹤父進程。在 fork() 前設置:
set follow-fork-mode child 跟蹤子進程set follow-fork-mode parent 跟蹤父進程exec 族函數將當前進程的用戶空間代碼/數據(.text/.data)替換為新程序,從新程序入口開始執行,進程 ID 不變(「換核不換殼」)。六大成員:
execl(path, arg, ..., NULL):列表參數,指定完整路徑execlp(file, arg, ..., NULL):列表參數,搜索 PATHexecle(path, arg, ..., NULL, envp[]):指定環境變量execv(path, argv[]):數組參數,指定路徑execvp(file, argv[]):數組參數,搜索 PATHexecve(path, argv[], envp[]):唯一真正的系統調用,其餘均封裝之成功不返回,失敗返回 -1。常配合 perror() 和 exit() 使用。

// 子進程執行 ls -l -a
execlp("ls", "ls", "-l", "-a", NULL);
// 等價於
char *argv[] = {"ls", "-l", "-a", NULL};
execvp("ls", argv);
子進程終止後,PCB 滯留內核成為僵屍進程,佔用 PID。父進程需調用 wait() 或 waitpid() 回收資源、獲取退出狀態。若父進程先結束,子進程成為孤兒進程,被 init(PID 1)收養。
wait(&status):阻塞回收任意子進程waitpid(pid, &status, options):可指定 PID、非阻塞(WNOHANG)、進程組回收狀態宏:WIFEXITED 正常退出、WEXITSTATUS 取退出碼;WIFSIGNALED 信號終止、WTERMSIG 取信號號;WIFSTOPPED 暫停、WSTOPSIG 取停止信號。
// 非阻塞回收多個子進程
while ((wpid = waitpid(-1, NULL, WNOHANG)) > 0) n--;
sleep(1);
進程地址空間隔離,需借助內核機制交換數據。常用方式:
MAP_SHARED 實現進程間高速共享
int pipe(int fd[2]) 創建管道,fd[0] 讀端、fd[1] 寫端。數據從寫端流入、讀端流出。讀寫行為:
SIGPIPE 異常終止;管道未滿寫入返回字節數;管道滿則阻塞典型應用:ls | wc -l 父子/兄弟進程通信,配合 dup2() 重定向標準輸入輸出。
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) 將文件映射至進程地址空間。關鍵參數:
prot:PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITEflags:MAP_SHARED(修改回寫磁盤,進程間共享)、MAP_PRIVATE(寫時復制,私有)offset:必須 4KB 對齊映射區釋放用 munmap(addr, len)。注意事項:文件大小不能為 0;映射權限不超過文件打開權限(MAP_SHARED);文件關閉不影響已建立映射;嚴禁指針算術運算;必須檢查返回值(MAP_FAILED)。

使用 MAP_ANONYMOUS | MAP_SHARED 配合 fd=-1 創建無文件依賴的共享映射區,適用於父子進程。無血緣進程通過映射同一文件、設置 MAP_SHARED 實現高速數據交換。
信號是軟中斷,異步通知進程處理事件。生命周期:產生 → 未決(阻塞導致) → 遞達 → 處理。PCB 包含阻塞信號集(屏蔽字)與未決信號集(位圖)。
Ctrl+C→SIGINT)、硬件異常(除零→SIGFPE、非法訪問→SIGSEGV)、系統調用(kill()、alarm())、軟件條件(定時器)
kill(pid, sig):發送信號,pid>0 指定進程、pid=0 同進程組、pid<0 進程組、pid=-1 所有可達進程raise(sig):自我發送,等價 kill(getpid(), sig)alarm(sec):設置實時定時器,到期發送 SIGALRMsetitimer():微秒精度、支持周期定時(ITIMER_REAL/VIRTUAL/PROF)sigset_t 位圖類型,操作函數:sigemptyset/fillset/addset/delset/ismember。sigprocmask(how, set, oldset) 修改阻塞集:SIG_BLOCK 添加、SIG_UNBLOCK 移除、SIG_SETMASK 替換。sigpending(set) 讀取未決集。
struct sigaction 核心字段:sa_handler(處理函數)、sa_mask(處理期間臨時阻塞集)、sa_flags(屬性標誌)。捕捉特性:處理函數執行期間自動阻塞本信號;常規信號不排隊(多次產生僅記錄一次);實時信號支持排隊。

pause() 掛起等待信號,但存在時序競態:alarm() 後 pause() 前若發生中斷調度,信號可能被錯過。解決方案:sigsuspend(mask) 原子性地「設置臨時屏蔽集 + 掛起」,消除窗口期。
// 改進版 mysleep:阻塞 SIGALRM → alarm → sigsuspend(解除阻塞) → 恢復
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(seconds);
sigdelset(&suspmask, SIGALRM);
sigsuspend(&suspmask);
信號處理函數必須為可重入:無全局/靜態變量、不調用 malloc/free、不使用標準 I/O。man 7 signal 列出安全函數列表。全局變量在異步信號上下文中被修改會導致競態,需用鎖保護(後續課程講解)。
子進程終止/停止/繼續產生 SIGCHLD(默認忽略)。捕捉該信號並在處理函數中循環 waitpid(-1, &status, WNOHANG) 可非阻塞回收所有僵屍子進程。注意:信號不排隊,處理函數必須用 while 循環而非 if。
sigqueue(pid, sig, value) 攜帶整數或指釖參數(union sigval)。捕捉端設置 sa_flags = SA_SIGINFO,使用 sa_sigaction(int, siginfo_t*, void*) 獲取豐富信息(siginfo_t 包含發送者 PID、UID、用戶數據等)。
慢速系統調用(read/write/pause/wait 等)被信號中斷後返回 -1、errno=EINTR。sa_flags 控制行為:SA_RESTART 自動重啟、SA_INTERRUPT 不重啟(歷史殘留)。
ttyname(fd) 查詢設備名getpgrp()/getpgid()/setpgid() 操作。Shell 管道命令各屬一組setsid() 創建新會話、脫離控制終端、成為會話首進程(需非組長)fork()→父退出→setsid()→chdir("/")→umask(0)→關閉文件描述符→重定向到 /dev/null→核心邏輯

mmap 映射源/目標文件,fork() N 個子進程分塊拷貝(最後一個處理餘數),MAP_SHARED 回寫磁盤>/<)、管道(|)、多重管道,fork()+dup2()+exec() 實現
