前言
最近在學習GNU/Linux核心,看到mmap的時候書上說:
mmap/munmap接口函數是使用者最常用的兩個系統調用接口,無論是在使用者程式中配置設定記憶體、讀寫大檔案、連結動态庫檔案,還是多程序間共享記憶體,都可以看到mmap/munmap的身影。
這句話說的很正确,雖然我們日常沒有直接使用mmap,但是其實我們都間接地使用了mmap/mumap函數。
舉個例子,我們使用動态連結庫的時候,我們都知道,動态連結不會把動态庫中的代碼整合到目标檔案中,相反,動态庫跟目标檔案獨立。
那為什麼運作時,程式能夠獲得指定的符号連結?這正是mmap的力量,程式運作時,他将儲存在實體記憶體的動态庫的内容(如果實體記憶體中沒有,則先加載如記憶體)映射到自身的程序位址空間,這樣符号所對應的指令資料便存在了。
(通過ldd可擷取所依賴的動态庫,Linux怎麼擷取到的,我懷疑跟ELF檔案格式有關,待解答)
mmap
mmap是記憶體映射檔案的方法
mmap将一個檔案或者其它對象映射進記憶體。mmap在使用者空間映射調用系統中作用很大。
mmap()必須以PAGE_SIZE為機關進行映射,而記憶體也隻能以頁為機關進行映射,若要映射非PAGE_SIZE整數倍的位址範圍,要先進行記憶體對齊,強行以PAGE_SIZE的倍數大小進行映射。
頭檔案
函數原型
void* mmap(void* addr, size_t length,int prot,int flags,int fd,off_t offset); int munmap(void* start,size_t length);
- addr: 用于指定映射到程序位址的起始位址,為了應用程式的可移植性,一般設為nullptr;
- length: 表示映射到程序位址空間的大小;
- prot: 表示設定記憶體映射區域的讀寫屬性等;
- flags: 用于設定記憶體映射的屬性,例如共享映射、私有映射、匿名映射等;
- fd: 檔案句柄,如果不是檔案映射,則置為0;
- offset: 檔案映射時的檔案偏移量。
重複一遍:由于GNU/Linux中,記憶體配置設定是以頁為機關的,是以length長度不足1頁(預設4KB),則按1頁來處理。
prot參數detail:
flags參數detail:
共享記憶體
共享記憶體Shared Memory,顧名思義就是允許兩個不相關的程序通路同一個邏輯記憶體,共享記憶體是兩個正在運作的程序之間共享和傳遞資料的一種非常有效的方式。
具體來說,共享記憶體就是一段真實存在的實體記憶體,不同程序通過通路和修改該段實體記憶體,最終達到共享記憶體的目的。
PS: 共享記憶體不提供資料同步機制,如一個程序在寫過程中,另一個程序可以進行讀操作和寫操作,是以需要通過信号量來解決同步問題。
建立共享記憶體
函數介紹
函數原型如下:
#include #include /* For mode constants */#include /* For O_* constants */int shm_open(const char *name, int oflag, mode_t mode);int shm_unlink(const char *name);Link with -lrt.
shm_open()建立并打開一個新的或現有的POSIX共享記憶體對象。POSIX共享記憶體對象實際上是一個句柄,它可以被與mmap共享記憶體相同區域的無關程序使用。
shm_unlink()函數的作用是:删除先前由shm_open()建立的對象。
函數用法
int fd = shm_open("/shm01", O_CREAT | O_RDWR, 0777);
當oflag使用O_CREAT時,mode表示建立檔案時的權限,權限寫法跟指令chmod一樣(https://www.runoob.com/linux/linux-comm-chmod.html)。
open與shm_open差別
或許你會問:如果是建立一個檔案何必使用shm_open呢?為什麼不使用open?
理論上來說兩個方法可以互相替換。
但是,很重要的一點是:shm_open會将檔案建立在/dev/shm目錄下,該目錄下挂載的檔案系統格式是tmpfs,該目錄下的檔案也隻存儲在記憶體(主存)中。如果你使用open方法在該目錄下建立和打開檔案其實兩者就沒有本質差別了。
如圖所示,/dev/shm是tmpfs檔案系統
程序間通訊執行個體
本測試用例需要依賴googletest測試架構,當然讀者可以通過分别建立兩個項目來實作下述執行個體;
執行個體原理
如圖所示,共享記憶體建立後儲存在真實的實體記憶體中,通過mmap函數我們将該實體記憶體位址映射到程序位址空間中,最終實作操作共享記憶體的功能。
執行個體功能
服務端:建立共享記憶體,并将記憶體映射到程序位址空間中,然後進行定時修改記憶體内容。
用戶端:映射共享記憶體到程序位址空間中,定時擷取記憶體内容。
執行個體代碼
服務端代碼
#include #include #include #include TEST(shm_test, server) { int fd = shm_open("/shm01", O_CREAT | O_RDWR, 0777); // int fd = open("/dev/shm/shm01", O_CREAT | O_RDWR); if (fd < 0) { std::cout << "error to create or open" << std::endl; return; } std::cout << "create or open ok" << std::endl; ftruncate(fd, 4096);// 配置設定4KB記憶體 char *ptr = (char *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); char a[] = "00"; for (char k = 0; k < 26; ++k) { memset(a, 'a' + k, 1 *sizeof(char)); strcpy(ptr, a); sleep(1); } strcpy(ptr, a); mumap((void *)ptr, 4096); // 關閉映射 exit(0);}
用戶端代碼
#include #include #include #include TEST(test, client) { int fd = shm_open("/shm01", O_RDWR, 0777); // int fd = open("/dev/shm/shm01", O_CREAT | O_RDWR); if (fd < 0) { std::cout << "error to open" << std::endl; return; } char *ptr = (char *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); while(true) { std::cout << ptr << std::endl; sleep(1); } exit(0);}
執行結果
開頭的z是之前儲存在共享記憶體中的資料
可以看到用戶端輸出了英語字母。
遇到的問題
- 找不到shm_open的符号連結
答:在編譯時連結rt庫
- 使用googletest時,缺失pthread
答:連結pthread庫
總結
本篇文章,我們知道了共享記憶體,通過共享記憶體和mmap我們能很輕易地完成程序間通訊(IPC Inter-Process Communication),程序間的semaphore(信号量)實作方式也是通過mmap和共享變量實作的。
文章出處:https://my.oschina.net/StupidZhe/blog/4647178