進(jìn)程間通信---POSIX信號(hào)量實(shí)現(xiàn)機(jī)制

1. 什么是POSIX

????首先,我們需要弄清楚的第一個(gè)問題是,什么是POSIX?

? ? POSIX是可移植操作系統(tǒng)接口(Portable Operating System Interface)的縮寫,是IEEE為了在各種UNIX操作系統(tǒng)(POSIX最后一個(gè)字母來自UNIX的X)上運(yùn)行軟件而定義的一系列API標(biāo)準(zhǔn)總稱,正式稱呼為IEEE 1003,目前,此協(xié)議已經(jīng)被大多數(shù)操作系統(tǒng)所支持。

? ? 現(xiàn)在,我們弄明白了什么是POSIX,所謂的POSIX就是一套API的標(biāo)準(zhǔn),各個(gè)操作系統(tǒng)廠商如果支持POSIX協(xié)議,那么此協(xié)議下的API都要按照協(xié)議的要求來實(shí)現(xiàn),以方便軟件的系統(tǒng)移植工作。

? ? 那么,什么是POSIX信號(hào)量?

? ? 顧名思義,POSIX信號(hào)量,就是在POSIX標(biāo)準(zhǔn)下實(shí)現(xiàn)的信號(hào)量,至于什么是信號(hào)量,請(qǐng)查看《進(jìn)程間通信----信號(hào)量》一節(jié)。

? ? 在軟件的開發(fā)過程中,如果你需要使用某個(gè)POSIX標(biāo)準(zhǔn)定義的接口,那么首先你需要先確認(rèn)目標(biāo)工作機(jī)器的操作系統(tǒng)是否支持POSIX標(biāo)準(zhǔn)(目前大多數(shù)的操作系統(tǒng)都支持)。

2. POSIX信號(hào)量的分類

? ? POSIX信號(hào)量分別為有名信號(hào)量和無名信號(hào)量?jī)深悾?/p>

2.1 有名信號(hào)量

? ? 有名信號(hào)量通過一個(gè)名字來作為標(biāo)識(shí),名字的格式為"/somename",這個(gè)名字的長(zhǎng)度為MAX_NAME - 4(NAME_MAX是一個(gè)宏定義,大小為,定義在頭文件 中,各個(gè)系統(tǒng)都會(huì)實(shí)現(xiàn)這個(gè)宏的定義,抱歉,本人未查詢到NAME_MAX的相關(guān)定義),信號(hào)量名字以'/'為開始,以'\0'字符為結(jié)尾,并且中間字符串中不能再有'/'。

? ? 兩個(gè)進(jìn)程之間可以通過sem_open函數(shù)來操作同一個(gè)有名信號(hào)量,本小結(jié)中出現(xiàn)的函數(shù)會(huì)在第4章節(jié)中進(jìn)行詳細(xì)的介紹。

? ? 用戶可以通過sem_open函數(shù)接口創(chuàng)建一個(gè)新的有名信號(hào)量或者打開一個(gè)已有的有名信號(hào)量。有名信號(hào)量被打開之后,進(jìn)程或線程(有名信號(hào)量一般應(yīng)用在進(jìn)程之間的同步控制,下小節(jié)介紹的無名信號(hào)量一般應(yīng)用在線程之間的同步控制)可以通過sem_post或者sem_wait接口進(jìn)行信號(hào)量操作。當(dāng)一個(gè)進(jìn)程不在使用有名信號(hào)量后,可以通過sem_close接口來關(guān)閉它,當(dāng)系統(tǒng)中所有的進(jìn)程都不在使用某個(gè)有名信號(hào)量后,可以通過sem_unlink接口將它從系統(tǒng)中移除。

2.2 無名信號(hào)量

? ? 與有名信號(hào)量不同的,無名信號(hào)量并沒有名字,那么如何去標(biāo)識(shí)一個(gè)無名信號(hào)量呢,POSIX標(biāo)準(zhǔn)只是規(guī)定,無名信號(hào)量必須被放置在一段可以被多線程或者多進(jìn)程所共享的內(nèi)存區(qū)域中。

? ? 無名信號(hào)量有一個(gè)共享屬性,分為線程共享屬性和進(jìn)程共享屬性兩類。

? ? 一個(gè)線程共享屬性(無名信號(hào)量的共享屬性在后續(xù)章節(jié)中會(huì)介紹,它分為線程共享屬性和進(jìn)程共享屬性兩類)的無名信號(hào)量必須被放置在一個(gè)可以被進(jìn)程中所有線程所共享的內(nèi)存區(qū)域中,比如全局變量。

? ? 一個(gè)進(jìn)程共享屬性的無名信號(hào)量必須被放置在共享內(nèi)存區(qū)域,系統(tǒng)V(System V,后續(xù)文章會(huì)對(duì)System V標(biāo)準(zhǔn)進(jìn)行介紹)通過shmget(他是一個(gè)System V標(biāo)準(zhǔn)定義的一個(gè)接口)接口實(shí)現(xiàn)共享內(nèi)存,POSIX通過shm_open接口來實(shí)現(xiàn)共享內(nèi)存區(qū)域。

? ? 無名信號(hào)量在使用之前必須通過sem_init接口進(jìn)行初始化,之后可以通過sem_wait和sem_post接口進(jìn)行操作,當(dāng)不再使用無名信號(hào)量,或者放置無名信號(hào)的區(qū)域被釋放之前,用戶都應(yīng)該通過sem_destroy接口來釋放無名信號(hào)量。

3. POSIX信號(hào)量實(shí)現(xiàn)了哪些接口函數(shù)

? ? 本章節(jié)主要針對(duì)第二章節(jié)中涉及到的POSIX標(biāo)準(zhǔn)下的信號(hào)量接口進(jìn)行詳細(xì)的描述,依據(jù)《Linux用戶手冊(cè)release4.04》。

3.1 有名信號(hào)量接口說明

? ? 3.1.1?sem_open

????我們?cè)谑褂靡粋€(gè)有名信號(hào)量的時(shí)候,第一步就是要?jiǎng)?chuàng)建或者打開一個(gè)已有的有名信號(hào)量,我們需要的函數(shù)就是sem_open,函數(shù)原型如下:

? ? (1)sem_t *sem_open(const char *name, int oflag);

? ? (2)sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

? ? 函數(shù)功能:創(chuàng)建并初始化或者打開一個(gè)已有的有名信號(hào)量。

? ? 返回值:成功的話,函數(shù)返回有名信號(hào)量的地址,這個(gè)返回值會(huì)在其它一些相關(guān)的函數(shù)中作為操作對(duì)象的索引而被使用,比如sem_post、sem_wait等函數(shù)。失敗的話返回SEM_FAILED。我們?cè)谑褂么撕瘮?shù)的時(shí)候,都應(yīng)該去檢查返回值,以確保我們的調(diào)用是正確的。

? ? 參數(shù):

? ? ? ? name:有名信號(hào)量的名字,在2.1章節(jié)進(jìn)行過詳細(xì)的描述,這里不再重復(fù)。

? ? ? ? oflag:此參數(shù)控制函數(shù)內(nèi)部的具體操作行為,如果oflag參數(shù)里有O_CREAT位,那么如果此信號(hào)量不存在則創(chuàng)建此信號(hào)量并初始化它,此時(shí)信號(hào)量的user ID被設(shè)置為調(diào)用進(jìn)程的有效user ID,group ID被設(shè)置成調(diào)用進(jìn)程的group ID,如果oflag參數(shù)里既包含O_CREAT位也包含O_EXCL位,那么如果指定名字的信號(hào)量已存在那么就會(huì)返回錯(cuò)誤。

? ? ? ? mode:如果oflag參數(shù)里面包含O_CREAT位,那么我們就應(yīng)用調(diào)用第二個(gè)同名函數(shù)(2),并將操作權(quán)限設(shè)置到mode參數(shù)里面,只要用戶想使用此信號(hào)量,那么讀寫權(quán)限都應(yīng)該設(shè)置到mode參數(shù)里(這里的讀寫權(quán)限,跟我們常用的打開文件函數(shù)open一樣,比如:O_RDONLY O_WRONLY O_RDWR等)。

? ? ? ? value:想要設(shè)置的有名信號(hào)量的初始值,無符號(hào)整形值,即不小于0的整形值。

????如果O_CREAT被設(shè)置,并且信號(hào)量已經(jīng)存在,那么系統(tǒng)會(huì)自動(dòng)忽略mode跟value參數(shù)。

? ? 我們?cè)谡{(diào)用sem_open函數(shù)的時(shí)候,一般有這么幾種使用方法:

? ? ? ? ? ? (1)新建一個(gè)有名信號(hào)量 sem_open("/name", O_CREAT, O_RDWR, 1);

????????????成功:若果有名信號(hào)量不存在,則創(chuàng)建一個(gè)名字為”/name"可讀可寫初始value為1的有名信號(hào)量。如果信號(hào)量存在,則返回已有有名信號(hào)量的地址,并且忽略mode跟value參數(shù)的值。

? ? ? ? ? ? 失敗:返回SEM_FAILED。

? ? ? ? ? ? (2)必須新建一個(gè)信號(hào)量 sem_open("/name", O_CREAT|O_EXCL, O_RW, ?1);

? ? ? ? ? ? 若信號(hào)量不存在,則創(chuàng)建一個(gè)名字為”/name"可讀可寫初始value為1的有名信號(hào)量,如果信號(hào)量存在,則失敗返回SEM_FAILED。

3.2 無名信號(hào)量接口說明

? ? 3.2.1 sem_init

? ? 函數(shù)原型:

? ??int sem_init(sem_t *sem, int pshared, unsigned int value)

? ? 函數(shù)sem_init的作用是在參數(shù)sem指向的地址上初始化一個(gè)無名信號(hào)量,其value值由參數(shù)value指定。

????參數(shù)pshared指定此無名信號(hào)量實(shí)在進(jìn)程之間被共享使用,還是在線程之間被共享使用。

? ? 如果參數(shù)pshared等0,那么此信號(hào)量只能在一個(gè)進(jìn)程內(nèi)的線程之間共享使用,所以,它應(yīng)該被實(shí)現(xiàn)在可以被所有線程都能訪問到的地址上,如全局變量或者動(dòng)態(tài)分配在堆上。

? ? 如果參數(shù)pshared是一個(gè)非0值,那么此信號(hào)量被多個(gè)進(jìn)程共享使用,它應(yīng)該被實(shí)現(xiàn)在共享內(nèi)存里,或者父子進(jìn)程之間使用。

? ? 注意:如果使用此函數(shù)去初始化一個(gè)已經(jīng)被初始化的信號(hào)量,此函數(shù)的行為是未被定義的。

? ? 返回值:成功返回0,失敗返回-1.

? ? 3.2.2 sem_destroy

? ? 函數(shù)原型:

? ??int sem_destroy(sem_t *sem)

? ? 描述:函數(shù)sem_destroy的作用是銷毀由參數(shù)sem指向的無名信號(hào)量。只有用sem_init初始化的無名信號(hào)量才可以用此函數(shù)來銷毀,并且程序開發(fā)人員也應(yīng)該使用此函數(shù)去銷毀這個(gè)無名信號(hào)量。

? ? 當(dāng)銷毀一個(gè)無名信號(hào)量時(shí),其它應(yīng)為調(diào)用sem_wait函數(shù)而堵塞在此信號(hào)量上的進(jìn)程或線程的行為是未定義的。

? ? 當(dāng)使用一個(gè)已經(jīng)被銷毀的信號(hào)量時(shí),后果也是未定義的。

? ? 返回值:成功返回0,失敗返回-1

? ? 注意:當(dāng)銷毀一段內(nèi)存時(shí),如果此內(nèi)存上有已經(jīng)初始化的無名信號(hào)量,那么在銷毀內(nèi)存之前,應(yīng)該先調(diào)用sem_destroy函數(shù)來銷毀信號(hào)量。

3.3 信號(hào)量通用接口說明

?3.3.1 sem_wait

? ? 函數(shù)原型:

? ? (1)int sem_wait(sem_t *sem);

? ? (2)int sem_trywait(sem_t *sem);

? ? (3)int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

sem_wait系列操作,即我們所說的PV操作中的P操作,目的是鎖住一個(gè)信號(hào)量。

? ? (1) sem_wait函數(shù)將由參數(shù)sem指定的信號(hào)量減一,即上鎖操作。如果信號(hào)量的值大于0,那么執(zhí)行減一操作,并且函數(shù)立即返回。如果信號(hào)量當(dāng)前的值為0,那么調(diào)用進(jìn)程會(huì)一直阻塞直到信號(hào)量變成大于0(由其它進(jìn)程執(zhí)行了sem_post操作,sem_post函數(shù)會(huì)在3.1.3章節(jié)進(jìn)行箱明細(xì)描述,它執(zhí)行信號(hào)量加一操作)或者被信號(hào)中斷此調(diào)用。

? ? (2)sem_trywait函數(shù)同sem_wait函數(shù)的作用一樣,不同是如果不能立即執(zhí)行加一操作,則調(diào)用進(jìn)程不會(huì)堵塞而是返回一個(gè)錯(cuò)誤,errno會(huì)被設(shè)置成EAGAIN。

? ? (3)sem_timedwait函數(shù)同sem_wait函數(shù)的作用一樣,不同是如果不能立即執(zhí)行加一操作,則調(diào)用進(jìn)程會(huì)堵塞一定的時(shí)間段,這個(gè)時(shí)間段由函數(shù)參數(shù)abs_timeout指定。如果在指定的時(shí)間內(nèi)信號(hào)量仍不能被鎖住,則函數(shù)返回超時(shí)錯(cuò)誤,errno會(huì)被設(shè)置成ETIMEDOUT。如果信號(hào)量的減一操作可以被立即執(zhí)行,則此函數(shù)永遠(yuǎn)都不會(huì)返回超時(shí)錯(cuò)誤,并且參數(shù)abs_timeout的有效性也不會(huì)被檢查。

? ? 返回值:成功返回0,失敗:信號(hào)量的值不變,返回-1。

????3.3.2 sem_post

? ? 函數(shù)原型:

? ? #include

? ??int sem_post(sem_t *sem)

? ? Link with -pthread

與sem_wait相對(duì)應(yīng)的函數(shù)就是sem_post,即我們PV操作里面的V操作。

此函數(shù)將sem指向的信號(hào)量解鎖(加一操作),加一操作后如果信號(hào)量的值變成大于0,那么另外一個(gè)因?yàn)檎{(diào)用sem_wait函數(shù)而被堵塞的進(jìn)程或者線程將會(huì)被喚醒并且去執(zhí)行對(duì)信號(hào)量的加鎖操作。

返回值:成功返回0;失敗返回-1,并且信號(hào)量值不變。

注意:sem_post函數(shù)是異步信號(hào)安全的,它可以在一個(gè)信號(hào)處理函數(shù)中被調(diào)用。

? ? 3.3.3 sem_getvalue

? ? 函數(shù)原型:

? ??int sem_getvalue(sem_t *sem, int *sval)

? ? sem_getvalue函數(shù)將參數(shù)sem指向的信號(hào)量的當(dāng)前值存儲(chǔ)到參數(shù)sval指向的整形變量中。

? ? 如果,當(dāng)前有一個(gè)或多個(gè)進(jìn)程或線程正在因?yàn)檎{(diào)用sem_wait函數(shù)而堵塞中,那么POSIX標(biāo)準(zhǔn)規(guī)定可以返回兩種數(shù)據(jù)到sval中,一種是0,另外一種是絕對(duì)值等于等待進(jìn)程或線程的數(shù)量的和的負(fù)數(shù)。

? ? 返回值:成功返回0,失敗返回-1。

? ? 注意:在實(shí)際的應(yīng)用開發(fā)中很少使用此函數(shù),作為程序開發(fā)人員也應(yīng)該盡量避免使用此函數(shù),因?yàn)楫?dāng)獲取到value值后,信號(hào)量的值可能已經(jīng)改變了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容