zoukankan      html  css  js  c++  java
  • kernel内存、地址【转】

    转自:https://blog.csdn.net/ivychend/article/details/79785303

    1 物理地址、虚拟地址、总线地址
    2 编址方式
    2.1 外设访问
    2.1.1 映射
    3 虚拟地址映射
    4 内存布局
    4.1 动态映射区
    4.2 永久映射区
    4.3 固定映射区
    4.4 high memory
    4.5 DMA
    4.6 实际内存布局
    5 /proc/目录下的内存结点
    5.1 iomem
    5.2 meminfo
    5.3 vmallocinfo
    6 分配内存函数
    6.1 kmalloc
    6.2 vmalloc
    6.3 malloc
    1 物理地址、虚拟地址、总线地址
      kernel中常出现物理地址、虚拟地址、总线地址,驱动代码中使用到的是虚拟地址,物理地址是给MMU使用的,在实际使用时,虚拟地址通MMU转换成物理地址。

    物理地址

      CPU地址总线传来的地址,物理地址中很大一部分是留给内存的,一部分给总线用(访问外设),这是由硬件设计来决定的。32bit的处理器,其寻址范围是4G,物理地址空间4G,但是并不是4G空间都给内存使用。

    总线地址

      一个完整的产品,会包含许多的外设,这些外设是挂载到总线上的,而soc内部也包含了许多的控制器,也是通过总线与cpu连接的。cpu是通过总线访问外设的,这个时候使用的地址就是总线地址。

    虚拟地址

      kernel中的代码大部分使用虚拟地址,实际kernel编程使用的都虚拟地址。虚拟地址是跟MMU相关的,虚拟地址、物理地址的相互转换是需要通过MMU来完成的。有的时候还会用到逻辑地址,逻辑地址就是虚拟地址。

    2 编址方式
      cpu访问外设是通过读写外设寄存器来完成的,外设寄存器也称为I/O端口。arm架构CPU对存储的管理普遍采用UMA(unifed memory access)架构,在UMA架构下,RAM、ROM是统一编址的。外设寄存器的地址会被映射到内存指定位置,cpu读写对应的地址就是对外设的访问。下图间6818存储控制器模块图

    图 2-1
      可以看到6818对存储是统一编址的,包括ROM、DDR(RAM)、I/O端口、cpu内存SRAM、ROM,他们的地址如下表

    区域 MCU-S MCU-A Normal I/O 保留 Internal SRAM
    地址范围 0x0000_0000 ~ 0x3FFF_FFFF 0x4000_0000 ~ 0xBFFF_FFFF 0xC000_0000 ~ 0xDFFF_FFFF 0xE000_0000 ~ 0xFFFE_FFFF 0xFFFF_0000 ~ 0xFFFF_FFFF
    备注 ROM的地址是从0开始的,包含了cpu内部ROM RAM是DDR,最大支持2G容量 内存再往上是I/O端口使用的空间 保留 0xFFFF_0000以上是cpu内部RAM的空间
    2.1 外设访问
      cpu访问外设是通过读写外设寄存器完成的,外设寄存器也称为I/O端口。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。arm cpu都是采用内存映射方式,映射完成返回虚拟地址,对虚拟地址的读写就是等同于对外设寄存器的读写。
      Linux使用一个通用的数据结构resource来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)
      内核中使用常用request_mem_region、ioremap完成I/O端口映射。

    2.1.1 映射
      kernel中访问外设前需要映射得到其虚拟地址,有两种映射方式:动态映射(ioremap)方式、静态映射(map_desc)方式。静态映射会在cpu初始化的map_io函数中完成,而动态映射方式会在驱动注册的probe函数中完成。动态映射I/O资源会被包装成resource结构,通过设备数据传递给驱动,在驱动中完成映射。
      

    3 虚拟地址映射
      cpu初始化时会有下面代码   

    MACHINE_START(S5P6818, CFG_SYS_CPU_NAME)
    .atag_offset = 0x00000100,
    .fixup = cpu_fixup,
    .map_io = cpu_map_io, //这个函数用来创建虚拟地址映射的
    .init_irq = nxp_cpu_irq_init,
    .handle_irq = gic_handle_irq,
    .timer = &nxp_cpu_sys_timer,
    .init_machine = cpu_init_machine,
    #if defined CONFIG_CMA && defined CONFIG_ION
    .reserve = cpu_mem_reserve,
    #endif
    MACHINE_END

      cpu_map_io部分源码

    static void __init cpu_map_io(void)
    {
    int cores = LIVE_NR_CPUS;
    // 1 cpu_iomap_desc映射数组
    unsigned long io_end = cpu_iomap_desc[ARRAY_SIZE(cpu_iomap_desc)-1].virtual +
    cpu_iomap_desc[ARRAY_SIZE(cpu_iomap_desc)-1].length;
    #if defined(CFG_MEM_PHY_DMAZONE_SIZE)
    unsigned long dma_start = CONSISTENT_END - CFG_MEM_PHY_DMAZONE_SIZE;
    #else
    unsigned long dma_start = CONSISTENT_END - SZ_2M; // refer to dma-mapping.c
    #endif
    if (io_end > dma_start)
    printk(KERN_ERR " ****** BUG: Overlapped io mmap 0x%lx with dma start 0x%lx ****** ",
    io_end, dma_start);
    _IOMAP();
    // 2 创建映射
    iotable_init(cpu_iomap_desc, ARRAY_SIZE(cpu_iomap_desc));

    #if defined(CFG_MEM_PHY_DMAZONE_SIZE)
    printk(KERN_INFO "CPU : DMA Zone Size =%2dM, CORE %d ", CFG_MEM_PHY_DMAZONE_SIZE>>20, cores);
    init_consistent_dma_size(CFG_MEM_PHY_DMAZONE_SIZE);
    #else
    printk(KERN_INFO "CPU : DMA Zone Size =%2dM, CORE %d ", SZ_2M>>20, cores);
    #endif
    ...
    }

      cpu_iomap_desc定义

    #define PB_IO_MAP(_n_, _v_, _p_, _s_, _t_)
    {
    .virtual = _v_,
    .pfn = __phys_to_pfn(_p_),
    .length = _s_,
    .type = _t_
    },

    static struct map_desc cpu_iomap_desc[] =
    {
    #include <mach/s5p6818_iomap.h> //直接包含一个文件的内容,下面部分就是文件的所有内容
    };
    /*
    * Length must be aligned 1MB
    *
    * Refer to mach/iomap.h
    *
    * Physical : __PB_IO_MAP_ ## _n_ ## _PHYS
    * Virtual : __PB_IO_MAP_ ## _n_ ## _VIRT
    * name .virtual, .pfn, .length, .type
    * 这里.pfn实际上是物理地址,通过PB_IO_MAP转换成页帧号,一个部分都映射到0xF0000000以上的虚拟地址。
    */
    PB_IO_MAP( REGS, 0xF0000000, 0xC0000000, 0x00300000, MT_DEVICE ) /* NOMAL IO, Reserved */
    PB_IO_MAP( CCI4, 0xF0300000, 0xE0000000, 0x00100000, MT_DEVICE ) /* CCI-400 */
    PB_IO_MAP( SRAM, 0xF0400000, 0xFFF00000, 0x00100000, MT_DEVICE ) /* SRAM */
    PB_IO_MAP( NAND, 0xF0500000, 0x2C000000, 0x00100000, MT_DEVICE ) /* NAND */
    PB_IO_MAP( IROM, 0xF0600000, 0x00000000, 0x00100000, MT_DEVICE ) /* IROM */

      映射调用过程:cpu_map_io –> iotable_init –> create_mapping

    4 内存布局
      参考1 参考2  
      如无特别说明,本章节所说的内存,指的是虚拟内存空间。内存空间大小为4G,比较常见的分配方式是用户空间0 ~ 3G,内核空间3G ~ 4G。每个进程都有自己独立的3G地址空间,所有进程、kernel共享1G的内核地址空间。

    图 4-1
      下面是linux内存详细分布

    图 4-2
    4.1 动态映射区
      需要使用动态映射区里的内存时,使用vmalloc分配

    4.2 永久映射区
      用于将高端内存长久映射到内存虚拟地址。通过一下函数实现:
    void *kmap(struct page *page)

    4.3 固定映射区
       主要解决持久映射不能用于中断处理程序而增加的临时内核映射。通过下面函数实现:
    void *kmap_atomic(struct page *page)
    void __kunmap_atomic(void *kvaddr)

    4.4 high memory
      物理地址空间中,大于896M,也就是0x3800_0000以上的内存都被认为是高端内存,kernel对高端内存的映射不是线性的。虚拟地址空间中,lowmem以上的动态映射区、永久映射区、固定映射区都是高端内存,可以将物理高端内存映射到这几个区域。可以通过访问映射返回的虚拟地址来访问实际高端内存。
      64位kernel中并不存在高端内存的概念,因为64位kernel可以访问的内存大小是2的64次方。32位kernel存在高端内存是因为32位kernel内核空间只有1G,当实际物理内存大于1G时,超出部分无法直接访问,需要通过映射成高端内存方式访问。

    4.5 DMA
      这里没有提到为DMA预留的内存,因为不同的平台为DMA预留的内存位置、大小有所差异,x86平台是lowmem区开始的16M,arm平台由cpu厂商设置。6818的cpu_map_io 函数中关于DMA的代码

    unsigned long dma_start = CONSISTENT_END - CFG_MEM_PHY_DMAZONE_SIZE;  //DMA起始位置 0xffe00000 - 0x01000000 = 0xFEE00000,大小就是0x0100000(16M)

    printk(KERN_INFO "CPU : DMA Zone Size =%2dM, CORE %d ", CFG_MEM_PHY_DMAZONE_SIZE>>20, cores);
    init_consistent_dma_size(CFG_MEM_PHY_DMAZONE_SIZE);

    /* init_consistetn_dam_size用来检验DMA的起始地址是否满足要求的 */
    void __init init_consistent_dma_size(unsigned long size)
    {
    unsigned long base = CONSISTENT_END - ALIGN(size, SZ_2M);

    BUG_ON(consistent_pte); /* Check we're called before DMA region init */
    BUG_ON(base < VMALLOC_END);

    /* Grow region to accommodate specified size */
    if (base < consistent_base)
    consistent_base = base;
    }

    4.6 实际内存布局
      前面关于内存布局的并不针对特定平台,不同平台关于内存的布局会有差异,以实际代码为准。可以从内核的启动信息中看到关于内存的分配、使用范围。

    [ 0.000000] Memory: 2048MB = 2048MB total
    [ 0.000000] Memory: 1667052k/1667052k available, 430100k reserved, 1320960K highmem
    [ 0.000000] Virtual kernel memory layout:
    [ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4 kB)
    [ 0.000000] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
    [ 0.000000] vmalloc : 0xef800000 - 0xfee00000 ( 246 MB)
    [ 0.000000] lowmem : 0xc0000000 - 0xef600000 ( 758 MB)
    [ 0.000000] pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
    [ 0.000000] modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
    [ 0.000000] .text : 0xc0008000 - 0xc0842f0c (8428 kB)
    [ 0.000000] .init : 0xc0843000 - 0xc087a100 ( 221 kB)
    [ 0.000000] .data : 0xc087c000 - 0xc08f4f28 ( 484 kB)
    [ 0.000000] .bss : 0xc08f4f4c - 0xc0ad5588 (1922 kB)

      可以看到这里的内存布局,永久映射区(pkmap)放到了lowmem下面,固定映射区是从0xfff00000开始的896K,还多了个0xffff0000开始的4k异常向量表。可以看到modules是属于用户空间的范围的,也就是说执行insmod的ko会被看成是用户进程。所以arm架构处理器更常用的内存布局如下图所示:

    图 4-3
      
      关于内存分配的信息在/proc/目录下的结点也可以查看到,相关结点:iomem、ioports、dma-mappings、vmallocinfo、vmstat、meminfo。
    5 /proc/目录下的内存结点
      proc目录几个比较重要的结点:iomem、dma-mappings、vmallocinfo、vmstat、meminfo。ioports主要是x86平台上使用,这里不表。

    5.1 iomem
    cat /proc/iomem
    40000000-bfffffff : System RAM
    40008000-40842f0b : Kernel code
    4087c000-40ad5587 : Kernel data
    c0000000-c0000fff : pl08xdmac.0
    c0000000-c0000fff : pl08xdmac
    c0001000-c0001fff : pl08xdmac.1
    c0001000-c0001fff : pl08xdmac
    c0030000-c00300ff : nxp-ehci
    c0040000-c0050fff : dwc_otg
    c0040000-c0050fff : dwc_otg
    c005b000-c005b0ff : s3c64xx-spi.0
    c005b000-c005b0ff : s3c64xx-spi
    c0062000-c0062fff : dw_mmc.0
    c0069000-c0069fff : dw_mmc.2
    ...
    c00a0000-c00a0040 : nxp-uart.1
    c00a1000-c00a1040 : nxp-uart.0
    c00a2000-c00a2040 : nxp-uart.2
    c00a3000-c00a3040 : nxp-uart.3
    c00a5000-c00a5fff : s3c2440-i2c.1
    c00a5000-c00a5fff : s3c2440-i2c
    c00a6000-c00a6fff : s3c2440-i2c.2
    c00a6000-c00a6fff : s3c2440-i2c

      上面是cat /proc/iomem的部分信息,这里的地址实际上是物理地址。在内核中使用物理地址前需要映射,使用request_mem_region、ioremap,映射完成返回的虚拟地址才能使用。

    5.2 meminfo
    cat /proc/meminfo
    MemTotal: 1669320 kB //实际物理内存
    MemFree: 1240548 kB //实际剩余物理内存
    ...
    HighTotal: 1320960 kB //高端物理内存
    HighFree: 935868 kB
    LowTotal: 348360 kB //低端物理内存
    LowFree: 304680 kB
    SwapTotal: 0 kB
    SwapFree: 0 kB
    Mapped: 91460 kB
    PageTables: 6860 kB
    VmallocTotal: 251904 kB //虚拟高端内存
    VmallocUsed: 30836 kB
    VmallocChunk: 190180 kB

    5.3 vmallocinfo
    cat /proc/vmallocinfo
    0xbf000000-0xbf02f000 192512 module_alloc_update_bounds+0xc/0x5c pages=46 vmalloc
    0xbf03f000-0xbf099000 368640 module_alloc_update_bounds+0xc/0x5c pages=89 vmalloc
    0xef804000-0xef809000 20480 persistent_ram_init_ringbuffer+0x17c/0x4e8 vmap
    0xef820000-0xef831000 69632 suspend_ops_init+0x108/0x13c ioremap
    0xefdc0000-0xefde1000 135168 binder_mmap+0x7c/0x23c ioremap
    0xefe00000-0xefeff000 1044480 binder_mmap+0x7c/0x23c ioremap
    0xf0000000-0xf0300000 3145728 iotable_init+0x0/0xb4 phys=c0000000 ioremap
    0xf0600000-0xf0700000 1048576 iotable_init+0x0/0xb4 ioremap
    0xf0700000-0xf0800000 1048576 pmd_empty_section_gap+0x0/0x38 ioremap
    0xf0800000-0xf0878000 491520 cma_get_virt+0x94/0xcc vmap
    0xf0900000-0xf09ff000 1044480 binder_mmap+0x7c/0x23c ioremap
    0xf1d03000-0xf1d06000 12288 pcpu_extend_area_map+0x18/0xd4 pages=2 vmalloc
    0xf3300000-0xf33ff000 1044480 binder_mmap+0x7c/0x23c ioremap
    0xfedb8000-0xfee00000 294912 pcpu_get_vm_areas+0x0/0x5ec vmalloc

      vmallocinfo是动态映射区的内存分配信息,但是这是有module相关的,也是使用vmalloc分配的,地址比0xbf000000开始,也可以从vmallocinfo中看到。
      可以看到ioremap使用虚拟内存区域是在高端区的vmalloc区域,可以推测soc下控制器、I/O端口是映射到vmalloc区的(最起码有部分是,这就是IO mapping)。

    6 分配内存函数
      kernel中内存被分成不同的区域,使用不同的函数来获取。

    6.1 kmalloc
      kmalloc是从lowmem区域分配内存的,kmalloc分配的内存在物理地址、虚拟地址空间上都是连续的。但是分配的容量上会比较小。

    6.2 vmalloc
      vmalloc是从vmalloc区域分配内存的,module区域分配内存也是使用vmalloc。vmalloc分配的内存虚拟地址空间上是连续的,不保证物理地址空间的连续性。vmalloc可以分配容量较大的内存。vmalloc分配内存比kmalloc分配内存慢,因为lowmem区的页表是固定的,而高端内存区域的页根据使用分配,如果cache中没有,则产生缺页中断,找到空闲的物理内存,返回对应的页帧号。kmalloc函数分配内存时不存在这个过程。

    6.3 malloc
      malloc是应用层使用,用来分配内存的函数。其返回的地址是0 ~ 3G,从应用进程空间的0 ~ 3G分配内存。
    ————————————————
    版权声明:本文为CSDN博主「ivychend」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/ivychend/article/details/79785303

    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    BZOJ1299 [LLH邀请赛]巧克力棒
    BZOJ1046 [HAOI2007]上升序列
    BZOJ1798 [Ahoi2009]Seq 维护序列seq
    BZOJ2045 双亲数
    BZOJ2301 [HAOI2011]Problem b
    BZOJ1021 [SHOI2008]Debt 循环的债务
    BZOJ2618 [Cqoi2006]凸多边形
    BZOJ1069 [SCOI2007]最大土地面积
    BZOJ1051 [HAOI2006]受欢迎的牛
    2017年09月23日普级组 环
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/15128841.html
Copyright © 2011-2022 走看看