天天看點

linux程序間通信:system V 信号量

文章目錄

  • ​​概念描述​​
  • ​​通信原理​​
  • ​​程式設計接口​​
  • ​​使用流程​​
  • ​​程式設計案例​​

概念描述

  • 英文:​

    ​semaphore​

    ​ 簡稱SEM,主要用來進行程序間同步
  • 本質:核心維護的一個正整數,可對其進行各種+/-操作
  • 分類:systemV 信号量、POSIX 有名信号量、POSIX 無名信号量
  • 用途:用來标示系統中可用資源的的個數,協調各個程序有序的通路資源,防止發生沖突
  • P操作:程式在進入臨界區之前要對資源進行申請
  • V操作:程式離開臨界區後要釋放相應的資源

通信原理

  • 類似于房卡,不是單個值,而是一組(實際上是數組)信号量元素構成
  • 将信号量初始設定成一個絕對值
  • 在信号量目前值的基礎上加一個數量
  • 在信号量目前值的基礎上減去一個數量,降到0以下再去通路會引起阻塞
  • 阻塞程序一直等待其他程序修改該信号量的值,直到恢複正常運作
  • 信号量本身無意義,通常會與一塊臨界資源(共享記憶體)關聯使用

程式設計接口

  • 擷取信号量ID

    a. 頭檔案 ​

    ​<sys/ipc.h> <sys/sem.h>​

    ​ b. ​

    ​int semget(key_t key,int nsem, int semflg)​

    ​ c. 函數參數:
  • ​key:​

    ​ 用來表示信号量的鍵,通常使用值IPC_PRIVATE或者由ftok建立
  • ​nsem​

    ​: 信号的數量,所有的信号量放在一個數組裡
  • ​semflg​

    ​​:位掩碼,用來設定信号量的權限或者檢查一個已有信号量的權限

    IPC_CREAT: 如果找不到指定的key相關聯的信号量,建立一個信号量集合

    IPC_EXCL: 若指定了IPC_CREAT且指定key相關聯的信号量存在,報EXIST錯誤

d. 傳回值

  • 成功:傳回操作信号量描述符
  • 失敗:傳回-1
  • 設定信号量

    a. 頭檔案 ​

    ​<sys/ipc.h> <sys/sem.h>​

    ​ b. ​

    ​int semctl(int semid, int semnum, int cmd ..)​

    ​ c. 函數參數:
  • ​semid​

    ​: 信号量的描述符,辨別符;用于操作信号量
  • ​semnum​

    ​: 信号量的數量,所有的信号量放在一個數組内
  • ​cmd​

    ​​ :

    IPC_RMID :删除信号量及相關聯的核心smid_ds資料結構

    IPC_STAT: 擷取semid_ds的副本

    IPC_SET: 設定semid_ds的資料結構

    GETVAL: 擷取信号集中地 semnum個信号量的值

    GETALL: 擷取所有信号量的值

    SETVAL: 設定信号集中的第semnum個信号量的值

d. 函數傳回值

  • 成功:根據cmd指令,傳回不同的值
  • 失敗:-1
  • 信号量P/V操作

    a. 頭檔案​

    ​<sys/ipc.h> <sys/sem.h>​

    ​ b. ​

    ​int semop(int semid,struct sembuf *sops, size_t nsops);​

    ​ c. 函數參數:
  • ​semid​

    ​ :信号的IPC辨別符
  • ​sops​

    ​: 指向數組的指針,數組中包含了需要執行的操作
struct sembuf {
  unsigned short sem_num; // 辨別要操作的信号集中的信号量
  
  /*
  sem_op如下資訊:
  a.若大于0 将sem_op的值加到信号量值上
  b. 若等于0 ,則對信号量進行檢查,确定其目前值是否為0,若為0則操作結束;若不為0,則一直阻塞,直到為0結束
  c. 若小于0,則将信号量值減去sem_op,最後結果大于或等于0,操作立即結束;若最後結果小于0,則目前程序會阻塞
  */
  short sem_op;
  /*信号操作對标記,SEM_UNDO,IPC_NOWAIT*/
  short sem_flag;
}      
  • ​nsops​

    ​:數組的大小

d. 傳回值

  • 成功:根據cmd指令,傳回不同的值
  • 失敗:-1,并設定errno全局變量

使用流程

  • 使用semget 建立或打開一個信号量集
  • 使用semctl SETVAL 或 SETALL 操作初始化集合中的信号量(其中一個程序操作即可,核心中維護,對其它程序是全局可見的)
  • 使用semop操作信号量的值,多個程序通過多信号量值的操作來表示一些臨界資源的擷取和釋放
  • 當所有程序不再需要信号量集時,使用semctl IPC_RMID 操作删除這個信号量集(其中一個程序操作即可)

程式設計案例

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

union semnum {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};


int main ()
{
    int sem_id;
    key_t key;

    if ((key =ftok(".",111)) == -1) {
        printf("create key failed \n");
        _exit(-1);
    }
    
    //使用semget 建立或打開一個信号量集
    if ((sem_id = semget(key,3,IPC_CREAT | 0770)) == -1) {
        printf("create shm_id failed \n");
        _exit(-1);
    } 
    printf("sem_id is :%d\n",sem_id);

    //擷取信号量中的值
    int sem_value;
    sem_value = semctl(sem_id , 0, GETVAL);
    printf("sem value is %d\n",sem_value);

    //使用semctl SETVAL 或 SETALL 操作初始化集合中的信号量。通過sem_union設定信号量的值
    union semnum sem_union;
    sem_union.val = 2;
    semctl(sem_id, 0, SETVAL, sem_union);
    sem_value = semctl(sem_id ,0, GETVAL);
    printf("sem value after reset is %d\n",sem_value);

    //使用semop操作信号量,對信号量進行pv操作,這裡是-1
    struct sembuf sops,sops2,sops3;
    sops.sem_num = 0;
    sops.sem_op = -1;
    sops.sem_flg = SEM_UNDO;

    /*sops.sem_num = 1;
    sops.sem_op = -1;
    sops.sem_flg = SEM_UNDO;
    
    sops.sem_num = 2;
    sops.sem_op = -1;
    sops.sem_flg = SEM_UNDO;*/

    if( -1 == (semop(sem_id,&sops, 1)) ) {
        printf("semop set failed \n");
        _exit(-1);
    }
    sem_value = semctl(sem_id, 0, GETVAL);
    printf("sem value after semop is %d\n",sem_value);

    sleep(30);
    semctl(sem_id, 0 ,IPC_RMID);
    return 0;
}      
sem_id is :524314
sem value is 0
sem value after reset is 2
sem value after semop is 1