1. 程式人生 > >43-初窺記憶體對映mmap

43-初窺記憶體對映mmap

1. 檔案通訊

如果你已經完成了程序間通訊的學習,相信你對程序間通訊有了一個初步的解了。由於程序之間是相互獨立的,不能相互訪問,程序之間想要通訊必須藉助核心空間,因為核心空間對於程序之間是共享的。

像這樣的程序間通訊的方式有很多種,例如管道、訊號、共享記憶體、訊息佇列、套接字、命名管道等,在linux早期是使用檔案來完成程序間通訊的。

對於檔案通訊,本質上也是通過核心空間來完成的。首先系統核心會建立一個核心緩衝區,通過把檔案對映到核心緩衝區裡,然後程序將資料寫入到緩衝區,另一程序從緩衝區將資料讀走,完成程序間通訊。

接下來要講的記憶體對映和檔案通訊有很多相似之處。

2. mmap函式建立記憶體對映

記憶體對映是讓一個磁碟檔案與程序空間中的一塊虛擬記憶體區域對映,完成對映後,程序就可以通過在記憶體對映區中讀寫操作來訪問檔案中的資料,換句話說,你可以理解為對這塊記憶體的讀寫資料等同於讀寫檔案。

mmap函式就是用於完成記憶體對映的

<sys/mman.h>
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset); 

返回值說明:成功返回建立的對映區void *型別的首地址;失敗返回MAP_FAILED巨集(這個巨集其實就是個void *型別的01,即(void *)-1 )。

引數說明:

addr : 建立記憶體對映區的首地址,如果指定addr為NULL,那麼將由Linux核心自動指定一個合適的首地址。

length: 建立對映區的大小,以位元組為單位。

prot: 記憶體對映區的保護許可權,可以多個選項組合使用,例如PORT_READ | PORT_WRITE表示可讀寫。

                     值                                  描述
              PROT_EXEC                記憶體對映區域可以被執行
              PROT_READ                記憶體對映區域可以被讀取
             PROT_WRITE                 記憶體對映區域可以被寫入
             PROT_NONE                  記憶體對映區域不可訪問

flags:用於控制記憶體對映區操作的選項。

如果flags = MAP_PRIVATE,建立一個私有的對映區,在對映區所做的修改操作不會反映到物理磁碟的檔案上,對使用同一對映區的其他程序不可見。

如果flags = MAP_SHARED,建立一個共享的對映區,在對映區所做的修改操作會直接反映到物理磁碟的檔案上,那麼這一修改操作對其他程序都可見。

fd:用來建立記憶體對映區的檔案描述符

offset:從檔案的哪個位置開始對映,如果offset為0表示從檔案頭開始對映,注意offset必須是記憶體頁4k的整數倍,資料型別為off_t,在32位系統下為long int型別,64為linux系統為long long int型別。

3. mmap函式對映過程

                                                   圖1-記憶體對映檔案過程(圖片來自Linux/UNIX系統程式設計手冊)

length是對映到程序地址空間的位元組數,offset表示檔案的對映位置從檔案頭到第offset個位元組開始,通常offset設定為0表示從檔案頭開始對映,當對映成功,mmap將會返回對映區域的首地址。 

4. munmap函式

mmap函式是用於解除記憶體對映,即從程序的虛擬地址空間刪除對映。

#include <sys/mman.h>
int munmap(void *addr, size_t length);	

返回值說明:成功返回0,失敗返回-1

引數addr:由mmap返回的記憶體對映區首地址

引數length:對映區大小

5. 記憶體對映示例程式

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <errno.h>

int main(void){

        char *addr;
        int len = 0;
        int ret;

        int fd = open("test.txt", O_RDWR|O_CREAT, 0644);
        if (fd < 0){
                perror("open error");
        }


        //建立共享對映區,可讀寫
        addr =(char *)mmap(NULL , 1024 , PROT_WRITE|PROT_READ , MAP_SHARED , fd , 0);

        //建立私有對映區,可讀寫
        //addr = mmap(NULL, 1024, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);

        if (addr == MAP_FAILED){
                perror("mmap err: ");
        }

        //關閉檔案與檔案描述符間的關聯
        close(fd);

        //實際操作是通過mmap返回的檔案指標去操作檔案讀寫,不需要用到檔案描述符
        strcpy(addr, "AAAAABBBBB");
        printf("%s\n", addr);

        //解除對映
        ret = munmap(addr , 1024);
        if(ret < 0){
                perror("munmap error: ");
        }

        return 0;
}

在test檔案寫入hello world:

[[email protected] memory]# cat test.txt 
hello world
[[email protected] memory]# 

指定對映區為MAP_PRIVATE,程式執行結果:

指定MAP_PRIVATE建立私有對映區,對對映區所做的修改操作不會反映到物理磁碟上的檔案。

指定對映區為MAP_SHARED:

指定MAP_SHARED建立共享對映區,對對映區所做的修改操作會反映到物理磁碟上的檔案。

6. munmap函式使用細節

1. 如果引數addr和length指定的區域不存在對映關係,那麼呼叫munmap函式將不會發生任何事情並返回0(表示成功)。

2. 當一個程序終止或呼叫了exec系列函式後,程序中所有的對映關係將自動解除。

3. 為確保對映區的資料寫入物理磁碟上的檔案中,在呼叫munmap解除對映前需要呼叫msync函式。

4. munmap函式可以解除對映區中的部分割槽域的對映,一旦這樣做的話可能會使原來的對映區變小或分成兩個對映區,強烈建議不要這麼做。