實作步驟總覽
- semget:建立/獲取信號量集
- semctl + SETVAL:初始化信號量值
- semop (P操作):進入臨界區
- 執行臨界區程式碼
- semop (V操作):離開臨界區,喚醒等待者
- semctl + IPC_RMID:刪除信號量 (僅最後一個進程)
關鍵程式碼結構
1. 定義 union semun (Linux 必須)
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
2. 封裝核心操作
// 初始化
int init_sem(int semid, int value) {
union semun arg = {.val = value};
return semctl(semid, 0, SETVAL, arg);
}
// P 操作
int sem_p(int semid) {
struct sembuf op = {0, -1, SEM_UNDO};
return semop(semid, &op, 1);
}
// V 操作
int sem_v(int semid) {
struct sembuf op = {0, +1, SEM_UNDO};
return semop(semid, &op, 1);
}
// 刪除
int del_sem(int semid) {
return semctl(semid, 0, IPC_RMID);
}
3. 主程式邏輯 (偽代碼轉實作)
int main(int argc, char *argv[]) {
int semid = semget(KEY, 1, 0666 | IPC_CREAT);
if (argc > 1 && strcmp(argv[1], "-s") == 0) {
// 初始化/刪除模式
init_sem(semid, 1);
printf("[pid: %d] init semaphore\n", getpid());
// 模擬工作
sem_p(semid);
printf("[pid: %d] enter critical area\n", getpid());
printf("[pid: %d] running critical code\n", getpid());
sleep(2);
printf("[pid: %d] leave critical area\n", getpid());
sem_v(semid);
del_sem(semid);
printf("[pid: %d] delete semaphore\n", getpid());
} else {
// 僅 P/V 測試模式
sem_p(semid);
printf("[pid: %d] enter critical area\n", getpid());
printf("[pid: %d] running critical code\n", getpid());
sleep(1);
printf("[pid: %d] leave critical area\n", getpid());
sem_v(semid);
}
return 0;
}
執行驗證
$ ./a.out -s & # 後台:初始化 + 佔用鎖
[1] 2713
[pid: 2713] init semaphore
[pid: 2713] running nocritical code
$ ./a.out # 前台:嘗試獲取鎖,阻塞等待
[pid: 2714] running nocritical code
[pid: 2713] enter critical area
[pid: 2713] running critical code
[pid: 2713] leave critical area
[pid: 2714] enter critical area # 獲取到鎖
[pid: 2714] running critical code
[pid: 2714] leave critical area
$ [pid: 2713] delete semaphore
[1]+ Done ./a.out -s
關鍵觀察點
- ✅ 互斥生效:前台進程必須等待後台進程釋放鎖才能進入
- ✅ 同步正確:臨界區程式碼嚴格串行執行
- ✅ 資源清理:最後由初始化進程刪除信號量
進階擴充:多進程生產者-消費者
// 三個信號量
sem_t mutex = 1; // 保護緩衝區
sem_t empty = N; // 空槽位
sem_t full = 0; // 滿槽位
// 生產者
while (1) {
produce_item();
sem_p(empty); sem_p(mutex);
put_to_buffer();
sem_v(mutex); sem_v(full);
}
// 消費者
while (1) {
sem_p(full); sem_p(mutex);
take_from_buffer();
sem_v(mutex); sem_v(empty);
consume_item();
}
常見錯誤與修正
| 錯誤現象 | 原因 | 修正 |
| 編譯錯誤:union semun 未定義 | Linux 內核頭檔移除 | 自行定義 union semun |
| 死鎖:進程崩潰後信號量未釋放 | 未使用 SEM_UNDO | sembuf.sem_flg = SEM_UNDO |
| 信號量值異常增長 | 重複 V 操作或邏輯錯誤 | 嚴格配對 P/V,加入斷言檢查 |
| 權限被拒絕 | semflg 權限不足 | 使用 0666 或檢查 umask |
下一步學習方向
- POSIX 信號量 (sem_t, sem_init, sem_wait, sem_post) - 更現代、可移植
- 執行緒同步 (pthread_mutex, pthread_cond) - 進程內同步更輕量
- 無鎖程式設計 (atomic operations, RCU) - 極高效能場景
- 分散式鎖 (Redis, etcd, ZooKeeper) - 微服務架構同步