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信號量惹盼?
? ? 顧名思義庸汗,POSIX信號量,就是在POSIX標(biāo)準(zhǔn)下實(shí)現(xiàn)的信號量手报,至于什么是信號量蚯舱,請查看《進(jìn)程間通信----信號量》一節(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信號量的分類
? ? POSIX信號量分別為有名信號量和無名信號量兩類:
2.1 有名信號量
? ? 有名信號量通過一個(gè)名字來作為標(biāo)識揍鸟,名字的格式為"/somename"兄裂,這個(gè)名字的長度為MAX_NAME - 4(NAME_MAX是一個(gè)宏定義,大小為,定義在頭文件 中懦窘,各個(gè)系統(tǒng)都會實(shí)現(xiàn)這個(gè)宏的定義前翎,抱歉,本人未查詢到NAME_MAX的相關(guān)定義)畅涂,信號量名字以'/'為開始港华,以'\0'字符為結(jié)尾,并且中間字符串中不能再有'/'午衰。
? ? 兩個(gè)進(jìn)程之間可以通過sem_open函數(shù)來操作同一個(gè)有名信號量立宜,本小結(jié)中出現(xiàn)的函數(shù)會在第4章節(jié)中進(jìn)行詳細(xì)的介紹。
? ? 用戶可以通過sem_open函數(shù)接口創(chuàng)建一個(gè)新的有名信號量或者打開一個(gè)已有的有名信號量臊岸。有名信號量被打開之后橙数,進(jìn)程或線程(有名信號量一般應(yīng)用在進(jìn)程之間的同步控制,下小節(jié)介紹的無名信號量一般應(yīng)用在線程之間的同步控制)可以通過sem_post或者sem_wait接口進(jìn)行信號量操作帅戒。當(dāng)一個(gè)進(jìn)程不在使用有名信號量后灯帮,可以通過sem_close接口來關(guān)閉它,當(dāng)系統(tǒng)中所有的進(jìn)程都不在使用某個(gè)有名信號量后逻住,可以通過sem_unlink接口將它從系統(tǒng)中移除钟哥。
2.2 無名信號量
? ? 與有名信號量不同的,無名信號量并沒有名字瞎访,那么如何去標(biāo)識一個(gè)無名信號量呢腻贰,POSIX標(biāo)準(zhǔn)只是規(guī)定,無名信號量必須被放置在一段可以被多線程或者多進(jìn)程所共享的內(nèi)存區(qū)域中扒秸。
? ? 無名信號量有一個(gè)共享屬性播演,分為線程共享屬性和進(jìn)程共享屬性兩類。
? ? 一個(gè)線程共享屬性(無名信號量的共享屬性在后續(xù)章節(jié)中會介紹伴奥,它分為線程共享屬性和進(jìn)程共享屬性兩類)的無名信號量必須被放置在一個(gè)可以被進(jìn)程中所有線程所共享的內(nèi)存區(qū)域中写烤,比如全局變量。
? ? 一個(gè)進(jìn)程共享屬性的無名信號量必須被放置在共享內(nèi)存區(qū)域拾徙,系統(tǒng)V(System V顶霞,后續(xù)文章會對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ū)域锣吼。
? ? 無名信號量在使用之前必須通過sem_init接口進(jìn)行初始化,之后可以通過sem_wait和sem_post接口進(jìn)行操作蓝厌,當(dāng)不再使用無名信號量玄叠,或者放置無名信號的區(qū)域被釋放之前,用戶都應(yīng)該通過sem_destroy接口來釋放無名信號量拓提。
3. POSIX信號量實(shí)現(xiàn)了哪些接口函數(shù)
? ? 本章節(jié)主要針對第二章節(jié)中涉及到的POSIX標(biāo)準(zhǔn)下的信號量接口進(jìn)行詳細(xì)的描述读恃,依據(jù)《Linux用戶手冊release4.04》。
3.1 有名信號量接口說明
? ? 3.1.1?sem_open
????我們在使用一個(gè)有名信號量的時(shí)候,第一步就是要創(chuàng)建或者打開一個(gè)已有的有名信號量寺惫,我們需要的函數(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è)已有的有名信號量。
? ? 返回值:成功的話西雀,函數(shù)返回有名信號量的地址萨驶,這個(gè)返回值會在其它一些相關(guān)的函數(shù)中作為操作對象的索引而被使用,比如sem_post艇肴、sem_wait等函數(shù)腔呜。失敗的話返回SEM_FAILED。我們在使用此函數(shù)的時(shí)候再悼,都應(yīng)該去檢查返回值核畴,以確保我們的調(diào)用是正確的。
? ? 參數(shù):
? ? ? ? name:有名信號量的名字冲九,在2.1章節(jié)進(jìn)行過詳細(xì)的描述谤草,這里不再重復(fù)。
? ? ? ? oflag:此參數(shù)控制函數(shù)內(nèi)部的具體操作行為莺奸,如果oflag參數(shù)里有O_CREAT位丑孩,那么如果此信號量不存在則創(chuàng)建此信號量并初始化它,此時(shí)信號量的user ID被設(shè)置為調(diào)用進(jìn)程的有效user ID憾筏,group ID被設(shè)置成調(diào)用進(jìn)程的group ID嚎杨,如果oflag參數(shù)里既包含O_CREAT位也包含O_EXCL位,那么如果指定名字的信號量已存在那么就會返回錯(cuò)誤氧腰。
? ? ? ? mode:如果oflag參數(shù)里面包含O_CREAT位枫浙,那么我們就應(yīng)用調(diào)用第二個(gè)同名函數(shù)(2),并將操作權(quán)限設(shè)置到mode參數(shù)里面古拴,只要用戶想使用此信號量箩帚,那么讀寫權(quán)限都應(yīng)該設(shè)置到mode參數(shù)里(這里的讀寫權(quán)限,跟我們常用的打開文件函數(shù)open一樣黄痪,比如:O_RDONLY O_WRONLY O_RDWR等)紧帕。
? ? ? ? value:想要設(shè)置的有名信號量的初始值,無符號整形值桅打,即不小于0的整形值是嗜。
????如果O_CREAT被設(shè)置,并且信號量已經(jīng)存在挺尾,那么系統(tǒng)會自動忽略mode跟value參數(shù)鹅搪。
? ? 我們在調(diào)用sem_open函數(shù)的時(shí)候,一般有這么幾種使用方法:
? ? ? ? ? ? (1)新建一個(gè)有名信號量 sem_open("/name", O_CREAT, O_RDWR, 1);
????????????成功:若果有名信號量不存在遭铺,則創(chuàng)建一個(gè)名字為”/name"可讀可寫初始value為1的有名信號量丽柿。如果信號量存在恢准,則返回已有有名信號量的地址,并且忽略mode跟value參數(shù)的值甫题。
? ? ? ? ? ? 失斈倏稹:返回SEM_FAILED。
? ? ? ? ? ? (2)必須新建一個(gè)信號量 sem_open("/name", O_CREAT|O_EXCL坠非, O_RW, ?1)敏沉;
? ? ? ? ? ? 若信號量不存在,則創(chuàng)建一個(gè)名字為”/name"可讀可寫初始value為1的有名信號量麻顶,如果信號量存在赦抖,則失敗返回SEM_FAILED。
3.2 無名信號量接口說明
? ? 3.2.1 sem_init
? ? 函數(shù)原型:
? ??int sem_init(sem_t *sem, int pshared, unsigned int value)
? ? 函數(shù)sem_init的作用是在參數(shù)sem指向的地址上初始化一個(gè)無名信號量辅肾,其value值由參數(shù)value指定队萤。
????參數(shù)pshared指定此無名信號量實(shí)在進(jìn)程之間被共享使用,還是在線程之間被共享使用矫钓。
? ? 如果參數(shù)pshared等0要尔,那么此信號量只能在一個(gè)進(jìn)程內(nèi)的線程之間共享使用,所以新娜,它應(yīng)該被實(shí)現(xiàn)在可以被所有線程都能訪問到的地址上赵辕,如全局變量或者動態(tài)分配在堆上。
? ? 如果參數(shù)pshared是一個(gè)非0值概龄,那么此信號量被多個(gè)進(jìn)程共享使用还惠,它應(yīng)該被實(shí)現(xiàn)在共享內(nèi)存里,或者父子進(jìn)程之間使用私杜。
? ? 注意:如果使用此函數(shù)去初始化一個(gè)已經(jīng)被初始化的信號量蚕键,此函數(shù)的行為是未被定義的。
? ? 返回值:成功返回0衰粹,失敗返回-1.
? ? 3.2.2 sem_destroy
? ? 函數(shù)原型:
? ??int sem_destroy(sem_t *sem)
? ? 描述:函數(shù)sem_destroy的作用是銷毀由參數(shù)sem指向的無名信號量锣光。只有用sem_init初始化的無名信號量才可以用此函數(shù)來銷毀,并且程序開發(fā)人員也應(yīng)該使用此函數(shù)去銷毀這個(gè)無名信號量铝耻。
? ? 當(dāng)銷毀一個(gè)無名信號量時(shí)誊爹,其它應(yīng)為調(diào)用sem_wait函數(shù)而堵塞在此信號量上的進(jìn)程或線程的行為是未定義的。
? ? 當(dāng)使用一個(gè)已經(jīng)被銷毀的信號量時(shí)瓢捉,后果也是未定義的频丘。
? ? 返回值:成功返回0,失敗返回-1
? ? 注意:當(dāng)銷毀一段內(nèi)存時(shí)泡态,如果此內(nèi)存上有已經(jīng)初始化的無名信號量搂漠,那么在銷毀內(nèi)存之前,應(yīng)該先調(diào)用sem_destroy函數(shù)來銷毀信號量兽赁。
3.3 信號量通用接口說明
?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è)信號量刀崖。
? ? (1) sem_wait函數(shù)將由參數(shù)sem指定的信號量減一惊科,即上鎖操作。如果信號量的值大于0亮钦,那么執(zhí)行減一操作馆截,并且函數(shù)立即返回。如果信號量當(dāng)前的值為0蜂莉,那么調(diào)用進(jìn)程會一直阻塞直到信號量變成大于0(由其它進(jìn)程執(zhí)行了sem_post操作蜡娶,sem_post函數(shù)會在3.1.3章節(jié)進(jìn)行箱明細(xì)描述,它執(zhí)行信號量加一操作)或者被信號中斷此調(diào)用映穗。
? ? (2)sem_trywait函數(shù)同sem_wait函數(shù)的作用一樣窖张,不同是如果不能立即執(zhí)行加一操作,則調(diào)用進(jìn)程不會堵塞而是返回一個(gè)錯(cuò)誤蚁滋,errno會被設(shè)置成EAGAIN宿接。
? ? (3)sem_timedwait函數(shù)同sem_wait函數(shù)的作用一樣,不同是如果不能立即執(zhí)行加一操作辕录,則調(diào)用進(jìn)程會堵塞一定的時(shí)間段睦霎,這個(gè)時(shí)間段由函數(shù)參數(shù)abs_timeout指定。如果在指定的時(shí)間內(nèi)信號量仍不能被鎖住走诞,則函數(shù)返回超時(shí)錯(cuò)誤副女,errno會被設(shè)置成ETIMEDOUT。如果信號量的減一操作可以被立即執(zhí)行蚣旱,則此函數(shù)永遠(yuǎn)都不會返回超時(shí)錯(cuò)誤碑幅,并且參數(shù)abs_timeout的有效性也不會被檢查。
? ? 返回值:成功返回0姻锁,失斦碚浴:信號量的值不變,返回-1位隶。
????3.3.2 sem_post
? ? 函數(shù)原型:
? ? #include
? ??int sem_post(sem_t *sem)
? ? Link with -pthread
與sem_wait相對應(yīng)的函數(shù)就是sem_post拷窜,即我們PV操作里面的V操作。
此函數(shù)將sem指向的信號量解鎖(加一操作)涧黄,加一操作后如果信號量的值變成大于0篮昧,那么另外一個(gè)因?yàn)檎{(diào)用sem_wait函數(shù)而被堵塞的進(jìn)程或者線程將會被喚醒并且去執(zhí)行對信號量的加鎖操作。
返回值:成功返回0笋妥;失敗返回-1懊昨,并且信號量值不變。
注意:sem_post函數(shù)是異步信號安全的春宣,它可以在一個(gè)信號處理函數(shù)中被調(diào)用酵颁。
? ? 3.3.3 sem_getvalue
? ? 函數(shù)原型:
? ??int sem_getvalue(sem_t *sem, int *sval)
? ? sem_getvalue函數(shù)將參數(shù)sem指向的信號量的當(dāng)前值存儲到參數(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,另外一種是絕對值等于等待進(jìn)程或線程的數(shù)量的和的負(fù)數(shù)簿姨。
? ? 返回值:成功返回0距误,失敗返回-1。
? ? 注意:在實(shí)際的應(yīng)用開發(fā)中很少使用此函數(shù)扁位,作為程序開發(fā)人員也應(yīng)該盡量避免使用此函數(shù)准潭,因?yàn)楫?dāng)獲取到value值后,信號量的值可能已經(jīng)改變了域仇。