最近遇到一个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. windows PAE扩展和AWE编程

    在32位windows上只能看到最大3GB的内存空间,而且每个应用程序只能访问4GB的的内存,这个限制是windows独有的,为了使程序能够访问大于4GB的内存空间,需要使用AWE编程接口,同时需要开 ...

  2. linux中使用Python IDE pycharm教程

    今天使用vim编辑Python 并在linux中终端调试的时候,发现每次不是自己想要输出结果的时候,就要用vim编辑代码,再重新回到终端,比较浪费时间.搜索发现pycharm这一个Python ide ...

  3. SLAM入门之视觉里程计(5):单应矩阵

    在之前的博文OpenCV,计算两幅图像的单应矩阵,介绍调用OpenCV中的函数,通过4对对应的点的坐标计算两个图像之间单应矩阵\(H\),然后调用射影变换函数,将一幅图像变换到另一幅图像的视角中.当时 ...

  4. Mac 安装 Gradle

    首先下载 Gradle 通过官网进行下载 https://gradle.org 下载的文件名可能是 gradle-3.3-bin.zip 解压 将此文件解压到任意位置,如解压到 /usr/local ...

  5. 1c19b35b005744d55261682b361804fa 如何破解经过 MD5 算法处理的信息?

    Md5密文破解(解密)可以说是网络攻击中的一个必不可少的环节,是工具中的一个重要"辅助工具".md5解密主要用于网络攻击,在对网站等进行入侵过程,有可能获得管理员或者其他用户的账号 ...

  6. HttpClient(二)HttpClient使用Ip代理与处理连接超时

    前言 其实前面写的那一点点东西都是轻轻点水,其实HttpClient还有很多强大的功能: (1)实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等) (2)支持自动转向 (3)支持 ...

  7. 学习web前端技术的笔记,仅供自己查阅备忘,移动对font-size的控制(并非原创)

    假设根字体font-size的值是40px, 640/40=16,16就是px换算rem的值 function initHtmlFontSize(){ //获取可可视屏幕的宽度 var _width= ...

  8. BZOJ:1443: [JSOI2009]游戏Game

    原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1443 反正不看题解我是完全想不出系列…… 先把棋盘黑白染色,也就是同一对角线上颜色相同,使 ...

  9. 【Java学习笔记之三】java中的变量和常量

    变量和常量 在程序中存在大量的数据来代表程序的状态,其中有些数据在程序的运行过程中值会发生改变,有些数据在程序运行过程中值不能发生改变,这些数据在程序中分别被叫做变量和常量. 在实际的程序中,可以根据 ...

  10. 查找第k小的元素(O(n)递归解法)

    今天分享一个小技巧,虽然是小技巧但是还是很有价值的,曾经是微软的面试题.题目是这样的,一个无序的数组让你找出第k小的元素,我当时看到这道题的时候也像很多人一样都是按普通的思维,先排序在去第K个,但是当 ...