轉自: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 "\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分配內存。
————————————————
版權聲明:本文為CSDN博主「ivychend」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/ivychend/article/details/79785303