精华内容
下载资源
问答
  • Linux下内存分配与映射之一

    千次阅读 2013-05-05 18:32:26
    地址类型: ...内核逻辑地址:内存的部分或全部映射,大多数情况下,它物理地址仅差一个偏移量。如Kmalloc分配的内存。 内核虚拟地址:内核空间的地址映射到物理地址上,但映射不必是线性的。所有

    地址类型:

    32位的cpu,共4G空间,其中0-3G属于用户空间地址,3G-4G是内核空间地址。

    用户虚拟地址:用户空间程序的地址

    物理地址:cpu与内存之间的使用地址

    总线地址:外围总线和内存之间的使用地址

    内核逻辑地址:内存的部分或全部映射,大多数情况下,它与物理地址仅差一个偏移量。如Kmalloc分配的内存。

    内核虚拟地址:内核空间的地址映射到物理地址上,但映射不必是线性的。所有的逻辑地址都是内核虚拟地址,如Vmalloc分配到的地址。

    1、内存分配与释放

    kmalloc一般用于分配小于128KB的内存。

    Vmalloc分配的内存空间是一片连续的虚拟内存,但映射到物理内存却不一定连续。

    2、用户态和内核态内存交互

    copy_from_user

    copy_to_user

    access_ok

    在访问用户空间的内存时,必须先检查用户控件的指针是否合法。使用access_ok函数。

    3、物理地址到虚拟地址的映射

    CPU对外设I/O端口物理地址的编址方式分I/O映射方式和内存映射方式。在x86平台上,为外设专门实现了与RAM内存地址不同的一个单独的地址空间I/O方式。而在ARM、MIPS、PowerPC上外设I/O端口具有与内存相同的物理地址。

    ioremap函数用来将I/O内存资源的物理地址映射到核心地址空间(3G-4G)中。

    ioremap取消ipremap所做的映射。

    读写I/O的函数包括:

    _raw_writeb,_raw_writew,_raw_writel

    _raw_readb,_raw_readw,_raw_readl

    4、内核空间到用户空间的映射

    想在用户空间访问内核地址,可以采用mmap方法。

     映射一个设备意味着使用户空间的一段地址关联到设备内存上,这使得如果程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问


    展开全文
  • Linux下内存分配与映射之二

    千次阅读 2013-05-24 21:25:01
    Linux下内存管理 进程是运行于虚拟地址空间的一个程序。任何在Linux系统下运行的程序都是进程。大多数进程都需要虚拟内存。Linux支持虚拟内存,就是使用磁盘作为RAM的扩展,时可用内存相应的扩大。用作虚拟内存的...

    Linux下内存管理

    进程是运行于虚拟地址空间的一个程序。任何在Linux系统下运行的程序都是进程。大多数进程都需要虚拟内存。Linux支持虚拟内存,就是使用磁盘作为RAM的扩展,时可用内存相应的扩大。用作虚拟内存的这部分磁盘叫做交换空间(SWAP)。

    Linux操作系统采用请求式分页虚拟存储管理方法。系统为每个进程提供了4GB的虚拟存储空间。,各个进程的虚拟存储空间彼此独立。

    虚拟内存提供的功能包括:

           . 地址空间(0-4G)

           . 进程的保护

           . 内存映射

           . 公平的物理内存分配

           . 共享虚拟内存

     

    一 页

           内核把物理页作为内存管理的基本单位;内存管理单元(MMU)把虚拟地址转换为物理

    地址,通常以页为单位进行处理。MMU以页大小为单位来管理系统中的页表。

           32位系统:页大小4KB

           64位系统:页大小8KB

    内核用相应的数据结构表示系统中的每个物理页:

      <linux/mm_types.h>

      struct page {}

    内核通过这样的数据结构管理系统中所有的页,因此内核判断一个页是否空闲,谁有拥有这个页

    ,拥有者可能是:用户空间进程、动态分配的内核数据、静态内核代码、页高速缓存……

    系统中每一个物理页都要分配这样一个结构体,进行内存管理。

    二 区

           Linux内存寻址存在问题:

    一些硬件只能用某些特定的内存来执行DMA(直接内存访问)

    一些体系结构其内存的物理寻址范围必须你寻址范围大得多。这样导致一些内存不能永久映射到内核空间上。

           通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。当内核模块代码或线程访问内存时,

    代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射。因此内核空间地址为3~4G,

    最多只能映射到1G空间的内存,超出1G大小的内存将如何去问呢!

           由于存在上述条件的限制。Linux将内核空间地址划分为三个区:

    ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。

           ZONE_HIGHMEM即为高端内存,这就是内存高端内存概念的由来。

     

    在x86结构中,三种类型的区域如下:

      ZONE_DMA        内存开始的16MB

      ZONE_NORMAL       16MB~896MB

      ZONE_HIGHMEM       896MB ~ 结束

    同样每个区包含众多页,形成不同内存池,按照用途进行内存分配。

    用相应的数据结构来表示区:

      <linux/mmzone.h>

      struct zone {}

    三 获取页/内存

    static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)

    该函数分配2的order次方个连续的物理页,返回指向第一个页的page结构体指针。

     

    void *page_address(const struct page *page)

    返回指向给定物理页当前所在的逻辑地址

    extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

    extern unsigned long get_zeroed_page(gfp_t gfp_mask);

    释放:

    extern void __free_pages(struct page *page, unsigned int order);

    extern void free_pages(unsigned long addr, unsigned int order);

     

    内存的分配可能失败,内存的释放要准确!

     

    1 kmalloc

    kmalloc()函数与用户空间malloc一组函数类似,获得以字节为单位的一块内核内存。

    void *kmalloc(size_t size, gfp_t flags)

    void kfree(const void *objp)

     

    分配内存物理上连续。

    gfp_t标志:表明分配内存的方式。如:

    GFP_ATOMIC:分配内存优先级高,不会睡眠

    GFP_KERNEL:常用的方式,可能会阻塞。

     

    2 vmalloc    

     

    void *vmalloc(unsigned long size)

    void vfree(const void *addr)

    vmalloc()与kmalloc方式类似,vmalloc分配的内存虚拟地址是连续的,而物理地址则无需连续,与用户空间分配函数一致。

    vmalloc通过分配非连续的物理内存块,在修正页表,把内存映射到逻辑地址空间的连续区域中,虚拟地址是连续的。

           是否必须要连续的物理地址和具体使用场景有关。在不理解虚拟地址的硬件设备中,内存区都必须是连续的。

           通过建立页表转换成虚拟地址空间上连续,肯定存在一些消耗,带来性能上影响。

    所以通常内核使用kmalloc来申请内存,在需要大块内存时使用vmalloc来分配。

     

    四 slab层

           内核中经常进行内存的分配和释放。为了便于数据的频繁分配和回收,通常建立一个空

     

    闲链表——内存池。当不使用的已分配的内存时,将其放入内存池中,而不是直接释放掉。

           Linux内核提供了slab层来管理内存的分配和释放。

    频繁分配和回收必然导致内存碎片,缓存他们.

    slab层得设计实现

           slab层把不同的对象划分为所谓的高速缓存组。每个高速缓存组存放不同类型的对象。高速缓存划分为slab,

    slab由一个或多个物理上连续的页组成。每个slab处于三种状态之一:满,部分满,空。

    高速缓存,slab,对象之间的关系:

              图  1. slab 分配器的主要结构

     

     

           与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的分配,

    它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,

    从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复

    进行初始化。最后,slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,

    从而提高缓存的利用率并获得更好的性能。

     

    slab数据结构和接口:

    每个高速缓存用kmem_cache结构来表示:

           struct kmem_cache {

                  struct kmem_list3 **nodelists;

                  ……

           }

    缓存区包含三种slab:满,未满,空闲

    struct kmem_list3 {

           struct list_head slabs_partial; /* partial list first, better asm code */

           struct list_head slabs_full;

           struct list_head slabs_free;

           ……

    };

    每一个slab包含多个对象:

    struct slab {

                  struct list_head list;

                  unsigned long colouroff;

                  void *s_mem;            /* including colour offset */

                  unsigned int inuse;     /* num of objs active in slab */

                  kmem_bufctl_t free;

                  unsigned short nodeid;

    };

     

    相关接口:mm/slab.c

                  内核函数 kmem_cache_create 用来创建一个新缓存。这通常是在内核初始化时执行的,或者在首次加载内核模块时执行。

    struct kmem_cache *kmem_cache_create (

      const char *name,

      size_t size,

      size_t align,

      unsigned long flags,

      void (*ctor)(void *))

          

    name 参数定义了缓存名称,proc 文件系统(在 /proc/slabinfo 中)使用它标识这个缓存。

    size 参数指定了为这个缓存创建的对象的大小,

    align 参数定义了每个对象必需的对齐。

    flags 参数指定了为缓存启用的选项:

      kmem_cache_create 的部分选项(在 flags 参数中指定)

      SLAB_RED_ZONE    在对象头、尾插入标志,用来支持对缓冲区溢出的检查。

      SLAB_POISON  使用一种己知模式填充 slab,允许对缓存中的对象进行监视(对象属对象所有,不过可以在外部进行修改)。

      SLAB_HWCACHE_ALIGN      指定缓存对象必须与硬件缓存行对齐。

    ctor 和 dtor 参数定义了一个可选的对象构造器和析构器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象时,可以通过构造器进行初始化。

        要从一个命名的缓存中分配一个对象,可以使用 kmem_cache_alloc 函数。

     

    void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );

    这个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用 cache_alloc_refill 向缓存中增加内存。

    kmem_cache_alloc 的 flags 选项与 kmalloc 的

    cachep:所建立的缓存区

    flags参数:

      GFP_USER 为用户分配内存(这个调用可能会睡眠)。

      GFP_KERNEL    从内核 RAM 中分配内存(这个调用可能会睡眠)。

      GFP_ATOMIC   使该调用强制处于非睡眠状态(对中断处理程序非常有用)。

      GFP_HIGHUSER      从高端内存中分配内存。

     

    五 高端内存的映射

    永久映射:可能会阻塞

      映射一个给定的page结构到内核地址空间:

      void *kmap(struct page *page)

      解除映射:

      void kunmap(struct page *page)

     

    临时映射:不会阻塞     

    void *kmap_atomic(struct page *page)

     

    六 分配函数的选择

      l  连续的物理页:kmalloc或者低级页分配器

      l  高端内存分配:alloc_pages 指向page结构指针,不是逻辑地址指针。再通过kmap()把高端地址内存映射到内核的逻辑地址空间。

      l  无需连续物理地址:vmalloc 虚拟地址连续物理地址可能不连续,相对存在性能损失

      l  频繁创建和销毁很多较大数据结构:建立slab缓存区,提高对象分配和回收性能

     

     

    展开全文
  • linux下静态映射与动态映射原理分析

    千次阅读 2018-08-06 21:38:16
    2017年10月1号 (1)外设IO寄存器地址独立编址的CPU,这时该称外设IO寄存器为IO端口,访问IO寄存器可通过io...例如:x86平台普通使用了名为内存映射的技术,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就...

     

    2017年10月1号

    (1)外设IO寄存器地址独立编址的CPU,这时该称外设IO寄存器为IO端口,访问IO寄存器可通过ioport_map将其映射到虚拟地址空间,但实际上这是给开发人员制造的一个“”假象”,并没有映射到内核虚拟地址,仅仅是为了可以使用和IO内存一样的接口访问IO寄存器

    例如:x86平台普通使用了名为内存映射的技术,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访问内存一样。

     

    (2)外设IO寄存器地址统一编址的CPU,这时应该称外设IO寄存器为IO内存,访问IO寄存器可通过ioremap将其映射到虚拟地址空间,然后在使用read/write接口访问

    访问的方法:首先调用request()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release()申请的io内存资源

     

    2、动态映射和静态映射2017年12月18日补充

    2.1  这里涉及到动态映射和静态映射,比如某个公司开发可一块板子,针对linux2.6.35移植内核,该公司在内核里面添加针对自己板子物理地址对应虚拟地址的静态映射表,也就是4G的虚拟地址的某个地址(如0xFDF000240)对应的实际板子IO寄存器物理地址(0xE0001234),这个虚拟地址被静态绑定对应的物理地址后,我们的内核代码使用动态映射时无法在使用这个虚拟地址虚拟地址4G,实际物理可能只有256M,我们编写代码时,是想象有4G能使用,其实,很多时候是动态映射,如虚拟地址0xFF000000动态映射到物理地址0xEE000000,使用了这个地址的寄存器运算,因为我们写代码时想象有4G,所以虚拟地址用的很奢侈,0到4G的范围使用,就想在虚拟地址0xFFF0000F再对应一个新的物理地址,想一一对应,可是物理地址没有这么大,这个时候,0xFFF0000F对应的还是物理地址0xEE000000,上一个设备模块使用完后,释放了,映射关系被销毁,也有一些内存是直接一一对应映射的,虚拟等于物理,因为速度的要求和别的原因,因为建了动态映射,需要内核管理(申请,销毁等步骤),耗用时间,才换来了内存。静态映射只是绑定了虚拟地址到物理地址(动态映射是在这里耗费了时间,比如给一个物理地址你还要通过函数得到一个虚拟地址,之后都是一样的),并不是将虚拟地址空间同时分配占用了物理运行内存。

    2.2  上面说到的蜂鸣器补充:蜂鸣器绑定了一个物理地址,无法改变,当我们的代码申请虚拟地址后(只有申请到的虚拟地址,才有对应物理内存给你运行代码,并对你要操作的蜂鸣器所对应的物理地址所在寄存器赋值,这时,这个虚拟地址没有释放前,就像静态映射一样,当然也占用着同等大小对应物理内存运行它,别人是无法通过其他虚拟地址及占用其他物理运行内存去操控这个蜂鸣器,这个虚拟地址所用到的运行内存在运算,还将最后的值绑定似的赋给蜂鸣器所在物理地址,当释放后,这个虚拟地址被释放,对应占用的物理内存空间也被释放。程序再次用蜂鸣器物理地址申请蜂鸣器IO时,ioremap()返回一个新的空闲的虚拟地址,当然会有相应的物理内存提供这个虚拟地址的代码去运行,再去操作。)

    2.3  虚拟地址说的是虚拟地址空间,虚拟地址空间申请到后,会同时分配物理运行内存让其运行,这时可以理解虚拟地址空间就是物理运行内存空间,不同虚拟地址空间能在不同时刻对应相同的物理运行内存,这就是模拟出4G的原理。

     

    3、内核的虚拟地址映射方法

    3.1  为什么需要虚拟地址映射

            (1)mmu单元打开:内核、应用层都使用虚拟地址,关闭:全使用物理地址

    3.2  内核中有2套虚拟地址映射方法:动态和静态

    3.2.1  静态映射方法的特点:

            (1)内核移植时以代码的形式硬编码,如果要更改必须改源代码后重新编译内核,在内核启动时建立静态映射表,到内核关机时销毁,中间一直有效

            (2)对于移植好的内核,你用不用他都在那里

            (3)这个静态映射表就是我们平时说的直接映射,虚拟地址等于物理地址,就是我们制作开发板,官方将一些物理地址直接绑定一些功能

           (4)比如led灯物理地址的绑定,对于移植好的内核,你用不用这套机制都在那里,你去使用那就效率高,缺点就是绑定物理地址,占用内存。led也可以建立虚拟地址映射之后再去应用层操作,效率相对低一些,但是使用完就释放,内存使用率高。

    3.2.2  静态映射的硬编码代码:

     

    (1)映射表具体物理地址和虚拟地址的值相关的宏定义

    (2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
    在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
    smdkc110_map_io
    s5p_init_io
    iotable_init
            结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
     

    (3)开机时调用映射表建立函数

        问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
            start_kernel
             setup_arch
    paging_init
    devicemaps_init

            if (mdesc->map_io)
    mdesc->map_io();

           

    3.2.3  动态映射方法的特点:

    1 驱动程序根据需要随时动态的建立映射、使用、销毁映射

                      (1)比如我们通过芯片手册知道了物理地址而不知道虚拟地址,调用内核给我们提供的动态映射的函数,我们就能得到内存给我们临时分配得虚拟地址,实现机制不用你管,用完之后你在释放它

                     (2)给你分配的地址就是给你一块可以运行的内存段去运行你的代码(运算、操作你的硬件过程),然后达到目的后,你在释放这段内存,让别人去运行。

            2  先在芯片手册上找到硬件物理地址地址,通过下面函数得到虚拟地址,再去进行硬件操作   

            #define GPJ0CON_PA 0xe0200240

            #define GPJ0DAT_PA 0xe0200244

            // 使用动态映射的方式来操作寄存器

    if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))

    return -EINVAL;

            pGPJ0CON = ioremap(GPJ0CON_PA, 4);  //函数返回值返回一个指针,指向寄存器定义的虚拟地址
    pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);

    *pGPJ0CON = 0x11111111;
    *pGPJ0DAT = rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));

     

     

    4、如何选择虚拟地址映射方法

        (1)  2种映射并不排他,可以同时使用
        (2)  静态映射类似于C语言中全局变量,动态方式类似于C语言中malloc堆内存

     

        (3) 静态映射的好处是执行效率高,坏处是始终占用虚拟地址空间;动态映射的好处是按需使用虚拟地址空间,坏处是每次使用前后都需要代码去建立映射&销毁映射(还得学会使用那些内核函数的使用)

     

     

     

     

     

    展开全文
  • Linux - 内存映射

    千次阅读 2014-09-06 13:21:18
    Linux - 内存映射简介
    一  内存映射概述
              
              从原理上讲,Linux系统利用已有的存储管理机制可以很自然的实现进程间的共享存储。对于一段物理存储空间,只需通过进程的 虚存管理机构 就可以映射到各自的3G用户地址空间中。通过这种映射,在不同进程看来“私有”的数据事实上是同一段内存单元,它们被这些不同的进程所共享。
         
            在Linux系统实际运行时,内存中的页面要经常被换入或换出,共享存储区中的页面也不例外。一般而言,内存页面的换入/换出过程采用两种方式进行:
                   1. 普通页面因长时间未得到访问而被内核线程kswaps在系统空闲而得到调度时换出内存到磁盘上的页面交换区,或因为进程访问的页面不在内存引起缺页从而将曾被换出到页面交换区的页面重新换入。
                   2. 针对某个被打开的磁盘文件在内存中的页缓冲,在内资源不足而需要增加空闲页面时,由内核线程bdflush在系统空闲而得到调度时按照LRU算法将“脏”页面写回磁盘文件以回收空闲页面(或由用户强制“刷出”页面),或者因进程所读文件某段数据不在内存而启动磁盘IO读文件数据到内存中的文件缓冲区。
           
           方式1本身就是操作系统已实现的页调度机制,从用户角度来看,它是完全透明、不必额外关心的底层功能;而方式2依托某一种文件系统,需显式创建磁盘文件。因此该方式实现的页面交换功能对用户并不透明,需用户干涉。但从功能角度来看,它们却具有共同的本质:磁盘到物理内存之间的动态页面交换。

           mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
         mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

    二 内存映射的原理
     
          “映射” 主要是指  硬盘上文件  的位置与进程  逻辑地址空间 中一块大小相同的区域之间的一一对应,如下图所示。这种对应关系纯属是逻辑上的概念,物理上是不存在的,原因是进程的逻辑地址空间本身就是不存在。在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

     

      
      

                                                                        内存映射原理  


           既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。

           mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,如上图过程2所示。

          前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,图过程3所示。

           如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如图过程4所示。

      
            在Linux系统中,设计mmap()系统调用的本意是提高文件操作的效率。通过mmap(), 进程可以把一个文件的内容映射到它的虚存空间并以访问内存的方式实现文件的读写操作,这为文件读写提供了极大的方便; 如果多个不同的进程通过这种方式映射同一个文件,则可以共享该文件对应的物理存储空间。  如下图所示:
                               

         注:在进程的地址空间中,栈的下方是内存映射段,内核直接将文件的内容映射到内存,通过虚存管理机构将进程中映射的地址转换为内存中的物理地址。

           这恰恰完成了上述共享存储机制中的两个关键要 素:底层的页面换入换出(磁盘文件->内存)功能以及物理存储(内存)到进程空间的映射,并最终实现了一种进程间通信的手段。这种共享存储的功能虽然是mmap()系统调用的“副产品”,但无论从功能角度还是从实现原理角度来看,它都具备共享存储的特征。

                  

          使用mmap对于设备文件,最大的优点就是用户空间可以直接访问设备内存普通文件被映射到进程地址空间后,进程进程访问文件的速度也变快,不必再调read(),write()等系统调用,可以用memcpy,strcpy等操作写文件,写完后用msync()同步一下。mmap()的这种能力用于显示适配器一类的设备,屏幕帧的像素不再需要从一个用户空间到内核空间的复制过程

            从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如下图过程1,然后再将这些数据拷贝到用户空间,如 图中过程2,在这个过程中,实际上完成了  两次数据拷贝  ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了  一次数据拷贝 因此,内存映射的效率要比read/write效率高。                         
        
           

                                                         read系统调用原理


    小结 使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

    三、代码实现
       
        1、mmap系统调用
            #include <sys/mman.h>
           void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset );
          内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。

       参数含义:

            addr 指定映射的起始地址, 通常设为NULL由系统指定
         length:   映射到内存的文件长度,即可用访问的数据量
            prot 映射区的保护方式,它是下列常值的按位OR结果
                   PROT_EXEC: 映射区可被执行
                  PROT_READ: 映射区可被读取
                   PROT_WRITE: 映射区可被写入
                   PROT_NONE  映射区不可访问.

         flags: 映射区的特性, 可以是: MAP_SHARED、MAP_PRIVATE、MAP_FIXED
          MAP_SHARED:对此区域所做的修改内容奖写入文件内允许其他映射该文件的进程共享,意思是:n个mmap.out程序在运行,这n个进程的“虚拟内存区域”的物理空间空间都相同。

          

            MAP_PRIVATE:对此区域所做的修改不会更改原来的文件内容,对映射区的写入操作会产生一个映射区的复制(copy-on-write);意思是:n个mmap.out程序在运行,但是虚拟内存区域的物理地址内核另外分配

                  

          fd: 由open返回的文件描述符, 代表要映射的文件。
          offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。

     返回值:返回成功----函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址,该内存区域与可以通过一个打开的文件描述符访问的文件内容相关联。如果失败返回MAP_FAILED(-1),错误原因存于errno 中。

      2、munmap 函数
          int munmap(void *addr, size_t length);
          用于取消参数addr所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述符时不会解除映射。
        
      3、 msync函数
           int msync(const void *start, size_t length, int flags);
         把在该内存段的某个部分的或整段中的修改写回到被映射的文件中(或者从被映射文件中读出)。
       
      参数含义:
       start  修改部分的起始地址,length为长度。
       flags则有三个:
       MS_ASYNC : 采用异步写方式,请Kernel快将资料写入,发出回写请求后立即返回。
       MS_SYNC :  采用同步写范式,在msync结束返回前,将资料写入。
       MS_INVALIDATE:
    通知使用该共享区域的进程,数据已经修改,在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值。

    实例:
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <stdlib.h>
    
    
    
    typedef struct {
        int integer;
        char string[24];
    }RECORD;
    
    #define NRECORDS (100)
    
    int main(int argc,char**argv)
    {
        RECORD record,*mapped;
        int i,f;
        FILE *fp;
    
        //打开初始化文件
        fp = fopen("record.dat","w+");
        for (i=0;i<NRECORDS;i++)
        {
            record.integer = i;
            sprintf(record.string,"RECORD-%d",i);
            fwrite(&record,sizeof(record),1,fp);
        }
        fclose(fp);
    
        //把第43条记录中的整数值由43修改为143,并把它写入第43条记录中的字符串
        fp = fopen("record.dat","r+");
        fseek(fp,43*sizeof(record),SEEK_SET);
        fread(&record,sizeof(record),1,fp);
    
        record.integer = 143;
        sprintf(record.string,"RECORD-%d",record.integer);
    
        fseek(fp,43*sizeof(record),SEEK_SET);
        fwrite(&record,sizeof(record),1,fp);
        fclose(fp);
    
        //把这些记录映射到内存中,然后访问第43条记录,把它的整数值修改为243
        f = open("record.dat",O_RDWR);
        mapped = (RECORD*)mmap(0,NRECORDS*sizeof(record),PROT_READ|PROT_WRITE,MAP_SHARED,f,0);
        mapped[43].integer = 243;
        sprintf(mapped[43].string,"RECORD-%d",mapped[43].integer);
    
        msync((void*)mapped,NRECORDS*sizeof(record),MS_ASYNC);
        munmap((void*)mapped,NRECORDS*sizeof(record));
        close(f);
    
        exit(0);
    
        return 0;
    }
    
    
    
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc,char**argv)
    {
        int fd; //文件描述符
        char* mapped, *p;
        int flength      = 1024;
        void *start_addr = 0;
    
        if (argc < 2)
        {
            printf("argc less than 2\n");
            exit(-1);
        }
    
        fd = open(argv[1],O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
        flength = lseek(fd,1,SEEK_END);
        write(fd,"\0",1); //在文件末尾添加一个空字符
        lseek(fd,0,SEEK_SET);
        mapped = mmap(start_addr,flength,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        printf("%s\n",mapped);
    
        while((p=strstr(mapped,"test")))
        {
            memcpy(p,"map",3);
            p+=3;
        }
        munmap((void*)mapped,flength);
        close(fd);
    
        return 0;
    }


    四 小结
          在实际程序中大量运用了mmap,用到的正是mmap的这种“像访问普通内存一样对文件进行访问”的功能。实践证明,当要对一个文件频繁的进行访问,并且指针来回移动时,调用mmap比用常规的方法快很多。

    参考:
               Linux中mmap系统调用原理分析与实现
    展开全文
  • 如何将linux用户空间的一段内存(已分配)映射到内核空间,使得在内核访问用户时不用复制数据而直接访问数据 请问能不能给出具体的代码演示功能的实现呢? 万分感谢大家!!!!
  • linux内核地址映射

    千次阅读 2017-04-03 16:47:59
    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能...Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存
  • 一,线性映射与非线性映射1. 内存管理物理内存管理:Linux内存最小管理单位为页(page),通常一页为4K。初始化时,linux会为每个物理内存也建立一个page的管理结构,操作物理内存时实际上就是操作page页。某些设备...
  • 日期 内核版本 架构 作者 GitHub ...1 前景回顾1.1 内核映射区尽管vmalloc函数族可用于从高端内存域向内核映射页帧(这些在内核空间中通常是无法直接看到的), 但这并不是这些函数的实际用途.重要
  • linux不连续页分配和内核映射

    千次阅读 2012-09-02 11:52:47
    若要分配一段连续的内存页,而物理内存中又不存在这么多连续的内存页怎么办,可以把多个不连续的内存页映射到连续的虚拟地址空间中,当进程访问内存,进程看到的总是一块连续的空间。下面看看不连续页的分配分配...
  • Linux地址映射

    千次阅读 2019-09-04 17:10:46
    一、地址映射 .class文件→ jvm → linux 用【javap -c 字节码名】 可以打印出jvm的指令,最终转成linux系统平台的汇编指令来执行 c/c++,java,python,php,go语言都是指令(text)和数据(data) 在程序还没有...
  • 一,线性映射与非线性映射 1. 内存管理 物理内存管理: Linux内存最小管理单位为页(page),通常一页为4K。初始化时,linux会为每个物理内存也建立一个page的管理结构,操作物理内存时实际上就是操作page页。...
  • linux驱动-映射进程空间

    千次阅读 2017-02-12 10:11:44
    简述:内核映射进程空,就是由进程分配好空间(属于进程独占资源)后,将用户空间虚拟地址,传递到内核,然后内核映射成内核虚拟地址直接访问,此时内核访问的物理空间是位于用户空间。这样的好处是,不再是内核将...
  • Linux内核地址映射模型

    千次阅读 2013-08-26 10:05:16
    Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。 段页式机制如下图。 Linux内核地址空间划分 通常32...
  • (2) 动态内存映射区:该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。 (3
  • Linux mmap内存映射

    千次阅读 2013-04-03 14:10:16
    Linux系统调用mmap和mmap2,创建一个进程地址空间的映射,mmap/mmap2几个常用的场景: 1. 文件映射普通文件I/O操作,读写mmap映射后的内存,即可完成文件的读写操作,kernel和userspace操作同一块物理内存;而常规的...
  • linux mmap 内存映射

    千次阅读 2012-05-06 11:47:29
    Gralloc HAL的gralloc.cpp使用了mmap,如下: static int init_pmem_area_locked(private_module_t* m) 1)打开PMEM,open("/dev/pmem", O_RDWR, 0); 2)获取所有空间,ioctl(master_fd, ...3)分配这么多空间,sA
  • 0.11 Linux内核 内存映射与进程结构

    千次阅读 2013-11-16 21:11:00
    Linux0.11中的每个进程都有如下的结构: 在gdt中占有两项,一项是tss段描述符,一项是ldt段描述符。 在task数组中占有一项,指向一页物理内存,该物理内存低端是进程控制块task_struct(里面包括tss段和...
  • 端口映射原理与linux

    2008-12-06 09:26:08
    很经典的linux内核学习资料. 内网的一台电脑要上因特网,就需要端口映射 端口映射分为动态和静态 动态端口映射: 内网中的一台电脑要访问新浪网,会向NAT网关发送数据包,包头中包括对方(就是新浪网)IP、端口和本机IP...
  • linux高端内存映射

    千次阅读 2014-03-03 16:23:08
    用户空间:在Linux中,每个用户进程都可以访问4GB的线性虚拟内存空间。其中从0到3GB的虚存地址是用户空间,通过每个进程自己的页目录、页表,用户进程可以直接访问。 内核空间:从3GB到4GB的虚存地址为内核态空间,...
  • Linux下内存映射文件的用法

    千次阅读 2012-10-16 09:23:21
    学习了一下Linux下内存映射文件的用法,在这里共享一下自己的收获,希望大家提出宝贵意见,进行交流。    简介:  内存映射文件虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理...
  • 十一步让你理解linux的内存映射

    千次阅读 2011-03-12 13:27:00
    一:linux将物理内存完全一一映射到内核空间,这样很方便管理内存,任何页面的虚拟地址减去一个0xc0000000的偏移就可以得到物理地址。 二:内核还需要动态管理一些内存用于vmalloc或者设备临时映射等,因此不...
  • linux 内存映射 PCI内存映射 DMA映射

    千次阅读 2012-04-13 13:50:38
    内存映射, 就是指把外设的内存映射到用户空间访问。系统调用为:  #include  void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);  其中fd 可以为打开的普通文件或设备...
  • Linux中地址映射的全过程

    千次阅读 2016-06-03 17:20:42
    页式存储管理段式存储管理所要求的硬件支持不同。 i386 CPU一律对程序中使用的地址先进行段式映射,然后才能进行页式映射。 对于其它的处理器,比如说M68K、PowerPC等,不存在段式映射这一层。 Linux内核采取的...
  • Linux下内存映射文件的用法简介

    万次阅读 2012-02-02 17:08:43
    由于项目需要,所以学习了一下Linux下内存映射文件的用法,在这里共享一下自己的收获,希望大家提出宝贵意见,进行交流。    简介:  内存映射文件虚拟内存有些类似,通过内存映射文件可以保留一个地址空间...
  • Linux高端内存映射(下)

    千次阅读 2012-05-22 21:54:15
     非连续内存分配是指将物理地址不连续的页框映射到线性地址连续的线性地址空间,主要应用于大容量的内存分配。采用这种方式分配内存的主要优点是避免了外部碎片,而缺点是必须打乱内核页表,而且访问速度较连续分配...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 139,985
精华内容 55,994
关键字:

linux分配与映射

linux 订阅