最近遇到一个mmap的问题,然后为了测试该问题,写了如下测试代码:

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

#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
char *addr;
int fd;
struct stat sb;
off_t offset, pa_offset;
size_t length;
off_t file_len;
ssize_t s;
int iRet;

if (argc < 3 || argc > 4) {
fprintf(stderr, "%s file offset [length]\n", argv[0]);
exit(EXIT_FAILURE);
}

fd = open(argv[1], O_RDWR);
if (fd == -1)
handle_error("open");
#if 0
file_len = lseek(fd, 400*1024*1024, SEEK_CUR);------------lseek和ftruncate,truncate都可以达到修改文件可映射大小的结果,不过lseek可以在readonly的情况下修改,而truncate不行。
#endif
offset = 400*1024*1024;
iRet = ftruncate(fd,offset);
if (0 != iRet)
{
close(fd);
printf("ftruncate in OpenShem fail\n");
return 0;
}

if (fstat(fd, &sb) == -1) /* To obtain file size */
handle_error("fstat");

offset = atoi(argv[2]);
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
/* offset for mmap() must be page aligned */

if (offset >= sb.st_size) {
fprintf(stderr, "offset is past end of file\n");
exit(EXIT_FAILURE);
}

if (argc == 4) {
length = atoi(argv[3]);
if (offset + length > sb.st_size)
length = sb.st_size - offset;
/* Can't display bytes past end of file */

} else { /* No length arg ==> display to end of file */
length = sb.st_size - offset;
}

addr = mmap(NULL, length + offset - pa_offset, PROT_READ,MAP_PRIVATE, fd, pa_offset);---------map调用,进行映射,注意此处采用的是MAP_PRIVATE
if (addr == MAP_FAILED)
handle_error("mmap");

s = write(STDOUT_FILENO, addr + offset - pa_offset, length);-------------------第一次读取该map地址
if (s != length) {
if (s == -1)
handle_error("write");

fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
/*second excute*/
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);------------------第二次读取该map地址
if (s != length) {
if (s == -1)
handle_error("write");

fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}

exit(EXIT_SUCCESS);
}

测试发现,当mmap调用前,内存占用如下:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND

24441 root      20   0    4156    356    264 t   0.0  0.0   0:00.00 map.o

调用mmap之后,内存占用如下:

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24441 root 20 0 394784 356 264 t 0.0 0.0 0:00.00 map.o

很明显,虚拟内存增长了,但res和shr并没有增长。

然后调用到s = write(STDOUT_FILENO, addr + offset - pa_offset, length),第一次的时候,内存占用如下:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND

24441 root      20   0  394784 391072 390968 t   0.0  1.6   0:02.35 map.o

发现res和shr也增加了,但是res-shr的值,并没有显著增长。

第二次调用s = write(STDOUT_FILENO, addr + offset - pa_offset, length),内存占用不变,符合预期。

代码中关于几个关键函数的理解如下:

关于文件大小的修改部分--------------------

lseek和ftruncate,truncate都可以达到修改文件可映射大小的结果,注意是修改可映射大小,不是文件实际大小。

事实上,lseek不改变文件实际大小,而truncate 以及 ftruncate是会改变文件实际大小的。所谓的实际大小,是指通过fstat等接口获取的文件size,不是占用的block的大小。

lseek可以在readonly的情况下修改,而truncate不行。

truncate 在缩小了文件之后,如果原来seek的位置小于缩小之后的文件大小,则保持不变,如果大于,则位于文件尾,如果truncate 放大了文件,则seek位置不变。

关于mmap的参数部分--------------------

从MAP_PRIVATE的理解来看,因为使用的是copy-on-write mapping,那么在write之后,rss才随着virt增长是正常的。

不管是使用MAP_PRIVATE还是使用MAP_SHARED,当关闭fd之后,都不受影响,这个一般新手一开始都认为需要保持fd打开,其实不需要,fd也就是只是用来临时用一下,MAP_ANONYMOUS的时候,fd甚至都会忽略。比如fork调用的时候,实际参数就是MAP_ANONYMOUS | MAP_SHARED,不仅如此,甚至也可以使用unlink来删除该文件,也不影响其他已经map的进程来进行通信,因为unlink减少的文件引用记数,在内核中该文件还是存在的,不过显示为del状态。map的时候,内核的函数中,已经通过mmap_region函数,通过atomic_inc(&inode->i_writecount),增加了引用计数。

另外,测试结果表明,当使用MAP_PRIVATE的时候,map所写的内容不会影响到其他进程,也就是说你cat 对应的文件,也是看不到修改的内容的。但是如果map同样的

一个fd,然后A进程使用MAP_SHARED标志,B进程使用MAP_PRIVATE标志,则A修改的内容,可以在B进程中体现。而B修改的内容,在A中不可见。不但不可见,根据copy-on-write的原则,

之后A和B也不能通过该共享内存来通信了,因为这个时候已经指向两个不同的segment了。当然这个只是测试,有兴趣的同学可以测试一下,一般不会有谁这么变态对于同一个fd,一个采用

MAP_PRIVATE,一个采用MAP_SHARED,这个也是在帮别人查问题时候发现的,而且程序运行很多年是ok的原因是因为,private这一侧,从来都是读,不去写,没有触发写时复制。

要注意的是,mmap的返回值,在多个进程中,有可能是一样的,但是这个是不能充分说明map的内存是共用一个内核的页表项,因为这个只是该进程的虚拟地址而已。举个栗子,比如三个进程,A,B进程map完之后,得到的地址都是一样的,比如A使用MAP_SHARED标志,B使用MAP_PRIVATE标志映射同一个文件,

然后A使用unlink来删除这个文件,由于文件已经删除了,对应的inode号不一样了,A和B照样能够通过共享内存通信,然后C使用B一样的代码执行一份,得到的mmap返回值的地址也是和A,B一样的,但是C真正map的地址,和A以及B的mmap的内存,不是同一块内存,重要的事情说三遍,不是同一块内存。

mmap 测试的一些坑的更多相关文章

  1. 『动善时』JMeter基础 — 59、进行JMeter分布式测试遇到的坑

    目录 1.控制机端 (1)执行机没有关闭防火墙 (2)内存溢出 2.执行机端 (1)启动jmeter-server服务情况一 (2)启动jmeter-server服务情况二 (3)启动jmeter-s ...

  2. 一个Monkey测试的小坑

    环境:Genymotion模拟器+Custome Phone-6.0.0,API 23 操作步骤如下: cd data/app ls //为了获取待测apk的包名 获取结果如下: 执行命令,其中包名使 ...

  3. 说说初用 Mock 工具测试碰到的坑

    我是一个在校实习生,作为一个程序猿,是个菜鸟中战斗机!对于测试,只写过一点点简单到不能再简单了的 Junit 单元测试的例子(因为当时这足以应付学校课程的内容与要求).这几天在公司里要真枪实弹做测试的 ...

  4. Jmeter分布式测试的各种坑之jmeter-server修改ip

    第一坑:启动压力机的时候,直接./jmeter-server,会报如下错误 错误原因:127.0.0.1是本机, 一个回路地址, 没有指定地址 正确的启动方式:启动命令加一个参数, IP地址写压力机对 ...

  5. selenium IDE测试中的坑

    selenium IDE工具是firefox自带的一个网页自动化测试工具,因为它是IDE所以它很方便使用,但也因为它是IDE所以它有那么些坑. 问题:selenium回放中timeout问题 网页的打 ...

  6. Android定位测试(深坑)

    问题:我们是一个海外app,市场部去马来西亚打开那边的市场,发现了一个问题,就是我们的app定位有问题,还是成都的定位,主要原因是在马来西亚使用这个app,请求中带的经纬度参数是成都的,导致服务器返回 ...

  7. APP测试的那些坑

    在记录app测试走过的那些坑之前,先总结下app测试的工作主要有哪些:   1.功能测试,无论是什么软件产品,必不可少的就是功能测试.我们需要测试这款app产品的功能是否完善,是否符合客户需求,是否符 ...

  8. 从零开始学AB测试:躲坑篇

    AB测试的原理很简单,只用到了最简单的统计假设检验,但表面的简单通常都隐藏着陷阱,这一点没有经过实践的摸爬滚打是不容易看到的,今天我就把前人已经踩过的坑,一共15个,给大家分享一下.在分享之前,大家脑 ...

  9. 一次单体测试的采坑--MatcherAssert.assertThat---org.hamcrest 和org.mockito

    单体测试测试环境ci上报这个错, 本地没问题. org.hamcrest.Matcher.describeMismatch(Ljava/lang/Object;Lorg/hamcrest/Descri ...

随机推荐

  1. linux系统编程:自己动手写一个who命令

    who命令的作用用于显示当前有哪些用户登录到系统. 这个命令执行的原理是读取了系统上utmp文件中记录的所有登录信息,直接显示出来的 utmp文件在哪里呢? man who的时候,在手册下面有这么一段 ...

  2. [js高手之路] vue系列教程 - 绑定class与行间样式style(6)

    一.绑定class属性的方式 1.通过数组的方式,为元素绑定多个class <style> .red { color:red; /*color:#ff8800;*/ } .bg { bac ...

  3. 关于linux下的date日期,并以日期给文件命名

    在linux的终端中,我们输入date后会有以下显示: 然后博主也扩展了一下date的基础用法: date + "%-": %y 输出年份的后2位:%Y 输出完整年份 %m 输出月 ...

  4. mysql如何执行关联查询与优化

    mysql如何执行关联查询与优化 一.前言 在数据库中执行查询(select)在我们工作中是非常常见的,工作中离不开CRUD,在执行查询(select)时,多表关联也非常常见,我们用的也比较多,那么m ...

  5. 15.5 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表

    点击返回:自学Zabbix之路 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表  1. Actions表 actions表记录了当触发器触发时,需要采用的动作. 2.Aler ...

  6. [树莓派(raspberry pi)] 02、PI3安装openCV开发环境做图像识别(详细版)

    前言 上一篇我们讲了在linux环境下给树莓派安装系统及入门各种资料 ,今天我们更进一步,尝试在PI3上安装openCV开发环境. 博主在做的过程中主要参考一个国外小哥的文章(见最后链接1),不过其教 ...

  7. 前端包管理工具 yarn

    yarn 是一个  与 npm 类似的 前端包管理工具 安装 windows  要去官网下载 (一定要去官网下载 .mis 文件进行安装)   用npm 或者 cnpm  也能安装 但是这种安装 有缺 ...

  8. CSS根据子元素个数不同定义样式

    近日面试,遇见了一个这样的问题,不会,便记下来. 问题:如何根据子元素个数的不同定义不同的样式? 代码:HTML <ul> <li>1</li> <li> ...

  9. 浅析nodeJS中的Crypto模块,包括hash算法,HMAC算法,加密算法知识,SSL协议

    node.js的crypto在0.8版本,这个模块的主要功能是加密解密. node利用 OpenSSL库(https://www.openssl.org/source/)来实现它的加密技术, 这是因为 ...

  10. asp.net core 配置

    ASP.NET Core的配置系统已经和之前版本的ASP.NET有所不同了,之前是依赖于System.Configuration和XML配置文件web.config,现在支持各种格式的配置,比以前灵活 ...