1. 程式人生 > >linux程序間/執行緒間通訊(《unix網路程式設計-程序間通訊》讀書筆記)

linux程序間/執行緒間通訊(《unix網路程式設計-程序間通訊》讀書筆記)

linux程序間/執行緒間通訊

linux下的程序通訊手段基本上是從Unix平臺上的程序通訊手段繼承而來的。而對Unix發展做出重大貢獻的兩大主力AT&T的貝爾實驗室及BSD(加州大學伯克利分校的伯克利軟體釋出中心)在程序間通訊方面的側重點有所不同。前者對Unix早期的程序間通訊手段進行了系統的改進和擴充,形成了“system V IPC”,通訊程序侷限在單個計算機內;後者則跳過了該限制,形成了基於套介面(socket)的程序間通訊機制。Linux則把兩者繼承了下來,如圖示

Posix,全稱“Portable operating System Interface”,可移植作業系統介面。 是IEEE開發的一套編碼標準, 它已經是ISO/IEC採納的國際標準。

一般System V IPC,都需要 ftok() 使用路徑名生成key值, 而Posix IPC 直接使用路徑名,且Posix IPC介面函式裡面都有 "_" 連線符, 例如: mq_open/sem_open() 等。

linux下程序間通訊的幾種主要手段簡介:

  1. 管道(Pipe)及有名管道(named pipe):管道可用於具有親緣關係程序間的通訊,管道只能承載無格式位元組流。
  2. 有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係程序間的通訊;
  3. 訊號(Signal):訊號是比較複雜的通訊方式,用於通知接受程序有某種事件發生,除了用於程序間通訊外,程序還可以傳送訊號給程序本身;linux除了支援Unix早期訊號語義函式sigal外,還支援語義符合Posix.1標準的訊號函式sigaction(實際上,該函式是基於BSD的,BSD為了實現可靠訊號機制,又能夠統一對外介面,用sigaction函式重新實現了signal函式);
  4. 報文(Message)佇列(訊息佇列):訊息佇列是訊息的連結表,包括Posix訊息佇列system V訊息佇列。有足夠許可權的程序可以向佇列中新增訊息,被賦予讀許可權的程序則可以讀走佇列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺點。
  5. 訊號量(semaphore):主要作為程序間以及同一程序不同執行緒之間的同步手段。
  6. 共享記憶體:使得多個程序可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針對其他通訊機制執行效率較低而設計的。往往與其它通訊機制,如訊號量結合使用,來達到程序間的同步及互斥。
  7. 套介面(Socket):更為一般的程序間通訊機制,可用於不同機器之間的程序間通訊。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支援套接字。


1.管道

管道的主要侷限性正體現在它的特點上:

    只支援單向資料流;
    只能用於具有親緣關係的程序之間;
    沒有名字;
    管道的緩衝區是有限的(管道制存在於記憶體中,在管道建立時,為緩衝區分配一個頁面大小);
    管道所傳送的是無格式位元組流,這就要求管道的讀出方和寫入方必須事先約定好資料的格式,比如多少位元組算作一個訊息(或命令、或記錄)等等;

介面:
    pipe()/ fork()
    

2. 有名管道

    FIFO可以說是管道的推廣,克服了管道無名字的限制,使得無親緣關係的程序同樣可以採用先進先出的通訊機制進行通訊。
    
FIFO的開啟規則:
如果當前開啟操作是為讀而開啟FIFO時,若已經有相應程序為寫而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程序為寫而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,成功返回(當前開啟操作沒有設定阻塞標誌)。
如果當前開啟操作是為寫而開啟FIFO時,如果已經有相應程序為讀而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程序為讀而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,返回ENXIO錯誤(當前開啟操作沒有設定阻塞標誌)。

注意點:
    不管寫開啟的阻塞標誌是否設定,在請求寫入的位元組數大於4096時,都不保證寫入的原子性。但二者有本質區別:
對於阻塞寫來說,寫操作在寫滿FIFO的空閒區域後,會一直等待,直到寫完所有資料為止,請求寫入的資料最終都會寫入FIFO;

而非阻塞寫則在寫滿FIFO的空閒區域後,就返回(實際寫入的位元組數),所以有些資料最終不能夠寫入。



介面:
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char * pathname, mode_t mode)              //pathname 是一個普通的unix路徑名,例如 /home/sundh/111.txt; 不同於,posix介面要求的路徑名裡面不能出現兩次“/” 符號。

int open(const char *path, int oflag, ...  );   //oflag 模式是BLOCK的, 如果顯示的加上O_NONBLOCK,則不會出現open()阻塞等待另外一個執行緒呼叫open()函式。

int close()

int unlink(const char *path);    //對FIFO檔案進行刪除

區別:

有名管道是作為一個特殊的裝置檔案存在於磁碟當中,而管道存在於記憶體當中,通訊結束後,有名管道的檔案本身依然存在(除非呼叫unlink()函式對fifo檔案進行刪除),但是管道已經釋放了。

有名管道與檔案也是有區別的,檔案的話,當讀取其中的內容之後,資訊依然存在,但是有名管道中,通訊結束之後,資訊就會丟失

3. 訊號

kill -l 可以看到系統支援哪些訊號。


訊號是在軟體層次上對中斷機制的一種模擬,在原理上,一個程序收到一個訊號與處理器收到一箇中斷請求可以說是一樣的。
訊號是非同步的,一個程序不必通過任何操作來等待訊號的到達,事實上,程序也不知道訊號到底什麼時候到達。
訊號是程序間通訊機制中唯一的非同步通訊機制,可以看作是非同步通知,通知接收訊號的程序有哪些事情發生了。

訊號來源:
訊號事件的發生有兩個來源:硬體來源(比如我們按下了鍵盤或者其它硬體故障);
軟體來源,最常用傳送訊號的系統函式是kill, raise, about, alarm和setitimer以及sigqueue函式,軟體來源還包括一些非法運算等操作。

訊號值小於SIGRTMIN的訊號都是不可靠資訊,可能會丟失(不支援排隊,即不允許佇列中有多個相同的資訊,例如如果程序佇列中有SIGINT,再來一個SIGINT訊息,就不會插入到佇列中了);
訊號值位於SIGRTMIN和SIGRTMAX之間的訊號都是可靠訊號,它們支援排隊,且允許佇列中有多個相同的資訊。

函式介面:
 Linux在支援新版本的訊號安裝函式sigation()以及訊號傳送函式sigqueue()的同時,仍然支援早期的signal()訊號安裝函式,支援訊號傳送函式kill()。
 最常用傳送訊號的系統函式是kill, raise, about, alarm和setitimer以及sigqueue函式,軟體來源還包括一些非法運算等操作。
 

4. 訊息佇列

4.1 System V

訊息佇列就是一個訊息的連結串列。可以把訊息看作一個記錄,具有特定的格式以及特定的優先順序。對訊息佇列有寫許可權的程序可以向中按照一定的規則新增新訊息;

對訊息佇列有讀許可權的程序則可以從訊息佇列中讀走訊息。訊息佇列是隨核心持續的。

函式介面:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj);    //convert a pathname and a project identifier to a System V IPC key. 如果pathname所指的檔案不存在,則放回-1. msgget(-1, IPC_CREAT) 這個函式會隨機返回一個唯一值

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)        //將建立一個新的訊息佇列或取得一個已有訊號量
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);  //讀取一個訊息,並把訊息儲存在msgp指向的msgbuf結構中。
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);     //向msgid代表的訊息佇列傳送一個訊息,即將傳送的訊息儲存在msgp指向的msgbuf結構中,訊息的大小由msgze指定。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);         //該系統呼叫對由msqid標識的訊息佇列執行cmd操作,共有三種cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。

 比較:
 訊息佇列與管道以及有名管道相比,具有更大的靈活性,首先,它提供有格式位元組流,有利於減少開發人員的工作量;其次,訊息具有型別,在實際應用中,可作為優先順序使用。這兩點是管道以及有名管道所不能比的。

 同樣,訊息佇列可以在幾個程序間複用,而不管這幾個程序是否具有親緣關係,這一點與有名管道很相似;但訊息佇列是隨核心持續的,與有名管道(隨程序持續)相比,生命力更強,應用空間更大。

4.2 posix 訊息佇列

mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

//name 字串格式有固定的要求 “/somename". 必須以/開始,somename裡面不允許再有/符號。

//POSIX 訊息佇列生命期是核心生命期的;如果沒有使用mq_unlink(3) 刪除的話,一個訊息佇列會一直存在,直到系統關閉。

//建立的訊息佇列在一個虛擬的檔案系統裡,我們可以把掛載這個虛擬的訊息佇列檔案系統在 Linux 系統中。 這樣就可以看到const char *name 資料夾。

# mkdir /dev/mqueue

# mount -t mqueue none /dev/mqueue

mq_open.c 程式碼如下:

  1 #include <unistd.h>
  2 #include <fcntl.h>           /* For O_* constants */
  3 #include <sys/stat.h>        /* For mode constants */
  4 #include <mqueue.h>
  5 #include <stdio.h>
  6 #include <errno.h>
  7
  8 #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
  9
 10 int main(int argc, char**argv)
 11 {
 12         int c, flags;
 13         mqd_t mqd;
 14
 15         printf("*************argv[0]:%s, %s\n", argv[0], argv[1]);
 16         flags = O_RDWR|O_CREAT;
 17         while( (c=getopt(argc, argv, "e"))!= -1)
 18         {
 19         if(c =='e')
 20         {
 21         printf("have option e \n");
 22         flags = flags|O_EXCL;
 23         break;
 24         }
 25         break;
 26         }
 27
 28         mqd = mq_open(argv[optind], flags, FILE_MODE, NULL);
 29         printf("*************argv[optind]:%s, %d\n", argv[optind], optind);
 30         if(mqd == -1)
 31                 printf("create error \n");
 32         printf(" %d\n",  errno);
 33         perror("result:");
 34         mq_close(mqd);
 35         return 0;
 36
 37 }

執行這個可執行檔案 ./mq_open /test, 執行 ls /dev/mqueue就可以在看到 test檔案。

[email protected]:~/temp$ ls /dev/mqueue
test
[email protected]:~/temp$ cat /dev/mqueue/test
QSIZE:0          NOTIFY:0     SIGNO:0     NOTIFY_PID:0

int mq_close(mqd_t mqdes);

int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

int mq_unlink(const char *name);

5. 訊號量

5.1 Posix

Posix 訊號量分為兩種: 有名訊號量和基於記憶體的訊號量(也就是無名訊號量)。

先看有名訊號量:

sem_t *sem_open(const char *name, int oflag); ://creates a new POSIX semaphore or opens an existingsemaphore. 
建立一個有名的訊號量,它可以用於執行緒也可以用於程序間的同步。name變數不同於ftok (char*pathname, …)中的pathname, 只要兩個程序的name字串是一致且只有第一個字元是/,不能出現兩個/字元,就可以使用這個訊號量進行同步。
 (對於POSIX訊號量和共享記憶體的name引數,會在/dev/shm下建立對應的路徑名。 可參考 http://blog.csdn.net/anonymalias/article/details/9938865)

sem_close() : 只是關閉訊號量,並未從系統中刪除

sem_unlink(): 刪除該訊號量

sem_close() 和 sem_unlink()並不是一定要先呼叫sem_close(),然後呼叫sem_unlink(), 沒有這樣的呼叫要求.  可以先sem_unlink()從系統核心中刪除該訊號量的名字,既從/dev/shm 中刪除該訊號量對應的檔案, 然後還能呼叫sem_post() 和sem_wait()進行訊號量操作,最後呼叫 sem_close().   可參考 後面第6章的例子1。

《unix網路程式設計-程序間通訊》這樣解釋sem_close() 和 sem_unlink():



sem_close()不是強制要求呼叫的,程序退出時,如果有訊號量被這個程序開啟,沒有被關閉,退出時會自動關閉該訊號量。

sem_wait()/sem_trywait():當所指定的訊號量的值為0時,後者並不將呼叫者投入睡眠,而是立刻返回EAGAIN,即重試。

按照功能來分有兩種, 二進位制訊號量和記數訊號量。 

//process 1
#include <iostream>
#include <cstring>
#include <errno.h>


#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>


using namespace std;


#define SHM_NAME "/memmap"
#define SHM_NAME_SEM "/memmap_sem" 


char sharedMem[10];


int main()
{
    int fd;
    sem_t *sem;


    fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
    sem = sem_open(SHM_NAME_SEM, O_CREAT, 0666, 0);


    if (fd < 0 || sem == SEM_FAILED)
    {
        cout<<"shm_open or sem_open failed...";
        cout<<strerror(errno)<<endl;
        return -1;
    }


    ftruncate(fd, sizeof(sharedMem));


    char *memPtr;
    memPtr = (char *)mmap(NULL, sizeof(sharedMem), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);


    char msg[] = "yuki...";


    memmove(memPtr, msg, sizeof(msg));
    cout<<"process:"<<getpid()<<" send:"<<memPtr<<endl;


    sem_post(sem);
    sem_close(sem);


    return 0;
}


//process 2
#include <iostream>
#include <cstring>
#include <errno.h>


#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>


using namespace std;


#define SHM_NAME "/memmap"
#define SHM_NAME_SEM "/memmap_sem" 


int main()
{
    int fd;
    sem_t *sem;


    fd = shm_open(SHM_NAME, O_RDWR, 0);
    sem = sem_open(SHM_NAME_SEM, 0);


    if (fd < 0 || sem == SEM_FAILED)
    {
        cout<<"shm_open or sem_open failed...";
        cout<<strerror(errno)<<endl;
        return -1;
    }


    struct stat fileStat;
    fstat(fd, &fileStat);


    char *memPtr;
    memPtr = (char *)mmap(NULL, fileStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);


    sem_wait(sem);


    cout<<"process:"<<getpid()<<" recv:"<<memPtr<<endl;


    sem_close(sem);


    return 0;
}

程式執行結果:

# ./send 
process:13719 send:yuki...
# ./recv 
process:13720 recv:yuki...

對於POSIX訊號量和共享記憶體的名字會在/dev/shm下建立對應的路徑名,例如上面的測試程式碼,會生成如下的路徑名:

# ll /dev/shm/
total 8
-rw-r--r-- 1 root root 10 Aug 13 00:28 memmap
-rw-r--r-- 1 root root 32 Aug 13 00:28 sem.memmap_sem

5.1.1 有名訊號量

sem_open() 用於建立一個新的有名訊號量或者開啟一個已經存在的有名訊號量,它可用於執行緒間同步或者程序間同步。 有名訊號量是由核心維護的。不需要程式分配訊號量的記憶體空間。

一般主執行緒或者主程序呼叫 sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value) 來建立一個新的訊號量, mode_t mode 設定為O_CREAT。另外一個執行緒或者經常使用sem_t *sem_open(const char *name, int oflag)來開啟一個已經存在的訊號量。

5.1.2 無名訊號量

即基於記憶體的訊號量,隨程序結束也釋放。 它是有程序分配訊號量的記憶體空間,然後呼叫sem_init初始化。

sem_t temp;    //如果這個temp是分配在共享記憶體區,則可以用於程序間通訊;否則只能用於執行緒間通訊。

int sem_init(sem_t *sem, int pshared, unsigned int value); //pshared為0, 執行緒間通訊; 不為0時,sem_t temp必須在共享記憶體分配記憶體空間。

int sem_destroy(sem_t *sem);

很少有人會使用無名訊號量用於程序間通訊,所以也就不細說了。 用的較多的方法是先宣告一個結構體:

 struct shareMemory {    sem_t  sem;    char *buffer[MAX]};   在共享記憶體區上面分配一個這樣大小的記憶體,然後呼叫 sem_init(&shareMemory.sem, 1, 1).  這裡需要注意的是sem_init()只能被呼叫一次。對一個已經初始化的訊號量呼叫sem_init(),其結果是未定義的。  也就是說 主執行緒或者主程序呼叫 sem_init()後,另外的執行緒或者程序不允許再次呼叫sem_init(),這個另外的執行緒或者程序可以直接使用 shareMemory.sem進行同步。



如果pshared 的值為0,並且sem_t 這個訊號量是對所有執行緒都可見,例如:宣告為全域性變數,或者用new/malloc 分配的堆記憶體,或者訊號量是區域性變數,通過需要通過pthread_create(pthread_t* thread, pthread_attr_t, void *(*Func), void *arg)函式中的 void* arg形參在建立新執行緒的時候傳入,這時這個訊號量就可以執行緒間同步了。

  1. /* 
  2.  * simple_sem_app.c 
  3.  */
  4. #include "all.h"
  5. /* 每個字元輸出的間隔時間 */
  6. #define TEN_MILLION 5000000L
  7. #define BUFSIZE 1024
  8. void *threadout(void *args);  
  9. int main(int argc, char *argv[])  
  10. {  
  11.     int error;  
  12.        int i;  
  13.        int n;  
  14.     sem_t semlock;  
  15.        pthread_t *tids;  
  16.        if (argc != 2) {  
  17.            fprintf (stderr, "Usage: %s numthreads\n", argv[0]);  
  18.               return 1;  
  19.        }    
  20.        n = atoi(argv[1]);  
  21.        tids = (pthread_t *)calloc(n, sizeof(pthread_t));  
  22.        if (tids == NULL) {  
  23.            perror("Failed to allocate memory for thread IDs");  
  24.            return 1;  
  25.        }    
  26.        if (sem_init(&semlock, 0, 1) == -1) {  
  27.            perror("Failed to initialize semaphore");  
  28.            return 1;  
  29.        }    
  30.        for (i = 0; i < n; i++) {  
  31.            if (error = pthread_create(tids + i, NULL, threadout, &semlock)) {  
  32.                fprintf(stderr, "Failed to create thread:%s\n", strerror(error));  
  33.                   return 1;  
  34.           }  
  35.     }  
  36.        for (i = 0; i < n; i++) {  
  37.            if (error = pthread_join(tids[i], NULL)) {  
  38.                fprintf(stderr, "Failed to join thread:%s\n", strerror(error));  
  39.                  return 1;  
  40.               }  
  41.     }  
  42.     return 0;  
  43. }  
  44. void *threadout(void *args)  
  45. {  
  46.     char buffer[BUFSIZE];  
  47.        char *c;  
  48.        sem_t *semlockp;  
  49.        struct timespec sleeptime;  
  50.        semlockp = (sem_t *)args;  
  51.        sleeptime.tv_sec = 0;  
  52.        sleeptime.tv_nsec = TEN_MILLION;  
  53.        snprintf(buffer, BUFSIZE, "This is thread from process %ld\n",  
  54.                (long)getpid());  
  55.        c = buffer;  
  56.        /****************** entry section *******************************/
  57.        while (sem_wait(semlockp) == -1)  
  58.            if(errno != EINTR) {  
  59.                fprintf(stderr, "Thread failed to lock semaphore\n");  
  60.                  return NULL;  
  61.               }  
  62.        /****************** start of critical section *******************/
  63.        while (*c != '\0') {  
  64.               fputc(*c, stderr);  
  65.               c++;  
  66.               nanosleep(&sleeptime, NULL);  
  67.        }  
  68.        /****************** exit section ********************************/
  69.        if (sem_post(semlockp) == -1)  
  70.               fprintf(stderr, "Thread failed to unlock semaphore\n");  
  71.        /****************** remainder section ***************************/
  72.        return NULL;  

5.1.3二進位制訊號量

可用於互斥目的,就像互斥量一樣。它除了像互斥量那樣使用外,還有一個互斥量沒有的特性:互斥量必須總是由鎖住它的執行緒解鎖,二進位制訊號量的sem_wait() 和 sem_post()可處於不同的執行緒或者程序。


條件變數 和 訊號量的區別:

1. 條件變數一般用於執行緒間的同步; 訊號量一般用於程序間同步, 當然訊號量也可以用於執行緒間同步。  條件變數也可以用於程序間同步,不過這種用法使用的比較少,有特定要求,就是要在共享記憶體上面分配條件變數及其關聯的mutex互斥量的記憶體,不推薦這條件變數用於程序間同步。

2. 訊號量執行 sem_post()函式後,這個訊號量的值總是 >0 ,它會一直等到 使用者呼叫sem_wait() 來把訊號量的值減1.   但條件變數不是這樣,如果執行pthread_cond_signal()之前,沒有執行緒執行 pthread_cond_wait(), 那這個signal會被丟失。

5.2 System v

這種訊號量機制用的比較少,已經被淘汰了。
    
函式介面(Sytem V):
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok (char*pathname, char proj);//convert a pathname and a project identifier to a System V IPC key, 如果pathname所指的檔案不存在,則放回-1. semget(-1, IPC_CREAT) 這個函式會隨機返回一個唯一值

int semget(key_t key, int num_sems, int sem_flags); 

//semget()建立一個新訊號量集或取得一個已有訊號量集, 第一個引數key是整數值(唯一非零),不相關的程序可以通過它訪問一個訊號量。 key的值可以為IPC_PRIVATE,這時這個訊號量集用於執行緒間同步。  這個函式返回的是訊號量識別符號, semop() 和 semctl()能使用它。訊號量識別符號是int型,指向一個結構體地址,圖如下:

struct semid_ds{} 是一個訊號量集結構體,sem_base是一個指標,指向一組訊號量。  sem_nsems 標識訊號量集中有多少個訊號量。

semget() 函式中形參 int num_sems指定集合中訊號量個數。 如果我們不建立一個新的訊號量集,而只是訪問一個已存在的集合,那就可以把該引數指定為0,也可指定為已存在集合的訊號量個數. 

semget() 僅僅只是建立一個訊號量集,不進行初始化。 使用semctl(semid, 0, SETVAL, arg) 或者semctl(semid, 0,SETALL, arg) 進行初始化。

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);     //改變訊號量的值。
int semctl(int semid,int semnum,int cmd,union semun arg);        //該函式用來直接控制訊號量資訊

注意:
a.) semget()使用時,有一個特殊的訊號量key值,IPC_PRIVATE(通常為0),其作用是建立一個只有建立程序可以訪問的訊號量,可以線上程間使用,不能在程序間使用。
(某些Linux系統上,手冊頁將IPC_PRIVATE並沒有阻止其他的程序訪問訊號量作為一個bug列出。)
b.)  在semget() 建立一個訊號量後,需要使用 semctl( SETVAL) 來初始化這個訊號量。

6. 共享記憶體

共享記憶體可以說是最有用的程序間通訊方式,也是最快的IPC形式,不再涉及核心。兩個不同程序A、B共享記憶體的意思是,同一塊實體記憶體被對映到程序A、B各自的程序地址空間。程序A可以即時看到程序B對共享記憶體中資料的更新,反之亦然。由於多個程序共享同一塊記憶體區域,必然需要某種同步機制,互斥鎖和訊號量都可以。   

Linux的2.2.x核心支援多種共享記憶體方式,

                 - mmap()系統呼叫

                 - Posix共享記憶體

                 - 系統V共享記憶體

6.1  mmap()系統呼叫
    mmap()函式把一個檔案或者posix共享記憶體區物件對映到呼叫程序的地址空間。使用mmap函式的主要目的是:
        (1)對普通檔案提供記憶體對映I/O,可以提供無親緣程序間的通訊;
        (2)提供匿名記憶體對映,以供親緣程序間進行通訊。
        (3)對shm_open建立的POSIX共享記憶體區物件程序記憶體對映,以供無親緣程序間進行通訊。

(題外話: 一個12G的檔案,電腦記憶體只有4G,該如何讀取這個檔案的data?  理想情況可以這樣: 

int fd=open("data.txt", O_RDONLY); ptr = mmap(NULL, 4G,PORT_READ, MAP_SHARED, fd, 0); //對映檔案0~4G資料到記憶體

ptr = mmap(NULL, 4G,PORT_READ, MAP_SHARED, fd, 4G); //對映檔案4G~8G資料到記憶體

ptr = mmap(NULL, 4G,PORT_READ, MAP_SHARED, fd, 8G); //對映檔案8G~12G資料到記憶體)
        
函式:
    void *mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);
引數fd為即將對映到程序空間的檔案描述字,一般由open()返回. 

flags 可以為 MAP_SHARE 或 MAP_PRIVATE, 如果指定為MAP_PRIVATE,那麼呼叫程序對對映資料所作的修改只對該程序可見,而不改變其底層支撐物件(支撐物件要麼是普通檔案,要麼是匿名記憶體對映,要麼是shm_open建立的共享記憶體區)。 通俗的理解就是,如果共享物件是一個檔案,那該程序往這個檔案寫資料,別的程序都看不到,相當於執行緒間共享資料。 除此以外,flags為MAP_PRIVATE時,munmap()刪除共享記憶體時,呼叫程序對這個共享記憶體所做的修改的都會被丟棄掉。

如果flags為MAP_SHARE,則呼叫程序對共享記憶體區的修改對所有程序都可見。如果flags為MAP_ANONYMOUS ,它表示匿名對映,對映區不與任何檔案關聯。

同時,fd可以指定為-1,此時須指定flags引數中的MAP_SHARED|MAP_ANONYMOUS,表明進行的是匿名對映(不涉及具體的檔名,避免了檔案的建立及開啟,很顯然只能用於具有親緣關係的程序間通訊)。由於父子程序特殊的親緣關係,在父程序中先呼叫mmap(),然後呼叫 fork()。那麼在呼叫fork()之後,子程序繼承父程序匿名對映後的地址空間,同樣也繼承mmap()返回的地址,這樣,父子程序就可以通過對映區 域進行通訊了。
    
    int munmap(void *start, size_t len);  
    int msync(void *start, size_t len, int flags);     //如果我們修改了處於記憶體共享區中某個位置的內容,那麼核心將在稍後的某個時刻更新相應的檔案,如果我們希望資料立刻同步更新到檔案,則呼叫這個函式。

並不是所有的檔案都能進行記憶體對映。 例如一個訪問終端或者套接字的描述符是不能進行mmap對映的,它們只能使用read和write來訪問。
舉例:

int fd=open("/home/sunny/1.log",O_CREAT|O_RDWR|O_TRUNC,00777);
char * memPtr = (char *)mmap(NULL, sizeof(sharedMem), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
或者: char * p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
       MAP_SHARED|MAP_ANONYMOUS,-1,0); 
例子1:

6.2 Posix共享記憶體

POSIX共享記憶體建立在mmap函式之上,我們首先指定待開啟共享記憶體區的POSIXIPC名字來呼叫shm_open(),取得一個描述符後使用mmap函式把它對映到當前程序的記憶體空間!POSIX共享記憶體使用方法有以下兩個步驟:
    -  通過shm_open建立或開啟一個POSIX共享記憶體物件;
    -  然後呼叫mmap將它對映到當前程序的地址空間;

函式:
    int shm_open(const char *name, int oflag, mode_t mode);   //shm_open用於建立一個新的共享記憶體區物件或開啟一個已經存在的共享記憶體區物件。 這裡的name 和

sem_t *sem_open(const char *name, int oflag);中的 name 是一樣的。 只要兩個程序中的字串一樣,不管裡面的值,例如:"/tmp/log.txt" (不管是否真實的存在這個檔案),
這兩個程序就能共享。 因為系統會在 /dev/shm下建立對應的路徑名

    int shm_unlink(const char *name);

    int ftruncate(int fd, off_t length); // 修改 fd指向的普通檔案或者共享記憶體區物件的大小。

    int fstat(int fd, struct stat *bug);   //獲取fd該物件的資訊

//process 1
#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

using namespace std;

#define SHM_NAME "/memmap"