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\n", 18)      = 18 //read
lseek(3, 0, SEEK_SET)                   = 0 //lseek
write(3, "WWW.PERFGEEKS.COM\n", 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("-----------------------\n");
    brk_mm = (char *)malloc(100);
    memset(brk_mm, ‘\0‘, 100);
    mmap_mm = (char *)malloc(500 * 1024);
    memset(mmap_mm, ‘\0‘, 500*1024);
    free(brk_mm);
    free(mmap_mm);
    printf("-----------------------\n");
 
    return 1;
}
 
#gcc –o t_malloc t_malloc.c
#strace ./t_malloc
write(1, "-----------------------\n", 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, "-----------------------\n", 24-----------------------) = 24

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

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

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

/*
 *@file master.c
 */
[email protected]:/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\tgood job [%c]\n", (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\thello world\n", (long)time(&now));
}
 
/* command 4 */
void job_simle()
{
    time_t now;
    printf("%ld\t^_^\n", (long)time(&now));
}
 
/* command 6 */
void job_bye()
{
    time_t now;
    printf("%ld\t|<--\n", (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\n", command);*/
        switch(command)
        {
            case ‘0‘:
                printf("%ld\tslave is ready...\n", (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,输出例如以下

[email protected]:/data/tmp# echo “0″ > /tmp/shm_command

[email protected]:/data/tmp# ./master

1320939445 good job [3]

1320939446 good job [5]

1320939447 good job [7]

[email protected]:/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效率、匿名内存映射、共享内存进程通信。

mmap。,布布扣,bubuko.com

时间: 2024-08-01 19:53:06

mmap。的相关文章

Python多进程(2)——mmap模块与mmap对象

本文介绍Python mmap模块与mmap对象的用法. mmap 模块提供“内存映射的文件对象”,mmap 对象可以用在使用 plain string 的地方,mmap 对象和 plain string 的区别是: mmap 对象不提供字符串对象的方法: mmap 对象是可变的,而 str 对象是不可变的 mmap 对象同时对应于打开的文件,多态于一个Python file 对象 mmap 对象可以切片和索引,也可以为它的切片或索引赋值(因为 mmap 对象是可变的),为 mmap 对象的切片

Linux的mmap内存映射机制解析

在讲述文件映射的概念时,不可避免的要牵涉到虚存(SVR 4的VM).实际上,文件映射是虚存的中心概念, 文件映射一方面给用户提供了一组措施,好似用户将文件映射到自己地址空间的某个部分,使用简单的内存访问指令读写文件:另一方面,它也可以用于内核的基本组织模式,在这种模式种,内核将整个地址空间视为诸如文件之类的一组不同对象的映射.中的传统文件访问方式是,首先用open系统调用打开文件,然后使用read, write以及lseek等调用进行顺序或者随即的I/O.这种方式是非常低效的,每一次I/O操作都

Linux内存管理 (9)mmap(补充)

之前写过一篇简单的介绍mmap()/munmap()的文章<Linux内存管理 (9)mmap>,比较单薄,这里详细的梳理一下. 从常用的使用者角度介绍两个函数的使用:然后重点是分析内核的实现流程:最后对mmap()/munmap()进行一些验证测试. mmap系统调用并不完全是为了共享内存而设计的,它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件操作. mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以像访问普

linux服务器开发二(系统编程)--线程相关

线程概念 什么是线程 LWP:Light Weight Process,轻量级的进程,本质仍是进程(在Linux环境下). 进程:独立地址空间,拥有PCB. 线程:也有PCB,但没有独立的地址空间(共享). 进程与线程的区别:在于是否共享地址空间. 独居(进程). 合租(线程). Linux下: 线程:最小的执行单位. 进程:最小分配资源单位,可看成是一个线程的进程. 安装man文档 sudo apt-get install glibc-doc sudo apt-get install manp

xkungfoo 2015 参会笔记

有点像第二位演讲者玩命说的,"我给技术人员丢脸了",不同之处在于他演讲是去打广告的,而我挂着技术人员的称号,实际上却不太懂技术. 因此,往下看之前请慎重考虑是否要继续点鼠标或下拉条. 0x1 内容 跨进程域利用内核漏洞提升Android权限 打造移动金融核心程序安全流水线 关于Office Word程序的漏洞发掘 基于Binder调用攻击Android系统服务提权 社会工程学攻击的实例化 .... 0x2 跨进程利益内核漏洞提升Android权限 演讲者是申迪 @retme,之前曾搞过

Cgroup - Linux 内存资源管理

Hi ,我是 Zorro .这是我的微博地址,我会不定期在这里更新文章,如果你有兴趣,可以来关注我呦. 另外,我的其他联系方式: Email: [email protected] QQ: 30007147 本文PDF 在聊 cgroup 的内存限制之前,我们有必要先来讲解一下: Linux 内存管理基础知识 free 命令 无论从任何角度看, Linux 的内存管理都是一坨麻烦的事情,当然我们也可以用一堆.一片.一块.一筐来形容这个事情,但是毫无疑问,用一坨来形容它简直恰当无比.在理解它之前,我

转:Android Bander设计与实现 - 设计篇

转自:http://blog.csdn.net/universus/article/details/6211589 摘要 Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势.深入了解Binder并将之与传统IPC做对比有助于我们深入领会进程间通信的实现和性能优化.本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通

Android Binder设计与实现篇

摘要 Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势.深入了解Binder并将之与传统IPC做对比有助于我们深入领会进程间通信的实现和性能优化.本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通信模型和Binder通信协议了解Binder的设计需求:然后分别阐述Binder在系统不同部分的表述方式和起的作用:

内存申请和释放及堆连续

glibc 内存申请和释放及堆连续检查 C语言有两种内存申请方式: 1.静态申请:当你声明全局或静态变量的时候,会用到静态申请内存.静态申请的内存有固定的空间大小.空间只在程序开始的时候申请一次,并且不再释放(除非程序结束). 2.自动申请:当你声明自动变量的时候会使用自动申请.函数参数.局部变量都属于自动变量.这些变量空间在程序执行致相关语句块申请,离开语句块时释放. 还有一种内存申请方式:动态内存申请.C语言变量并不支持动态内存申请,这一功能由库函数实现.C里面没有动态这个存储类型!! 当你