转自: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