1. 程式人生 > >無名訊號量——相關程序間同步

無名訊號量——相關程序間同步

1、概述

  無名訊號量可以在相關程序間進行同步,所謂相關程序暫時先簡單的理解為父子程序,最後再詳細的解釋一下。在上一篇部落格 無名訊號量——執行緒間同步 https://www.cnblogs.com/Suzkfly/p/14336610.html中已經介紹過訊號量相關的各個函式,其中sem_init第二個引數,如果傳入的值是非0值,就表示使用的訊號量是在程序間共享。

  按照這個理解,先來寫一個簡單的範例程式:

 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 }

執行結果:

 

 

 程式碼分析:

  在程式碼第10行初始化了訊號量初值為1,在父程序中睡眠1秒,表示讓子程序先行,子程序wait_sem會成功,但是子程序sem_wait之後,訊號量的值就變為0了,到了父程序它再執行sem_wait的時候應該不成功,但從結果上來看,父程序sem_wait也成功了。其實稍微分析一下就能理解,因為fork函式會將程序資源複製一份,此時雖然sem地址值是一樣的,但實際上它們指向的是不同的sem。

  那麼要解決這個問題的思路就很清晰了,讓兩個程序都能找到同一個sem就可以了。

2、 mmap與munmap函式

  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。

[in]:length:對映的地址空間大小。

[in]:prot:對映區域的許可權。

      #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工具也看不出來。

  介紹這兩個函式是為了得到一塊共享的匿名地址區域。

3、程式碼修改

  改後代碼如下:

 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