mmap函数小实验

文章目的

本文是为了深入理解mmap的参数length与offset对mmap函数行为的影响,从而更好地理解内存映射。
man手册中看到的mmap的参数如下:
在这里插入图片描述
函数输入参数介绍:

  • 参数1:addr:指定映射的起始地址,通常设为NULL,由内核来分配,映射成功后,return 该地址
  • 参数2:length:代表将文件中映射到内存的部分的长度,单位为字节数,一般要求为页大小整数倍
  • 参数3:prot:映射区域的保护方式。可以为以下几种方式的组合:
    PROT_EXEC 映射区域可被执行
    PROT_READ 映射区域可被读取
    PROT_WRITE 映射区域可被写入
    PROT_NONE 映射区域不能存取
  • 参数4:flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
    MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此。
    MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
    MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
    等等,在man手册中查看其详细介绍。
  • 参数5:fd:要映射到内存中的文件描述符,即调用mmap前调用open函数的返回值。(如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。)
  • 参数6:offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍

必要知识点:

  • mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占用掉你的 virutal memory)
  • 普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作,然后你就可以用memcpy等操作写文件, 而不用write()了。
  • 内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下。
  • 取消内存映射,需调用munmap()
  • mmap映射完之后,相当于把文件中的内容在用户空间做了映射,内核空间对内容的修改也会反映到用户空间,用户空间对内核的修改也会反映到内核空间。(待确认)

参数 length 不是页大小的整数倍会怎样?

参数length不是页大小整数倍有两种情况,一是小于一个页的大小,二是大于页大小,第二种情况中,超出部分也不足一个页,其实都属于第一种情况。

于是可以用length小于一个页大小时,来实验分析mmap的行为:

  1. mmap实际占用的虚拟内存区域的大小是length的大小还是其他?
  2. 内存映射对象为普通文件时,修改该虚拟内存中大于length的部分,是否可共享?
  3. 内存映射对象为普通文件时,修改该虚拟内存中大于length的部分,是否被同步到普通文件中?

研究过程

使用mmap函数创建一个可读写、共享的虚拟内存区域,内存映射对象为Linux中普通文件a.txt,然后写一些数据到该虚拟内存区域中大于length的部分,最后,调用msync函数显式地同步这些数据到 a.txt 文件中。
mmap_test.c源码:

#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/mman.h>

/* Usage: ./mmap_test <file> <length> <offset>
 */
int main(int argc, char *argv[])
{
	int fd;
	uint8_t *pbase = NULL;
	if (argc < 4) {
		printf("Usage: ./mmap_test <file> <length> <offset> \n");
		return 0;
	}

	fd = open(argv[1], O_RDWR);
	if (fd == -1)
		printf("error: open filed!\n");

	const size_t length = atoi(argv[2]); 
	const size_t offset = atoi(argv[3]); 

	pbase = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
	if (MAP_FAILED == pbase) {
		perror("mmap failed, reason:");
	  	return EXIT_FAILURE;
	}

	close(fd);

	printf("page size is: %ld bytes \n", sysconf(_SC_PAGE_SIZE));

	const size_t end = length + 13;
	for (int i = length; i < end; i++) {
		pbase[i] = '6';
	}
	printf("\n");

	msync(pbase, end, MS_SYNC);
	

	return 0;
}

编译:

gcc mmap_test.c -o mmap_test

查看 a.txt 文件的初始内容(ASCII 码,共 110 字节):
在这里插入图片描述执行

./mmap_test a.txt 10 0

然后再查看a.txt内容:
在这里插入图片描述
结果发现,超出了mmap指定length的数据也照样被修改了,那如果mmap中length正好指定了4096Byte还会有同样的结果吗?

实验过程是这样的,我先将一个文件内填充超过一个扇区(4096字节)的数据,然后修改上面的代码,分以下几种情况:

  1. mmap的length参数为4096:试图修改超过第4096字节的数据,发现无法修改。
  2. mmap的length参数为4090:试图修改第4090到第4190字节的数据,发现只有第4091到第4096这6个字节的数据被修改了。

length结论

mmap实际创建的虚拟内存大小不小于length的页大小的最小整数倍。

注:期间遇到了个问题:我先打开了个空文件,然后想通过mmap往空文件中写东西,发现怎么都写不进去,因为这违反了mmap不分配空间的规定。

参数 offset 取不同的值时会怎样?

为了探索offset取不同的值时,mmap究竟表现如何,继续使用上面的源代码,共测试以下几种情况(先设定文件大小为超过4096字节但并不是4096的整数倍,length大小为文件对应最小整数被页的大小):

  • offset取负值,mmap的行为
  • offset取值为5时(即不是页面大小的整数倍),mmap的行为
  • offset取值为4096(即是页面大小的整数倍),mmap的行为
  • offset取值为内存映射文件的大小时,mmap的行为
  • offset取值为大于内存映射文件的大小但正好时页面大小的整数倍时,mmap的行为

研究过程

  1. offset取负值:报"mmap failed, reason:: Invalid argument"
  2. offset取值为5:报"mmap failed, reason:: Invalid argument"
  3. offset取值为4096:此时mmap得到的虚拟地址对应文件从起始位置偏移4096字节后的位置。
  4. offset取值为内存映射文件的大小时(但不是页面大小整数倍):报"mmap failed, reason:: Invalid argument"
  5. offset取值是页面大小的整数倍(但大于内存映射文件的大小):报Bus error

offset 结论

offset的取值必须是页面大小(4096字节)的整数倍,隐含该值必须大于等于零,一般取0,否则会报无效参数报错,且offset不能超过文件大小,否则有bus error。
如果你非要设置offset,按下方法设置:

//需要包含头文件 #include <unistd.h>
const auto true_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);

参考链接

计算机系统篇之虚拟内存(4):再探 mmap

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值