APUE讀書筆記-15進(jìn)程內(nèi)部通信(7)

8、信號量

信號量和我們前面提到的IPC不太一樣符喝,它是一個(gè)計(jì)數(shù)器闪彼,用來提供在多個(gè)進(jìn)程之間共享數(shù)據(jù)的訪問。

Single UNIX Specification 有一個(gè)對信號量的實(shí)時(shí)擴(kuò)展协饲,這里不對它進(jìn)行討論畏腕。

為了獲得一個(gè)共享的資源,進(jìn)程需要做如下的工作:

  1. 對控制資源的信號量進(jìn)行測試茉稠。
  2. 如果信號量的值是整數(shù)描馅,那么進(jìn)程可以對共享資源進(jìn)行訪問,這時(shí)候?qū)研盘柫康闹禍p少1而线,表示使用了一個(gè)單位的資源铭污。
  3. 如果信號量的值大于0恋日,那么進(jìn)程睡眠直到信號量大于0,然后進(jìn)程醒來執(zhí)行步驟1嘹狞。

如果一個(gè)進(jìn)程操作完信號量控制的共享數(shù)據(jù)谚鄙,那么將會(huì)給信號量加1,如果有其他進(jìn)程由于等待信號量而處于睡眠狀態(tài)刁绒,那么這些進(jìn)程將會(huì)被喚醒闷营。

為了正確地執(zhí)行信號量, 信號量值的測試和減少操作必須是一個(gè)原子操作 知市。因此傻盟,信號量一般在內(nèi)核中實(shí)現(xiàn)。

有一個(gè)叫二進(jìn)制的比較通用的信號量嫂丙,這個(gè)信號量控制一個(gè)單一的資源娘赴,它的值被初始化為1。一般來說跟啤,信號量可以被初始化成為任何正數(shù)诽表,數(shù)值表示可用的共享資源的單元數(shù)目。

XSI的信號量比這復(fù)雜多了隅肥,有三個(gè)特性導(dǎo)致了這樣的復(fù)雜性:

  1. 信號量并不簡單地是一個(gè)單個(gè)的非負(fù)值竿奏。相反,我們將信號量定義成了包含一個(gè)或者多個(gè)信號量值的 集合 腥放。當(dāng)創(chuàng)建了一個(gè)信號量的時(shí)候泛啸,我們指定集合中值的數(shù)目。
  2. 信號量的創(chuàng)建(semget)是和它的初始化(semctl)相獨(dú)立的秃症。這個(gè)缺點(diǎn)比較致命候址,因?yàn)槲覀儾荒軇?chuàng)建一個(gè)信號量集合同時(shí)初始化集合中的所有值。
  3. 由于所有形式的XSI IPC即使在沒有進(jìn)程使用它們的時(shí)候也會(huì)保持存在种柑,所以我們可能會(huì)擔(dān)心一個(gè)應(yīng)用程序沒有釋放分配給它的信號量就終止了岗仑。后面我們討論的undo特性用來處理這種情況。

內(nèi)核為每一個(gè)信號量集合維持一個(gè)semid_ds數(shù)據(jù)結(jié)構(gòu)聚请,如下:

struct semid_ds {
        struct ipc_perm  sem_perm;  /* see Section 15.6.2 */
        unsigned short   sem_nsems; /* # of semaphores in set */
        time_t           sem_otime; /* last-semop() time */
        time_t           sem_ctime; /* last-change time */
        .
};

以上是Single UNIX Specification標(biāo)準(zhǔn)定義的一些成員荠雕,具體實(shí)現(xiàn)還可以定義其他的成員。

每個(gè)信號量由至少有以下成員的匿名結(jié)構(gòu)體所表示:

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

對于信號量集合的系統(tǒng)限制這里就不給出了良漱,具體參見參考資料舞虱。

semget

首先調(diào)用semget獲得一個(gè)信號量ID。

#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);

返回值:如果成功返回信號量ID母市,如果錯(cuò)誤返回1矾兜。

前面我們討論了將一個(gè)關(guān)鍵字轉(zhuǎn)換成標(biāo)識的方法,以及創(chuàng)建一個(gè)新的信號量集合和對已經(jīng)存在的信號量集合的引用患久。當(dāng)創(chuàng)建一個(gè)新的信號量集合的時(shí)候椅寺,semid_ds結(jié)構(gòu)變量的如下成員將會(huì)被初始化浑槽。

  • ipc_perm結(jié)構(gòu)如同前面描述的那樣被初始化(即所有的成員都會(huì)被初始化)。結(jié)構(gòu)的mode成員將會(huì)被設(shè)置成相應(yīng)的flag權(quán)限位返帕。
  • sem_otime 被設(shè)置成0桐玻。
  • sem_ctime 被設(shè)置成當(dāng)前時(shí)間。
  • sem_nsems 被設(shè)置成nsems荆萤。

集合中的信號量的數(shù)目用nsems進(jìn)行表示镊靴。如果一個(gè) 新的集合被創(chuàng)建(一般這個(gè)集合都是被服務(wù)進(jìn)程創(chuàng)建),我們必須指定nsems ;如果我們 只是引用一個(gè)已經(jīng)存在的集合(一般集合被客戶進(jìn)程引用)链韭,我們可以指定nsems為0 偏竟。

semctl

semctl函數(shù)用來進(jìn)行各種類型的信號量操作。

#include <sys/sem.h>
int semctl(int semid, int semnum, int  cmd, ... /* union semun arg */);

返回值:參見后面敞峭。

第4個(gè)參數(shù)是可選的踊谋,它取決于請求的命令,如果存在旋讹,那么這個(gè)參數(shù)的類型是semun殖蚕,這是一個(gè)各種命令的參數(shù)的聯(lián)合。

union semun {
        int              val;    /* for SETVAL */
        struct semid_ds *buf;    /* for IPC_STAT and IPC_SET */
        unsigned short  *array;  /* for GETALL and SETALL */
};

我們需要注意的是沉迹, 這個(gè)可選的參數(shù)是一個(gè)實(shí)際的聯(lián)合變量睦疫,而不是一個(gè)指向聯(lián)合變量的指針

參數(shù)cmd指定了如下的10個(gè)命令胚股,這些命令對semid相應(yīng)的集合進(jìn)行操作笼痛。

有5各命令使用semnum指定集合中的一個(gè)成員以引用特定的信號量值裙秋。semnum的范圍是[0,nsems-1]琅拌。

  • IPC_STAT 獲取集合相應(yīng)的semid_ds結(jié)構(gòu),把它存放在第4個(gè)參數(shù)arg.buf中摘刑。
  • IPC_SET 設(shè)置和集合相關(guān)聯(lián)的semid_ds結(jié)構(gòu)變量的sem_perm.uid, sem_perm.gid,和sem_perm.mode成員进宝,設(shè)置的值來自arg.buf。這個(gè)命令的執(zhí)行進(jìn)程的有效用戶id必續(xù)和sem_perm.cuid或者sem_perm.uid相等枷恕,或者執(zhí)行這個(gè)命令的進(jìn)程是具有超級用戶權(quán)限的党晋。
  • IPC_RMID 從系統(tǒng)中刪除信號量集合。刪除的動(dòng)作立即生效徐块。任何使用這個(gè)信號量的進(jìn)程再次操作信號量的時(shí)候?qū)?huì)得到EIDRM錯(cuò)誤未玻。這個(gè)命令的執(zhí)行進(jìn)程的有效用戶id必續(xù)和sem_perm.cuid或者sem_perm.uid相等,或者執(zhí)行這個(gè)命令的進(jìn)程是具有超級用戶權(quán)限的胡控。
  • GETVAL 返回semnum對應(yīng)的信號量的信號量值扳剿。
  • SETVAL 設(shè)置semnum所對應(yīng)的信號量的值。這個(gè)值通過arg.val來進(jìn)行指定昼激。
  • GETPID 返回semnum對應(yīng)的信號量的sempid成員庇绽。
  • GETNCNT 返回semnum對應(yīng)的信號量的semncnt成員锡搜。
  • GETZCNT 返回semnum對應(yīng)的信號量的semzcnt成員。
  • GETALL 獲取集合中的所有信號量的值瞧掺。這些值被存放在arg.array所指向的數(shù)組中耕餐。
  • SETALL 設(shè)置集合中的所有信號量值,這些值來自arg.array數(shù)組辟狈。

semop

函數(shù)semop會(huì)原子性地對一個(gè)信號量集合執(zhí)行數(shù)組中指定的一系列操作肠缔。

#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);

返回值:如果成功返回0,如果錯(cuò)誤返回1哼转。

semoparray參數(shù)是一個(gè)指向信號量操作的數(shù)組的指針桩砰,其元素的數(shù)據(jù)結(jié)構(gòu)如下:

struct sembuf {
        unsigned short  sem_num;  /* member # in set (0, 1, ..., nsems-1) */
        short           sem_op;   /* operation (negative, 0, or positive) */
        short           sem_flg;  /* IPC_NOWAIT, SEM_UNDO */
};

nops參數(shù)指定操作數(shù)組中元素的數(shù)目。

對集合中的每個(gè)成員的操作通過相應(yīng)的sem_op值來進(jìn)行指定释簿。這個(gè)值可以是負(fù)數(shù)亚隅,0,或者是正數(shù)庶溶。(在下面的討論中煮纵,我們會(huì)引用到信號量的"undo"標(biāo)記,這個(gè)標(biāo)記相應(yīng)于sem_flg成員中的SEM_UNDO比特位)

情況I. 最簡單的情況是sem_op是正數(shù)偏螺。

這個(gè)情況相應(yīng)于進(jìn)程所返回的資源的數(shù)目行疏。sem_op的值會(huì)被添加到信號量的值中。如果undo標(biāo)記被指定了套像,那么會(huì)從這個(gè)進(jìn)程的信號量調(diào)整值減去sem_op酿联。

II. 如果sem_op是負(fù)數(shù),那么表示我們想要獲取信號量控制的資源夺巩。

如果信號量的值大于或者等于sem_op的絕對值(資源可用)贞让,那么會(huì)將信號量的值減去sem_op的絕對值。這保證信號量的結(jié)果值大于或者等于0柳譬。如果undo標(biāo)記被指定喳张,那么sem_op的絕對值也會(huì)被加到這個(gè)進(jìn)程的信號量調(diào)整值中。

如果信號量的值比sem_op的絕對值忻腊摹(資源不可用)销部,那么會(huì)發(fā)生如下的情況:

  1. 如果IPC_NOWAIT被指定了,那么semop會(huì)返回一個(gè)EAGAIN錯(cuò)誤制跟。
  2. 如果IPC_NOWAIT沒有被指定舅桩,那么這個(gè)信號量的semncnt值會(huì)增加(因?yàn)檎{(diào)用者將要睡覺),然后調(diào)用者會(huì)掛起直到如下的情況發(fā)生雨膨。
    1. 信號量的值變成了大于或者等于sem_op的絕對值的時(shí)候(也就是說其他的進(jìn)程釋放了一些資源)擂涛。信號量的semncnt的值會(huì)被減少(因?yàn)檎{(diào)用進(jìn)程正在進(jìn)行等待),同時(shí)sem_op的絕對值會(huì)被從信號量的值中被減去哥放。如果undo標(biāo)記被指定歼指,那么sem_op的絕對值也會(huì)被加到這個(gè)進(jìn)程的信號量調(diào)整值上爹土。
    2. 信號量被從系統(tǒng)中移除的時(shí)候。這個(gè)時(shí)候踩身,函數(shù)返回一個(gè)EIDRM錯(cuò)誤胀茵。
    3. 進(jìn)程捕獲到了一個(gè)信號,并且信號處理函數(shù)返回挟阻。這個(gè)情況琼娘,信號量的semncnt的值會(huì)被減少(因?yàn)檎{(diào)用進(jìn)程不會(huì)再進(jìn)行等待),并且函數(shù)返回一個(gè)EINTR錯(cuò)誤附鸽。

III. 如果sem_op的值是0脱拼,這表示調(diào)用進(jìn)程想要等待,一直到信號量的值變成為0坷备。

如果信號量的值當(dāng)前是0熄浓,那么函數(shù)會(huì)立即返回。

如果信號量的值為非0省撑,那么會(huì)根據(jù)如下情況進(jìn)行處理:

  1. 如果IPC_NOWAIT被指定了赌蔑,那么返回錯(cuò)誤EAGAIN。
  2. 如果沒有指定IPC_NOWAIT竟秫,那么信號量的semzcnt值會(huì)被增加(因?yàn)檎{(diào)用這將要進(jìn)行睡眠)娃惯,同時(shí)調(diào)用進(jìn)程掛起,直到如下的情況發(fā)生肥败。
    1. 信號量的值變成了0,信號量的semncnt的值會(huì)被減少(因?yàn)檎{(diào)用進(jìn)程正在進(jìn)行等待)趾浅。
    2. 信號量被從系統(tǒng)中移除的時(shí)候。這個(gè)時(shí)候馒稍,函數(shù)返回一個(gè)EIDRM錯(cuò)誤皿哨。
    3. 進(jìn)程捕獲到了一個(gè)信號,并且信號處理函數(shù)返回筷黔。這個(gè)情況往史,信號量的semncnt的值會(huì)被減少(因?yàn)檎{(diào)用進(jìn)程不會(huì)再進(jìn)行等待),并且函數(shù)返回一個(gè)EINTR錯(cuò)誤佛舱。

semop函數(shù)的操作是原子性質(zhì)的,要么數(shù)組中的操作全部被做挨决,要么一個(gè)也不做请祖。

在退出時(shí)候?qū)π盘柫康恼{(diào)整

我們前面說過,如果一個(gè)進(jìn)程通過信號量分配了資源脖祈,那么當(dāng)進(jìn)程結(jié)束的時(shí)候肆捕,可能會(huì)出現(xiàn)問題。當(dāng)我們?yōu)樾盘柫坎僮髦付⊿EM_UNDO標(biāo)記并且我們分配一個(gè)資源(sem_op值小于0)盖高,內(nèi)核會(huì)記住我們給那個(gè)信號量分配了多少資源(sem_op的絕對值)慎陵。當(dāng)進(jìn)程結(jié)束的時(shí)候眼虱,無論是主動(dòng)的還是非主動(dòng)地結(jié)束,內(nèi)核都會(huì)檢查這個(gè)進(jìn)程是否具有信號量調(diào)整值席纽,如果有捏悬,將會(huì)把對應(yīng)的信號量調(diào)整值疊加到信號量上,實(shí)現(xiàn)相應(yīng)的調(diào)整润梯。

如果我們通過SETVAL或者SETALL命令調(diào)用semctl設(shè)置信號量的值过牙,那么那個(gè)信號量在所有進(jìn)程的的調(diào)整值都被設(shè)置成0。

信號量和記錄鎖的時(shí)間對比的例子

如果我們在多個(gè)進(jìn)程之間共享一個(gè)單個(gè)的資源纺铭,我們可以使用信號量或者記錄鎖寇钉。

通過信號量技術(shù),我們創(chuàng)建了一個(gè)只有一個(gè)信號量成員的信號量集合舶赔,并且將這個(gè)成員信號量的值初始化為1扫倡。分配資源的時(shí)候,我們調(diào)用semop函數(shù)竟纳,其中的sem_op值為-1;釋放資源的時(shí)候镊辕,我們執(zhí)行同樣的函數(shù)但是其中的sem_op的值為+1。我們也可以為每一個(gè)操作指定SEM_UNDO蚁袭,以處理進(jìn)程結(jié)束而沒有釋放資源的情況征懈。

通過記錄鎖的技術(shù),我們創(chuàng)建一個(gè)空的文件揩悄,然后將文件的第一個(gè)字節(jié)做為鎖住的字節(jié)(不一定非得存在)卖哎。當(dāng)分配資源的時(shí)候,我們獲取一個(gè)在這個(gè)字節(jié)上面的寫鎖删性;釋放資源的時(shí)候亏娜,我們將這個(gè)字節(jié)解鎖。記錄鎖的特性可以保證如果一個(gè)進(jìn)程在持有鎖的時(shí)候結(jié)束了蹬挺,那么這個(gè)鎖會(huì)自動(dòng)被內(nèi)核釋放维贺。

下面的表格中,給出了Linux上面兩個(gè)技術(shù)的時(shí)間對比情況巴帮。每種情況下溯泣,資源被分配和釋放100,000此。通過三個(gè)不同的進(jìn)程同時(shí)進(jìn)行榕茧。表中的時(shí)間是所有三個(gè)進(jìn)程的總共秒數(shù)垃沦。

        時(shí)間對比的表格
+--------------------------------------------------+
|        Operation         | User | System | Clock |
|--------------------------+------+--------+-------|
| semaphores with undo     | 0.38 |  0.48  | 0.86  |
|--------------------------+------+--------+-------|
| advisory record locking  | 0.41 |  0.95  | 1.36  |
+--------------------------------------------------+

在Linux上面,使用記錄鎖的時(shí)間會(huì)比信號量鎖的時(shí)間多60%用押。

盡管記錄鎖比信號量鎖要慢肢簿,如果我們只是對一個(gè)單個(gè)的資源加鎖(例如共享內(nèi)存段)并且不需要XSI信號量提供的高級功能的化,我們還是喜歡使用記錄鎖。原因就是記錄鎖非常容易被使用池充,并且系統(tǒng)會(huì)自動(dòng)處理進(jìn)程結(jié)束的時(shí)候的資源釋放等問題桩引。

譯者注

  1. 這里的信號量其實(shí)是一個(gè)信號量集合,集合中每個(gè)信號量都有包含信號量的數(shù)目以及權(quán)限信息收夸。

  2. 使用semget獲取或創(chuàng)建信號量集合的標(biāo)識

  3. 使用semctl初始化坑匠,semun類型參數(shù)用于獲取或者設(shè)置信號量集合的相應(yīng)值

  4. 使用semop對信號量集合中的一個(gè)或多個(gè)信號量進(jìn)行申請釋放,sembuf類型參數(shù)咱圆,包含UNDO標(biāo)記笛辟,以便進(jìn)程異常結(jié)束后信號量的清理。

  5. 每個(gè)信號量集合的結(jié)構(gòu)包含信號量數(shù)目序苏,每個(gè)信號量是匿名結(jié)構(gòu)包含值和等待情況手幢。

  6. int semget(key_t key, int nsems, int flag) 中,創(chuàng)建的是一個(gè)信號量集忱详,而非單一信號量围来。權(quán)限是集合的權(quán)限;集合中每個(gè)信號量都有特定的值匈睁。

  7. int semctl(int semid, int semnum, int cmd, ... /* union semun arg */) 中监透, semnum表示是集合中的第 semnum 個(gè)信號量。

  8. int semop(int semid, struct sembuf semoparray[], size_t nops) 中航唆,設(shè)置SEM_UNDO標(biāo)記并非改變信號操作胀蛮,而是額外記錄一個(gè)調(diào)整值以備異常。

記錄鎖性能不如信號量但是使用簡單糯钙。信號量每次創(chuàng)建一個(gè)信號量集合粪狼,而非單個(gè)信號量,在某些場景比如:需要的資源不止一種的時(shí)候任岸,可能維護(hù)起來會(huì)相對方便再榄,因?yàn)榧卸x資源到集合中了,而非單獨(dú)各自定義享潜。

原文參考

參考: APUE2/ch15lev1sec8.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末困鸥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剑按,更是在濱河造成了極大的恐慌疾就,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吕座,死亡現(xiàn)場離奇詭異虐译,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吴趴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锣枝,你說我怎么就攤上這事厢拭。” “怎么了撇叁?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵供鸠,是天一觀的道長。 經(jīng)常有香客問我陨闹,道長楞捂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任趋厉,我火速辦了婚禮寨闹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘君账。我一直安慰自己繁堡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布乡数。 她就那樣靜靜地躺著椭蹄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪净赴。 梳的紋絲不亂的頭發(fā)上绳矩,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音玖翅,去河邊找鬼翼馆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛烧栋,可吹牛的內(nèi)容都是我干的写妥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼审姓,長吁一口氣:“原來是場噩夢啊……” “哼珍特!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起魔吐,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤扎筒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酬姆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗜桌,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年辞色,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骨宠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖层亿,靈堂內(nèi)的尸體忽然破棺而出桦卒,到底是詐尸還是另有隱情,我是刑警寧澤匿又,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布方灾,位于F島的核電站,受9級特大地震影響碌更,放射性物質(zhì)發(fā)生泄漏裕偿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一痛单、第九天 我趴在偏房一處隱蔽的房頂上張望嘿棘。 院中可真熱鬧,春花似錦桦他、人聲如沸蔫巩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽圆仔。三九已至,卻和暖如春蔫劣,著一層夾襖步出監(jiān)牢的瞬間坪郭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工脉幢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留歪沃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓嫌松,卻偏偏與公主長得像沪曙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子萎羔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348