System V信號量


1. System V IPC

概述

以下三種類型的IPC合稱為System V IPC:

  • System V信號量
  • System V消息隊列
  • System V共享內存

System V IPC在訪問它們的函數和內核為它們維護的信息上有一些類似點,主要包括:

  • IPC鍵和ftok函數
  • ipc_perm結構
  • 創建或打開時指定的用戶訪問權限
  • ipcs和ipcrm命令

下表匯總了所有System V IPC函數。

  信號量 消息隊列 共享內存
頭文件 sys/sem.h sys/msg.h sys/shm.h
創建或打開IPC的函數 semget msgget shmget
控制IPC操作的函數 semctl msgctl shmctl
IPC操作函數 semop msgsnd
msgrcv
shmat
shmdt

IPC鍵和ftok函數

三種類型的System V IPC都使用IPC鍵作為它們的標識,IPC鍵是一個key_t類型的整數,該類型在sys/types.h中定義。
IPC鍵通常是由ftok函數賦予的,該函數把一個已存在的路徑名pathname和一個非0整數id組合轉換成一個key_t值,即IPC鍵。

#include <sys/ipc.h>

//成功返回IPC鍵,失敗返回-1
key_t ftok(const char *pathname, int id);

參數說明:

  • pathname在是程序運行期間必須穩定存在,不能反復創建與刪除
  • id不能為0,可以是正數或者負數

ipc_perm結構

內核給每個IPC對象維護一個信息結構,即struct ipc_perm結構,該結構及System V IPC函數經常使用的常值定義在sys/ipc.h頭文件中。

struct ipc_perm
{
    uid_t   uid;   //owner's user id
    gid_t   gid;   //owner's group id
    uid_t   cuid;  //creator's group id
    gid_t   cgid;  //creator's group id
    mode_t  mode;  //read-write permissions
    ulong_t seq;   //slot usage sequence number
    key_t   key;   //IPC key
};

創建與打開IPC對象

創建或打開一個IPC對象使用相應的xxxget函數,它們都有兩個共同的參數:

  • 參數key,key_t類型的IPC鍵
  • 參數oflag,用於指定IPC對象的讀寫權限(ipc_perm.mode),並選擇是創建一個新的IPC對象還是打開一個已存在的IPC對象

對於參數key,應用程序有兩種選擇:

  • 調用ftok,給它傳pathname和id
  • 指定key為IPC_PRIVATE,這將保證會創建一個新的、唯一的IPC對象,但該標志不能用於打開已存在的IPC對象,只能是新建

對於參數oflag,如上所述,它包含讀寫權限、創建或打開這兩方面信息:

  • 可以指定IPC_CREAT標志,其含義和Posix IPC的O_CREAT一樣
  • 還可以設置為下表所示的常值來指定讀寫權限

ipcs和ipcrm命令

  • 由於System V IPC的三種類型不是以文件系統路徑名標識的,因此無法使用ls和rm命令查看與刪除它們
  • ipcs和ipcrm分別用於查看與刪除系統中的System V IPC
usage : ipcs -asmq -tclup 
    ipcs [-s -m -q] -i id
    ipcs -h for help.
usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
          [-Q msgkey] [-M shmkey] [-S semkey] ... ]

2. System V信號量

計數信號量集

我們已經知道了Posix信號量采用計數信號量,System V信號量在此基礎上增加了一級復雜度,它采用計數信號量集,計數信號量集是由一個或多個計數信號量構成的集合。
對於系統中的每個信號量集,內核都維護一個struct semid_ds信息結構,它定義在sys/sem.h頭文件中。

struct semid_ds
{
    struct ipc_perm  sem_perm;
    struct sem       *sem_base;  //指向信號量集的指針
    ushort           sem_nsems;  //信號量集中的信號量個數
    time_t           sem_otime;  //上一次調用semop的時間
    time_t           sem_ctime;  //創建時間或上一次以IPC_SET調用semctl的時間
};

其中,sem_base是指向信號量集的指針,信號量集中的每個成員都對應一個struct sem結構:

struct sem
{
    ushort_t  semval;  //信號量的值
    short     sempid;  //上一次成功調用semop,或以SETVAL、SETALL調用semctl的進程ID
    ushort_t  semncnt; //等待semval變為大於當前值的線程數
    ushort_t  semzcnt; //等待semval變為0的線程數
};

semget

semget用於創建一個新的信號量集或打開一個已存在的信號量集。

  • nsems參數指定集合中的信號量個數,如果是打開一個已存在的信號量集,就把該參數設為0
  • oflag參數可設置為IPC_CREAT,以及SEM_R和SEM_A指定的訪問權限,如果是打開一個已存在的信號量集,就把該參數設為0
  • 成功時返回一個稱為信號量標識符的非負整數,semop和semctrl函數將使用它
//成功返回信號量標識符,失敗返回-1
int semget(key_t key, int nsems, int oflag);

當實際操作為創建一個新的信號量集時,相應semid_ds結構中與集合中每個信號量關聯的struct sem結構並不初始化,而是在以SETVAL或SETALL命令調用semctrl時初始化的。
也就是說,創建一個新的System V信號量集(semget)並將它初始化(semctl)需要兩次函數調用,
這是System V信號量的一個致命缺陷。

semop

使用semget打開一個信號量集后,對其中一個或多個信號量值的操作就使用semop函數。

//成功返回0,失敗返回-1
int semop(int semid, struct sembuf *ops, size_t nops);
  • semid指定待操作的信號量集
  • nops為集合中的信號量個數
  • ops指向一個struct sembuf類型的結構數組,該數組中的每個元素給目標信號量集中某個特定的信號量指定sem_op操作,這個特定的信號量由sem_num指定

我們只保證sembuf含有以下三個成員,不保證這三個成員的順序,也不保證還有其他成員,因此sembuf數組元素必須采用如ops[0].sem_num = 0所示的方法進行初始化。

struct sembuf
{
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */
};

sem_op指定的操作有三類:

  • sem_op > 0:操作內容為semval += sem_op,這對應於釋放某個信號量控制的資源
  • sem_op = 0:調用者阻塞等待直到semval變為0
  • sem_op < 0:調用者阻塞等待直到semval >= abs(sem_op),調用者阻塞等待直到semval>這對應於分配資源

sem_flg可設置的值有:0、IPC_NOWAIT、SEM_UNDO,一般使用0,對於其余兩個值:

  • IPC_NOWAIT用於給信號量集中某個特定信號量設置非阻塞標志
  • SEM_UNDO為System V信號量特有的復舊機制

semctl

semctl函數對一個信號量集執行各種控制操作。

//成功根據cmd返回相應的非負值,失敗返回-1
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
  • semid指定待控制的信號量集
  • semnum指定信號量集內的某個成員,僅在cmd為GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID時使用
  • cmd指定控制命令
  • arg是可選的,取決於cmd的具體值

第四個參數arg是可選的,取決於cmd的值,當需要用到該參數時,應用程序必須按如下結構定義union semun:

union semun
{
   int              val;    /* Value for SETVAL */
   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   unsigned short  *array;  /* Array for GETALL, SETALL */
   struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

定義如下術語用於后續cmd說明:

  • semval:信號量的當前值
  • sempid:上一次成功調用semop,或以SETVAL、SETALL調用semctl的進程ID
  • semncnt:等待semval變為大於當前值的線程數
  • semzcnt:等待semval變為0的線程數

System V支持下列cmd值,除非特殊說明,否則成功時返回值均為0。

cmd 說 明
GETVAL 把semval的當前值作為函數返回值返回,它是一個unsigned short類型的整數
SETVAL 把semval的值設為arg.val
GETPID 把sempid的當前值作為函數返回值返回
GETNCNT 把semncnt的當前值作為函數返回值返回
GETZCNT 把semzcnt的當前值作為函數返回值返回
GETALL 返回信號量集內每個成員的semval值,這些值通過arg.array返回,arg.array需由調用者分配內存
SETALL 設置信號量集內每個成員的semval值,這些值通過arg.array指定,此時第二個參數semnum設為0即可
IPC_STAT 返回信號量集當前的semid_ds結構,該結構通過arg.buf返回,arg.buf需由調用者分配內存,可以用該命令獲得信號量集中的信號量個數
IPC_SET 設置信號量集對應semid_ds結構中的sem_perm.uid、sem_perm.gid和sem_perm.mode,設置的值來自arg.buf
IPC_RMID 刪除由semid指定的信號量集,此時第二個參數semnum設為0即可

其中,前五個命令針對的都是信號量集semid中由semnum指定的信號量。

3. 測試程序

代碼實現

semcreate.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1
#define SEM_MODE        0666

/*
#define SEM_MODE_OWNER  SEM_R | SEM_A
#define SEM_MODE_GROUP  (SEM_R >> 3) | (SEM_A >> 3)
#define SEM_MODE_OTHER  (SEM_R >> 6) | (SEM_A >> 6)
#define SEM_MODE        (SEM_MODE_OWNER | SEM_MODE_GROUP | SEM_MODE_OTHER)
*/

int main()
{
    int nsems = 3;
    int oflag = IPC_CREAT | SEM_MODE;
    key_t key = ftok(FTOK_FILE, FTOK_ID);
    int semid = semget(key, nsems, oflag);

    if (semid >= 0)
    {
        printf("semcreate success, semid = %d\n", semid);
    }

    return 0;
}

這里遇到個問題,SEM_MODE一開始是按注釋部分定義的,但man semget給出的三個頭文件都已經包含了,編譯時卻報錯,提示SEM_R和SEM_A未定義,不知道為什么,只能用0666定義了。

semrmid.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

int main()
{
    key_t key = ftok(FTOK_FILE, FTOK_ID);
    int semid = semget(key, 0, 0);
    semctl(semid, 0, IPC_RMID);

    return 0;
}

semsetvalues.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

int main(int argc, char **argv)
{
    key_t key;
    int semid;
    int nsems;
    unsigned short *semvals;
    union semun arg;
    struct semid_ds seminfo;
    int i;

    /* 打開semcreate創建的信號量集 */
    key = ftok(FTOK_FILE, FTOK_ID);
    semid = semget(key, 0, 0);

    /* 獲得信號量集中的信號量個數 */
    arg.buf = &seminfo;
    semctl(semid, 0, IPC_STAT, arg);
    nsems = arg.buf->sem_nsems;

    /* 設置信號量集中每個信號量的值 */
    semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));

    for (i = 0; i < nsems; i++)
    {
        semvals[i] = atoi(argv[i + 1]); //通過命令行參數分別指定集合中每個信號量的值
    }

    arg.array = semvals;
    semctl(semid, 0, SETALL, arg);

    return 0;
}

semgetvalues.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

int main(int argc, char **argv)
{
    key_t key;
    int semid;
    int nsems;
    unsigned short *semvals;
    union semun arg;
    struct semid_ds seminfo;
    int i;

    /* 打開semcreate創建的信號量集 */
    key = ftok(FTOK_FILE, FTOK_ID);
    semid = semget(key, 0, 0);

    /* 獲得信號量集中的信號量個數 */
    arg.buf = &seminfo;
    semctl(semid, 0, IPC_STAT, arg);
    nsems = arg.buf->sem_nsems;

    /* 獲得信號量集中每個信號量的值 */
    semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));
    arg.array = semvals;
    semctl(semid, 0, GETALL, arg);

    for (i = 0; i < nsems; i++)
    {
        printf("semvals[%d] = %d\n", i, semvals[i]);
    }

    return 0;
}

semops.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

int main(int argc, char **argv)
{
    key_t key;
    int semid;
    int nsems;
    union semun arg;
    struct semid_ds seminfo;
    struct sembuf *semops;
    int i;

    /* 打開semcreate創建的信號量集 */
    key = ftok(FTOK_FILE, FTOK_ID);
    semid = semget(key, 0, 0);

    /* 獲得信號量集中的信號量個數 */
    arg.buf = &seminfo;
    semctl(semid, 0, IPC_STAT, arg);
    nsems = arg.buf->sem_nsems;

    /* 對信號量集中的所有信號量進行相同的semop操作 */
    semops = (struct sembuf *)calloc(nsems, sizeof(struct sembuf));

    for (i = 0; i < nsems; i++)
    {
        semops[i].sem_num = i;
        semops[i].sem_op = atoi(argv[1]); //操作類型由命令行參數指定,>0, 0, <0
        semops[i].sem_flg = 0;
    }

    semop(semid, semops, nsems);

    return 0;
}

運行測試

  • 運行semcreate,通過運行前后的ipcs -s,可以確認信號量集創建成功

  • 運行semsetvalues,將三個信號量的值分別設為1、2、3
  • 然后運行semgetvalues,打印信息和我們設置的值一致,符合預期

  • 運行semops,並指定sem_op > 0
  • 然后運行semgetvalues,打印信息顯示每個信號量的值都增加了1,符合預期

  • 運行semrmid,通過運行前后的ipcs -s,可以確認信號量集已經從系統刪除


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM