精华内容
下载资源
问答
  • linux mmap
    2021-01-14 16:22:43

    =====文件名:abc.c=====

    一。

    linux mmap使用例子

    转自:http://blog.163.com/zhaoxin851055@126/blog/static/8112929820122872212734/

    #include/* for mmap and munmap */

    #include/* for open */

    #include/* for open */

    #include/* for open */

    #include/* for lseek and write */

    #include

    int main(int argc, char **argv)

    {

    int fd;

    char *mapped_mem, * p;

    int flength = 1024;

    void * start_addr = 0;

    fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

    flength = lseek(fd, 1, SEEK_END);

    write(fd, "\0", 1); /* 在文件最后添加一个空字符,以便下面printf正常工作 */

    lseek(fd, 0, SEEK_SET);

    mapped_mem = mmap(start_addr, flength, PROT_READ, //允许读

    MAP_PRIVATE, //不允许其它进程访问此内存区域

    fd, 0);

    /* 使用映射区域. */

    printf("%s\n", mapped_mem); /* 为了保证这里工作正常,参数传递的文件名最好是一个文本文件 */

    close(fd);

    munmap(mapped_mem, flength);

    return 0;

    }

    编译运行此程序:

    gcc -Wall mmap.c

    ./a.out text_filename

    上面的方法因为用了PROT_READ,所以只能读取文件里的内容,不能修改,如果换成PROT_WRITE就可以修改文件的内容了。又由于 用了MAAP_PRIVATE所以只能此进程使用此内存区域,如果换成MAP_SHARED,则可以被其它进程访

    #include/* for mmap and munmap */

    #include/* for open */

    #include/* for open */

    #include/* for open */

    #include/* for lseek and write */

    #include

    #include/* for memcpy */

    int main(int argc, char **argv)

    {

    int fd;

    char *mapped_mem, * p;

    int flength = 1024;

    void * start_addr = 0;

    fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

    flength = lseek(fd, 1, SEEK_END);

    write(fd, "\0", 1); /* 在文件最后添加一个空字符,以便下面printf正常工作 */

    lseek(fd, 0, SEEK_SET);

    start_addr = 0x80000;

    mapped_mem = mmap(start_addr, flength, PROT_READ|PROT_WRITE, //允许写入

    MAP_SHARED, //允许其它进程访问此内存区域

    fd, 0);

    /* 使用映射区域. */

    printf("%s\n", mapped_mem); /* 为了保证这里工作正常,参数传递的文件名最好是一个文本文 */

    while((p = strstr(mapped_mem, "Hello"))) /* 此处来修改文件 内容 */

    {

    memcpy(p, "Linux", 5);

    p += 5;

    }

    close(fd);

    munmap(mapped_mem, flength);

    return 0;

    }

    /*

    Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, 先来看一下mmap的函数声明:

    头文件:

    原型: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offsize);

    返回值: 成功则返回映射区起始地址, 失败则返回MAP_FAILED(-1).

    参数:

    addr: 指定映射的起始地址, 通常设为NULL, 由系统指定.

    length: 将文件的多大长度映射到内存.

    prot: 映射区的保护方式, 可以是:

    PROT_EXEC: 映射区可被执行.

    PROT_READ: 映射区可被读取.

    PROT_WRITE: 映射区可被写入.

    PROT_NONE: 映射区不能存取.

    flags: 映射区的特性, 可以是:

    MAP_SHARED: 对映射区域的写入数据会复制回文件, 且允许其他映射该文件的进程共享.

    MAP_PRIVATE: 对映射区域的写入操作会产生一个映射的复制(copy-on-write), 对此区域所做的修改不会写回原文件.

    此外还有其他几个flags不很常用, 具体查看linux C函数说明.

    fd: 由open返回的文件描述符, 代表要映射的文件.

    offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射.

    下面说一下内存映射的步骤:

    用open系统调用打开文件, 并返回描述符fd.

    用mmap建立内存映射, 并返回映射首地址指针start.

    对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).

    用munmap(void *start, size_t lenght)关闭内存映射.

    用close系统调用关闭文件fd.*/

    二。下面这个代码比较经典,但是我们基于这个修改ppc8280的gpio操作时,没有成功。

    /*linux下使用mmap控制GPIO

    2008-07-12 7:41

    欢迎看看我的另一个小窝,说不定有意外的惊喜哦 ^_^ www.devres.info

    如果没有/dev/mem,则执行

    mknod /dev/mem c 1 1

    编译下面的代码*/

    #include

    #include

    #include

    #include

    #include

    #include

    #define GPIO_CTL_BASE 0x56000000

    #define rGPBCON 0x10

    #define rGPBDAT 0x14

    #define rGPBUP 0x18

    unsigned int *GPBCON,*GPBDAT,*GPBUP;

    void Led_Display(int data)

    {

    *(volatile unsigned int *)GPBDAT= (~data & 0xf)<<7;//因为是第7-10位,且只有4位,故 左移7

    }

    int main(int argc, char** argv)

    {

    int gpio_fd, ip=0, i=0;

    unsigned char *gpio_map;

    gpio_map = NULL;

    GPBCON = NULL;

    GPBDAT = NULL;

    GPBUP = NULL;

    gpio_fd =open("/dev/mem",O_RDWR);

    if (gpio_fd == -1)

    {

    printf("can't open /dev/mem.\n");

    return ;

    }

    gpio_map = (unsigned char *)mmap(0, 0xbc,PROT_READ | PROT_WRITE, MAP_SHARED,gpio_fd, GPIO_CTL_BASE);

    GPBCON = (volatile unsigned int *) (gpio_map+rGPBCON);

    GPBDAT = (volatile unsigned int *) (gpio_map+rGPBDAT);

    GPBUP = (volatile unsigned int *) (gpio_map+rGPBUP);

    //初始化io

    *(volatile unsigned int *)GPBCON=0x154000;

    *(volatile unsigned int *)GPBUP=0x7ff;

    for(i=0;i<16;i++)

    {

    Led_Display(i);

    sleep(1);

    }

    munmap(0, 0xbc);

    if (gpio_fd != 0x0)

    {

    close(gpio_fd);

    }

    printf("GPIO Control Test end\n");

    }

    更多相关内容
  • Linux mmap讲解

    2022-06-05 19:10:39
    Linux 提供了非常强大的 mmap(2) 系统调用; 它使开发人员能够将任何内容直接映射到进程虚拟地址空间 (VAS)。 此内容包括文件数据、硬件设备(适配器)内存区域,或只是通用内存区域。 在本文中,我们将只关注使用 ...

    0 引言

    Linux 提供了非常强大的 mmap(2) 系统调用; 它使开发人员能够将任何内容直接映射到进程虚拟地址空间 (VAS)。 此内容包括文件数据、硬件设备(适配器)内存区域,或只是通用内存区域。 在本文中,我们将只关注使用 mmap(2) 将常规文件的内容映射到进程 VAS 中。

    1 mmap简介

    mmap(2)系统调用接口如下

    #include <sys/mman.h>
    void *mmap(void *addr, size_t length, int prot, int flags,
               int fd, off_t offset);

    如果将文件的给定区域,从给定的偏移量offset和长度length字节映射到我们的进程虚拟地址空间 VAS; 下图描绘了想要实现的目标的简单视图:

    为了实现这个文件映射到进程的VAS,可使用 mmap(2) 系统调用。

    关于mmap的相关参数解释如下

    fd    由open系统调用等打开的文件描述符

    offset  文件映射的偏移位置

    length  文件映射的长度

    addr    向内核提示应该在进程 VAS 中的哪个位置创建映射; 建议在这里传递 0 (NULL),允许操作系统决定新映射的位置

    prot  给定区域的内存保护标志位

    flag  是一个称为标志的位掩码; 有几个标志,它们影响映射的许多属性。

    上述prot具体由如下四个标志构成

    PROT_NONE
    没有访问权限
    PROT_READ
    拥有读权限
    PROT_WRITE
    拥有写权限
    PROT_EXEC
    
    拥有可执行权限

    2 文件和匿名映射

    mmap映射可分为:文件映射和匿名区映射。

    mmap中的flag标记由如下概念

    MAP_SHARED: 映射是共享的; 其他进程可能同时在同一个映射上工作(事实上,这是实现 IPC 机制(共享内存)的通用方式)。 在文件映射的情况下,如果写入内存区域,则更新底层文件! (您可以使用 msync(2) 来控制将内存写入刷新到底层文件。)

    MAP_PRIVATE: 这建立了一个私有映射; 如果它是可写的,它意味着 COW 语义)。 私有的文件映射区域不会对底层文件进行写入。 实际上,私有文件映射在 Linux 上很常见:这正是在开始执行进程时,加载器将二进制可执行文件的文本和数据以及 进程使用的所有共享库的文本和数据。

    3 mmap的优点

    mmap(2) 通过在内部将包含文件数据(从存储设备读入)的内核页面缓存页面直接映射到进程虚拟地址空间来设置文件映射。这是零拷贝技术的一种实现。

    下图更加形象的说明mmap的工作过程

     一个map并不是一个copy。

    4 代码示例

    该部分代码可参考

    #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 = nullptr;
      int fd{-1};
    
      fd = open(argv[1], O_RDONLY);
      if (fd == -1)
        handle_error("open");
    
      addr = static_cast<char*>(mmap(NULL, 10 * sysconf(_SC_PAGE_SIZE), PROT_READ,
                           MAP_SHARED, fd, 0));
      if (addr == MAP_FAILED)
        handle_error("mmap");
    
      while (true) {
        sleep(10);
      }        
      
      exit(EXIT_SUCCESS);
    }
    

    譬如我运行两次

    ./main test1.cc
    ./main test1.cc

    在我的机器上通过fincore test1.cc显示如下结果

    RES PAGES  SIZE FILE
     4K     1  818B test1.cc

    5 总结

    本文总结了mmap的基本概念和使用。

    展开全文
  • linux 字符驱动函数,基于内存隐射--mmap。无需修改,可以运行。
  • Linux mmap 详解

    千次阅读 2020-01-05 20:49:54
    Linux mmap详解一、mmap()是什么二、mmap()原理三、mmap和常规文件操作的区别四、mmap相关函数五、mmap使用细节六、对mmap()返回地址的访问例子:情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置...

    声明:本文绝大多数内容来自于深度分析mmap:是什么 为什么 怎么用 性能总结

    一、mmap()是什么

    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图:
    在这里插入图片描述
    由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。

    linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:
    在这里插入图片描述
    vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。

    二、mmap()原理

    mmap内存映射的实现过程,总的来说可以分为三个阶段:
    (一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

    1. 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
    2. 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
    3. 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化
    4. 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

    (二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

    1. 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表(每个进程有自己的文件描述符表)中找到对应的文件描述符(由在调用用户空间mmap前的open操作完成映射文件的打开),通过文件描述符,链接到内核“已打开文件集”(文件集是存储在内核空间的由系统里的所有进程共享的数据结构)中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

    2. 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

    struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件都对应于一系列的操作,这就是file_operations,用来执行一系列的系统调用。

    1. 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

    2. 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

    Linux内核提供了remap_pfn_range函数来实现将内核空间的内存映射到用户空间:

    /**
       * remap_pfn_range - remap kernel memory to userspace
       * @vma: user vma to map to
       * @addr: target user address to start at
       * @pfn: physical address of kernel memory
       * @size: size of map area
       * @prot: page protection flags for this mapping
       *
       *  Note: this is only safe if the mm semaphore is held when called.
      */
     int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
                 unsigned long pfn, unsigned long size, pgprot_t prot);
    

    上面的注释对参数进行了说明。当用户调用mmap时,驱动中的file_operations->mmap会被调用,可以在mmap中调用remap_pfn_range,它的大部分参数的值都由VMA提供

    (三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
    注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

    1. 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

    2. 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

    3. 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

    4. 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

    注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

    三、mmap和常规文件操作的区别

    常规文件系统操作(调用read/fread等类函数)中,函数的调用过程:

    1. 进程发起读文件请求。
    2. 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。
    3. inode在address_space上查找要请求的文件页是否已经缓存在页缓存中。如果存在,则直接返回这片文件页的内容。
    4. 如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。

    总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

    而使用mmap操作文件中,创建新的虚拟内存区域建立文件磁盘地址与虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

    总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

    四、mmap相关函数

    void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
    int munmap( void * addr, size_t len ) 
    int msync( void *addr, size_t len, int flags )
    

    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。

    可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

    五、mmap使用细节

    1. 使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。

    2. 内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。具体情形参见“情形三”。

    3. 映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。

    六、对mmap()返回地址的访问

    linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,容纳文件被映射部分大小的最少页面个数,决定了进程从mmap()返回的地址开始能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:
    在这里插入图片描述
    注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。

    例子:

    情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。

    分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。映射后的对应关系如下图所示:
    在这里插入图片描述
    此时:
    (1)读/写前5000个字节(0~4999),会返回操作文件内容。
    (2)读字节5000~8191时,结果全为0。写5000~8191时,进程不会报错,但是所写的内容不会写入原文件(磁盘文件)中 。
    (3)读/写8192以外的磁盘部分,会返回一个SIGSECV错误。
    实际代码发现读写整页区域都不会报错,写超过最后一页的内存时会有段错误。

    #include<sys/types.h>
    #include<fcntl.h>
    #include<unistd.h>
    #include<stdio.h>
    #include<string.h>
    
    typedef struct{
    	char name[4];
    	int age;
    }people;
    
    int main(int argc, char** argv)
    {
    	int fd,i;
    	people* p_map;
    	char temp;
    
    	fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 00777);
    	//物理文件
    	lseek(fd, sizeof(people)*5-1, SEEK_SET);
    	write(fd, "", 1);
    	
    	//内存一页4096B,people结构体8B。一页可以放4096/8=512 people结构体。10个peoplez在一页内。
    	p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    	close(fd);
    
    	temp = 'a';
    	
    	//here, we can write 10 people info in memory, but 6-10 people info can not write to file.
    	for(int i=0; i<10; i++)
    	{
    		temp+=1;
    		memcpy((*(p_map+i)).name, &temp, sizeof(temp));
    		(*(p_map+i)).age = 20+i;
    	}
    	
    	//we can access 6-10 people's memory in the same process. not generate error.
    	for(int i=0; i<10; i++)
    	{
    		printf("name: %s, age: %d\n", (*(p_map+i)).name, (*(p_map+i)).age);
    	}
    	
    	temp+=1;
    	//为mmap并未映射但是一页范围内的区域赋值,不会报错
    	memcpy((*(p_map+11)).name, &temp, sizeof(temp));
    	(*(p_map+11)).age = 20+11;
    
    	//为mmap并未映射超出一页范围内的区域赋值,将产生段错误
    	temp+=1;
    	memcpy((*(p_map+513)).name, &temp, sizeof(temp));
    	(*(p_map+513)).age = 20+12;
    
    	printf("initialize over \n");
    	sleep(10);
    
    	munmap(p_map, sizeof(people)*10);
    	printf("ummap ok\n");
    }
    

    虽然上边代码向6-10个people所在的内存写了值,但是关联同一个文件的另一个程序读不到,6-10个people的信息。

    #include<sys/mman.h>
    #include<sys/types.h>
    #include<fcntl.h>
    #include<unistd.h>
    #include<stdio.h>
    #include<string.h>
    
    typedef struct{
    	char name[4];
    	int age;
    }people;
    
    int main(int argc, char** argv)
    {
    	int fd,i;
    	people* p_map;
    
    
    	fd = open(argv[1], O_CREAT|O_RDWR, 00777);
    
    	p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    	close(fd);
    
    	for(int i=0; i<10; i++)
    	{
    		printf("name: %s age: %d\n", (*(p_map+i)).name, (*(p_map+i)).age);
    	}
    	
    	//1.可以读一页内未与文件建立映射的内存
    	//2.一页可以放512个people结构体,513people结构体的name会读到一个EOF
    	//3.可以读取4096个people结构体不报错
    	printf("name: %s, age: %d\n", (*(p_map+11)).name, (*(p_map+11)).age);
    	printf("name: %s, age: %d\n", (*(p_map+511)).name, (*(p_map+511)).age);
    	printf("name: %s, age: %d\n", (*(p_map+512)).name, (*(p_map+512)).age);//out one page-frame memory adress
    	printf("name: %s, age: %d\n", (*(p_map+513)).name, (*(p_map+513)).age);//out one ...
    	printf("name: %s, age: %d\n", (*(p_map+4096)).name, (*(p_map+4096)).age);//out one ...
    
    	munmap(p_map, sizeof(people)*10);
    }
    
    情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。

    分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。如下图所示:
    在这里插入图片描述
    此时:
    (1)进程可以正常读/写被映射的前5000字节(0~4999),写操作的改动会在一定时间后反映在原文件中。
    (2)对于5000~8191字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中。
    (3)对于8192~14999字节,进程不能对其进行读写,会报SIGBUS错误。
    (4)对于15000以外的字节,进程不能对其读写,会引发SIGSEGV错误。

    情形三:一个文件初始大小为0,使用mmap操作映射了1000*4K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr。

    分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。

    但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。例如,文件扩充4096字节,ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页(映射范围)内,ptr都可以对应操作相同的大小。

    这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费

    七、性能总结

    大家关于“mmap()”更快的认识来自于 read() 是需要内存拷贝的;当今硬件技术的发展,使得内存拷贝消耗的时间已经极大降低了;但“mmap()”的开销在于一次 pagefault,这个开销相比而言已经更高了,而且 pagefault 的处理任务现在比以前还更多了;而且,mmap之后,再有读操作不会经过系统调用,在 LRU 比较最近使用的页的时候不占优势;于是,普通读情况下(排除反复读之类的文艺与2B读操作),read() 通常会比 mmap() 来得更快。

    展开全文
  • GPIO的控制,使用MMAP来注册GPIO,之后再以 *rGPBDAT = 0x11 等等直接更改位址
  • linux mmap系统调用

    2021-08-26 19:40:54
    linux 提供了两个比较重要的系统调用brk 和mmap,用于向内核申请相应用户空间,内核会根据系统运行状态判定是否申请新的VMA来管理新申请的用户空间,brk和mmap在整个系统中都占有非常重要的地位。 brk()系统调用被...

    brk/mmap

    linux 提供了两个比较重要的系统调用brk 和mmap,用于向内核申请相应用户空间,内核会根据系统运行状态判定是否申请新的VMA来管理新申请的用户空间,brk和mmap在整个系统中都占有非常重要的地位。

    • brk()系统调用 被gblic进行了进一步封装成malloc接口,用户层程序一般都是通过调用malloc,由glibc间接调用brk来向内核申请用户空间。brk申请的用户空间属于堆空间。
    • mmap系统调用也可以向内核申请用户空间,不过与brk不同的是,mmap申请的空间属于mapping映射空间部分。

    用户空间部分如下图所示:

     mmap

    mmap系统调用函数接口如下:

    #include <sys/mman.h>
    
    void * mmap(void *addr,size_t length, int prot, int flags, int fd,off_t offset)

     参数:

    • addr: 指定映射被放置的虚拟地址,如果将addr指定为NULL,那么内核会为映射分配一个合适的地址。如果addr为一个非NULL值,则内核在选择地址映射时会将该参数值作为一个提示信息来处理。不管采用何种方式,内核会选择一个不与任何既有映射冲突的地址。在处理过程中, 内核会将指定的地址舍入到最近的一个分页边界处。
    • length:参数指定了映射的字节数。尽管length 无需是一个系统分页大小的倍数,但内核会以分页大小为单位来创建映射,因此实际上length会被向上提升为分页大小的下一个倍数。
    • prot: 参数掩码,用于指定映射上的保护信息:标记有:
    描述
    PROT_NONE区域无法访问
    PROT_READ 区域内容可读取
    PROT_WRITE 区域内容可修改
    PROT_EXEC 区域内容可执行
    •  flags:用于指定映射类型,
    flags描述
    MAP_PRIVATE创建一个私有映射。映射区域中内存发生变化对使用同一映射 的其他进程不可见。对文件映射来讲,所发生的变更将不会反应在底层文件上。
    MAP_SHARED区域中内容上所发生的变更对使用同一个映射区域的其他进程可见
    MAP_ANONYMOUS创建一个匿名映射
    MAP_FIXED原样解释addr
    MAP_LOCKED将映射分页锁进内存
    MAP_HUGETLB创建一个使用巨页的映射
    MAP_HUGE_2MB与MAP_HUGETLB一起使用,映射的巨页的页大小为2MB
    MAP_NORESERVE控制交换空间预留
    MAP_POPULATE填充一个映射的分页
    MAP_UNINITALIZED防止一个匿名映射被清零
    MAP_32BIT仅在x86-64系统下支持,映射空间位于前2G空间
    MAP_STACK目前在linux中没有实现,映射空间为stack 空间
    MAP_SHARED_VALIDATE与MAP_SHARED功能一样,区别就是MAP_SHARED会忽略未知flag设置,而MAP_SHARED_VALIDATE会对flags进行检查,如果是unknow flags将会返回EONNOTSUPP
    MAP_SYNC只有和MAP_SHARED_VALIDATE一起使用才有效,仅支持DAX 文件,如果是其他类型文件将会返回错误。

     匿名映射/文件映射

    mmap按照映射的类型主要可以分为文件映射和匿名映射。

    • 文件映射:文件映射是将一个文件的一部分直接映射到调用进程的虚拟内存中,一旦一个文件被映射之后就可以通过在相应内存区域中操作字节来访问文件内容了。映射的分页会在需要的时候从文件中(自动)加载。这种映射被称为基于文件映射或内存映射文件。
    • 匿名映射:一个匿名映射没有相应的文件。相反,这种映射的页面会被初始化为0.

    同时又按照私有映射(MAP_PRIVATE)和共享映射分别将文件映射和匿名映射划分成不同使用用途: 

    可见性文件映射匿名映射
    私有映射(MAP_PRIVATE) 根据文件内存初始化内存,其他进程不可见内存分配
    共享映射(MAP_SHARED)内存映射I/O:进程间共享内存(IPC)进程间共享内存(IPC)

     匿名映射

    匿名映射没有对应文件映射,其创建方法有两种:

    • 在flags中指定MAP_ANONYMOUS并将fd指定为-1
    • 打开/dev/zero设备文件并将得到的文件描述符传递给mmap.。/dev/zero是一个虚拟设备,当从中读取数据时它总是会返回0,而写入到这个设备中的数据总是被丢弃。/dev/zero的一个常见用途使用0来组装一个文件。

    文件映射

    文件映射创建需要执行下面的步骤:

    • 获取文件的一个描述符,通常通过调用open()来完成。
    • 将文件描述符作为fd参数传入mmap()调用。

    执行上述步骤之后mmap()会将 打开的文件内容映射到调用进程的地址空间中。一旦mmap()被调用之后就能够关闭文件描述符,而不会对映射产生任何影响:

    offset参数指定了从文件区域中的哪个字节开始映射,它必须是系统分页大小的倍数。将offset指定为0会导致从文件的起始位置开始映射。length参数指定了映射的字节数,offset和length参数一起确定了文件的哪个区域会被映射进内存。

    共享文件映射

    当多个进程创建同一个文件区域的共享映射时,它们会共享同样的内存物理分页。此外,对映射内存变更将会反应到文件上:

     两个进程共享同一个文件映射,当进程A修改文件的内容将会立即被进程B看到。

    内存映射I/O

    由于共享文件映射中的内容是从文件初始化而来的,并且对映射内存所做出的变更都会自动反应到文件上,因此可以简单地通过访问内存中的文件来执行文件I/O,而依靠内核来确保对内存的变更会传递到映射文件中。(一般来讲,一个程序会定义一个结构化数据类型来与磁盘文件中的内容对应起来,然后使用该数据类型来转换映射的内容。)这项技术被称为内容映射I/O,它是使用read()和write()来访问文件内容这种方法的替代方案。

    • 内存映射I/O映射具备两个潜在的优势:
    • 使用内存访问来取代read()和write()系统调用,可以像访问内存一样访问文件

    在一些情况下,它能够比使用传统的I/O系统调用执行文件I/O这种做法提供更好的性能。

    内存映射之所以带来性能优势:

    • 正常的read()或write()需要两次传输:一次是在文件和内存高速缓冲区之间,另一次是在高速缓冲区和用户空间缓冲之间。使用mmap()就无限第二次传输。对于输入来讲,一旦内核将相应得文件块映射进内存之后用户进程就能够使用这些数据。对于输出来讲,用户进程仅仅需要修改内存中的内容,然后可以依靠内核内存管理全来自动更新底层文件。
    • 除了节省了内存空间和用户空间之间的一次传输之外,mmap()还能够通过减少所需使用的内存来提升性能。当使用read()或write()时,数据将被保存在两个缓冲区中:一个位于用户空间,另一个位于内核空间。当使用mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,如果多个数据正在同一个文件上执行I/O,那么它们通过使用mmap()就能够共享同一个内核缓冲区,从而又能够节省内存的消耗。

    内存映射I/O所带来的性能优势在大型文件中执行重复随机访问时最有可能体现出来。如果顺序访问一个文件,并假设执行I/O时使用的缓冲区大小足够大以至于能够避免执行大量的I/O系统调用,那么与read()和write()相比,mmap带来的性能上的提升就非常有限或者说根本就没有带来性能上的提升。性能提升的幅度之所以非常有限的原因是不管使用何种技术,真个文件的内容在磁盘和内存之间只传输一次,效率的提高主要得益于减少了用户空间和内核空间之间得一次数据传输,并且与磁盘I/O所需得时间相比,内存使用量得降低通常是可以忽略的。

    对于小数据量I/O的开销(即映射、分页故障、接触映射以及更新硬件内存管理单元的超前转换缓冲器)实际上要比简单的read()和write()大。此外,有些时候内核难以高效处理可写入映射的回写(在这种情况下,使用msync()或sync_file_range()有助于提高效率)。

    MAP_FIXED

    MAP_FIXED是mmap() flag中的一个标记位,主要作用就是强制内核原样地解释用户传递的addr地址,按照应用程序指定的地址进行分配空间,而不是仅仅作为一种提升信息。如果 mmap函数指定了MAP_FIXED标记位,addr地址必须是页对齐的。

    特别要注意的是如果要增强程序的可移植性,不建议使用MAP_FIXED。因为一旦使用该标记位 就是由用户强制指定分配的用户空间地址,而每个系统千差万别,可移植性较差。

    如果在调用mmap()时指定了MAP_FIXED,并且内存区域的起始地址为addr,覆盖的length字节与之前的映射分页重叠了,那么重叠的分页会被新映射替代。使用这个特性可移植地将一个文件的多个部分映射进一块连续的内存区域。

    可以利用上述这个特性来增强使用MAP_FIXED的可移植性,具体使用方法:

    • 1:使用mmap()创建一个匿名映射。在mmap()调用中将addr指定为NULL 并且不指定MAP_FIXED标记,这样就允许内核为映射选择一个地址。
    • 2:使用一系列指定MAP_FIXED标记的mmap()调用来将文件区域映射(即重叠)在上一步中创建的映射的不同部分。

    尽管可以忽略第一个步骤而直接使用一系列mmap() MAP_FIXED操作来在应用程序选中的地址范围内创建一组连续的映射,但这种做法的可移植性与上面这种两步做法相比可移植性要差。一个应用程序应该避免在固定的地址处创建新映射。上面的第一步避免了可移植性问题的出现,因为这一步让内核选择一个连续的地址范围,然后在该地址范围内创建新映射。

    MAP_FIXED安全风险

    正如上面说讲述的,MAP_FIXED可以由应用程序来指定映射的虚拟地址,如果映射的虚拟地址空间已经存在,则会将旧的VMA给清掉,重新生成一个新的VMA。虽然很多驱动是这样做的,但是这样操作非常危险,因为应用程序不小心搞错 指定的虚拟地址是一个很关键的空间,则会将整个程序破坏掉,同时也会造成很重要的安全问题。

    Any mmap() call allows the calling process to specify an address for the mapping. In normal operation, though, this address is simply a hint that the kernel is free to ignore. MAP_FIXED exists for cases where the mapping really has to be placed at the requested address or the application will fail to work. The kernel takes this flag seriously, to the point that, if there is already another mapping in the given address range, the existing mapping will be destroyed to make room for the new one. This seems like a strange semantic; if an application wants a mapping at a given area, it should probably be able to take responsibility for making room for that mapping. But mmap() is specified to work that way, so that is what happens.

    Needless to say, that can be problematic if the application wasn't aware of the conflicting mapping — something that could occur as the result of a bug, address-space layout randomization, disagreements between libraries, or deliberate manipulation by an attacker. The data contained within that mapping (or the overlapping part of it, at least) will be silently dropped on the floor and the new mapping will show up in its place. The chances of things working correctly after that are likely to be fairly small. In some cases, security vulnerabilities can result; see, for example, CVE-2017-1000253. In that case, the kernel's internal use of MAP_FIXED to load programs into memory was exploited to corrupt the stack.

    MAP_FIXED_NOREPLACE

    为了 解决MAP_FIXED 所带来的风险,内核社区从4.17版本中开始引入新的flag标记位MAP_FIXED_NOREPLACE,该标记位意思是如果指定的虚拟地址 已经存在,则不执行替换覆盖旧的VMA动作,直接返回。如果不是特殊需求,建议使用MAP_FIXED_NOREPLACE增加程序安全性。

    A solution can be found in Michal Hocko's MAP_FIXED_SAFE patch set. It adds a new mmap() flag called, surprisingly, MAP_FIXED_SAFE with semantics similar to MAP_FIXED with one exception: the operation will fail if the targeted address range is not free. The kernel's ELF loader is modified to use this new flag when mapping programs into memory; that will cause program loading to fail if two mappings collide, but that is better than the alternative. It is expected that new code would use this new flag in almost all cases, and that older programs would eventually be switched as well

    MAP_FIXED解决方案是由Michal Hocko提出 刚开始新的字段命名位MAP_FIED_SAFE意味着是安全操作,代码审核阶段有人提出了该标记位命名并不能让人一看一目了然,正式合入内核中将其修改位MAP_FIXED_NOREPLACE。 

    参考资料

    《linux/UNIX 系统编程手册》

    MAP_FIXED_SAFE [LWN.net]

    展开全文
  • linux mmap详解

    2020-07-14 19:38:53
    mmap基础概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式...
  • Linux mmap内存映射

    2021-03-25 21:19:37
    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系,函数原型如下 void *mmap(void *addr, size_t length, int prot,...
  • mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而 Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享...
  • Linux mmap

    千次阅读 2018-07-03 17:50:24
    mmap基础概念1.mmap内存映射原理mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系;实现这样的映射关系后,进程就...
  • 1.mmap使用前需要一次读取文件的操作 2.作为内存映射的文件,文件本身要存在,文件要具有权限(简单体现 linux上文件上不能有把锁的图表) 3.作为内存映射的文件,文件不能为空 4.通过内存映射把内容写回文件,...
  • linux mmap内存文件映射

    千次阅读 2019-04-12 14:55:29
    linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址...
  • linux mmap 用法(1)

    2020-11-17 10:32:53
    linux mmap 用法(1)mmap mmap mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。 ...
  • c++ linux mmap函数使用

    2022-08-31 21:51:59
    mmap
  • void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); int munmap(void *addr, size_t length); int msync(void *addr, size_t length, int flags);
  • linux mmap 底层原理

    千次阅读 2018-09-02 18:08:18
    aarch32 linux3.18 userspace 的mmap使用方法,mmap 用法如下 void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset); int munmap(void *start, size_t length); 返回说明: 成功...
  • Linuxmmap

    2022-03-16 09:26:29
    创作人QQ:851301776,邮箱:lfr... 欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点! 个人座右铭: 1.... 2.... 其中memory Mapping Segment主要用作mmap。 3.1 mmap系统调用说明 mm...
  • linux mmap 实现

    2021-03-03 23:09:07
    内核态 mmap 用户态mmap函数
  • 如题,在调用mmap后进行编译时,提示错误PORT_READ undeclared和PORT_READ undeclared。 什么原因呢? 哈哈! 原来是拼写错了。 不是PORT_READ 是PROT_READ 不是PORT_WRITE 而是PROC_WRITE 很多人首次用mmap...
  • Linuxmmap函数简介

    2021-05-14 12:12:32
    本文主要讲述mmap 函数的使用,与驱动中 mmap 函数的实现mmap 怎么使用,怎么实现,为什么 mmap 可以减少额外的拷贝?下面简单详情。一、 mmap 的使用#include void *mmap(void *addr, size_t len, int prot, int ...
  • 原创 宋宝华 Linux阅码场 2018-03-05前面有童鞋在微信群里面问mmap,然后敲了很多字,不想浪费,把这些字搬家到公众号。本文总结归纳mmap的数种情况,进行一个归类。我们都知道,Linux一个进程使用的内存分为2种:...
  • Linux编程之mmap示例

    2021-06-19 18:26:46
    一、Linux下,针对we传统的读写文件一般来说, 修改一个文件的内容需要如下3个步骤: 把文件内容读入到内存中。 修改内存中的内容。 把内存的数据写入到文件中。 过程如图 1 所示:如果使用代码来实现上面的过程,...
  • Mmap可以在某些方面帮助你,我会用一些假设的例子来解释:第一件事:假设你的内存不足,你的应用程序有一个100MB的内存不足,它的50%被交换出来,这意味着操作系统必须写入50MB的交换文件,如果你需要读回来,你已经写过,...
  • linux mmap内存映射文件的理解

    千次阅读 2018-01-04 00:51:12
    利用ioremap访问硬件,需要经过两次拷贝 ...mmap的原理使用技巧: https://www.cnblogs.com/huxiao-tee/p/4660352.html https://www.cnblogs.com/xiaocry/p/5529712.html
  • linux库函数mmap()原理及用法详解

    千次阅读 2021-08-25 00:45:14
    1.mmap基本概念 2.mmap内存映射原理 3.mmap和常规文件操作的区别 4.mmap优点总结 5.mmap相关函数 6.mmap使用细节 1.mmap基本概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址...
  • Linux mmap原理

    2018-10-31 17:46:56
    进程用mmap来分配内存,读取大文件,进程间通讯等 void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset) 当创建共享的匿名映射时,内核会打开dev/zero结点,把共享匿名映射到文件。...
  • linuxmmap()函数

    2021-05-13 10:30:02
    最近看完了Beginning Linux Programming的文件系统部分,感觉还是有颇多收获,对系统调用有了一个初步的概念,同时也了解了标准I/O库和Linux系统调用函数的关系.不过关于mmap的内存映射机制理解的不透彻,毕竟书上讲的...
  • linux/module.h> #include <linux/init.h> #include <linux/types.h> //定义dev_t类型 #include <linux/cdev.h> //定义struct cdev结构体及相关操作 #include <linux/slab.h> //...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 64,099
精华内容 25,639
关键字:

linux mmap