第一個程序:共享內存讀取程序(消費者)
該程序作為消費者,從共享內存中讀取數據,通過信號量保證只有當生產者寫入數據后才能讀取。
/*4 - 讀共享內存*/
#include<stdio.h> // 標準輸入輸出庫
#include<stdlib.h> // 標準庫,包含exit等函數
#include<unistd.h> // 包含UNIX系統調用,如sleep
#include <sys/types.h> // 定義系統數據類型
#include <sys/ipc.h> // 包含IPC(進程間通信)相關函數定義
#include <sys/shm.h> // 共享內存相關函數定義
#include <sys/sem.h> // 信號量相關函數定義
#include <string.h> // 字符串處理函數庫int main()
{//1、確定 文件路徑名 ---獲取消息隊列的key值key_t key = ftok(".", 'a'); // 用當前目錄和字符'a'生成唯一key值,用于標識共享內存和信號量if(key == -1){ // 檢查ftok函數調用是否失敗perror("ftok error"); // 輸出錯誤信息return -1; // 程序異常退出}//2、根據key值 獲取共享內存的ID,如果共享內存不存在則創建// IPC_CREAT表示不存在則創建,0666表示權限(所有者、組、其他用戶都可讀寫)int shmid = shmget(key,100,IPC_CREAT|0666); if(shmid==-1){ // 檢查shmget函數調用是否失敗perror("shmget"); // 輸出錯誤信息exit(-1); // 程序異常退出}//根據key創建/獲取信號量集,創建1個信號量int semid = semget(key,1,IPC_CREAT|0666); if(semid==-1){ // 檢查semget函數調用是否失敗perror("semget"); // 輸出錯誤信息exit(-1); // 程序異常退出}//3.映射共享內存到當前進程的地址空間,第二個參數0表示由系統自動分配地址void *p = shmat(shmid,0,0); if(p==(void *)-1){ // 檢查shmat函數調用是否失敗perror("shmat"); // 輸出錯誤信息exit(-1); // 程序異常退出}//4.使用共享內存(消費者邏輯)struct sembuf buf; // 定義信號量操作結構體while(1){ // 無限循環,持續讀取數據buf.sem_num = 0; // 操作第0個信號量(信號量集索引)buf.sem_op = -1; // 執行P操作(申請資源,信號量值減1)buf.sem_flg = 0; // 0表示阻塞模式,若資源不可用則等待semop(semid,&buf,1); // 執行信號量操作printf("data = %d\n",*(int *)p); // 從共享內存讀取整數并打印}//5.不再使用可以解除映射(實際不會執行,因為上面是無限循環)shmdt(p);//6.不再需要可以刪除共享內存(注釋掉表示不自動刪除)//shmctl(shmid,IPC_RMID,0);return 0;
}
第二個程序:共享內存寫入程序(生產者)
該程序作為生產者,向共享內存中寫入數據,通過信號量通知消費者可以讀取數據。
/*5 - 信號量集保護共享內存*/
#include<stdio.h> // 標準輸入輸出庫
#include<stdlib.h> // 標準庫,包含exit等函數
#include<unistd.h> // 包含UNIX系統調用,如sleep
#include <sys/types.h> // 定義系統數據類型
#include <sys/ipc.h> // 包含IPC(進程間通信)相關函數定義
#include <sys/shm.h> // 共享內存相關函數定義
#include <string.h> // 字符串處理函數庫
#include <sys/sem.h> // 信號量相關函數定義int main()
{//1、確定 文件路徑名 ---獲取消息隊列的key值key_t key = ftok(".", 'a'); // 用當前目錄和字符'a'生成唯一key值,與消費者保持一致if(key == -1){ // 檢查ftok函數調用是否失敗perror("ftok error"); // 輸出錯誤信息return -1; // 程序異常退出}//2、根據key值 獲取共享內存的ID,如果共享內存不存在則創建// IPC_CREAT表示不存在則創建,0666表示權限(所有者、組、其他用戶都可讀寫)int shmid = shmget(key,100,IPC_CREAT|0666); if(shmid==-1){ // 檢查shmget函數調用是否失敗perror("shmget"); // 輸出錯誤信息exit(-1); // 程序異常退出}//根據key創建/獲取信號量集,創建1個信號量int semid = semget(key,1,IPC_CREAT|0666); if(semid==-1){ // 檢查semget函數調用是否失敗perror("semget"); // 輸出錯誤信息exit(-1); // 程序異常退出}//設置信號量的初始值為0(用于同步,初始狀態下沒有數據可讀)semctl(semid,0,SETVAL,0);//3.映射共享內存到當前進程的地址空間,第二個參數0表示由系統自動分配地址void *p = shmat(shmid,0,0); if(p==(void *)-1){ // 檢查shmat函數調用是否失敗perror("shmat"); // 輸出錯誤信息exit(-1); // 程序異常退出}struct sembuf buf; // 定義信號量操作結構體//生產者邏輯while(1){ // 無限循環,持續寫入數據printf("請輸入一個整數:"); // 提示用戶輸入scanf("%d",(int *)p); // 將用戶輸入的整數寫入共享內存buf.sem_num = 0; // 操作第0個信號量(信號量集索引)buf.sem_op = 1; // 執行V操作(釋放資源,信號量值加1)buf.sem_flg = 0; // 0表示阻塞模式semop(semid,&buf,1); // 執行信號量操作,通知消費者有新數據}//5.不再使用可以解除映射(實際不會執行,因為上面是無限循環)shmdt(p);//6.不再需要可以刪除共享內存(注釋掉表示不自動刪除)//shmctl(shmid,IPC_RMID,0);return 0;
}
兩個程序的整體作用
這兩個程序配合實現了基于共享內存和信號量的進程間通信,具體說明如下:
核心功能:
- 生產者程序(第二個)從用戶輸入獲取整數,寫入共享內存,并通過信號量通知消費者。
- 消費者程序(第一個)通過信號量等待,當檢測到生產者寫入數據后,從共享內存讀取并打印該整數。
信號量的作用:
- 實現了生產者和消費者的同步:消費者必須等待生產者寫入數據后才能讀取,避免讀取到空數據或舊數據。
- 信號量初始值為 0,生產者寫入數據后執行 V 操作(值 + 1),消費者讀取前執行 P 操作(值 - 1),確保數據讀寫順序正確。
共享內存的作用:
提供了一個兩個進程都能訪問的內存區域,實現高效的數據傳遞(相比管道等方式,共享內存無需數據拷貝,速度更快)。
通過這種機制,兩個獨立進程可以安全、有序地進行數據交換。
如果沒有 PV 操作(或類似的同步互斥機制),在多任務 / 多進程環境中會出現一系列嚴重問題,核心是共享資源訪問沖突和任務執行順序混亂,具體表現如下:
1. 共享資源 “爭搶” 導致數據混亂(互斥問題)
假設有兩個任務同時操作一個共享變量(比如銀行賬戶余額):
- 任務 A 要給賬戶加 100 元,讀取當前余額為 500 元,準備計算成 600 元;
- 此時任務 B 突然介入,讀取余額還是 500 元,減去 50 元(計算成 450 元)并寫入;
- 任務 A 接著執行,把自己計算的 600 元寫入。
最終余額變成 600 元(正確結果應為 500+100-50=550 元),數據被破壞。
沒有 PV 操作時,多個任務會 “同時” 搶占共享資源,導致操作交叉覆蓋,結果錯誤。
2. 任務執行順序失控(同步問題)
比如任務 A 負責采集傳感器數據,任務 B 負責處理數據:
- 任務 B 可能在任務 A 還沒采集到數據時就開始處理,導致處理 “空數據”;
- 任務 A 可能連續采集了多組數據,但任務 B 只處理了其中一部分,導致數據積壓或丟失。
沒有 PV 操作時,任務間缺乏 “等待 - 通知” 機制,無法按邏輯順序執行,系統功能紊亂。
3. 系統陷入 “死鎖” 或 “饑餓”
- 死鎖:兩個任務分別占用對方需要的資源,且都不釋放,互相等待對方放手,導致系統卡死。例如:任務 A 占用打印機,等待掃描儀;任務 B 占用掃描儀,等待打印機,兩者永遠僵持。
- 饑餓:高優先級任務持續搶占資源,低優先級任務永遠得不到執行機會(比如一個后臺統計任務始終無法運行)。
沒有 PV 操作時,無法合理分配資源使用權,容易出現這類極端問題。
4. 實時系統失去 “實時性”
在實時系統(如工業控制、自動駕駛)中,任務必須在嚴格時間內響應。
- 若多個任務無序爭搶 CPU 或外設,關鍵任務(如緊急剎車控制)可能被低優先級任務打斷,導致響應超時,引發安全事故。
沒有 PV 操作時,無法保證高優先級任務的優先執行權,實時性無從談起。
總結
PV 操作(及信號量機制)是多任務系統的 “交通規則”:
- 沒有規則,車輛(任務)會亂搶車道(資源),導致撞車(數據錯誤)、堵車(死鎖)、緊急車輛被堵(實時性失效)。
- 因此,任何支持多任務的系統(如 μC/OS、Linux 內核)都必須有類似 PV 操作的同步互斥機制,否則根本無法穩定運行。
P 和 V 是荷蘭語 “Proberen” 和 “Verhogen” 的縮寫)。在操作系統和多任務編程中,PV 操作是配合信號量使用的一組原子操作,主要作用是實現任務間的同步與互斥,確保共享資源的安全訪問。
具體作用可以用兩個生活場景理解:
1. 實現 “互斥”:防止多個任務同時操作共享資源
比如多個任務需要使用同一個打印機(共享資源):
- P 操作:相當于 “申請使用打印機”。如果打印機空閑(信號量 > 0),任務占用它;如果被占用(信號量 = 0),任務就排隊等待,不能強行使用。
- V 操作:相當于 “用完打印機歸還”。釋放打印機后,通知排隊的任務 “現在可以使用了”。
通過 PV 操作,能保證同一時間只有一個任務使用打印機,避免打印內容混亂。
2. 實現 “同步”:控制任務的執行順序
比如任務 A 需要先生產數據,任務 B 才能處理數據:
- 任務 A 生產完數據后,執行V 操作(相當于 “通知 B 可以處理了”)。
- 任務 B 開始前執行P 操作(相當于 “等待 A 的通知”),如果沒收到通知(信號量 = 0),就暫停等待,直到 A 通知后再執行。
通過 PV 操作,能確保 B 在 A 完成后才執行,避免 B 因沒數據而 “做無用功”。
總結
PV 操作的核心作用是通過信號量的 “申請 - 釋放” 機制,協調多個任務的執行節奏:
- 防止多個任務同時操作共享資源(互斥);
- 保證任務按預期順序執行(同步);
- 避免數據混亂、資源沖突等問題,讓多任務系統穩定運行。
這在嵌入式系統(如 μC/OS)、操作系統內核等場景中非常重要。