zoukankan      html  css  js  c++  java
  • mmap。

    linux mmap 内存映射

    mmap() vs read()/write()/lseek()

    通过strace统计系统调用的时候,常常能够看到mmap()与mmap2()。系统调用mmap()能够将某文件映射至内存(进程空间),如此能够把对文件的操作转为对内存的操作,以此避免很多其它的lseek()与read()、write()操作,这点对于大文件或者频繁訪问的文件而言尤其受益。但有一点必须清楚:mmap的addr与offset必须对齐一个内存页面大小的边界,即内存映射往往是页面大小的整数倍,否则maaped_file_size%page_size内存空间将被闲置浪费。

    演示一下,将文件/tmp/file_mmap中的字符转成大写,分别使用mmap与read/write二种方法实现。

    /*
    * @file: t_mmap.c
    */
    #include <stdio.h>
    #include <ctype.h>
    #include <sys/mman.h> /*mmap munmap*/
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
     
    int main(int argc, char *argv[])
    {
        int fd;
        char *buf;
        off_t len;
        struct stat sb;
        char *fname = "/tmp/file_mmap";
     
        fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
        if (fd == -1)
        {
            perror("open");
            return 1;
        }
        if (fstat(fd, &sb) == -1)
        {
            perror("fstat");
            return 1;
        }
     
        buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (buf == MAP_FAILED)
        {
            perror("mmap");
            return 1;
        }
     
        if (close(fd) == -1)
        {
            perror("close");
            return 1;
        }
     
        for (len = 0; len < sb.st_size; ++len)
        {
            buf[len] = toupper(buf[len]);
            /*putchar(buf[len]);*/
        }
     
        if (munmap(buf, sb.st_size) == -1)
        {
            perror("munmap");
            return 1;
        }
        return 0;
    }
    #gcc –o t_mmap t_mmap.c
    #strace ./t_mmap
    open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即文件大小18
    mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap文件fd=3
    close(3)                                = 0 //close文件fd=3
    munmap(0xb7867000, 18)                  = 0  //munmap,移除0xb7867000这里的内存映射

    尽管没有看到read/write写文件操作,但此时文件/tmp/file_mmap中的内容已由www.perfgeeks.com改变成了WWW.PERFGEEKS.COM .这里mmap的addr是0(NULL),offset是18,并非一个内存页的整数倍,即有4078bytes(4kb-18)内存空间被闲置浪费了。

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <ctype.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
     
    int main(int argc, char *argv[])
    {
        int fd, len;
        char *buf;
        char *fname = "/tmp/file_mmap";
        ssize_t ret;
        struct stat sb;
     
        fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
        if (fd == -1)
        {
            perror("open");
            return 1;
        }
        if (fstat(fd, &sb) == -1)
        {
            perror("stat");
            return 1;
        }
     
        buf = malloc(sb.st_size);
        if (buf == NULL)
        {
            perror("malloc");
            return 1;
        }
        ret = read(fd, buf, sb.st_size);
        for (len = 0; len < sb.st_size; ++len)
        {
            buf[len] = toupper(buf[len]);
            /*putchar(buf[len]);*/
        }
     
        lseek(fd, 0, SEEK_SET);
        ret = write(fd, buf, sb.st_size);
        if (ret == -1)
        {
            perror("error");
            return 1;
        }
     
        if (close(fd) == -1)
        {
            perror("close");
            return 1;
    }
    free(buf);
        return 0;
    }
    #gcc –o t_rw t_rw.c
    open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 当中文件大小18
    brk(0)                                  = 0x9845000  //brk, 返回当前中断点
    brk(0x9866000)                          = 0x9866000  //malloc分配内存,堆当前最后地址
    read(3, "www.perfgeeks.com
    ", 18)      = 18 //read
    lseek(3, 0, SEEK_SET)                   = 0 //lseek
    write(3, "WWW.PERFGEEKS.COM
    ", 18)     = 18 //write
    close(3)                                = 0 //close

    这里通过read()读取文件内容,toupper()后,调用write()写回文件。由于文件太小,体现不出read()/write()的缺点:频繁訪问大文件,须要多个lseek()来确定位置。每次编辑read()/write(),在物理内存中的双份数据。当然,不能够忽略创建与维护mmap()数据结构的成本。须要注意:并没有详细測试mmap vs read/write,即不能一语断言谁孰谁劣,详细应用场景详细评測分析。你仅仅是要记住:mmap内存映射文件之后,操作内存即是操作文件,能够省去不少系统内核调用(lseek, read, write)。

    mmap() vs malloc()

    使用strace调试的时候,通常能够看到通过mmap()创建匿名内存映射的身影。比方启用dl(‘apc.so’)的时候,就能够看到例如以下语句。
    mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 //30M

    通常使用mmap()进行匿名内存映射,以此来获取内存,满足一些特别需求。所谓匿名内存映射,是指mmap()的时候,设置了一个特殊的标志MAP_ANONYMOUS,且fd能够忽略(-1)。某些操作系统(像FreeBSD),不支持标志MAP_ANONYMOUS,能够映射至设备文件/dev/zero来实现匿名内存映射。使用mmap()分配内存的优点是页面已经填满了0,而malloc()分配内存后,并没有初始化,须要通过memset()初始化这块内存。另外,malloc()分配内存的时候,可能调用brk(),也可能调用mmap2()。即分配一块小型内存(小于或等于128kb),malloc()会调用brk()调高断点,分配的内存在堆区域,当分配一块大型内存(大于128kb),malloc()会调用mmap2()分配一块内存,与堆无关,在堆之外。相同的,free()内存映射方式分配的内存之后,内存立即会被系统收回,free()堆中的一块内存,并不会立即被系统回收,glibc会保留它以供下一次malloc()使用。

    这里演示一下malloc()使用brk()和mmap2()。

    /*
    * file:t_malloc.c
    */
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
     
    int main(int argc, char *argv)
    {
        char *brk_mm, *mmap_mm;
     
        printf("-----------------------
    ");
        brk_mm = (char *)malloc(100);
        memset(brk_mm, '', 100);
        mmap_mm = (char *)malloc(500 * 1024);
        memset(mmap_mm, '', 500*1024);
        free(brk_mm);
        free(mmap_mm);
        printf("-----------------------
    ");
     
        return 1;
    }
     
    #gcc –o t_malloc t_malloc.c
    #strace ./t_malloc
    write(1, "-----------------------
    ", 24-----------------------) = 24
    brk(0)                                  = 0x85ee000
    brk(0x860f000)                          = 0x860f000   //malloc(100)
    mmap2(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7702000 //malloc(5kb)
    munmap(0xb7702000, 516096)              = 0 //free(), 5kb 
    write(1, "-----------------------
    ", 24-----------------------) = 24

    通过malloc()分别分配100bytes和5kb的内存,能够看出事实上分别调用了brk()和mmap2(),对应的free()也是不回收内存和通过munmap()系统回收内存。

    mmap()共享内存,进程通信

    内存映射mmap()的还有一个外常见的使用方法是,进程通信。相较于管道、消息队列方式而言,这样的通过内存映射的方式效率明显更高,它不须要任务数据拷贝。这里,我们通过一个样例来说明mmap()在进程通信方面的应用。我们编写二个程序,各自是master和slave,slave依据master不同指令进行不同的操作。Master与slave就是通过映射同一个普通文件进行通信的。

    /*
     *@file master.c
     */
    root@liaowq:/data/tmp# cat master.c 
    #include <stdio.h>
    #include <time.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
     
    void listen();
     
    int main(int argc, char *argv[])
    {
        listen();
        return 0;
    }
     
    void listen()
    {
        int fd;
        char *buf;
        char *fname = "/tmp/shm_command";
     
        char command;
        time_t now;
     
        fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
        if (fd == -1)
        {
            perror("open");
            exit(1);
        }
        buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (buf == MAP_FAILED)
        {
            perror("mmap");
            exit(1);
        }
        if (close(fd) == -1)
        {
            perror("close");
            exit(1);
        }
     
        *buf = '0';
        sleep(2);
        for (;;)
        {
            if (*buf == '1' || *buf == '3' || *buf == '5' || *buf == '7')
            {
                if (*buf > '1')
                    printf("%ld	good job [%c]
    ", (long)time(&now), *buf);
                (*buf)++;
            }
            if (*buf == '9')
            {
                break;
            }
            sleep(1);
        }
     
        if (munmap(buf, 4096) == -1)
        {
            perror("munmap");
            exit(1);
        }
    }
     
    /*
     *@file slave.c
     */
    #include <stdio.h>
    #include <time.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
     
    void ready(unsigned int t);
    void job_hello();
    void job_smile();
    void job_bye();
    char get_command(char *buf);
    void wait();
     
    int main(int argc, char *argv[])
    {
        wait();
        return 0;
    }
     
    void ready(unsigned int t)
    {
        sleep(t);
    }
     
    /* command 2 */
    void job_hello()
    {
        time_t now;
        printf("%ld	hello world
    ", (long)time(&now));
    }
     
    /* command 4 */
    void job_simle()
    {
        time_t now;
        printf("%ld	^_^
    ", (long)time(&now));
    }
     
    /* command 6 */
    void job_bye()
    {
        time_t now;
        printf("%ld	|<--
    ", (long)time(&now));
    }
     
    char get_command(char *buf)
    {
        char *p;
        if (buf != NULL)
        {
            p = buf;
        }
        else
        {
            return '0';
        }
        return *p;
    }
     
    void wait()
    {
        int fd;
        char *buf;
        char *fname = "/tmp/shm_command";
     
        char command;
        time_t now;
     
        fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
        if (fd == -1)
        {
            perror("open");
            exit(1);
        }
        buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (buf == MAP_FAILED)
        {
            perror("mmap");
            exit(1);
        }
        if (close(fd) == -1)
        {
            perror("close");
            exit(1);
        }
     
        for (;;)
        {
            command = get_command(buf);
            /*printf("%c
    ", command);*/
            switch(command)
            {
                case '0':
                    printf("%ld	slave is ready...
    ", (long)time(&now));
                    ready(3);
                    *buf = '1';
                    break;
                case '2':
                    job_hello();
                    *buf = '3';
                    break;
                case '4':
                    job_simle();
                    *buf = '5';
                    break;
                case '6':
                    job_bye();
                    *buf = '7';
                    break;
                default:
                    break;
            }
            if (*buf == '8')
            {
                *buf = '9';
                if (munmap(buf, 4096) == -1)
                {
                    perror("munmap");
                    exit(1);
                }
                return;
            }
            sleep(1);
        }
        if (munmap(buf, 4096) == -1)
        {
            perror("munmap");
            exit(1);
        }
    }

    运行master与slave,输出例如以下
    root@liaowq:/data/tmp# echo “0″ > /tmp/shm_command
    root@liaowq:/data/tmp# ./master
    1320939445 good job [3]
    1320939446 good job [5]
    1320939447 good job [7]
    root@liaowq:/data/tmp# ./slave
    1320939440 slave is ready…
    1320939444 hello world
    1320939445 ^_^
    1320939446 |<--

    master向slave发出job指令2,4,6。slave收到指令后,运行相关逻辑操作,完毕后告诉master,master知道slave完毕工作后,打印good job而且发送一下job指令。master与slave通信,是通过mmap()共享内存实现的。

    总结

    1、 Linux採用了投机取巧的分配策略,用到时,才分配物理内存。也就是说进程调用brk()或mmap()时,仅仅是占用了虚拟地址空间,并没有真正占用物理内存。这也正是free –m中used并不意味着消耗的全都是物理内存。
    2、 mmap()通过指定标志(flag) MAP_ANONYMOUS来表明该映射匿名内存映射,此时能够忽略fd,可将它设置为-1。假设不支持MAP_ANONYMOUS标志的类unix系统,能够映射至特殊设备文件/dev/zero实现匿名内存映射
    3、 调用mmap()时就决定了映射大小,不能再添加。换句话说,映射不能改变文件的大小。反过来,由文件被映射部分,而不是由文件大小来决定进程可訪问内存空间范围(映射时,指定offset最好是内存页面大小的整数倍)。
    4、通常使用mmap()的三种情况.提高I/O效率、匿名内存映射、共享内存进程通信。


  • 相关阅读:
    kuberbetes1.17.3二进制安装
    jvm启动cpu和负载高分析
    kubeadm安装的k8s集群证书过期处理
    Mongodb 相关链接
    perl 之 正则表达式 (简)
    ssh-copy-id 拷贝用户秘钥
    unicode & utf-8
    Python之路,Day3
    Python Day2 (二)
    find xargs 简单组合使用
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/3923058.html
Copyright © 2011-2022 走看看