mmap
可以把磁碟檔案的一部分直接映射到內存,這樣檔案中的位置直接就有對應的內存地址,對檔案的讀寫可以直接用指針來做而不需要read
/write
函數。
#include <sys/mman.h> void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off); int munmap(void *addr, size_t len);
該函數各參數的作用圖示如下:
如果addr
參數為NULL
,內核會自己在進程地址空間中選擇合適的地址建立映射。如果addr
不是NULL
,則給內核一個提示,應該從什麼地址開始映射,內核會選擇addr
之上的某個合適的地址開始映射。建立映射後,真正的映射首地址通過返回值可以得到。len
參數是需要映射的那一部分檔案的長度。off
參數是從檔案的什麼位置開始映射,必須是頁大小的整數倍(在32位體系統結構上通常是4K)。filedes
是代表該檔案的描述符。
prot
參數有四種取值:
PROT_EXEC表示映射的這一段可執行,例如映射共享庫
PROT_READ表示映射的這一段可讀
PROT_WRITE表示映射的這一段可寫
PROT_NONE表示映射的這一段不可訪問
flag
參數有很多種取值,這裡只講兩種,其它取值可查看mmap(2)
MAP_SHARED多個進程對同一個檔案的映射是共享的,一個進程對映射的內存做了修改,另一個進程也會看到這種變化。
MAP_PRIVATE多個進程對同一個檔案的映射不是共享的,一個進程對映射的內存做了修改,另一個進程並不會看到這種變化,也不會真的寫到檔案中去。
如果mmap
成功則返回映射首地址,如果出錯則返回常數MAP_FAILED
。當進程終止時,該進程的映射內存會自動解除,也可以調用munmap
解除映射。munmap
成功返回0,出錯返回-1。
下面做一個簡單的實驗。
$ vi hello (編輯該檔案的內容為“hello”) $ od -tx1 -tc hello 0000000 68 65 6c 6c 6f 0a h e l l o \n 0000006
現在用如下程序操作這個檔案(注意,把fd
關掉並不影響該檔案已建立的映射,仍然可以對檔案進行讀寫)。
#include <stdlib.h> #include <sys/mman.h> #include <fcntl.h> int main(void) { int *p; int fd = open("hello", O_RDWR); if (fd < 0) { perror("open hello"); exit(1); } p = mmap(NULL, 6, PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap"); exit(1); } close(fd); p[0] = 0x30313233; munmap(p, 6); return 0; }
然後再查看這個檔案的內容:
$ od -tx1 -tc hello 0000000 33 32 31 30 6f 0a 3 2 1 0 o \n 0000006
請讀者自己分析一下實驗結果。
mmap
函數的底層也是一個系統調用,在執行程序時經常要用到這個系統調用來映射共享庫到該進程的地址空間。例如一個很簡單的hello world程序:
#include <stdio.h> int main(void) { printf("hello world\n"); return 0; }
用strace
命令執行該程序,跟蹤該程序執行過程中用到的所有系統調用的參數及返回值:
$ strace ./a.out execve("./a.out", ["./a.out"], [/* 38 vars */]) = 0 brk(0) = 0x804a000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fca000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=63628, ...}) = 0 mmap2(NULL, 63628, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fba000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260a\1"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0644, st_size=1339816, ...}) = 0 mmap2(NULL, 1349136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7e70000 mmap2(0xb7fb4000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x143) = 0xb7fb4000 mmap2(0xb7fb7000, 9744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fb7000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7e6f000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb7e6f6b0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xb7fb4000, 4096, PROT_READ) = 0 munmap(0xb7fba000, 63628) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fc9000 write(1, "hello world\n", 12hello world ) = 12 exit_group(0) = ? Process 8572 detached
可以看到,執行這個程序要映射共享庫/lib/tls/i686/cmov/libc.so.6
到進程地址空間。也可以看到,printf
函數的底層確實是調用write
。