一、內存映射
內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功后,用戶對這段內存區域的修改可以直接反映到內核空間,同樣,內核空間對這段區域的修改也直接反映用戶空間。那么對于內核空間和用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。
以下是一個把普遍文件映射到用戶空間的內存區域的示意圖:
二、mmap函數
1.基本介紹
mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系。
實現這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫臟頁面到對應的文件磁盤上,即完成了對文件的操作而不必再調用read,write等系統調用函數。相反,內核空間對這段區域的修改也直接反映用戶空間,從而可以實現不同進程間的文件共享。如下圖所示:
2.mmap的優勢
通過系統調用mmap ,程序可以高效地訪問文件數據,而無需通過傳統的read
或write
系統調用進行數據的復制,具體來說為:
- 傳統的
read
和write
分為兩步(下面以write為例)- 第一步:用戶態空間緩沖區 → 內核頁緩沖區(其實就是內核文件緩沖區)
- 第二步:內核頁緩沖區 → 磁盤
- 使用
mmap
- 映射階段:內核將文件映射到進程的虛擬地址空間,但物理內存尚未分配(這是延遲申請,當真正訪問階段需要的時候才缺頁中斷分配)
- 修改數據:進程直接修改映射的內存(相當于直接修改內核的頁緩存),無需調用 write,也無用戶態到內核態的數據拷貝
注意:當內核頁緩存修改好了,我們就認為改好了,我們不關心內核緩沖區到磁盤的刷新這一過程(復雜)
三、mmap接口介紹
當我們建立好映射以后,就可以直接對“開辟”的空間進行操作(虛擬地址)
我們對虛擬地址的操作,都是直接修改映射的內存。
1. mmap建立映射
頭文件:<sys/mman.h>
函數原型
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
參數說明
addr
:建議的映射起始地址(通常設為NULL
,由內核自動選擇)length
:映射區域的長度(必須是頁大小(4 KB)
的整數倍,如4096
字節)prot
:映射區的內存保護模式選項(用|
鏈接)PROT_READ
:映射區可讀PROT_WRITE
:映射區可寫PROT_EXEC
:映射區可執行- 注意:映射權限必須
<=
文件打開權限
flags
:映射類型(如MAP_SHARED
或MAP_PRIVATE
)MAP_PRIVATE
:創建?個私有映射。對映射區域的修改不會反映到底層文件中。(即:修改僅對當前進程有效(寫時復制,類似fork
)MAP_SHARED
:創建?個共享映射。對映射區域的修改會反映到底層文件中(即:修改會同步到文件,其他進程可見)MAP_ANONYMOUS
:指定要創建?個匿名內存映射
fd
:文件描述符(匿名映射時設為 -1)offset
:文件偏移量(開始映射的位置相較于0位置處的偏移)(必須是頁大小的整數倍)
返回值
- 成功:返回映射區的起始地址(虛擬地址)
- 失敗:返回
(void*) -1
或者MAP_FAILED
(等效的)
注意
- 映射的要是一個已經打開的文件!
- 文件大小為
0
的文件無法映射,需要先調整文件大小ftruncate(fd, SIZE)
(會把文件的內容全部初始化成\0
)
- 映射的長度如果 > 文件的大小,則可能導致未定義行為
- 因為
mmap
需要讀取文件元數據(如大小):所以,即使你只需要寫入權限,也需要在open
文件的時候賦予讀權限
2.munmap取消映射
函數原型
int munmap(void *addr, size_t length);
參數介紹
addr
:映射空間的起始地址length
:空間長度(大小)
返回值
- 成功:
0
- 錯誤:
-1
(錯誤碼會被設置)
四、mmap的用法
1.寫入映射
#include<iostream>
#include<cstdio>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>#define FILENAME "log.txt"
#define SIZE 1024int main()
{//open fileint fd = open(FILENAME, O_RDWR|O_APPEND|O_CREAT, 0666);if (fd < 0){perror("open");return 1;}// 調整file sizeftruncate(fd, SIZE);//建立映射/*void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);*/char* mmap_addr = (char*)mmap(nullptr, SIZE, PROT_WRITE, MAP_SHARED, fd, 0);if (mmap_addr == MAP_FAILED){perror("mmap");return 2;}//寫入操作for (int c = 'a', i = 0; c <= 'z'; c++, i++){mmap_addr[i] = c;}//取消映射munmap(mmap_addr, SIZE);//關閉文件close(fd);std::cout << "寫入映射完畢" << std::endl;return 0;
}
2.讀取映射
#include<iostream>
#include<cstdio>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>#define FILENAME "log.txt"int main()
{//open fileint fd = open(FILENAME, O_RDONLY);if (fd < 0){perror("open");return 1;}struct stat st; // struct stat 類型的結構體用于記錄文件的屬性fstat(fd, &st);//建立映射char* mmap_addr = (char*)mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);if (mmap_addr == MAP_FAILED){perror("mmap");return 2;}//讀取操作std::cout << mmap_addr << std::endl;//取消映射munmap(mmap_addr, st.st_size);//關閉文件close(fd);std::cout << "讀取映射完畢" << std::endl;return 0;
}
3.簡單模擬實現malloc
在malloc里,對應大塊的內存通常是使用mmap
來分配的,而對應小塊的內存,是用brk
來分配的。
這里要再介紹一個flags
選項:MAP_ANONYMOUS
MAP_ANONYMOUS
:指定要創建?個匿名內存映射。- 當使使用
MAP_ANONYMOUS
標志時,mmap
會分配?段不與任何?件相關聯的內存區域(即這段內存沒有?件作為其后端存儲)。 - 這種類型的映射通常用于需要分配私有內存的場景,例如進程內部的內存分配
下面用mmap
簡單模擬實現一下malloc
:
#include<iostream>
#include<cstdio>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>#define SIZE 1024void* MyMalloc(int size)
{//建立映射(匿名映射)--- 可讀可寫void* mmap_addr = mmap(nullptr, size, PROT_WRITE|PROT_READ, MAP_SHARED|MAP_ANONYMOUS, -1, 0);if (mmap_addr == MAP_FAILED){perror("mmap");exit(EXIT_FAILURE);}return mmap_addr;
}void* MyFree(void* mmap_addr, int size)
{if(munmap(mmap_addr, size) == -1){perror("munmap");exit(EXIT_FAILURE);}
}int main()
{char* ptr = (char*)MyMalloc(SIZE);//寫入for (int c = 'a', i = 0; c <= 'z'; c++, i++){ptr[i] = c;}//讀取std::cout << "寫入后的addr content: " << ptr << std::endl;MyFree(ptr, SIZE);return 0;
}
4.makefile
.PHONY:all
all:write read malloc
write:mmap_write.ccg++ -o $@ $^ -std=c++11
read:mmap_read.ccg++ -o $@ $^ -std=c++11
malloc:mmap_malloc.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f write read malloc