1. 程式人生 > >c-linux-IPC-訊號量semaphore-學習

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

  1. struct sembuf{  
  2. unsigned short sem_num; //訊號在訊號集中的索引,0代表第一個訊號,1代表第二個訊號  
  3. short sem_op;     //操作型別  
  4. short sem_flg;    //操作標誌  
  5. };  

   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
  1. union semun{    
  2.     int val;    
  3.     struct semid_ds *buf;    
  4.     unsigned short *array;    
  5.     struct seminfo *__buf;  
  6. };   

對於該函式,只有當command取某些特定的值的時候,才會使用到第4個引數,第4個引數它通常是一個union semun結構,定義如下:

[cpp]  view plain  copy
  1. union semun{    
  2.     int val;    
  3.     struct semid_ds *buf;    
  4.     unsigned short *arry;    
  5. };    

當執行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