c-linux-IPC-訊號量semaphore-學習
###概念###
場景:某個執行緒(程序)能從訊號拿到鎖,則執行,否則阻塞等待。
訊號量:可以理解為訊號集中某個訊號當前鎖的數值
正值:尚可接受的程序數 0:無可用,無等待 負值:阻塞等待在該訊號上的程序數
###PV操作###
p:訊號量-1 v:訊號量+1
P: 程序從訊號鎖池拿鎖,能拿到,則執行;否則,若鎖池已空,則阻塞等待。
V: 程序釋放鎖到訊號鎖池,若鎖池已滿(一般不會),則阻塞等待;
否則,釋放鎖之後,若有等待的程序,系統會喚醒一個等待在該訊號上的程序繼續執行。
###訊號量實現執行緒互斥###
可以認為訊號量關聯一組執行緒,儲存一個指標,指向執行緒陣列的首地址;
比如當前訊號量為-1,一個執行緒對其進行P操作,訊號量變為-2,說明沒有拿到鎖,執行緒等待;
此時,取值為-2,說明有兩個執行緒等待在該訊號上;
這個時候,其他執行緒進行V操作,訊號量加1,為-1,訊號量通知等待的執行緒中,第一個執行緒繼續執行,第二個執行緒繼續等待。
也就是說,P操作等待的情況是減1後,訊號量小於0;
P操作繼續執行的情況有兩種:a、減1後,訊號量大於等於0,不需等待,直接執行;
b、減1後,訊號量小於0,等待中,其他人進行了V操作,通知這個執行緒,繼續執行。
###函式介面###
1.int semget(key_t key, int num_sems, int sem_flags); //建立一個新訊號量(集)或取得一個已有訊號量(集)
//key: 關聯訊號量semid的鍵值 e.g. (key_t)201808
//num_sems: 訊號集中訊號的數量 一般為1
//sem_flgs: IPC_EXCL - 檢查是否存在 IPC_CREAT - 建立
// 位或組合 IPC_CREAT | IPC_EXCL則可以建立一個新的,唯一 的訊號量,如果訊號量已存在,返回一個錯誤
//返回值:成功返回一個相應訊號識別符號(非零),失敗返回-1
2.int semop(int sem_id, struct sembuf *sops, size_t nsops); //操作訊號量
//sem_id: 訊號量識別符號
//struct sembuf *sops: 對應一個特定訊號的操作
//nsops: 要進行操作的訊號的個數 一般為1
- struct sembuf{
- unsigned short sem_num; //訊號在訊號集中的索引,0代表第一個訊號,1代表第二個訊號
- short sem_op; //操作型別
- short sem_flg; //操作標誌
- };
sem_flg: IPC_NOWAIT 無阻塞等待,特殊臨界情況直接返回EAGAIN
SEM_UNDO
該引數可設定為 IPC_NOWAIT 或 SEM_UNDO 兩種狀態。只有將 sem_flg 指定為 SEM_UNDO 標誌後,semadj (所指定訊號量針對呼叫程序的調整 值)才會更新。 此外,如果此操作指定SEM_UNDO,系統更新過程中會撤消此訊號燈的計數(semadj)。此操作可以隨時進行---它永遠不會強制等待的過 程。呼叫程序必須有改變訊號量集的許可權。
sem_flg公認的標誌是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,當該程序終止時它將會自動撤消。
sem_op > 0 : V操作,訊號量加上對應的值,說明程序在釋放鎖
sem_op < 0: P操作,訊號量減去對應的絕對值,說明程序在請求鎖
sem_op = 0:
//返回值:成功返回 0,失敗返回 -1
//該函式所做的對於訊號量的操作都是原子操作,即整個行為是一個整體,是不可打斷的;
//所有操作是否可以立即執行,取決於sem_flg的IPC_NOWAIT標誌是否存在。
3.int semctl(int sem_id, int sem_num, int command, ...);
//sem_id: 訊號量識別符號
//sem_num: 訊號集中訊號的索引值 (第一個0,第二個1)
//command:命令型別
IPC_STAT:獲取某個訊號量集合的semid_ds結構,並將其儲存在semun聯合體的buf引數所指的地址之中
IPC_SET:設定某個集合的semid_ds結構的ipc_perm成員的值,該命令所取的值是從semun聯合體的buf引數中取到的
IPC_RMID:從核心刪除該訊號量集合
GETALL:用於獲取集合中所有訊號量的值,整數值存放在無符號短整數的一個數組中,該陣列有聯合體的array成員所指定
GETNCNT:返回當前正在等待資源的程序的數目
GETPID:返回最後一次執行PV操作(semop函式呼叫)的程序的PID
GETVAL:返回集合中某個訊號量的值
GETZCNT:返回正在等待資源利用率達到百分之百的程序的數目
SETALL:把集合中所有訊號量的值,設定為聯合體的array成員所包含的對應值
SETVAL:將集合中單個訊號量的值設定為聯合體的val成員的值
//第四個引數:某些特定操作用到
其中semun聯合體的結構如下:
[cpp] view plain copy- union semun{
- int val;
- struct semid_ds *buf;
- unsigned short *array;
- struct seminfo *__buf;
- };
對於該函式,只有當command取某些特定的值的時候,才會使用到第4個引數,第4個引數它通常是一個union semun結構,定義如下:
[cpp] view plain copy- union semun{
- int val;
- struct semid_ds *buf;
- unsigned short *arry;
- };
當執行SETVAL命令時用到這個成員,他用於指定要把訊號量設定成什麼值,涉及成員:val
在命令IPC_STAT/IPC_SET中使用,它代表核心中所使用內部訊號量資料結構的一個複製 ,涉及成員:buf
在命令GETALL/SETALL命令中使用時,他代表指向整數值一個數組的指標,在設定或獲取集合中所有訊號量的值的過程中,將會用到該陣列,涉及成員:array
###測試例程###
/*ĐĹşĹÁż sem.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <iostream>
using namespace std;
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
static int getsemval();
static void childforkProc();
static void childforkProc(){
int sn=getsemval();
cout << "[" << getpid() << ":] " << "semNum:" << sn << " " << endl;
semaphore_p();
std::cout << "[" << getpid() << ":] " << "critical region opperator..." << std::endl; //臨界共享區操作
sleep(5);
semaphore_v();
exit(-1);
}
//帶引數第一次執行 不帶引數第二次執行
// ./sem 1 & ./sem
int main(int argc,char *argv[]){
char message='F';
int i=0;
int flg=0;
//建立訊號量
sem_id = semget((key_t)1234,1,0666|IPC_CREAT);
if(!set_semvalue()){ //初始化訊號量
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
for(int i=0;i<5;i++){
flg=fork();
if(flg > 0){
//no do
}else if(flg ==0){ //子程序
childforkProc();
}
}
if(flg > 0){
sleep(100);
del_semvalue();
}
return 0; //exit(EXIT_SUCCESS);
}
static int set_semvalue(){
//用於初始化訊號量 在使用訊號量前必須這樣做
union semun sem_union;
sem_union.val=1;
if(semctl(sem_id,0,SETVAL,sem_union) == -1){
return 0;
}
return 1;
}
static void del_semvalue(){
//刪除訊號量
union semun sem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union) == -1){
fprintf(stderr,"failed to delete semaphore \n");
}else{
fprintf(stdout,"has deled semaphore \n");
}
}
static int semaphore_p(){
//訊號量減1操作 即等待P(s)
struct sembuf sem_b;
sem_b.sem_num=0; //第一個訊號
sem_b.sem_op=-1; //P() 結果 >= 0 ,執行
sem_b.sem_flg=SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
fprintf(stderr, "semaphore_p failed\n");
return 0;
}
return 1;
}
static int semaphore_v(){
//釋放操作 使訊號量變為可用 即傳送訊號V(s)
struct sembuf sem_b;
sem_b.sem_num=0; //第一個訊號
sem_b.sem_op=1; //V()
sem_b.sem_flg=SEM_UNDO;
if(semop(sem_id,&sem_b,1)==-1){
fprintf(stderr, "semaphore_v failed\n");
return 0;
}
return 1;
}
//獲取當前訊號量的的值
static int getsemval(){
int num=0; //無可用 無等待
num=semctl(sem_id,0,GETVAL);
return num;
}
###參考###
https://blog.csdn.net/qq_30168505/article/details/53041825
https://www.cnblogs.com/nzbbody/p/4219957.html