信號量分析

以struct和union爲線索來觀察信號量數組


第一部分 semid_ds ipc_permui

內核爲每一個信號量集合維護一個結構體semid_ds:進程

struct semid_ds {事件

       struct ipc_perm     sem_perm;ip

       unsigned short       sem_nsems;    /* # of semaphore in set */資源

       time_t                   sem_otime;     /* last-semop() time */get

       time_t                   sem_ctime;     /* last-change time */cmd

       …it

};io

semid_ds的一個結構體成員ipc_perm是XSI IPC爲每個IPC結構設置的, 定義以下:

struct ipc_perm {

       uid_t      uid;        /* owner’s effective user id */

       gid_t      gui;        /* owner’s effective group id */

       uid_t      cuid;       /* creator’s effective user id */

       gid_t      cgid;       /* creator’s effective group id */

       mode_t   mode;     /* access mode */

       …..

};

好, 有了以上兩個結構體, 咱們先認識semget

#include <sys/sem.h>

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

返回值: 若成功則返回信號量ID, 若出錯則返回-1

先說semget和這兩個struct的關係:

若是是建立一個新的信號量集合, 內核爲這個信號量集合維護一個結構體semid_ds, 同時對semid_ds的成員進行初始化:

ipc_perm結構賦初值, ipc_perm中的mode被設置爲flag中的相應權限位.

sem_nsems設置爲nsems

sem_otime設置爲0.

sem_ctime設置爲當前時間.

另外, 注意若是建立新集合, 則必須指定nsems, 若是引用一個集合, 則nsems設定爲0;

一句話歸納, 就是semget建立信號量集合的時候初始化了semid_ds.

第二部分 semun和一個無名結構體

semctl參數中使用了一個union, 定義以下

union semun {

       int                         val;         /* for SETVAL */

       struct semid_ds      *buf;       /* for IPC_STAT and IPC_SET */

       unsigned short              *array;    /* for GETALL and SETALL */

};

還要認識一個無名結構體. (我總以爲無名結構體聽起來就很cool). 每一個信號量由這樣一個結構體表示. 定義以下:

struct {

       unsigned short              semval;   /* semaphore value, always >= 0 */

       pid_t                     sempid;   /* pid for last operation */

       unsigned short              semncnt; /* # processes awaiting semval>curval */

       unsigned short              semzcnt; /* # processes awaiting semval == 0 */

       ….

};

有必要說明一下, semget建立的是一個信號量集合, 因此semid_ds是針對這個信號量集合的. 而上面這個無名結構體是針對的一個信號量.

好, 有了以上的一個union和一個struct, 咱們引出semctl的定義.

#include <sys/sem.h>

int semctl (int semid, int semnum, int cmd, … /* union semun arg */);

返回值: 有點複雜, 下面詳細說明

先說semctl和semun的關係.

semctl的第四個參數爲可選參數, 若是使用, 則應該爲semun類型, 要注意的是semun必須顯式的定義在用戶的程序中. 具體用法和cmd有關係.

再說semctl和無名結構體的關係. 具體用法仍是和cmd有關係.

因此, 咱們必須介紹一下cmd的用法.

cmd包括十種命令, 而針對的信號量用semnum指定. 範圍爲[0, nsems-1]

IPC_STAT
讀取semid_ds到arg.buf指向的struct中

IPC_SET
將arg.buf指向的struct設置到sem_perm.uid, sem_perm.gid和sem_perm.mode

IPC_RMID
刪除信號量集合

GETVAL
返回semnum信號量的無名結構體的成員semval值

SETVAL
arg.val設置到由semnum信號量的無名結構體成員semval中

GETPID
返回無名結構體成員sempid

GETNCNT
返回無名結構體成員semncnt

GETZCNT
返回無名結構體成員semzcnt

GETALL
取該集合中全部信號量(無名結構體)的值, 存放在arg.array指向的數組中

SETALL
將arg.array指向的數組的值設置該集合全部信號量的值(無名結構體)


總結一下.

圍繞union semun來講:

1.       int val由SETVAL使用, semctl將會把arg.val設置到信號量的semval中.

2.       struct semid_ds *buf由IPC_STAT和IPC_SET使用, 讀取時將信號量集合的semid_ds讀取到arg.buf中. 設置時使用arg.buf的三項內容.

3.       unsigned short *array由GETALL和SETALL使用, 將讀取和設置集合中的全部信號量

圍繞無名結構體來講:

1.       semval爲R/W, 讀取時直接爲semop的返回值, 設置時將arg.val設置給semval

2.       sempid, semncnt, semzcnt爲只讀, 讀取時職位爲semop的返回值.

一句話歸納. semctl讀取和設置整個信號量集合, 讀取和設置信號量集合中的每一個信號量, 刪除信號量集合

注意. semget只是建立了信號量集合, 在使用以前必須使用semctl設置你要使用的信號量

第三部分 sembuf

semop的一個參數爲struct sembuf類型, 定義以下:

struct sembuf {

       unsigned short              sem_num;      /* member # in set [0, nsems-1] */

       short                     sem_op;         /* operation (negative, 0, or positive) */

       short                     sem_flg;         /* IPC_NOWAIT, SEM_UNDO */

};

#include <sys/sem.h>

int semop (int semid, struct sembuf semoparray[], size_t nops);

返回值: 若成功返回0, 若出錯則返回

參數nops規定該數組中操做的數量(元素數)

先說明一下, 第二個參數之因此定義爲當前形式, 而沒有定義成struct sembuf *semoparray. 是由於semop能夠執行數組中的nops個操做.

整個semop圍繞sembuf來進行不一樣的操做.

成員sem_num指定了信號量

成員sem_op和sem_flg聯合指定了semop的行爲. 根據書上的描述, 以下:

1.       sem_op爲正, 則將sem_op加到semval上.

2.       sem_op爲負,
semval大於或等於sem_op的絕對值, 則從semval減去sem_op的絕對值.
semval小於sem_op的絕對值, 由於信號量爲非負值, 則根據sem_flg有以下行爲:
(a) 設定了IPC_NOWAIT, 則semop返回EAGAIN.
(b) 沒設定IPC_NOWAIT, 則semncnt值加1, 而後進程掛起直到下列時間之一發生.
    (i) semval變成大於或等於sem_op的絕對值. semncnt值減一.
    (ii) 信號量被刪除, semop返回EIDRM
    (iii) 進程捕獲到一個信號, 並從信號處理程序返回.semncnt減1, semop返回EINTR.

3.       sem_op爲0的狀況
semval爲0, 正常返回.
semval非0, 則:

(a) 指定了IPC_NOWAIT, sem_op返回EAGAIN
(b) 未指定IPC_NOWAIT, semzcnt加1, 進程掛起, 直到下列事件之一發生
    (i) semval變爲0, semzcnt減1
    (ii) 信號量被刪除, semop返回EIDRM

(iii) 進程捕獲到一個信號, 並從信號處理程序返回,semzcnt減1,semop返回EINTR

4.       若是設置了SEM_UNDO, 則在調用semop時, 對semval的操做將會記錄到信號量調整值上, 當前進程退出後, 內核將按調整值對信號量進行處理. 可是若是使用帶有SETVAL或SETALL的semctl設置某一信號量, 則在全部進程中, 該信號量的調整值都設置爲0.

書上說的很嚴謹, 可是帶來的問題就是羅嗦. 其實咱們簡單的理解, 就是, semop用來得到和釋放資源, 釋放資源時比較簡單, 得到資源時, 若是資源不足, 則根據IPC_NOWAIT標誌, 掛起或者直接返回. 取消掛起有三個條件, 有足夠的資源了, 信號量被刪除了, 捕獲並處理完了一個信號. 進程醒來後semop返回相應的值.

一句話歸納, semop根據semoparray進行nops個獲取和釋放資源的動做.

第四部分 信號量和記錄鎖

書中最後對比了信號量和記錄鎖. 給出的結論爲若是隻需鎖一個資源, 而且不須要使用XSI信號量的全部花哨(fancy)的功能, 則寧肯使用記錄鎖, 儘管記錄鎖比信號量耗時, 可是記錄鎖使用簡單, 且進程終止時系統會處理任何遺留下來的鎖.

相關文章
相關標籤/搜索