linux內存管理——mmap函數詳解

linux內存管理——mmap函數詳解

來自專欄 我是程序員

mmap函數是unix/linux下的系統調用。

當存在客戶-服務程序中複製文件時候,其數據流如下,要經歷四次數據複製,開銷很大。

如果採用共享內存的方式,那麼將大大優化IO操作,數據流變成了如下,數據只複製兩次:

映射文件或設備到內存中,取消映射就是munmap函數。

語法如下:

void *mmap(void *addr, size_t length, int prot, int flags,

int fd, off_t offset);

int munmap(void *addr, size_t length);

該函數主要用途有三個:

l? ? 將普通文件映射到內存中,通常在需要對文件進行頻繁讀寫時使用,用內存讀寫取代I/O讀寫,以獲得較高的性能;

l? ? 將特殊文件進行匿名內存映射,為關聯進程提供共享內存空間;

l? ? 為無關聯的進程間的Posix共享內存(SystemV的共享內存操作是shmget/shmat)

我們來看下函數的入參選擇:

??? 參數addr:

指向欲映射的內存起始地址,通常設為 NULL,代表讓系統自動選定地址,映射成功後返回該地址。

??? 參數length:

代表將文件中多大的部分映射到內存。

??? 參數prot:

映射區域的保護方式。可以為以下幾種方式的組合:

PROT_EXEC 映射區域可被執行

PROT_READ 映射區域可被讀取

PROT_WRITE 映射區域可被寫入

PROT_NONE 映射區域不能存取

??? 參數flags:

影響映射區域的各種特性。在調用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。

MAP_FIXED 如果參數start所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此。

MAP_SHARED對映射區域的寫入數據會複製迴文件內,而且允許其他映射該文件的進程共享。

MAP_PRIVATE 對映射區域的寫入操作會產生一個映射文件的複製,即私人的「寫入時複製」(copy on write)對此區域作的任何修改都不會寫回原來的文件內容。

MAP_ANONYMOUS建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。

MAP_DENYWRITE只允許對映射區域的寫入操作,其他對文件直接寫入的操作將會被拒絕。

MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。

??? 參數fd:

要映射到內存中的文件描述符。如果使用匿名內存映射時,即flags中設置了MAP_ANONYMOUS,fd設為-1。

??? 參數offset:

文件映射的偏移量,通常設置為0,代表從文件最前方開始對應,offset必須是分頁大小的整數倍。如下圖內存映射文件的示例。

1.1.1.1 共享映射

修改共享內存中的文件內容:

#include <sys/mman.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <error.h>

int

main (int argc, char **argv)

{

int fd, nread, i;

struct stat sb;

char *mapped;

if ( argc <= 1 )

{

printf("%s: Need file path!
",argv[0]);

exit(-1);

}

/* 打開文件 */

if ((fd = open (argv[1], O_RDWR)) < 0)

{

perror ("open");

}

/* 獲取文件的屬性 */

if ((fstat (fd, &sb)) == -1)

{

perror ("fstat");

}

/* 將文件映射至進程的地址空間 */

if ((mapped = (char *) mmap (NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) -1)

{

perror ("mmap");

}

/* 映射完後, 關閉文件也可以操縱內存 */

close (fd);

printf ("%s", mapped);

/* 修改一個字元,同步到磁碟文件 */

mapped[0] = 0;

if ((msync ((void *) mapped, sb.st_size, MS_SYNC)) == -1)

{

perror ("msync");

}

/* 釋放存儲映射區 */

if ((munmap ((void *) mapped, sb.st_size)) == -1)

{

perror ("munmap");

}

return 0;

}

編譯後,即可執行,參數為當前目錄的一個文件名字。

例如在當前目錄下創建一個hello.txt,存入hello單詞,然後執行./a.out hello.txt,完畢後發現文件中的第一個字母h變成了0。程序將文件映射到了內存中,並將第一個字元進行了修改並同步到了磁碟文件hello.txt中。

1.1.1.2 父子進程通信

使用fork創建進程,父子進程分別往共享內存中寫入各自字元串,並讀出。

#include <sys/mman.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#define BUF_SIZE 100

int

main (int argc, char **argv)

{

char *p_map;

/* 匿名映射,創建一塊內存供父子進程通信 */

p_map = (char *) mmap (NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

if (fork () == 0)

{

sleep (1);

printf ("child got a message: %s
", p_map);

sprintf (p_map, "%s", "from u son");

munmap (p_map, BUF_SIZE); //實際上,進程終止時,會自動解除映射。

exit (0);

}

sprintf (p_map, "%s", "from u father");

sleep (2);

printf ("parent got a message: %s
", p_map);

return 0;

}

1.1.1.3 內存訪問溢出

linux採用的是頁式管理機制,使用mmap()映射普通文件後,進程會在自己的地址空間新增一塊空間,空間大小由mmap()的len參數指定。但是,進程並不一定能夠對全部新增空間都能進行有效訪問。進程能夠訪問的有效地址大小取決於文件被映射部分的大小。決定進程能訪問的大小是容納文件被映射部分的最小頁面數。如下圖。

#include <sys/mman.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

int

main (int argc, char **argv)

{

int fd, i;

int pagesize, offset;

char *p_map;

struct stat sb;

/* 取得page size */

pagesize = sysconf (_SC_PAGESIZE);

printf ("pagesize is %d
", pagesize);

/* 打開文件 */

fd = open (argv[1], O_RDWR, 00777);

fstat (fd, &sb);

printf ("file size is %zd
", (size_t) sb.st_size);

offset = 0;

p_map = (char *) mmap (NULL, pagesize * 2, PROT_READ | PROT_WRITE,

MAP_SHARED, fd, offset);

close (fd);

// p_map[sb.st_size] = 9; /* 導致匯流排錯誤 */

p_map[pagesize+1] = 9; /* 導致段錯誤 */

munmap (p_map, pagesize * 2);

return 0;

}

參考:

《UNIX網路編程卷2》

原文鏈接

更多技術乾貨敬請關注云棲社區知乎機構號:阿里云云棲社區 - 知乎


推薦閱讀:

掌握了這四個價值,用戶運營不再難!
華為麒麟960如何成就性能王
線上抓娃娃會是曇花一現的風口?
15年如一日,他要用「產業新城+智慧升級」持續發展縣域經濟,有三大核心!
用戶體驗詞條-35.文獻法分析

TAG:互聯網 | Linux | 函數 |