kernel内存、地址

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-SMCU-ANormal I/O保留Internal SRAM
地址范围0x0000_0000 ~ 0x3FFF_FFFF0x4000_0000 ~ 0xBFFF_FFFF0xC000_0000 ~ 0xDFFF_FFFF0xE000_0000 ~ 0xFFFE_FFFF0xFFFF_0000 ~ 0xFFFF_FFFF
备注ROM的地址是从0开始的,包含了cpu内部ROMRAM是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 "\n****** BUG: Overlapped io mmap 0x%lx with dma start 0x%lx ******\n",
            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\n", 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\n", 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\n", 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分配内存。
  

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值