C/C++ 處理序間通訊 記憶體共用

來源:互聯網
上載者:User

標籤:邏輯地址   system   傳遞   process   地址傳遞   原型   返回   types   net   

介紹記憶體共用前,說下之前的誤區,覺得,可以用指標來在父子進程中傳遞資料,其實,在fork()後,父子進程的地址空間是相互獨立的!所以在父子進程間傳遞指標是沒有意義的。

這裡就涉及到物理地址和邏輯地址(或稱虛擬位址)的概念。

從邏輯地址到物理地址的映射稱為地址重新導向。分為:

靜態重新導向--在程式裝入主存時已經完成了邏輯地址到物理地址和變換,在程式執行期間不會再發生改變。

動態重新導向--程式執行期間完成,其實現依賴於硬體地址變換機構,如基底位址暫存器。

邏輯地址:CPU所產生的地址。CPU產生的邏輯地址被分為 :p (頁號) 它包含每個頁在實體記憶體中的基址,用來作為頁表的索引;d (頁位移),同基址相結合,用來確定送入記憶體裝置的實體記憶體地址。

物理地址:記憶體單元所看到的地址。

使用者程式看不見真正的物理地址。使用者只產生邏輯地址,且認為進程的地址空間為0到max。物理位址範圍從R+0到R+max,R為基地址,地址映射-將程式地址空間中使用的邏輯地址變換成記憶體中的物理地址的過程。由記憶體管理單元(MMU)來完成。

fork()會產生一個和父進程完全相同的子進程,但子進程在此後多會exec系統調用,出於效率考慮,linux中引入了“寫時複製“技術,也就是只有進程空間的各段的內容要發生變化時,才會將父進程的內容複寫一份給子進程。在fork之後exec之前兩個進程用的是相同的物理空間(記憶體區),子進程的程式碼片段、資料區段、堆棧都是指向父進程的物理空間,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。當父子進程中有更改相應段的行為發生時,再為子進程相應的段分配物理空間,如果不是因為exec,核心會給子進程的資料區段、堆棧段分配相應的物理空間(至此兩者有各自的進程空間,互不影響),而程式碼片段繼續共用父進程的物理空間(兩者的代碼完全相同)。而如果是因為exec,由於兩者執行的代碼不同,子進程的程式碼片段也會分配單獨的物理空間。

 

fork時子進程獲得父進程資料空間、堆和棧的複製,所以變數的地址(當然是虛擬位址)也是一樣的。

每個進程都有自己的虛擬位址空間,不同進程的相同的虛擬位址顯然可以對應不同的物理地址。因此地址相同(虛擬位址)而值不同沒什麼奇怪。
具體過程是這樣的:
fork子進程完全複製父進程的棧空間,也複製了頁表,但沒有複製物理頁面,所以這時虛擬位址相同,物理地址也相同,但是會把父子共用的頁面標記為“唯讀”(類似mmap的private的方式),如果父子進程一直對這個頁面是同一個頁面,知道其中任何一個進程要對共用的頁面“寫操作”,這時核心會複製一個物理頁面給這個進程使用,同時修改頁表。而把原來的唯讀頁面標記為“可寫”,留給另外一個進程使用。

這就是所謂的“寫時複製”。正因為fork採用了這種寫時複製的機制,所以fork出來子進程之後,父子進程哪個先調度呢?核心一般會先調度子進程,因為很多情況下子進程是要馬上執行exec,會清空棧、堆。。這些和父進程共用的空間,載入新的程式碼片段。。。,這就避免了“寫時複製”拷貝共用頁面的機會。如果父進程先調度很可能寫共用頁面,會產生“寫時複製”的無用功。所以,一般是子進程先調度滴。

假定父進程malloc的指標指向0x12345678, fork 後,子進程中的指標也是指向0x12345678,但是這兩個地址都是虛擬記憶體地址 (virtual memory),經過記憶體位址轉換後所對應的 物理地址是不一樣的。所以兩個進城中的這兩個地址相互之間沒有任何關係。

 

(注1:在理解時,你可以認為fork後,這兩個相同的虛擬位址指向的是不同的物理地址,這樣方便理解父子進程之間的獨立性)
(注2:但實際上,Linux為了提高 fork 的效率,採用了 copy-on-write 技術,fork後,這兩個虛擬位址實際上指向相同的物理地址(記憶體頁),只有任何一個進程試圖修改這個虛擬位址裡的內容前,兩個虛擬位址才會指向不同的物理地址(新的物理地址的內容從原物理地址中複製得到))

參考:http://blog.csdn.net/xy010902100449/article/details/44851453

           http://blog.csdn.net/gatieme/article/details/51005811

在 linux 系統中,每個進程的虛擬記憶體是被分為許多頁面的。這些記憶體頁面中包含了實際的資料。每個進程都會維護一個從記憶體位址到虛擬記憶體頁面之間的映射關係。儘管每個進程都有自己的記憶體位址,不同的進程可以同時將同一個記憶體頁面映射到自己的地址空間中,從而達到共用記憶體的目的。

分配一個新的共用記憶體塊會建立新的記憶體頁面。因為所有進程都希望共用對同一塊記憶體的訪問,只應由一個進程建立一塊新的共用記憶體。再次分配一塊已經存在的記憶體塊不會建立新的頁面,而只是會返回一個標識該記憶體塊的標識符。

一個進程如需使用這個共用記憶體塊,則首先需要將它綁定到自己的地址空間中。

這樣會建立一個從進程本身虛擬位址到共用頁面的映射關係。當對共用記憶體的使用結束之後,這個映射關係將被刪除。

當再也沒有進程需要使用這個共用記憶體塊的時候,必須有一個(且只能是一個)進程負責釋放這個被共用的記憶體頁面。

所有共用記憶體塊的大小都必須是系統頁面大小的整數倍。系統頁面大小指的是系統中單個記憶體頁麵包含的位元組數。在 Linux 系統中,記憶體頁面大小是4KB,不過您仍然應該通過調用 getpagesize 擷取這個值。

共用記憶體的實現分為兩個步驟:

  • 建立共用記憶體,使用shmget函數。

  • 映射共用記憶體,將這段建立的共用記憶體映射到具體的進程空間去,使用shmat函數。

用於共用記憶體的函數

共用記憶體的使用,主要有以下幾個API:ftok()shmget()shmat()shmdt()及shmctl()。

#include <sys/shm.h>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這兩個標頭檔。

用ftok()函數獲得一個ID號

應用說明,在IPC中,我們經常用用key_t的值來建立或者開啟訊號量,共用記憶體和訊息佇列。

key_t ftok(const char *pathname, int proj_id);
  • 1
  • 1
參數 描述
pathname 一定要在系統中存在並且進程能夠訪問的
proj_id 一個1-255之間的一個整數值,典型的值是一個ASCII值。

當成功執行的時候,一個key_t值將會被返回,否則-1被返回。我們可以使用strerror(errno)來確定具體的錯誤資訊。

考慮到應用系統可能在不同的主機上應用,可以直接定義一個key,而不用ftok獲得:

#define IPCKEY 0x344378

 

建立共用記憶體

進程通過調用shmget(Shared Memory GET,擷取共用記憶體)來分配一個共用記憶體塊。

int shmget(key_t key ,int size,int shmflg)
參數 描述
key 一個用來標識共用記憶體塊的索引值
size 指定了所申請的記憶體塊的大小
shmflg 操作共用記憶體的標識

傳回值:如果成功,返回共用記憶體表示符,如果失敗,返回-1。

  • 該函數的第二個參數key是一個用來標識共用記憶體塊的索引值。

彼此無關的進程可以通過指定同一個鍵以擷取對同一個共用記憶體塊的訪問。不幸的是,其它程式也可能挑選了同樣的特定值作為自己分配共用記憶體的索引值,從而產生衝突。

用特殊常量IPC_PRIVATE作為索引值可以保證系統建立一個全新的共用記憶體塊。|

key標識共用記憶體的索引值:0/IPC_PRIVATE。當key的取值為IPC_PRIVATE,則函數shmget將建立一塊新的共用記憶體;如果key的取值為0,而參數中又設定了IPC_PRIVATE這個標誌,則同樣會建立一塊新的共用記憶體。

  • 該函數的第二個參數size指定了所申請的記憶體塊的大小。

因為這些記憶體塊是以頁面為單位進行分配的,實際分配的記憶體塊大小將被擴大到頁面大小的整數倍。

  • 第三個參數shmflg是一組標誌,通過特定常量的按位或操作來shmget。這些特定常量包括:

IPC_CREAT:這個標誌表示應建立一個新的共用記憶體塊。通過指定這個標誌,我們可以建立一個具有指定索引值的新共用記憶體塊。

IPC_EXCL:這個標誌只能與 IPC_CREAT 同時使用。當指定這個標誌的時候,如果已有一個具有這個索引值的共用記憶體塊存在,則shmget會調用失敗。也就是說,這個標誌將使線程獲得一個“專屬”的共用記憶體塊。如果沒有指定這個標誌而系統中存在一個具有相同索引值的共用記憶體塊,shmget會返回這個已經建立的共用記憶體塊,而不是重新建立一個。

模式標誌:這個值由9個位組成,分別表示屬主、屬組和其它使用者對該記憶體塊的存取權限。

其中表示執行許可權的位將被忽略。指明存取權限的一個簡單辦法是利用

映射共用記憶體

shmat()是用來允許本進程訪問一塊共用記憶體的函數,將這個記憶體區映射到本進程的虛擬位址空間。

int shmat(int shmid,char *shmaddr,int flag)
參數 描述
shmid 那塊共用記憶體的ID,是shmget函數返回的共用儲存標識符
shmaddr 是共用記憶體的起始地址,如果shmaddr為0,核心會把共用記憶體映像到調用進程的地址空間中選定位置;如果shmaddr不為0,核心會把共用記憶體映像到shmaddr指定的位置。所以一般把shmaddr設為0。
shmflag 是本進程對該記憶體的操作模式。如果是SHM_RDONLY的話,就是唯讀模式。其它的是讀寫入模式

成功時,這個函數返回共用記憶體的起始地址。失敗時返回-1。

要讓一個進程擷取對一塊共用記憶體的訪問,這個進程必須先調用 shmat(SHared Memory Attach,綁定到共用記憶體)。

將 shmget 返回的共用記憶體標識符 SHMID 傳遞給這個函數作為第一個參數。

該函數的第二個參數是一個指標,指向您希望用於映射該共用記憶體塊的進程記憶體位址;如果您指定NULL則Linux會自動選擇一個合適的地址用於映射。第三個參數是一個標誌位,包含了以下選項:

SHM_RND表示第二個參數指定的地址應被向下靠攏到記憶體頁面大小的整數倍。如果您不指定這個標誌,您將不得不在調用shmat的時候手工將共用記憶體塊的大小按頁面大小對齊。 
SHM_RDONLY表示這個記憶體塊將僅允許讀取操作而禁止寫入。 如果這個函數調用成功則會返回綁定的共用記憶體塊對應的地址。通過 fork 函數建立的子進程同時繼承這些共用記憶體塊;

如果需要,它們可以主動脫離這些共用記憶體塊。 當一個進程不再使用一個共用記憶體塊的時候

共用記憶體解除映射

當一個進程不再需要共用記憶體時,需要把它從進程地址空間中多裡。

int shmdt(char *shmaddr)
參數 描述
shmaddr 那塊共用記憶體的起始地址

成功時返回0。失敗時返回-1。

應通過調用 shmdt(Shared Memory Detach,脫離共用記憶體塊)函數與該共用記憶體塊脫離。將由 shmat 函數返回的地址傳遞給這個函數。如果當釋放這個記憶體塊的進程是最後一個使用該記憶體塊的進程,則這個記憶體塊將被刪除。對 exit 或任何exec族函數的調用都會自動使進程脫離共用記憶體塊。

控制釋放

shmctl控制對這塊共用記憶體的使用

函數原型

int  shmctl( int shmid , int cmd , struct shmid_ds *buf );
參數 描述
shmid 是共用記憶體的ID。
cmd 控制命令
buf 一個結構體指標。IPC_STAT的時候,取得的狀態放在這個結構體中。如果要改變共用記憶體的狀態,用這個結構體指定。

其中cmd的取值如下

cmd 描述
IPC_STAT 得到共用記憶體的狀態
IPC_SET 改變共用記憶體的狀態
IPC_RMID 刪除共用記憶體

傳回值: 成功:0 失敗:-1

調用 shmctl(”Shared Memory Control”,控制共用記憶體)函數會返回一個共用記憶體塊的相關資訊。同時 shmctl 允許程式修改這些資訊。

該函數的第一個參數是一個共用記憶體塊標識。 
要擷取一個共用記憶體塊的相關資訊,則為該函數傳遞 IPC_STAT 作為第二個參數,同時傳遞一個指向一個 struct shmid_ds 對象的指標作為第三個參數。

要刪除一個共用記憶體塊,則應將 IPC_RMID 作為第二個參數,而將 NULL 作為第三個參數。當最後一個綁定該共用記憶體塊的進程與其脫離時,該共用記憶體塊將被刪除。

您應當在結束使用每個共用記憶體塊的時候都使用 shmctl 進行釋放,以防止超過系統所允許的共用記憶體塊的總數限制。調用 exit 和 exec 會使進程脫離共用記憶體塊,但不會刪除這個記憶體塊。 要查看其它有關共用記憶體塊的操作的描述,請參考shmctl函數的手冊頁。

樣本簡易對應一塊共用記憶體
#include <stdio.h>#include <stdlib.h>#include <sys/ipc.h>#include <sys/shm.h>#include <string.h>#define IPCKEY 0x366378typedef struct st_setting{    char agen[10];    unsigned char file_no;}st_setting;int main(int argc, char** argv){    int         shm_id;    //key_t       key;    st_setting  *p_setting;    //  首先檢查共用記憶體是否存在,存在則先刪除    shm_id = shmget(IPCKEY , 1028, 0640);    if(shm_id != -1)    {        p_setting = (st_setting *)shmat(shm_id, NULL, 0);        if (p_setting != (void *)-1)        {            shmdt(p_setting);            shmctl(shm_id,IPC_RMID,0) ;        }    }    //  建立共用記憶體    shm_id = shmget(IPCKEY, 1028, 0640 | IPC_CREAT | IPC_EXCL);    if(shm_id == -1)    {        printf("shmget error\n");        return -1;    }    //  將這塊共用記憶體區附加到自己的記憶體段    p_setting = (st_setting *)shmat(shm_id, NULL, 0);    strncpy(p_setting->agen, "gatieme", 10);    printf("agen : %s\n", p_setting->agen);    p_setting->file_no = 1;    printf("file_no : %d\n",p_setting->file_no);    system("ipcs -m");//  此時可看到有進程關聯到共用記憶體的資訊,nattch為1    //  將這塊共用記憶體區從自己的記憶體段刪除出去    if(shmdt(p_setting) == -1)       perror(" detach error ");    system("ipcs -m");//  此時可看到有進程關聯到共用記憶體的資訊,nattch為0    //  刪除共用記憶體    if (shmctl( shm_id , IPC_RMID , NULL ) == -1)    {        perror(" delete error ");    }    system("ipcs -m");//  此時可看到有進程關聯到共用記憶體的資訊,nattch為0    return EXIT_SUCCESS;}

C/C++ 處理序間通訊 記憶體共用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.