原文地址:blogof33.com/post/9/編程
在 POSIX 系統中,進程間通訊是一個頗有意思的話題。bash
POSIX信號量進程是3種 IPC(Inter-Process Communication) 機制之一,3種 IPC 機制源於 POSIX.1 的實時擴展。Single UNIX Specification 將3種機制(消息隊列,信號量和共享存儲)置於可選部分中。在 SUSv4 以前,POSIX 信號量接口已經被包含在信號量選項中。在 SUSv4 中,這些接口被移至了基本規範,而消息隊列和共享存儲接口依然是可選的。多線程
POSIX 信號量接口意在解決 XSI 信號量接口的幾個缺陷。函數
相比於 XSI 接口,POSIX 信號量接口考慮了更高性能的實現。post
POSIX 信號量使用更簡單:沒有信號量集,在熟悉的文件系統操做後一些接口被模式化了。儘管沒有要求必定要在文件系統中實現,可是一些系統的確是這麼實現的。性能
POSIX 信號量在刪除時表現更完美。回憶一下,當一個 XSI 信號量被刪除時,使用這個信號量標識符的操做會失敗,並將 errno 設置成 EIDRM。使用 POSIX 信號量時,操做能繼續正常工做直到該信號量的最後一次引用被釋放。ui
——摘自《UNIX高級環境編程(中文第3版)》465-466頁spa
前段時間筆者在寫管道通訊的時候,探究了一下 POSIX 進程間的兩種信號量通訊方式:有名信號量和無名信號量。有不少人認爲進程間通訊只能使用有名信號量,無名信號量只能用於單進程間的多線程通訊。其實無名信號量也能夠進行進程間通訊。線程
有名信號量和無名信號量的差別在於建立和銷燬的形式上,可是其餘工做同樣。指針
無名信號量只能存在於內存中,要求使用信號量的進程必須能訪問信號量所在的這一塊內存,因此無名信號量只能應用在同一進程內的線程之間(共享進程的內存),或者不一樣進程中已經映射相同內存內容到它們的地址空間中的線程(即信號量所在內存被通訊的進程共享)。意思是說無名信號量只能經過共享內存訪問。
相反,有名信號量能夠經過名字訪問,所以能夠被任何知道它們名字的進程中的線程使用。
單個進程中使用 POSIX 信號量時,無名信號量更簡單。多個進程間使用 POSIX 信號量時,有名信號量更簡單。
不管是有名信號量仍是無名信號量,均可以經過如下函數進行信號量值操做。
weit 爲信號量值減一操做,總共有三個函數,函數原型以下:
#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 編譯時,要加 -pthread.返回值:若成功,返回 0 ;若出錯,返回-1
複製代碼
其中,第一個函數的做用是,若 sem 小於 0 ,則線程阻塞於信號量 sem ,直到 sem 大於 0 ;不然信號量值減1。
第二個函數做用與第一個相同,只是此函數不阻塞線程,若是 sem 小於 0,直接返回一個錯誤(錯誤設置爲 EAGAIN )。
第三個函數做用也與第一個相同,第二個參數表示阻塞時間,若是 sem 小於 0 ,則會阻塞,參數指定阻塞時間長度。 abs_timeout 指向一個結構體,這個結構體由從 1970-01-01 00:00:00 +0000 (UTC) 開始的秒數和納秒數構成。結構體定義以下:
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
複製代碼
若是指定的阻塞時間到了,可是 sem 仍然小於 0 ,則會返回一個錯誤 (錯誤設置爲 ETIMEDOUT )。
post 爲信號量值加一操做,函數原型以下:
#include <semaphore.h>
int sem_post(sem_t *sem);
Link with -pthread.返回值:若成功,返回 0 ;若出錯,返回-1
複製代碼
有名信號量建立能夠調用 sem_open
函數,函數說明以下:
#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.返回值:若成功,返回指向信號量的指針;若出錯,返回SEM_FALLED
複製代碼
其中第一種函數是當使用已有的有名信號量時調用該函數,flag 參數設爲 0
。
若是要調用第二種函數,flag 參數應設爲 O_CREAT
,若是有名信號量不存在,則會建立一個新的,若是存在,則會被使用而且不會再初始化。
當咱們使用 O_CREAT
標誌時,須要提供兩個額外的參數:
mode 參數指定誰能夠訪問信號量,即權限組,mode 的取值和打開文件的權限位相同,好比
0666
表示 全部用戶可讀寫 。由於只有讀和寫訪問要緊,因此實現常常爲讀和寫打開信號量。value 指定信號量的初始值,取值範圍爲 0~SEM_VALUE_MAX 。
若是信號量存在,則調用第二個函數會忽略後面兩個參數(即 mode 和 value )。
當完成信號量操做之後,能夠調用 sem_close
函數來釋聽任何信號量的資源。函數說明以下:
#include <semaphore.h>
int sem_close(sem_t *sem);
Link with -pthread.返回值:若成功,返回 0 ;若出錯,返回-1
複製代碼
若是進程沒有調用該函數便退出了,內核會自動關閉任何打開的信號量。不管是調用該函數仍是內核自動關閉,都不會改變釋放以前的信號量值。
可使用 sem_unlink
函數銷燬一個有名信號量。函數說明以下:
#include <semaphore.h>
int sem_unlink(const char *name);
Link with -pthread.返回值:若成功,返回 0 ;若出錯,返回-1
複製代碼
sem_unlink
函數會刪除信號量的名字。若是沒有打開的信號量引用,則該信號量會被銷燬,不然,銷燬會推遲到最後一個打開的引用關閉時才進行。
例如,管道通訊中,若是父進程使用 fork()
建立兩個子進程1和2,子進程1,2按順序向管道寫一段文字,最後父進程從管道將子進程寫入的內容讀出來,要保證進程執行的前後順序,能夠用有名信號量來解決。
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include<fcntl.h>
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);//創建一個無名管道
pid1 = fork();
if(pid1<0){
printf("error in the first fork!");
}else if(pid1==0){//子進程1
resource1=sem_open("name_sem1",O_CREAT,0666,0);
Cpid1 = getpid();
close(fd[0]);//關掉讀出端
lockf(fd[1],1,0);//上鎖,則鎖定從當前偏移量到文件結尾的區域
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]);//關掉寫端
read(fd[0],inpipe,200);
printf("%s\n",inpipe);
sem_close(resource2);
exit(0);
}
sem_unlink("name_sem1");
sem_unlink("name_sem2");
}
return 0;
}
複製代碼
無名信號量能夠經過 sem_init
函數建立,函數說明以下:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
Link with -pthread.返回值:若成功,返回 0 ;若出錯,返回-1
複製代碼
pshared 參數指示該信號量是被一個進程的多個線程共享仍是被多個進程共享。
若是 pshared 的值爲 0 ,那麼信號量將被單進程中的多線程共享,而且應該位於某個地址,該地址對全部線程都可見(例如,全局變量或變量在堆上動態分配)。
若是 pshared 非零,那麼信號量將在進程之間共享,而且信號量應該位於共享內存區域。
若是無名信號量使用完成,能夠調用 sem_destory
函數銷燬該信號量。函數說明以下:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
Link with -pthread.返回值:若成功,返回 0 ;若出錯,返回-1
複製代碼
注意:
- 銷燬其餘進程或線程當前被阻塞的信號量會產生未定義的行爲。
- 使用已銷燬的信號量會產生未定義的結果,除非使用
sem_init
從新初始化信號量。- 一個無名信號量應該在它所在的內存被釋放前用
sem_destroy
銷燬。若是不這樣作,可能會致使某些實現出現資源泄漏。
使用無名信號量實現有名信號量中的例子:
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include<fcntl.h>
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);//建立一個共享內存,返回一個標識符
if(shmid == -1){
perror("shmat :");
exit(0);
}
shm = shmat(shmid, 0, 0);//返回指向共享內存第一個字節的指針
shared = (sem_t *)shm;
sem_init(shared, 1, 0);//初始化共享內存信號量值爲0
pipe(fd);//創建一個無名管道
pid1 = fork();
if(pid1<0){
printf("error in the first fork!");
}else if(pid1==0){//子進程1
Cpid1 = getpid();
close(fd[0]);//關掉讀出端
lockf(fd[1],1,0);//上鎖,則鎖定從當前偏移量到文件結尾的區域
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);//同步,保證子進程先寫父進程再讀
close(fd[1]);//關掉寫端
read(fd[0],inpipe,200);
printf("%s\n",inpipe);
exit(0);
}
}
return 0;
}
複製代碼