mmap

 

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);

該函數各參數的作用圖示如下:

圖 28.4. mmap函數

mmap函數

如果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

 

 

flags parameter

The parameter flags provides other information about the handling of the mapped pages. The options are defined in sys/mman.h as:

MAP_SHARED Share changes. 
MAP_PRIVATE Changes are private. 
MAP_FIXED Interpret addr exactly. 
MAP_ANONYMOUS Map /dev/zero without open or close.

 

MAP_SHARED and MAP_PRIVATE describe the disposition of write references to the file. If MAP_SHARED is specified, write references will change the file. IfMAP_PRIVATE is specified, the initial write reference will create a private copy of the modified file page and redirect the mapping to the copy. EitherMAP_SHARED or MAP_PRIVATE must be specified, but not both. The mapping type is retained across a fork(2).

Note that the private copy is not created until either the first write or the page is locked for write access (see memcntl(2)); until then, other users who have the file mapped MAP_SHARED can change the image of the file observed in the mapped page.

MAP_FIXED informs the system that the value of pa must be addr, exactly. The use of MAP_FIXED is discouraged, as it may prevent an implementation from making the most effective use of system resources.

The MAP_ANONYMOUS flag provides a mapping identical to that obtained by mapping /dev/zero. However, there is no need for the program to open and close a device.

When MAP_ANONYMOUS is specified fd must be set to ``-1''.

Using MAP_ANONYMOUS is faster than mapping /dev/zero, even after device /dev/zero is already open, due to the system's ability to completely bypass the device layer. Also see ``Usage'' and ``Standards Conformance'', below.

 

發佈了23 篇原創文章 · 獲贊 2 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章