Linux/Unix 編程 —— System V信號量

目錄

1 介紹

2 信號量各種操作合集

1. 創建信號量

2. 初始化信號量集

3. 信號量控制

4. 關聯結構

5 信號量操作

6. 信號量刪除

3 二元信號量協議實現(可重用)


1 介紹

信號量提供一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它, 也就是說信號量是用來調協進程對共享資源的訪問的,防止出現因多個程序同時訪問一個共享資源而引發的一系列問題。其中共享內存的使用就要用到信號量。

一個信號量是一個由內核維護的整數,其值被限制爲大於或等於0。在-一個信 號量上可以執行各種操作( 即系統調用),包括:

  • 將信號量設置成-一個絕對值;
  • 在信號量當前值的基礎上加上一個數量;
  • 在信號量當前值的基礎上減去一個數量;
  • 等待信號量的值等於0。

 

使用SystemV信號量的常規步驟如下。

  • 使用 semget()創建或打開一個信號量集。
  • 使用semct()SETVAL或SETALL操作初始化集合中的信號量。(只有一個進程需要完成這個任務。)
  • 使用semop()操作信號量值。使用信號量的進程通常會使用這些操作來表示一種共享資源的獲取和釋放。
  • 當所有進程都不再需要使用信號量集之後使用semctl() IPC_RMID操作刪除這個集合。(只有一個進程需要完成這個任務。)

2 信號量各種操作合集

1. 創建信號量

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

int semget(key_t key, int nsems, int semflg);

key參數是使用45.2節中描述的其中一種方法生成的鍵(通常使用值IPC_PRIVATE或由ftok()返回的鍵)。

如果使用semget()創建一個新信號量集,那麼nsems會指定集合中信號量的數量,並且其值必須大於0。如果使用semget()來獲取一個既有集的標識符,那麼nsems必須要小於或等於集合的大小(否則會發生EINVAL錯誤)。無法修改一個既有集中的信號量數量。

semflg參數是一個位掩碼,它指定了施加於新信號量集之上的權限或需檢查的一個既有集合的權限。指定權限的方式與爲文件指定權限的方式是一樣的(表15-4)。此外,在semflg中可以通過對下列標記中的零個或多個取OR來控制semget()的操作。

IPC_CREAT

如果不存在與指定的key相關聯的信號量集,那麼就創建一個新集合。

IPC_EXCL

如果同時指定了IPC_CREAT並且與指定的key關聯的信號量集已經存在,那麼返回EEXIST錯誤。

semget()在成功時會返回新信號量集或既有信號量集的標識符。後續引用單個信號量的系統調用必須要同時指定信號量集標識符和信號量在集合中的序號。一個集合中的信號量從0開始計數。

 

2. 初始化信號量集

根據SUSv3的要求,實現無需對由semget()創建的集合中的信號量值進行初始化。相反,程序員必須要使用semctl()系統調用顯式地初始化信號量。(在Linux上,semget()返回的信號量實際上會被初始化爲0,但爲取得移植性就不能依賴於此。)前面曾經提及過,信號量的創建和初始化必須要通過單獨的系統調用而不是單個原子步驟來完成的事實可能會導致在初始化一個信號量時出現競爭條件。

union semun arg;
struct sembuf sop;
arg.val = 0;
/* So initialize it to 0 */
if (semctl(semid, 0, SETVAL, arg) == -1)
    errExit(" semctl" );

3. 信號量控制

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

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

semid參數是操作所施加的信號量集的標識符。對於那些在單個信號量上執行的操作,semnum參數標識出了集合中的具體信號量。對於其他操作則會忽略這個參數,並且可以將其設置爲0。cmd參數指定了需執行的操作。

一些特定的操作需要向semctl()傳入第四個參數,在本節餘下的部分中將這個參數命名爲arg。這個參數是一個union semun, 程序清單47-2給出了其定義。在程序中必須要顯式地定義這個union。

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) */
       };

常規控制操作

下面的操作與可應用於其他類型的System V IPC對象上的操作是一樣的。 所有這些操作都會忽略semnum參數。45.3 節提供了有關這些操作的更多細節,包括調用進程所需的特權和權限。

IPC_RMID

立即刪除信號量集及其關聯的semid ds數據結構。所有因在semop()調用中等待這個集合中的信號量而阻塞的進程都會立即被喚醒,semop()會報告錯誤EIDRM。這個操作無需arg參數。

IPC_STAT

在arg.buf指向的緩衝器中放置一份與這個信號量集相關聯的semid_ds數據結構的副本。

IPC_SET

使用arg.buf指向的緩衝器中的值來更新與這個信號量集相關聯的semid_ds數據結構中選中的字段。

獲取和初始化信號量值

下面的操作可以獲取或初始化一個集合中的單個或所有信號量的值。獲取一個信號量的值需具備在信號量上的讀權限,而初始化該值則需要修改(寫)權限。

GETVAL

semctl()返回由semid指定的信號量集中第semnum個信號量的值。這個操作無需arg參數。

SETVAL

將由semid指定的信號量集中第semnum個信號量的值初始化爲arg.val。

GETALL

獲取由semid指向的信號量集中所有信號量的值並將它們放在arg.array 指向的數組中。程序員必須要確保該數組具備足夠的空間。(通過由IPC_STAT操作返回的semid_ds 數據結構中的sem_nsems 字段可以獲取集合中的信號量數量。)這個操作將忽略semnum參數。

SETALL

使用arg.array 指向的數組中的值初始化semid指向的集合中的所有信號量。這個操作將忽略semnum參數。

獲取單個信號量的信息

下面的操作返回(通過函數結果值) semid引用的集合中第semnum個信號量的信息。所有這些操作都需要在信號量集合中具備讀權限,並且無需arg參數。

GETPID

返回上一個在該信號量上執行semop()的進程的進程ID;這個值被稱爲sempid 值。如果還沒有進程在該信號量上執行過semop(),那麼就返回0。

GETNCNT

返回當前等待該信號量的值增長的進程數;這個值被稱爲semnent值。

GETZCNT

返回當前等待該信號量的值變成0的進程數;這個值被稱爲semzcnt值。

4. 關聯結構

struct semid_ds {
               struct ipc_perm sem_perm;  /* Ownership and permissions
               time_t          sem_otime; /* Last semop time */
               time_t          sem_ctime; /* Last change time */
               unsigned short  sem_nsems; /* No. of semaphores in set */
       };      
struct ipc_perm {
       key_t key;            /* Key supplied to semget() */
       uid_t uid;            /* Effective UID of owner */
       gid_t gid;            /* Effective GID of owner */
       uid_t cuid;           /* Effective UID of creator */
       gid_t cgid;           /* Effective GID of creator */
       unsigned short mode;  /* Permissions */
       unsigned short seq;   /* Sequence number */
   };

sem_perm

在創建信號量集時按照45.3中所描述的那樣初始化這個子結構中的字段。通過IPC_SET能夠更新uid、gid 以及mode子字段。

sem_otime

在創建信號量集時會將這個字段設置爲0,然後在每次成功的semop()調用或當信號量值因SEM_UNDO操作而發生變更時將這個字段設置爲當前時間(參見47.8 節)。這個字段和sem_ctime的類型爲time_t,它們存儲自新紀元到現在的秒數。

sem_ctime

在創建信號量時以及每個成功的IPC_ SET、SETALL和SETVAL操作執行完畢之後將這個字段設置爲當前時間。

sem_ nsems

在創建集合時將這個字段的值初始化爲集合中信號量的數量。

5 信號量操作

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

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

int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);

sops參數是一個指向數組的指針, 數組中包含了需要執行的操作,nsops 參數給出了數組的大小(數組至少需包含一個元素)。操作將會按照在數組中的順序以原子的方式被執行。sops數組中的元素是形式如下的結構。

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

sem_num字段標識出了在集合中的哪個信號量上執行操作。sem_op 字段指定了需執行的操作。

  • 如果sem_op大於0。那麼就將sem_op的值加到信號量值上,其結果是其他等待減小信號量值的進程可能會被喚醒並執行它們的操作。調用進程必須要具備在信號量上的修改(寫)權限。
  • 如果sem_op等於0,那麼就對信號量值進行檢查以確定它當前是否等於0。如果等於0,那麼操作將立即結束,否則semop()就會阻塞直到信號量值變成0爲止。調用進程必須要具備在信號量上的讀權限。
  • 如果sem_op小於0,那麼就將信號量值減去sem_op。 如果信號量的當前值大於或等於sem_op的絕對值,那麼操作會立即結束。否則semop()會阻塞直到信號量值增長到在執行操作之後不會導致出現負值的情況爲止。調用進程必須要具備在信號量上的修改權限。

當semop()調用阻塞時,進程會保持阻塞直到發生下列某種情況爲止。

  • 另一個進程修改了信號量值使得待執行的操作能夠繼續向前。
  • 一個信號中斷了semop調用。發生這種情況時會返回EINTR錯誤。(在21.5節中指出過semop在被一個信號處理器中斷之後是不會自動重啓的。)
  • 另一個進程刪除了semid引用的信號量。發生這種情況時semop會返回EIDRM錯誤。

semtimedop中的timeout參數是一個指向timespec 結構(參見23.4.2節)的指針,通過這個結構能夠將一個時間間隔表示爲秒數和納秒數。如果在信號量操作完成之前所等待的時間已經超過了規定的時間間隔,那麼semtimedop會返回EAGAIN錯誤。如果將timeout指定爲NULL,那麼semtimedop就與semop完全一樣了。

6. 信號量刪除

semctl(semid, semnum, IPC_RMID)

3 二元信號量協議實現(可重用)

頭文件

/* binary_sems.h

   Header file for binary_sems.c.
*/
#ifndef BINARY_SEMS_H           /* Prevent accidental double inclusion */
#define BINARY_SEMS_H

#include "tlpi_hdr.h"

/* Variables controlling operation of functions below */

extern Boolean bsUseSemUndo;            /* Use SEM_UNDO during semop()? */
extern Boolean bsRetryOnEintr;          /* Retry if semop() interrupted by
                                           signal handler? */

int initSemAvailable(int semId, int semNum);

int initSemInUse(int semId, int semNum);

int reserveSem(int semId, int semNum);

int releaseSem(int semId, int semNum);

#endif

源文件

/* binary_sems.c

   Implement a binary semaphore protocol using System V semaphores.
*/
#include <sys/types.h>
#include <sys/sem.h>
#include "semun.h"                      /* Definition of semun union */
#include "binary_sems.h"

Boolean bsUseSemUndo = FALSE;
Boolean bsRetryOnEintr = TRUE;

int                     /* Initialize semaphore to 1 (i.e., "available") */
initSemAvailable(int semId, int semNum)
{
    union semun arg;

    arg.val = 1;
    return semctl(semId, semNum, SETVAL, arg);
}

int                     /* Initialize semaphore to 0 (i.e., "in use") */
initSemInUse(int semId, int semNum)
{
    union semun arg;

    arg.val = 0;
    return semctl(semId, semNum, SETVAL, arg);
}

/* Reserve semaphore (blocking), return 0 on success, or -1 with 'errno'
   set to EINTR if operation was interrupted by a signal handler */

int                     /* Reserve semaphore - decrement it by 1 */
reserveSem(int semId, int semNum)
{
    struct sembuf sops;

    sops.sem_num = semNum;
    sops.sem_op = -1;
    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;

    while (semop(semId, &sops, 1) == -1)
        if (errno != EINTR || !bsRetryOnEintr)
            return -1;

    return 0;
}

int                     /* Release semaphore - increment it by 1 */
releaseSem(int semId, int semNum)
{
    struct sembuf sops;

    sops.sem_num = semNum;
    sops.sem_op = 1;
    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;

    return semop(semId, &sops, 1);
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章