無名信號量能夠在相關進程間進行同步,所謂相關進程暫時先簡單的理解爲父子進程,最後再詳細的解釋一下。在上一篇博客 無名信號量——線程間同步 http://www.javashuo.com/article/p-bppejpth-vc.html中已經介紹過信號量相關的各個函數,其中sem_init第二個參數,若是傳入的值是非0值,就表示使用的信號量是在進程間共享。html
按照這個理解,先來寫一個簡單的範例程序:函數
1 #include <stdio.h> 2 #include <semaphore.h> 3 4 int main(int argc, const char *argv[]) 5 { 6 int pid = 0; 7 sem_t sem; 8 int ret = 0; 9 10 ret = sem_init(&sem, 1, 1); /* 初始化信號量的值爲1 */ 11 printf("ret = %d\n", ret); 12 13 pid = fork(); 14 15 if (pid == 0) { /* 子進程 */ 16 printf("I'm child\n"); 17 printf("child sem_wait...\n"); 18 sem_wait(&sem); 19 printf("child sem_wait ok\n"); 20 } else if (pid > 0) { /* 父進程 */ 21 sleep(1); 22 printf("I'm parent\n"); 23 printf("parent sem_wait...\n"); 24 sem_wait(&sem); 25 printf("parent sem_wait ok\n"); 26 } 27 28 return 0; 29 }
運行結果:工具
代碼分析:post
在代碼第10行初始化了信號量初值爲1,在父進程中睡眠1秒,表示讓子進程先行,子進程wait_sem會成功,可是子進程sem_wait以後,信號量的值就變爲0了,到了父進程它再執行sem_wait的時候應該不成功,但從結果上來看,父進程sem_wait也成功了。其實稍微分析一下就能理解,由於fork函數會將進程資源複製一份,此時雖然sem地址值是同樣的,但實際上它們指向的是不一樣的sem。測試
那麼要解決這個問題的思路就很清晰了,讓兩個進程都能找到同一個sem就能夠了。spa
mmap在framebuffer程序中用到過,它能用來將一個文件映射到內存中,經過操做內存達到操做文件的效果,而且它映射出來的地址是進程共享的。簡單介紹一下mmap函數:線程
函數原型 | void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); |
頭文件 | sys/mman.h |
功能 | 虛擬地址映射 |
參數 | [in]:addr:指定映射的起始地址,若是填入NULL,則表示由系統自動分配地址,通常都填入NULL。code [in]:length:映射的地址空間大小。htm [in]:prot:映射區域的權限。blog #PROT_READ:可讀 #PROT_WRITE:可寫 #PRTO_EXEC:可執行 #PRTO_NONE:不能被訪問 [in]:flags: 標誌位參數,經常使用標誌以下(其餘標誌也能夠在man手冊中查到) #MAP_SHARED:與其餘進程共享 #MAP_PRIVATE:不與其餘進程共享 MAP_SHARED與MAP_PRIVATE兩者必選其一。 #MAP_ANONYMOUS:同MAP_ANON,匿名映射,映射區不與文件關聯。 [in]:fd:文件描述符,若是是匿名映射,則該處填入-1。 [in]:offset:文件偏移量,必須是4096的整數倍。 |
返回 | 成功返回映射區地址,失敗返回MAP_FAILED,其值爲((void*)-1),因爲mmap很容易失敗,所以必需要去接收mmap的返回值。 |
mmap映射的地址用完以後要使用munmap釋放,munmap的函數介紹以下:
函數原型 | int munmap(void *addr, size_t length); |
頭文件 | sys/mman.h |
參數 | [in]:addr:映射區首地址,就是mmap返回的那個地址。 [in]:length:映射區長度,就是mmap傳入的長度。 |
返回 | 成功返回0,失敗返回-1 |
我不知道若是不使用munmap釋放會形成什麼後果,我本身寫了測試程序,發現不管用不用munmap釋放,用free命令查看都看不出區別, 而且用valgrind工具也看不出來。
介紹這兩個函數是爲了獲得一塊共享的匿名地址區域。
改後代碼以下:
1 #include <stdio.h> 2 #include <sys/mman.h> 3 #include <string.h> 4 #include <semaphore.h> 5 6 int main(int argc, const char *argv[]) 7 { 8 sem_t *p_sem = NULL; 9 int pid = 0; 10 int ret = 0; 11 12 p_sem = mmap(NULL, /* 由系統分配地址 */ 13 sizeof(sem_t), /* 申請的地址大小 */ 14 PROT_READ | PROT_WRITE, /* 讀寫權限 */ 15 MAP_SHARED | MAP_ANON, /* 進程共享,映射區與文件不關聯 */ 16 -1, /* 文件描述符,匿名映射傳入-1 */ 17 0); /* 偏移量 */ 18 19 printf("p_sem = %p\n", p_sem); 20 21 ret = sem_init(p_sem, 1, 1); /* 進程間使用的信號量,初值爲1 */ 22 printf("ret = %d\n", ret); 23 24 pid = fork(); 25 26 if (pid == 0) { /* 子進程 */ 27 printf("I'm child\n"); 28 printf("child sem_wait...\n"); 29 sem_wait(p_sem); 30 printf("child sem_wait ok\n"); 31 } else if (pid > 0) { /* 父進程 */ 32 sleep(1); 33 printf("I'm parent\n"); 34 printf("parent sem_wait...\n"); 35 sem_wait(p_sem); 36 printf("parent sem_wait ok\n"); 37 } 38 39 return 0; 40 }
執行結果:
此時因爲子進程獲取到了信號量而且沒有釋放,所以父進程將一直阻塞。該程序中mmap映射的區域不會由於fork而被一分爲二,所以p_sem指向的區域實際上是同一個東西。malloc有相似的功能,可是malloc出來的地址空間在fork以後也會變爲兩份。
那麼問題來了,如今我知道了p_sem的地址,我若是另外編寫一個程序去調用sem_post會怎樣呢。設改程序爲程序A,另外一個測試程序稱爲程序B,程序B的代碼以下:
1 #include <stdio.h> 2 #include <semaphore.h> 3 4 int main(int argc, const char *argv[]) 5 { 6 sem_t *p_sem = (sem_t *)0xb7758000; /* 該地址爲p_sem的地址 */ 7 8 sem_post(p_sem); 9 10 return 0; 11 }
注意上面程序傳入的p_sem的值根據另外一個程序打印出來的結果爲準。在程序A的父進程等待信號量的時候執行程序B,程序A能繼續執行下去嗎,程序B的運行結果以下:
段錯誤!
我想若是程序B能運行下去,而且讓程序A成功得到信號量,那纔怪事了,這正是虛擬地址存在的意義啊。這也解釋了無名信號量在進程將的同步應該是父子進程的緣由。
下面附上在父子進程中實現拉鋸操做的代碼:
1 #include <stdio.h> 2 #include <sys/mman.h> 3 #include <string.h> 4 #include <semaphore.h> 5 6 int main(int argc, const char *argv[]) 7 { 8 sem_t *p_sem = NULL; 9 int pid = 0; 10 11 p_sem = mmap(NULL, /* 由系統分配地址 */ 12 2 * sizeof(sem_t), /* 申請的地址大小 */ 13 PROT_READ | PROT_WRITE, /* 讀寫權限 */ 14 MAP_SHARED | MAP_ANON, /* 進程共享,映射區與文件不關聯 */ 15 -1, /* 文件描述符,匿名映射傳入-1 */ 16 0); /* 偏移量 */ 17 18 sem_init(&p_sem[0], 1, 0); /* 進程間使用的信號量,初值爲0 */ 19 sem_init(&p_sem[1], 1, 1); /* 進程間使用的信號量,初值爲1 */ 20 21 pid = fork(); 22 23 if (pid == 0) { /* 子進程 */ 24 while (1) { 25 sem_wait(&p_sem[0]); 26 printf("I'm child\n"); 27 sem_post(&p_sem[1]); 28 } 29 } else if (pid > 0) { /* 父進程 */ 30 while (1) { 31 sem_wait(&p_sem[1]); 32 printf("I'm parent\n"); 33 sem_post(&p_sem[0]); 34 } 35 } 36 37 return 0; 38 }
執行結果爲交替打印「I'm parent」和「I'm child」,若是地18和第19行sem_init第二個參數傳入0,那麼結果會是隻打印一遍「I'm parent」和「I'm child」。