进程间通信——POSIX 有名信号量与无名信号量
原文地址:blogof33.com/post/9/
前言
在 POSIX 系統(tǒng)中,進(jìn)程間通信是一個(gè)很有意思的話題。
POSIX信號量進(jìn)程是3種 IPC(Inter-Process Communication) 機(jī)制之一,3種 IPC 機(jī)制源于 POSIX.1 的實(shí)時(shí)擴(kuò)展。Single UNIX Specification 將3種機(jī)制(消息隊(duì)列,信號量和共享存儲)置于可選部分中。在 SUSv4 之前,POSIX 信號量接口已經(jīng)被包含在信號量選項(xiàng)中。在 SUSv4 中,這些接口被移至了基本規(guī)范,而消息隊(duì)列和共享存儲接口依然是可選的。
POSIX 信號量接口意在解決 XSI 信號量接口的幾個(gè)缺陷。
-
相比于 XSI 接口,POSIX 信號量接口考慮了更高性能的實(shí)現(xiàn)。
-
POSIX 信號量使用更簡單:沒有信號量集,在熟悉的文件系統(tǒng)操作后一些接口被模式化了。盡管沒有要求一定要在文件系統(tǒng)中實(shí)現(xiàn),但是一些系統(tǒng)的確是這么實(shí)現(xiàn)的。
-
POSIX 信號量在刪除時(shí)表現(xiàn)更完美。回憶一下,當(dāng)一個(gè) XSI 信號量被刪除時(shí),使用這個(gè)信號量標(biāo)識符的操作會(huì)失敗,并將 errno 設(shè)置成 EIDRM。使用 POSIX 信號量時(shí),操作能繼續(xù)正常工作直到該信號量的最后一次引用被釋放。
——摘自《UNIX高級環(huán)境編程(中文第3版)》465-466頁
前段時(shí)間筆者在寫管道通信的時(shí)候,探究了一下 POSIX 進(jìn)程間的兩種信號量通信方式:有名信號量和無名信號量。有很多人認(rèn)為進(jìn)程間通信只能使用有名信號量,無名信號量只能用于單進(jìn)程間的多線程通信。其實(shí)無名信號量也可以進(jìn)行進(jìn)程間通信。
區(qū)別
有名信號量和無名信號量的差異在于創(chuàng)建和銷毀的形式上,但是其他工作一樣。
無名信號量只能存在于內(nèi)存中,要求使用信號量的進(jìn)程必須能訪問信號量所在的這一塊內(nèi)存,所以無名信號量只能應(yīng)用在同一進(jìn)程內(nèi)的線程之間(共享進(jìn)程的內(nèi)存),或者不同進(jìn)程中已經(jīng)映射相同內(nèi)存內(nèi)容到它們的地址空間中的線程(即信號量所在內(nèi)存被通信的進(jìn)程共享)。意思是說無名信號量只能通過共享內(nèi)存訪問。
相反,有名信號量可以通過名字訪問,因此可以被任何知道它們名字的進(jìn)程中的線程使用。
單個(gè)進(jìn)程中使用 POSIX 信號量時(shí),無名信號量更簡單。多個(gè)進(jìn)程間使用 POSIX 信號量時(shí),有名信號量更簡單。
聯(lián)系
無論是有名信號量還是無名信號量,都可以通過以下函數(shù)進(jìn)行信號量值操作。
wait
weit 為信號量值減一操作,總共有三個(gè)函數(shù),函數(shù)原型如下:
#include <semaphore.h> int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);Link with -pthread.這一句表示 gcc 編譯時(shí),要加 -pthread.返回值:若成功,返回 0 ;若出錯(cuò),返回-1 復(fù)制代碼其中,第一個(gè)函數(shù)的作用是,若 sem 小于 0 ,則線程阻塞于信號量 sem ,直到 sem 大于 0 ;否則信號量值減1。
第二個(gè)函數(shù)作用與第一個(gè)相同,只是此函數(shù)不阻塞線程,如果 sem 小于 0,直接返回一個(gè)錯(cuò)誤(錯(cuò)誤設(shè)置為 EAGAIN )。
第三個(gè)函數(shù)作用也與第一個(gè)相同,第二個(gè)參數(shù)表示阻塞時(shí)間,如果 sem 小于 0 ,則會(huì)阻塞,參數(shù)指定阻塞時(shí)間長度。 abs_timeout 指向一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體由從 1970-01-01 00:00:00 +0000 (UTC) 開始的秒數(shù)和納秒數(shù)構(gòu)成。結(jié)構(gòu)體定義如下:
struct timespec {time_t tv_sec; /* Seconds */long tv_nsec; /* Nanoseconds [0 .. 999999999] */}; 復(fù)制代碼如果指定的阻塞時(shí)間到了,但是 sem 仍然小于 0 ,則會(huì)返回一個(gè)錯(cuò)誤 (錯(cuò)誤設(shè)置為 ETIMEDOUT )。
post
post 為信號量值加一操作,函數(shù)原型如下:
#include <semaphore.h>int sem_post(sem_t *sem);Link with -pthread.返回值:若成功,返回 0 ;若出錯(cuò),返回-1 復(fù)制代碼應(yīng)用實(shí)例
有名信號量
創(chuàng)建
有名信號量創(chuàng)建可以調(diào)用 sem_open 函數(shù),函數(shù)說明如下:
#include <semaphore.h> sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); Link with -pthread.返回值:若成功,返回指向信號量的指針;若出錯(cuò),返回SEM_FALLED 復(fù)制代碼其中第一種函數(shù)是當(dāng)使用已有的有名信號量時(shí)調(diào)用該函數(shù),flag 參數(shù)設(shè)為 0 。
如果要調(diào)用第二種函數(shù),flag 參數(shù)應(yīng)設(shè)為 O_CREAT ,如果有名信號量不存在,則會(huì)創(chuàng)建一個(gè)新的,如果存在,則會(huì)被使用并且不會(huì)再初始化。
當(dāng)我們使用 O_CREAT 標(biāo)志時(shí),需要提供兩個(gè)額外的參數(shù):
mode 參數(shù)指定誰可以訪問信號量,即權(quán)限組,mode 的取值和打開文件的權(quán)限位相同,比如 0666 表示 所有用戶可讀寫 。因?yàn)橹挥凶x和寫訪問要緊,所以實(shí)現(xiàn)經(jīng)常為讀和寫打開信號量。
value 指定信號量的初始值,取值范圍為 0~SEM_VALUE_MAX 。
如果信號量存在,則調(diào)用第二個(gè)函數(shù)會(huì)忽略后面兩個(gè)參數(shù)(即 mode 和 value )。
釋放
當(dāng)完成信號量操作以后,可以調(diào)用 sem_close 函數(shù)來釋放任何信號量的資源。函數(shù)說明如下:
#include <semaphore.h>int sem_close(sem_t *sem);Link with -pthread.返回值:若成功,返回 0 ;若出錯(cuò),返回-1 復(fù)制代碼如果進(jìn)程沒有調(diào)用該函數(shù)便退出了,內(nèi)核會(huì)自動(dòng)關(guān)閉任何打開的信號量。無論是調(diào)用該函數(shù)還是內(nèi)核自動(dòng)關(guān)閉,都不會(huì)改變釋放之前的信號量值。
銷毀
可以使用 sem_unlink 函數(shù)銷毀一個(gè)有名信號量。函數(shù)說明如下:
#include <semaphore.h>int sem_unlink(const char *name);Link with -pthread.返回值:若成功,返回 0 ;若出錯(cuò),返回-1 復(fù)制代碼sem_unlink 函數(shù)會(huì)刪除信號量的名字。如果沒有打開的信號量引用,則該信號量會(huì)被銷毀,否則,銷毀會(huì)推遲到最后一個(gè)打開的引用關(guān)閉時(shí)才進(jìn)行。
例子
例如,管道通信中,如果父進(jìn)程使用 fork()創(chuàng)建兩個(gè)子進(jìn)程1和2,子進(jìn)程1,2按順序向管道寫一段文字,最后父進(jìn)程從管道將子進(jìn)程寫入的內(nèi)容讀出來,要保證進(jìn)程執(zhí)行的先后順序,可以用有名信號量來解決。
int main(){int pid1,pid2;sem_t *resource1; sem_t *resource2; int Cpid1,Cpid2=-1;int fd[2];//0為讀出段,1為寫入端char outpipe1[100],inpipe[200],outpipe2[100];pipe(fd);//建立一個(gè)無名管道pid1 = fork();if(pid1<0){printf("error in the first fork!");}else if(pid1==0){//子進(jìn)程1resource1=sem_open("name_sem1",O_CREAT,0666,0);Cpid1 = getpid();close(fd[0]);//關(guān)掉讀出端lockf(fd[1],1,0);//上鎖,則鎖定從當(dāng)前偏移量到文件結(jié)尾的區(qū)域sprintf(outpipe1,"Child process 1 is sending a message!");write(fd[1],outpipe1,strlen(outpipe2));lockf(fd[1],0,0);//解鎖sem_post(resource1);sem_close(resource1);exit(0);}else{pid2 = fork();if(pid2<0){printf("error in the second fork!\n");}else if(pid2==0){ resource1=sem_open("name_sem1",O_CREAT,0666,0);resource2=sem_open("name_sem2",O_CREAT,0666,0);Cpid2 = getpid();sem_wait(resource1);close(fd[0]);lockf(fd[1],1,0);sprintf(outpipe2,"Child process 2 is sending a message!");write(fd[1],outpipe2,strlen(outpipe2));lockf(fd[1],0,0);//解鎖sem_post(resource2);sem_close(resource1);sem_close(resource2);exit(0);}if(pid1 > 0 && pid2 >0){resource2=sem_open("name_sem2",O_CREAT,0666,0);sem_wait(resource2);waitpid(pid1,NULL,0);waitpid(pid2,NULL,0);close(fd[1]);//關(guān)掉寫端read(fd[0],inpipe,200);printf("%s\n",inpipe);sem_close(resource2);exit(0);}sem_unlink("name_sem1");sem_unlink("name_sem2");}return 0; }復(fù)制代碼無名信號量
創(chuàng)建
無名信號量可以通過 sem_init 函數(shù)創(chuàng)建,函數(shù)說明如下:
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);Link with -pthread.返回值:若成功,返回 0 ;若出錯(cuò),返回-1 復(fù)制代碼pshared 參數(shù)指示該信號量是被一個(gè)進(jìn)程的多個(gè)線程共享還是被多個(gè)進(jìn)程共享。
如果 pshared 的值為 0 ,那么信號量將被單進(jìn)程中的多線程共享,并且應(yīng)該位于某個(gè)地址,該地址對所有線程均可見(例如,全局變量或變量在堆上動(dòng)態(tài)分配)。
如果 pshared 非零,那么信號量將在進(jìn)程之間共享,并且信號量應(yīng)該位于共享內(nèi)存區(qū)域。
銷毀
如果無名信號量使用完成,可以調(diào)用 sem_destory 函數(shù)銷毀該信號量。函數(shù)說明如下:
#include <semaphore.h>int sem_destroy(sem_t *sem);Link with -pthread.返回值:若成功,返回 0 ;若出錯(cuò),返回-1 復(fù)制代碼注意:
- 銷毀其他進(jìn)程或線程當(dāng)前被阻塞的信號量會(huì)產(chǎn)生未定義的行為。
- 使用已銷毀的信號量會(huì)產(chǎn)生未定義的結(jié)果,除非使用 sem_init 重新初始化信號量。
- 一個(gè)無名信號量應(yīng)該在它所在的內(nèi)存被釋放前用 sem_destroy 銷毀。如果不這樣做,可能會(huì)導(dǎo)致某些實(shí)現(xiàn)出現(xiàn)資源泄漏。
例子
使用無名信號量實(shí)現(xiàn)有名信號量中的例子:
int main(){int pid1,pid2;int Cpid1,Cpid2=-1;int fd[2];//0為讀出段,1為寫入端char outpipe1[100],inpipe[200],outpipe2[100];void *shm = NULL;sem_t *shared;int shmid = shmget((key_t)(1234), sizeof(sem_t *), 0666 | IPC_CREAT);//創(chuàng)建一個(gè)共享內(nèi)存,返回一個(gè)標(biāo)識符if(shmid == -1){perror("shmat :");exit(0);}shm = shmat(shmid, 0, 0);//返回指向共享內(nèi)存第一個(gè)字節(jié)的指針shared = (sem_t *)shm;sem_init(shared, 1, 0);//初始化共享內(nèi)存信號量值為0pipe(fd);//建立一個(gè)無名管道pid1 = fork();if(pid1<0){printf("error in the first fork!");}else if(pid1==0){//子進(jìn)程1Cpid1 = getpid();close(fd[0]);//關(guān)掉讀出端lockf(fd[1],1,0);//上鎖,則鎖定從當(dāng)前偏移量到文件結(jié)尾的區(qū)域sprintf(outpipe1,"Child process 1 is sending a message!");write(fd[1],outpipe1,strlen(outpipe1));lockf(fd[1],0,0);//解鎖sem_post(shared);exit(0);}else{pid2 = fork();if(pid2<0){printf("error in the second fork!\n");}else if(pid2==0){sem_wait(shared);Cpid2 = getpid();close(fd[0]);lockf(fd[1],1,0);sprintf(outpipe2,"Child process 2 is sending a message!");write(fd[1],outpipe2,strlen(outpipe2));lockf(fd[1],0,0);//解鎖exit(0);}if(pid1 > 0 && pid2 >0){waitpid(pid2,NULL,0);//同步,保證子進(jìn)程先寫父進(jìn)程再讀close(fd[1]);//關(guān)掉寫端read(fd[0],inpipe,200);printf("%s\n",inpipe);exit(0);}}return 0; } 復(fù)制代碼總結(jié)
以上是生活随笔為你收集整理的进程间通信——POSIX 有名信号量与无名信号量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 806. Number of Lines
- 下一篇: 动手---sbt(2)