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 *類型的-1,即(void *)-1 )。

 

參數說明:

addr : 建立內存映射區的首地址,如果指定addr爲NULL,那麼將由Linux內核自動指定一個合適的首地址。

length: 創建映射區的大小,以字節爲單位(建議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");
        }

        //拓展文件大小1024
        lseek(fd , 1024, SEEK_SET);
        write(fd , "0" , 1);

        //指向文件開頭
        lseek(fd , 0 , SEEK_SET);

        //創建共享映射區,可讀寫
        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:

[root@localhost memory]# cat test.txt 
hello world
[root@localhost memory]# 

 

指定映射區爲MAP_PRIVATE,程序執行結果:

指定MAP_PRIVATE創建私有映射區,對映射區所做的修改操作不會反映到物理磁盤上的文件。

 

指定映射區爲MAP_SHARED:

指定MAP_SHARED創建共享映射區,對映射區所做的修改操作會反映到物理磁盤上的文件。

 

6. munmap函數使用細節

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

2. 當一個進程終止或調用了exec系列函數後,進程中所有的映射關係將自動解除。

3. 爲確保映射區的數據寫入物理磁盤上的文件中,在調用munmap解除映射前需要調用msync函數。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章