最近遇到一个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. 第二章:Python基础の快速认识基本数据类型和操作实战

    本课主题 字符串和操作实战 二进制操作实战 List 列表和操作实战 Tuple 元組和操作实战 Dict 字典和操作实战 作業需求 引言 这遍文章简单介绍了 Python 字符串和集合的方法和应用, ...

  2. winform展示Unity3D文件(支持动态改变文件路径)

    winform下展示Unity3D文件可以支持对Unity3D实现的模块进行包装,以及在其他的项目中需要展示Unity3D的界面时候,恰到适宜地进行打开展示,这里我展示如何使用winform打开Uni ...

  3. js调取本地可执行文件exe

    通过ie支持的activex控件来读取文程序的安装路径,从而调用本地exe文件 function callExe () { try{ var command; var shell = new Acti ...

  4. Kafka、Logstash、Nginx日志收集入门

    Nginx作为网站的第一入口,其日志记录了除用户相关的信息之外,还记录了整个网站系统的性能,对其进行性能排查是优化网站性能的一大关键. Logstash是一个接收,处理,转发日志的工具.支持系统日志, ...

  5. 设计模式六大原则(PHP)

    设计模式的目的是为了更好的代码重用性,可读性,可靠性和可维护性.常用的六大设计模式有:单一职责原则(SRP),里氏替换原则(LSP),依赖倒转原则(DIP),接口隔离原则(ISP),迪米特法则(LOD ...

  6. Pandas系列之入门篇

    Pandas系列之入门篇 简介 pandas 是 python用来数据清洗.分析的包,可以使用类sql的语法方便的进行数据关联.查询,属于内存计算范畴, 效率远远高于硬盘计算的数据库存储.另外pand ...

  7. iOS 应用全部添加滑动返回

    if ([self  class] == [HomeViewController class]||[self  class] == [ComprehensivefinanceViewControlle ...

  8. 【Java】Java中BigDecimal的基本运算

    BigDecimal一共有4个够造方法,让来看看其中比较常用的两种用法: 第一种:BigDecimal(double val)Translates a double into a BigDecimal ...

  9. Java与算法之(11) - 合并排序

    天下事,合久必分,分久必合.合并排序的基本思想正是先分再合. 例如对3, 1这个数列排序,首先是分,分为3和1两个数列,然后再合并并排序.合并需要额外的辅助空间,即建立一个两个数列长度之和的空数组用于 ...

  10. html笔记2

    html css的用法 <style type="text/css">代表我要使用css了 <html> <head> <style ty ...