轉自:http://www.perfgeeks.com/?p=723
通過strace統計系統調用的時候,經常可以看到mmap()與mmap2()。系統調用mmap()可以將某檔案對應至記憶體(進程空間),如此可以把對檔案的操作轉為對記憶體的操作,以此避免更多的lseek()與read()、write()操作,這點對於大檔案或者頻繁訪問的檔案而言尤其受益。但有一點必須清楚:mmap的addr與offset必須對齊一個記憶體頁面大小的邊界,即記憶體映射往往是頁面大小的整數倍,否則maaped_file_size%page_size記憶體空間將被閑置浪費。
示範一下,將檔案/tmp/file_mmap中的字元轉成大寫,分別使用mmap與read/write二種方法實現。
/** @file: t_mmap.c*/#include <stdio.h>#include <ctype.h>#include <sys/mman.h> /*mmap munmap*/#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h> int main(int argc, char *argv[]){ int fd; char *buf; off_t len; struct stat sb; char *fname = "/tmp/file_mmap"; fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); return 1; } if (fstat(fd, &sb) == -1) { perror("fstat"); return 1; } buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { perror("mmap"); return 1; } if (close(fd) == -1) { perror("close"); return 1; } for (len = 0; len < sb.st_size; ++len) { buf[len] = toupper(buf[len]); /*putchar(buf[len]);*/ } if (munmap(buf, sb.st_size) == -1) { perror("munmap"); return 1; } return 0;}#gcc –o t_mmap t_mmap.c#strace ./t_mmapopen("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即檔案大小18mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap檔案fd=3close(3) = 0 //close檔案fd=3munmap(0xb7867000, 18) = 0 //munmap,移除0xb7867000這裡的記憶體映射
雖然沒有看到read/write寫檔案操作,但此時檔案/tmp/file_mmap中的內容已由www.perfgeeks.com改變成了WWW.PERFGEEKS.COM .這裡mmap的addr是0(NULL),offset是18,並不是一個記憶體頁的整數倍,即有4078bytes(4kb-18)記憶體空間被閑置浪費了。
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h> int main(int argc, char *argv[]){ int fd, len; char *buf; char *fname = "/tmp/file_mmap"; ssize_t ret; struct stat sb; fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { perror("open"); return 1; } if (fstat(fd, &sb) == -1) { perror("stat"); return 1; } buf = malloc(sb.st_size); if (buf == NULL) { perror("malloc"); return 1; } ret = read(fd, buf, sb.st_size); for (len = 0; len < sb.st_size; ++len) { buf[len] = toupper(buf[len]); /*putchar(buf[len]);*/ } lseek(fd, 0, SEEK_SET); ret = write(fd, buf, sb.st_size); if (ret == -1) { perror("error"); return 1; } if (close(fd) == -1) { perror("close"); return 1;}free(buf); return 0;}#gcc –o t_rw t_rw.copen("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 其中檔案大小18brk(0) = 0x9845000 //brk, 返回當前中斷點brk(0x9866000) = 0x9866000 //malloc分配記憶體,堆當前最後地址read(3, "www.perfgeeks.com\n", 18) = 18 //readlseek(3, 0, SEEK_SET) = 0 //lseekwrite(3, "WWW.PERFGEEKS.COM\n", 18) = 18 //writeclose(3) = 0 //close
這裡通過read()讀取檔案內容,toupper()後,調用write()寫迴文件。因為檔案太小,體現不出read()/write()的缺點:頻繁訪問大檔案,需要多個lseek()來確定位置。每次編輯read()/write(),在實體記憶體中的雙份資料。當然,不可以忽略建立與維護mmap()資料結構的成本。需要注意:並沒有具體測試mmap vs read/write,即不能一語斷言誰孰誰劣,具體應用情境具體評測分析。你只是要記住:mmap記憶體對應檔之後,操作記憶體即是操作檔案,可以省去不少系統核心調用(lseek, read, write)。 mmap() vs malloc()
使用strace調試的時候,通常可以看到通過mmap()建立匿名記憶體映射的身影。比如啟用dl(‘apc.so’)的時候,就可以看到如下語句。
mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 //30M
通常使用mmap()進行匿名記憶體映射,以此來擷取記憶體,滿足一些特別需求。所謂匿名記憶體映射,是指mmap()的時候,設定了一個特殊的標誌MAP_ANONYMOUS,且fd可以忽略(-1)。某些作業系統(像FreeBSD),不支援標誌MAP_ANONYMOUS,可以映射至裝置檔案/dev/zero來實現匿名記憶體映射。使用mmap()分配記憶體的好處是頁面已經填滿了0,而malloc()分配記憶體後,並沒有初始化,需要通過memset()初始化這塊記憶體。另外,malloc()分配記憶體的時候,可能調用brk(),也可能調用mmap2()。即分配一塊小型記憶體(小於或等於128kb),malloc()會調用brk()調高斷點,分配的記憶體在堆地區,當分配一塊大型記憶體(大於128kb),malloc()會調用mmap2()分配一塊記憶體,與堆無關,在堆之外。同樣的,free()記憶體映射方式分配的記憶體之後,記憶體馬上會被系統收回,free()堆中的一塊記憶體,並不會馬上被系統回收,glibc會保留它以供下一次malloc()使用。
這裡示範一下malloc()使用brk()和mmap2()。
/** file:t_malloc.c*/#include <stdio.h>#include <string.h>#include <stdlib.h> int main(int argc, char *argv){ char *brk_mm, *mmap_mm; printf("-----------------------\n"); brk_mm = (char *)malloc(100); memset(brk_mm, '\0', 100); mmap_mm = (char *)malloc(500 * 1024); memset(mmap_mm, '\0', 500*1024); free(brk_mm); free(mmap_mm); printf("-----------------------\n"); return 1;} #gcc –o t_malloc t_malloc.c#strace ./t_mallocwrite(1, "-----------------------\n", 24-----------------------) = 24brk(0) = 0x85ee000brk(0x860f000) = 0x860f000 //malloc(100)mmap2(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0