進程間通信之共享內存

共享內存
共享內存是第二種IPC工具。他允許兩個無關的進程訪問相同的邏輯內存。共享內存是在兩個運行的程序之間傳遞數據的有效手段。儘管X/Open標準並沒有要求,很可能絕大數的共享內存實現都是會將不同進程之間正在共享的內存安排在相同的物理內存中。
共享內存爲在多個進程之間共享與傳遞數據提供一個有效的手段。因爲他並沒有提供同步的方法,所以通常我們需要使用其他的機制來同步對共享內存的訪問。通常,我們也許會使用共享內存來提供對大塊內存區的有效訪問,並且傳遞少量的消息來同步對此內存的訪問。
共享內存是由IPC爲一個進程所創建並且出現在這個進程的地址空間中的一段特殊的地址序列。其他的進程可以將同樣的共享內存段關聯到他們自己的地址空間
中。所有的進程都可以訪問這段內存地址,就如同這段內存是由malloc所分配的。如果一個進程寫入共享內存,這些改變立即就可以爲訪問相同共享內存的其
他進程所見。
就其自身而言,共享內存並沒有提供任何共享方法。並沒有自動的方法來阻止在第一個進程完成寫入共享內存之前第二個進程開始讀取共享內存。同步訪問是程序員的責任。圖14-2顯示共享內存是如何工作的。
每一個箭頭顯示的是每一個進程的邏輯地址空間到可用的物理內存的映射。實際的情形要更爲複雜,因爲可用的內存是由物理內存與交換到磁盤上的內存混合構成的。
用於共享內存的函數如下:
#include 
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);
與信號量相類似,通常需要在包含shm.h文件之前包含sys/types.h與sys/ipc.h這兩個頭文件。
shmget
我們使用shmget函數創建共享內存:
int shmget(key_t key, size_t size, int shmflg);
與信號量相類似,這個函數也提供了key,這可以有效的命名共享內存段,而且shmget函數會返回一個共享內存標識符,這個標識符可以用於後續的共享內
存函數中。還有一個特殊的關鍵值,IPC_PRIVATE,這可以創建進程私有的共享內存。我們通常並不會使用這個值,而且與信號量相類似,我們會發現私
有的共享內存在許多Linux系統上實際上並不是私有的。
第二個參數,size,以字節形式指定了所需要的內存數量。
第三個參數,shmflg,是由9個權限標記所組成的,這些標記的使用與用於創建文件的模型參數相同。IPC_CREAT定義了一個特殊位,必須與權限標
記進行位或操作來創建一個新的共享內存段。設置IPC_CREAT標記並且傳遞一個已經存在的共享內存段並不是錯誤。如果不需要,IPC_CREAT只是
簡單的被忽略掉。
權限標記是十分有用的,因爲這些權限標記可以允許創建共享內存所有者進程可以寫入而其他用戶所創建的進程只能讀取的共享內存。我們可以應用這個特點通過將數據放入共享內存中,從而提供對於只讀數據的有效訪問,而不必擔心數據被其他用戶修改的風險。
如果共享內存成功創建,shmget會返回一個非負整數,共享內存標識符。如果失敗,則會返回-1。
shmat
當我們第一次創建一個共享內存段時,他並不能爲任何進程所訪問。爲了能夠訪問共享內存,我們必須將其與一個進程地址空間關聯到一起。我們可以使用shmat函數來達到這一目的:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一個參數,shm_id,是由shmget函數所返回的共享內存標識符。
第二個參數,shm_addr,是將要關聯到當前進程的共享內存所在的位置。這個參數應總是一個空指針,從而可以允許系統來選擇內存出現的地址。
第三個參數,shmflg,是一個位標記集合。兩個可能的值爲SHM_RND與SHM_RDONLY。前者與shm_addr聯合,控制將被關聯的共享內
存所在的地址;而後者使得關聯的內存只讀。通常很少需要來控制被關聯的內存所在的地址;我們通常應允許系統來爲我們選擇一個地址,否則就會使得程序變得高
度硬件相關。
如果shmat調用成功,他會返回一個指向共享內存第一字節的指針。如果失敗,則會返回-1。
共享內存將會依據所有者(共享內存的創建者),權限與當前進程的所有者而具有讀或寫權限。共享內存上的權限與文件上的權限相類似。
shmfgl & SHM_RDONLY爲真的情況是這個規則的一個例外。此時共享內存並不可寫,儘管權限已經允許了寫訪問。
shmdt
shmdt函數將共享內存與當前進程相分離。他傳遞一個指向由shmat所返回的地址的指針。如果成功,則會返回0;如果失敗,則會返回-1。注意,分離共享內存並不會刪除他;他只是使得內存對於當前進程不可用。
shmctl
共享內存的控制函數要比複雜的信號量控制函數簡單得多:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shmid_ds結構至少具有下列成員:
struct shmid_ds {
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
}
第一個參數,shm_id,是由shmget所返回的標記符。
第二個參數,command,是要執行的動作。他可以有三個值:
命令        描述
IPC_STAT    設置shmid_ds結構中的數據反射與共享內存相關聯的值。
IPC_SET        如果進程有相應的權限,將與共享內存相關聯的值設置爲shmid_ds數據結構中所提供的值。
IPC_RMID    刪除共享內存段。
第三個參數,buf,是一個指向包含共享內存模式與權限的結構的指針。
如果成功,則返回0,如果失敗,則會返回-1。X/Open並沒有說明如果我們嘗試刪除一個已經關聯的共享內存時會發生什麼。通常,一個已經關聯但是被刪
除的共享內存通常會繼續發揮作用,直到他與最後一個進程相分離。然而,因爲這個行爲並沒有被規範,所以最好不要依賴於他。

試驗--共享內存

現在我們已經瞭解了共享內存函數,我們可以編寫一些代碼來使用這些函數。我們將會編寫一對程序,shm1.c與shm2.c。第一個程序(消費者)將會創
建一個共享內存段並且顯示寫入共享內存中的數據。第二個程序(生產者)將會關聯已經存在的共享內存段並且允許我們進入內存段中的數據。
1 首先,我們創建一個通用頭文件來描述我們希望傳遞的共享內存。我們將其命名爲shm_com.h。
#ifndef _SHM_COM_H
#define _SHM_COM_H 1
#define TEXT_SZ 2048
struct shared_use_at
{
    int written_by_you;
    char some_text[TEXT_SZ];
};
#endif
這個文件定義了在消費者程序與生產者程序中都會用到的結構。當數據已經寫入結構的其他部分並且認爲我們需要傳送2k文本時,我們使用一個int標記written_by_you來通知消費者。
2 我們的第一個程序用於消費者。在包含頭文件之後,我們通過調用shmget函數,指定IPC_CREAT位來創建一個共享內存段(我們共享內存結構的大小):
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "shm_com.h"
int main()
{
    int running = 1;
    void *shared_memory = (void *)0;
    struct shared_use_st *shared_stuff;
    int shmid;
    srand((unsigned int) getpid());
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
    if(shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
3 我們現在使用共享內存可以爲程序所訪問:
    shared_memory = shmat(shmid, (void *)0, 0);
    if(shared_memory == (void *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
    printf("Memory attached at %X\n", (int)shared_memroy);
4
程序的接下來部分將shared_memroy段賦給shared_stuff,後者會輸出written_by_you中的任何文本。程序繼續循環直到
written_by_you中的文本爲end。sleep調用會強制消費者停留在其臨界區中,這會使得生產者程序等待。
    shared_stuff = (struct_shared_use_st *)shared_memory;
    shared_stuff->written_by_you = 0;
    while(running)
    {
        if(shared_stuff->written_by_you)
        {
            printf("You wrote: %s", shared_stuff->some_text);
            sleep(rand() % 4);
            shared_stuff->written_by_you = 0;
            if(strncmp(shared_stuff->some_text, "end", 3)==0)
            {
                running = 0;
            }
        }
    }
5 最後共享內存被分離並被刪除:
    if(shmdt(shared_memory)==-1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    if(shmctl(shmid, IPC_RMID, 0)==-1)
    {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}
6 我們的第二個程序,shm2.c,是生產者程序;他允許我們進入消費者的數據。這個程序與shm1.c程序十分相似:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "shm_com.h"
int main()
{
    int runnint = 1;
    void *shared_memory = (void *)0;
    struct shared_use_st *shared_stuff;
    char buffer[BUFSIZ];
    int shmid;
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
    if(shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
    shared_memory = shmat(shmid, (void *)0, 0);
    if(shared_memory == (void *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
    printf("Memory attached at %X\n", (int)shared_memory);
    shared_stuff = (struct shared_use_st *)shared_memory;
    while(running)
    {
        while(shared_stuff->written_by_you == 1)
        {
            sleep(1);
            printf("waiting for client...\n");
        }
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
        shared_stuff->written_by_you = 1;
        if(strncmp(buffer, "end", 3) == 0)
        {
            running = 0;
        }
    }
    if(shmdt(shared_memory) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}
當我們運行這些程序,我們會得到下面的輸出:
$ ./shm1 &
[1] 294
Memory attached at 40017000
$ ./shm2
Memory attached at 40017000
Enter some text: hello
You wrote: hello
waiting for client...
waiting for client...
Enter some text: Linux!
You wrote: Linux!
waiting for client...
waiting for client...
waiting for client...
Enter some text: end
You wrote: end
$

工作原理

第一個程序,shm1,創建共享內存段並其關聯到他的地址空間。我們在共享內存的第一部分揭示了shared_use_st結構。這個結構有一個標
記,written_by_you,當數據可用時會設置這個標記。當設置了這個標記時,程序會讀取文本,輸出文本,並且清除標記來表示程序已經讀取數據
了。我們使用一個特殊的字符串,end,來進行由循環中的退出。程序然後分離共享內存並且刪除他。
第二個程序,shm2,獲得並關聯共享內存段,因爲他使用相同的鍵值,1234。然後他提示用戶輸入一些文本。如果設置了written_by_you標
記,shm2就會知道客戶端程序還沒有讀取前面輸入的數據並且進行等待。當其他進程清除了這個標記,shm2會寫入新的數據並且設置標記。他也使用字符串
end來結束並分離共享內存段。
注意,我們必須提供我們自己的,相當粗糙的同步標記,written_by_you,這會導致一個低效的忙等待。在實際的程序中,我們會傳遞一個消息,或者使用管道,或者使用IPC消息(我們會在稍後討論),生成信息,或是使用信號量來在程序的讀取與寫入部分提供同步。
                
發佈了13 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章