• mmap 测试的一些坑


    最近遇到一个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] ", 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 ");
    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 ");
    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的内存,不是同一块内存,重要的事情说三遍,不是同一块内存。

    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    jquery 查找子元素的几种方法
    kvm虚拟化之kvm虚拟机控制台登陆
    KVM虚拟化之windows虚拟机性能调整
    KVM虚拟机的日常管理与配置
    windows kvm虚拟机安装
    linux kvm虚拟机安装
    如何在linux上安装使用virt-manager
    KVM虚拟环境安装
    SSH批量管理 expect自动交互
    LVS+keepalived快速搭建测试环境
  • 原文地址:https://www.cnblogs.com/10087622blog/p/7423984.html
Copyright © 2020-2023  润新知