arm的2级页表在Linux内核创建过程解析
用户空间/内核空间划分为2G/2G。
create_mapping:pgd = 0x80007f98, addr = 0xfe700000, phys =0xffff0000, next = 0xfe710000
pte =0x3fffd000, pmdval=0x3fffd801 //这里的pte已经转成物理地址了
*pte = 0xffff045f, *(pte+(2048>>2))=0xffff045e
*pte = 0xffff145f, *(pte+(2048>>2))=0xffff145e
*pte = 0xffff245f, *(pte+(2048>>2))=0xffff245e
*pte = 0xffff345f, *(pte+(2048>>2))=0xffff345e
*pte = 0xffff445f, *(pte+(2048>>2))=0xffff445e
*pte = 0xffff545f, *(pte+(2048>>2))=0xffff545e
*pte = 0xffff645f, *(pte+(2048>>2))=0xffff645e
*pte = 0xffff745f, *(pte+(2048>>2))=0xffff745e
*pte = 0xffff845f, *(pte+(2048>>2))=0xffff845e
*pte = 0xffff945f, *(pte+(2048>>2))=0xffff945e
*pte = 0xffffa45f, *(pte+(2048>>2))=0xffffa45e
*pte = 0xffffb45f, *(pte+(2048>>2))=0xffffb45e
*pte = 0xffffc45f, *(pte+(2048>>2))=0xffffc45e
*pte = 0xffffd45f, *(pte+(2048>>2))=0xffffd45e
*pte = 0xffffe45f, *(pte+(2048>>2))=0xffffe45e
*pte = 0xfffff45f, *(pte+(2048>>2))=0xfffff45e
读取:level 1 页表
0x00007F98: 3FFFD801 3FFFDC01
读取: level 2 页表
0x3FFFD400: FFFF045F FFFF145F FFFF245F FFFF345F
0x3FFFD410: FFFF445F FFFF545F FFFF645F FFFF745F
0x3FFFD420: FFFF845F FFFF945F FFFFA45F FFFFB45F
0x3FFFD430: FFFFC45F FFFFD45F FFFFE45F FFFFF45F /* 这个位置是linux 页表,也就是所谓的软件页表 */
0x3FFFDC00: FFFF045E FFFF145E FFFF245E FFFF345E
0x3FFFDC10: FFFF445E FFFF545E FFFF645E FFFF745E
0x3FFFDC20: FFFF845E FFFF945E FFFFA45E FFFFB45E
0x3FFFDC30: FFFFC45E FFFFD45E FFFFE45E FFFFF45E /* 这个位置是硬件页表,是给ARM硬件MMU使用的 */
0x3FFFD000: 00000000 00000000 00000000 00000000
0x3FFFD010: 00000000 00000000 00000000 00000000
0x3FFFD020: 00000000 00000000 00000000 00000000
0x3FFFD030: 00000000 00000000 00000000 00000000 /* 这个位置是linux 页表,也就是所谓的软件页表 */
0x3FFFD800: 00000000 00000000 00000000 00000000
0x3FFFD810: 00000000 00000000 00000000 00000000
0x3FFFD820: 00000000 00000000 00000000 00000000
0x3FFFD830: 00000000 00000000 00000000 00000000 /* 这个位置是硬件页表,是给ARM硬件MMU使用的 */
static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,
pmdval_t prot)
{
pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;/* HW page table offset is 512*4 = 2048 */
printk("pte =0x%x, pmdval=0x%x\n", pte, pmdval);
pmdp[0] = __pmd(pmdval); //第1个页表项
#ifndef CONFIG_ARM_LPAE //这个宏没有定义,所以1次要填充PGD的8个字节的页表
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); //第2个页表项
#endif
flush_pmd_entry(pmdp);
}
static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)
{
if (pmd_none(*pmd)) {
pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
__pmd_populate(pmd, __pa(pte), prot);
}
BUG_ON(pmd_bad(*pmd));
return pte_offset_kernel(pmd, addr);
}
static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
unsigned long end, unsigned long pfn,
const struct mem_type *type)
{
pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
do {
set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
printk("*pte = 0x%x, *(pte+(2048>>2))=0x%x\n", *pte, *(pte + (2048>>2)));
pfn++;
} while (pte++, addr += PAGE_SIZE, addr != end);
}
void __init create_mapping(struct map_desc *md)
{
unsigned long addr, length, end;
phys_addr_t phys;
const struct mem_type *type;
pgd_t *pgd;
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx"
" at 0x%08lx in user region\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
return;
}
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET &&
(md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
printk(KERN_WARNING "BUG: mapping for 0x%08llx"
" at 0x%08lx out of vmalloc space\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
}
type = &mem_types[md->type];
#ifndef CONFIG_ARM_LPAE
/*
* Catch 36-bit addresses
*/
if (md->pfn >= 0x100000) {
create_36bit_mapping(md, type);
return;
}
#endif
addr = md->virtual & PAGE_MASK;
phys = __pfn_to_phys(md->pfn);
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not "
"be mapped using pages, ignoring.\n",
(long long)__pfn_to_phys(md->pfn), addr);
return;
}
pgd = pgd_offset_k(addr);
end = addr + length;
do {
unsigned long next = pgd_addr_end(addr, end);
//if(phys == 0xffff0000)
printk("%s:pgd = 0x%x, addr = 0x%x, phys =0x%x, next = 0x%x\n", __func__, (u32)pgd, addr, phys, next);
alloc_init_pud(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
一级页表的内容是:3FFFD801 3FFFDC01
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
按照linux的方式映射:
pgd_index = (0xfe7 >> 1) = 0x7f3
所以0xfe600000和0xfe700000组成的2MB空间是放到一个pgd[2]数组里的。0xfe6对应的是pgd[0],0xfe7对应的是pgd[1]。
一级页表的在TTB中的TTB_offset = 0x7f3 * 8 = 0x3f98
所以1级页表的地址为0x4000+0x3f98 = 0x7f98
所以二级页表的地址计算过程为:
第1个表项0x3fffd801 ----> 0xfe600000虚拟地址
0x3fffd| 1|000 0000 0001
hex bin bin bin
bit31 bit11 |bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 0 0 0 0 0 0 0 0 0 0 0 即 0x3fffd800 就是上面的硬件二级物理页表地址。
第2个表项0x3fffdc01 ----> 0xfe700000虚拟地址
0x3fffd| 1|100 0000 0001
hex bin bin bin
bit31 bit11 |bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 1 0 0 0 0 0 0 0 0 0 0 即 0x3fffdc00 就是上面的硬件二级物理页表地址。
所以从linux页表角度来看,bit31-bit11为2级页表的基地址,而bit10-bit2为二级页表的索引(偏移)。而bit10-bit2来自虚拟地址(MVA)的bit20-bit12。
虚拟地址0xfe600000: bit20-bit12 是0b0 0000 0000
虚拟地址0xfe700000: bit20-bit12 是0b1 0000 0000
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下面是从硬件角度做页表转换:
虚拟地址0xfe700000
pgd_index = 0xfe7
TTB_offset = 0xfe7 * 4 = 0x3f9c
所以1级页表的地址为0x4000+0x3f9c = 0x7f9c,所以硬件找到的1级表项内容是3FFFDC01
下面是找2级页表的过程
0x3fffd| 11|00 0000 0001
hex bin bin bin
bit31 bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 1 |0 0 0 0 0 0 0 0 0 0 即 0x3fffdc00 就是上面的硬件二级物理页表地址。
假定MMU要访问第1个1MB空间,即虚拟地址为0xfe60000,即pgd_index = 0xfe6
TTB_offset = 0xfe6 * 4 = 0x3f98
0x3fffd| 10|00 0000 0001
hex bin bin bin
bit31 bit10 bit9 ...... bit2 bit1 bit0
0x3fffd| 1 0 |0 0 0 0 0 0 0 0 0 0 即 0x3fffd800 就是上面的硬件二级物理页表地址。
bit9...bit2 来自虚拟机地址的bit19~bit12。
虚拟地址0xfe600000: bit19-bit12 是0b0 0000 0000
虚拟地址0xfe700000: bit19-bit12 是0b0 0000 0000
---------------------------------------------------------------------------------------------------------------------------------------------------
总结:
bit21 bit20
pg_index = 0xfe7-->0b 1111 1110 0 1 1 1 xxxx xxxx xxxx xxxx xxxx
对于linux来说实际是以2MB为单位映射的,pgd一定是8个字节对齐的,比如这里的0x7f98而不是0x7f9c。bit21为0则映射到这两2MB第1MB空间,而bit21为1的话则映射到这2MB第1MB空间,我们举的这个例子,是映射到第2个1MB空间。我们知道硬件的二级页表本来是256项,即256*4KB = 1MB空间,现在明显Linux把它改成512项, 512*4KB = 2MB。所以linux是这样处理的,通过定义1级pgd为8个字节,即有两个表项,2级页表pte有512个表项,L1的第1个表项,指向L2的前256个表项,L1的第2个表项,指向L2的后256个表项,
总体来看和硬件是保持一致的。
pmdp[0] = __pmd(pmdval); //第1个页表项, 对应第1个1MB空间 (bit21 = 0)
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); //第2个页表项, 跳过256个表项,即跳过1MB地址空间,进入到第2个1MB空间 (bit21 = 1)
一次性把L1的pgd的2个表项都填充完毕,linux的映射是以2MB空间为单位映射的。
bit21在linux中只是前256个表项还是后256个表项。
#define PTRS_PER_PTE 512
#define pte_index(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
最后2级页表的布局:
0x3fffd000--> Linux page table offset: 0
256个表项(1st 1MB)
0x3fffd400--> Linux page table
256个表项(2st 1MB)
0x3fffd800--> HW page table offset: 2048
256个表项(1st 1MB)
0x3fffdc00--> HW page table
256个表项(2st 1MB)
=============================arm-linux内存页表创建 ================================
linux的内存(正式)页表是在内核代码执行到start_kernel函数后执行paging _init函数建立的,这里要注意一个事情就是说,这里paging_init函数可以正常创建内存页表的条件有两个:
1、 meminfo已初始化:即初始化物理内存各个node的各个bank,一般对于小型arm嵌入式设备,不涉及多个内存就是一个node和一个bank;这部分初始化是在paging_init函数前面的对uboot所传参数的解析中完成的(可在内核的arm_add_memory函数中加入打印信息验证);
2、 全局变量init_mm的代码段首尾、数据段首尾四个成员已初始化:在paging_init前面有对这四个成员的初始化,它们规定了内核镜像的代码段起始、代码段结尾、数据段起始、数据段结尾(数据段结尾也是整个内核镜像的结尾),给这四个成员赋的地址值都是在vmlinux.lds.S链接脚本中规定的(即虚拟地址),界定它们的意义在于能够正确的界定内核镜像的运行需要在虚拟地址占用的空间位置及大小,以利于其他内容在内核空间位置的确定。
Paging_init函数首先调用的是build_mem_type_table,这个函数做的事情就是给静态全局变量mem_types赋值,这个变量就在本文件(arch/arm/mm/mmu.c)定义,它的用处就是在create_mapping函数创建映射时配置MMU硬件时需要;build_mem_type_table函数里面是完全与本arm芯片自身体系结构相关的配置,我还没完全搞明白。。。后续再补充吧。
接下来调用的是sanity_check_meminfo,这个函数主要做两件事情,首先是确定本设备物理内存的各个node各个bank中到底有没有高端内存,根据是否存在高端内存决定每个bank的highmem成员值;然后是对于每个bank的正确性进行检测;下面分别描述:
由下面代码判断每个物理内存bank是否属于高端内存:
if (__va(bank->start) > VMALLOC_MIN || __va(bank->start) < (void *)PAGE_OFFSET)
highmem = 1;
即:该bank的物理内存起始虚拟地址大于VMALLOC_MIN,或者小于PAGE_OFFSET;PAGE_OFFSET是内核用户空间的交界,在这里定义为0xc0000000也就是arm-linux普遍适用值3G/1G;VMALLOC_MIN就定义在本文件(arch/arm/mm/mmu.c),如下:
#define VMALLOC_MIN (void *)(VMALLOC_END - vmalloc_reserve)
VMALLOC_END在arch/arm/mach-XXX/include/mach/vmalloc.h文件中定义,可见是不同arm设备可以不同的,它标志着vmalloc区域的结尾在哪里,这里定义为3G+768M,如下:
#define VMALLOC_END (PAGE_OFFSET + 0x30000000)
静态全局变量vmalloc_reserve定义在本文件(arch/arm/mm/mmu.c),它是可以由用户指定的,指示了vmalloc区域的大小,默认值为128M,用户指定的方式是通过uboot中指定“命令行”参数的vmalloc参数(“vmalloc=”,通过paging_init前的__early_param方式指定)修改内核中该变量的值,这里采用的就是默认值128M;
用vmalloc区域的结尾(3G+768M)减去该区域的大小(128M)即得到了vmalloc区域的起始,3G + 768M - 128M = 3G + 640M(0xe8000000);
所以,一个bank的物理内存属于高端内存的条件是:
1、 起始地址不大于vmalloc区域的起始虚拟地址;
2、 起始地址不小于内核用户交界的虚拟地址;
当属于高端内存时,该bank的highmem成员将置1。
除了界定物理内存bank是否属于高端内存,sanity_check_meminfo函数还对每个物理内存bank的正确性进行检测,这部分个人认为不是重点,主要注意下在存在高端内存情况下(代码中定义宏CONFIG_HIGHMEM情况下),若低端内存太大(起始位置在VMALLOC_MIN之前,结尾位置超过VMALLOC_MIN),则超过VMALLOC_MIN的部分将被算进另一个bank并且判定为高端内存。
接下来调用的是prepare_page_table,它的作用是清除在内核代码执行到start_kernel之前时创建的大部分临时内存页表,这里需要对arm-linux内存页表的机制原理进行理解:
首先一个是,什么是内存页表,都有哪些属于内存(注意这里的内存是广义上的内存,不单单是物理内存)?
具体的说,内存页表,更应该叫内存映射,对于有MMU的CPU来说,CPU访问物理内存或某个SOC硬件寄存器,所做的操作并非是直接把它们的物理地址放在CPU的地址总线,而是把一个虚拟地址交给MMU,如果MMU硬件存在这个虚拟地址对应的物理地址(这个映射关系就是需要创建的内容,也就是内存映射!),那么它就会把对应的物理地址放在地址总线上。这样做最大的好处是,避免了软件程序直接访问一个不存在的地址导致出现问题 + 用户程序可使用的“内存”很大。
再说哪些东西属于内存,很简单,不仅仅物理内存,所有通过CPU地址总线连接的都属于内存,比如SOC的硬件寄存器,这里有个方法验证这个道理,函数create_mapping是最终创建内存映射的函数,看看它都被哪些函数调用:
map_memory_bank:这是为物理内存创建内存映射
devicemaps_init:这是为中断向量创建内存映射
iotable_init:这是为SOC硬件寄存器创建内存映射
就以上三个调用需求。
函数create_mapping是最终创建内存映射的函数,先不管哪些需求去调用这个函数,先看这个函数本身:
这个函数只需要一个参数struct map_desc *md,这个结构体的定义在文件arch/arm/include/asm/mach/map.h中,只有4个参数非常简单:
unsigned long virtual; /*虚拟地址起始*/
unsigned long pfn; /*物理地址起始*/
unsigned long length; /*长度*/
unsigned int type; /*说明这个区间所属的域,以及是否可读、写、可高速缓存等属性,arm硬件相关*/
第一个参数非常好理解,虚拟地址起始;第二个参数是物理地址起始;第三个参数是要映射的长度,最后一个比较复杂,但实际用到的往往只有MT_MEMORY(代表物理内存)、MT_HIGH_VECTORS/MT_LOW_VECTORS(代表中断向量)、MT_DEVICE(代表硬件IO寄存器),实际含义和arm硬件相关,指的是MMU不同页表的映射权限暂可先不关心。
那么给定这四个参数,create_mapping函数是怎么创建映射呢?现在必须描述一下arm-linux的分页机制:
32位的arm芯片的寻址能力就是2^32 = 4G,地址范围即0-0xffffffff,如果按照1M大小为单位进行映射,则4G = 4096 * 1M也就是需要4096个条目,每个条目负责1M大小的地址范围;比如需要映射一个大小为128M的物理内存,那么就需要填写128个条目即可;事实上这就是所谓的段式映射或所谓一级映射,使用1MB的粗页表,优点是占用条目较少仅4096个条目,每个条目4字节即每个进程(包括内核进程自己)仅占用16K空间,但缺点是粒度太大了不利于linux内存管理,比如某进程或某内核代码(如模块)申请一些较小的空间不足1M,却也得分配这么大空间,当申请频繁时,物理内存消耗将很快。这个缺点是必须要克服的,arm-linux肯定要引入二级页表,但首先要理解这种段式映射或称为一级映射的页表是怎么样的,理解了一级映射才能理解二级映射,如下:
现在正式看内核进程的页表创建,先分析道理,再对应源码(后面有足量的代码注释),在mm/init-mm.c文件中,有全局变量init_mm,如下:
struct mm_struct init_mm = {
.mm_rb = RB_ROOT,
.pgd = swapper_pg_dir,
.mm_users = ATOMIC_INIT(2),
.mm_count = ATOMIC_INIT(1),
.mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem),
.page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
.mmlist = LIST_HEAD_INIT(init_mm.mmlist),
.cpu_vm_mask = CPU_MASK_ALL,
};
每个进程都有描述自己内存使用情况的结构mm_struct,内核进程也不例外,从这个文件的目录可见这个变量是不以平台区分的,现在重点看第二个成员pgd,这是该进程内存页表的虚拟地址,值为swapper_pg_dir,这是个在head.S中定义的变量,值设置为KERNEL_RAM_VADDR - 0x4000 = 0xc0004000,由前面已知段式内存页表的大小为16K,所以内核的内存页表的虚拟地址范围是[0xc0004000: 0xc0008000]。
内核的内存页表,在源码中paging_init函数都映射了什么?由前面已知,映射了三方面内容,分别是:物理内存、中断向量、硬件IO寄存器,先看物理内存的情况,这里marvell设备物理内存为256M,PHYS_OFFSET为0,
函数调用顺序是:bootmem_init-> bootmem_init_node-> map_memory_bank,关注函数map_memory_bank,把物理内存的参数填充到struct map_desc结构体变量map,并用它调用函数create_mapping,正式开始:
1、首先一个判断(md->virtual != vectors_base() && md->virtual < TASK_SIZE),这是为了防止虚拟地址不是中断表地址并且在用户区(0~3G)的情况;然后又是一个判断((md->type == MT_DEVICE || md->type == MT_ROM) && md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END),这是为了防止内存类型为IO型或ROM但虚拟地址为低端内存申请区(3G~3G + 768MB)的情况;这些判断暂无需关注;
2、type = &mem_types[md->type];由前面可知这是获取所映射内存区间所属的域,以及是否可读、写、可高速缓存等属性,暂无需关注;
3、判断(md->pfn >= 0x100000)的情况,这个就不要关注了(超过4G的情况);
4、判断(type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK))的情况,意思是本区间不为段式映射(type->prot_l1 == 0)但该区间可以按1M对齐((addr | phys | length) & ~SECTION_MASK),记住这是一个原则,能按1M对齐的映射空间就按段式映射,不足1M的空间才需要二级映射;
5、pgd = pgd_offset_k(addr);,一级一级的看这个函数的实现,最后这个函数相当于(init_mm)->pgd + ((addr) >> 21) = (pgd_t *)0xc0004000 + (addr >> 21),这是在找这个虚拟地址在内核进程的页表中的位置,由前面已知内核进程的页表从虚拟地址0xc0004000开始,到0xc0008000结束,页表大小为0x4000,这个范围标识0-0xffffffff这4G的范围,现在addr的值为0xc0000000,那么它在内核进程的页表中的位置可以算出来是0xc0004000 + 0x4000*3/4 = 0xc0007000,为什么是3/4?因为0xc0000000在0-0xffffffff这4G的范围是整好3/4的位置即4G中的最后一个G,addr值为0xc0000000,它右移21位值为0x600,(pgd_t *)0xc0004000 + 0x600 = 0xc0007000,貌似很奇怪为什么不是0xc0004600?这是因为前面有强制类型转换(pgd_t *),而这个结构的定义是两个ulong,所以(pgd_t *)0xc0004000 + 0x600 =(pgd_t *)0xc0004000 + 0x600*8 = 0xc0007000,至于为什么addr要右移21位,以及为什么有强制类型转换(pgd_t *),一会再说先关注段式映射的情况;
6、end = addr + length;,end指的是要映射的虚拟地址的结尾,它的值为0xc0000000 + 0x10000000(256M) = 0xd0000000;
7、至此,要映射的空间的虚拟地址起始值addr为0xc0000000,虚拟地址结尾值end为0xd0000000,长度为0x10000000,物理地址起始值为0,映射类型为变量type的值(暂不关心细节),接下来的do while循环是真正映射了:
8、do {
unsigned long next = pgd_addr_end(addr, end);
alloc_init_section(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
第一行的意思是,只要不超过end,就获得下一个2MB的虚拟起始地址,所以传给alloc_init_section的next参数,要么与addr相差一整段(2MB),要么是end则不足一整段(2MB),我们这里的内存256M,不存在end与addr相差不足2M的情况;
进入函数alloc_init_section,它的参数分别是“一级页表(段页表)地址pgd、虚拟起始地址addr、虚拟结尾地址end(现在就是addr + 2MB)、物理起始地址phys、内存类型type”;
9、pmd_t *pmd = pmd_offset(pgd, addr);,这个pmd_t结构就只是一个ulong大小了,这里函数pmd_offset的实现就是pmd = (pmd_t *)pgd,地址不变,但类型转变,意思很明显;
10、判断(((addr | end | phys) & ~SECTION_MASK) == 0),我们这里的都是2M对齐的,必然1M也对齐,底下的(addr & SECTION_SIZE)对于我们这里不会成立,我们这里都是2M对齐,即addr值的第21位一直都会是偶数;
11、下面的内容是配置段式页表的值和写页表:
do {
*pmd = __pmd(phys | type->prot_sect);
phys += SECTION_SIZE;
} while (pmd++, addr += SECTION_SIZE, addr != end);
flush_pmd_entry(p);
第一行,页表的这个条目pmd,写入的值是什么,可见,把物理地址和type的映射方式(prot_sect)写进去了;
第二行,累加1M的物理地址值;
第三行,只要虚拟地址起始值addr再累加1M,没有超过end(这里是addr+2M),那么写下一个页表条目(pmd+1)的值,很明显,我们这里会再循环一次,即do里的内容总共运行过两次;
第四行,最终写入MMU,这个p指向pmd。
先不要管上面为什么这么实现,先看结果,总共256M的物理内存,最后结果是:
页表条目索引 | 条目所在地址 | 页表填的内容 | 对应的虚拟地址 |
0xfff | 0xc0008000 |
| 0xffffffff |
…… | …… | …… | …… |
0x3fc | 0xc00073fc | 0x10000000+type | 0xd0000000 |
…… | …… | …… | …… |
0xc05 | 0xc0007014 | 0x00500000+type | 0xc0500000 |
0xc04 | 0xc0007010 | 0x00400000+type | 0xc0400000 |
0xc03 | 0xc000700c | 0x00300000+type | 0xc0300000 |
0xc02 | 0xc0007008 | 0x00200000+type | 0xc0200000 |
0xc01 | 0xc0007004 | 0x00100000+type | 0xc0100000 |
0xc00 | 0xc0007000 | 0x00000000+type | 0xc0000000 |
…… | …… | …… | …… |
0x000 | 0xc0004000 |
| 0x00000000 |
上面就是对物理内存映射后的情况(红色部分),第一列是页表的索引,256M的物理内存的的映射部分在第0xc00到0x3fc部分,对应页表本身所在地址从0xc0007000到0xc00073fc,这部分页表填充的内容是物理内存和映射类型的或运算结果,它们实际上对应虚拟地址的0xc0000000到0xd0000000。
如果把一个虚拟地址0xc1234567给CPU去访问,那么CPU把它发给MMU,MMU会根据已经建立的映射关系发现这个地址对应的是0x012这个段,然后把后面低20位的部分0x34567和0x012拼接起来,结果是0x01234567。
事实上这里还有很多细节,比如MMU到底是怎么能够识别是0x012段的细节,这牵扯到arm硬件体系结构内容,如果不是特殊需要可不特别关心,关注到内核这步即可。
上面是物理内存页表创建的结果,但还有个问题没有说,就是arm页表在linux中的融合问题,这部分不理解将影响对全局的理解:
要知道,linux要实现高效的内存管理,是不可能按1M的区间管理的,这样很容易产生问题,前面说过这个问题,事实上linux是以4K为一页作为管理单位即粒度,这是怎么定的呢?在arch/arm/include/asm/page.h文件中规定宏PAGE_SHIFT为12导致宏PAGE_SIZE为4096即4K;
既然是4K为一页,那么按理说linux需要描述4G虚拟内存的话,需要多少个这样的4K呢?很简单:2^20 * 2^12 = 4G,所以是2^20为1M,即页表的条目个数为1M个,每个条目占用4字节,即页表大小为4M,每个进程包括内核进程都需要一个内存页表,这就大量消耗物理内存在页表上;
所以这里将引入多级页表的概念,linux内核定义的标准是这样的:最高级pgd为页目录表,它找到每个进程mm-_struct结构的pgd成员,用它定位到下一级pmd;第二级pmd为中间页表,它定位到下一级pte;第三级pte是页表,它就能定位到哪个页了;最后虚拟地址的最后12位定位的是该页的偏移量;
为什么搞的这么麻烦?因为linux还要适配64位处理器,到那时是真的需要这么麻烦,因为否则页表占用空间太大了,所以内核必须搞的级数多一些。
那么arm呢,arm体系结构的MMU实际上支持两级页表,一级是刚才描述的段式映射即一级映射,再就是支持第二级映射,包括1K、4K、64K的页实际上使用的是4K页,这里就牵扯到arm页表机制和linux页表机制融合的问题;这里记住,arm的第一级页表条目数为4096个,对于4K页第二级目录条目个数为256个,一级二级条目都是每个条目4字节;
像这种物理的级数支持少的,砍掉中间目录pmd就可以,从本质看就是函数pmd_offset(pgd, addr)的实现是pgd,即可什么pmd在arm-linux形同虚设。ARM在linux下二级分页如下:
虚拟地址——> PGD转换——> PTE转换——>物理地址
此外linux的内存管理中,有对页的置属性为“access”、“dirty”的需求,可是arm的MMU没有提供这种属性可以设置;
综合各种原因,最终arm-linux假装第一级目录只有2048个条目,但其实每个条目是2个ulong大小即8字节,所以最终设置MMU的还是4096个条目,只是每访问1个pgd条目将可以访问到2个pte条目,linux为了实现其内存管理功能又在后面加上2个对应的假pte表,这个假pte表专门给linux内核代码自己用的,不会影响arm硬件(事实上还有一个重要原因是,linux要求pte表长度为4K即一页),在arch/arm/include/asm/pgtable.h中:
* pgd pte
* | |
* +--------+ +0
* | |-----> +------------+ +0
* +- - - - + +4 | h/w pt 0 |
* | |-----> +------------+ +1024
* +--------+ +8 | h/w pt 1 |
* | | +------------+ +2048
* +- - - - + | Linux pt 0 |
* | | +------------+ +3072
* +--------+ | Linux pt 1 |
* | | +------------+ +4096
这个东西可以看看英文注释,比较容易懂。
看懂这个就很容易弄懂所有的创建内存映射的内容了,像为什么PTRS_PER_PTE定义为512、为什么PTRS_PER_PGD是2048、为什么PGDIR_SHIFT是21等等,下面是完全源码注释:
void __init paging_init(struct machine_desc *mdesc)
{
void *zero_page;
/*根据不同的arm版本,设置不同mem_type*/
build_mem_type_table();
/*判断各个bank是否有highmem即高端内存,根据是否存在高端内存置位或复位各个bank的highmem,
同时检查是否存在某bank的内存与vmalloc区域重合现象,如存在则需要去除(通过削减该bank的size的方式)*/
sanity_check_meminfo();
/*清除临时的内存映射(不包括vmalloc_end以上的高端内存区域)*/
prepare_page_table();
/*为主(物理)内存创建映射;建立bootmem分配器;初始化重要全局变量*/
bootmem_init();
/*为设备I/O内存创建映射,附带首先要清除vmalloc_end以上的高端内存区域的临时页表表项*/
devicemaps_init(mdesc);
/*对于marvell,没有高端内存,无需关心,
对于高端内存映射初始化,也是从bootmem获取页表表项占用的空间,
大小为1页(4K),并在表项中填入表项地址值和属性内容即可*/
kmap_init();
/*给全局变量top_pmd赋值,值为0xffff0000在一级页表中的表项位置,
地址0xffff0000到4G之间为copy_user_page/clear_user_page等函数使用*/
top_pmd = pmd_off_k(0xffff0000);
/*
* allocate the zero page. Note that this always succeeds and
* returns a zeroed result.
*/
/*由bootmem分配器获取一页物理内存,转换为虚拟地址后赋给zero_page,
把对应的物理页地址赋值给empty_zero_page并刷新该页*/
zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
empty_zero_page = virt_to_page(zero_page);
flush_dcache_page(empty_zero_page);
}
***************************************************************************
/*清除汇编阶段创建的临时页表的全部的段映射设置(即16K一级页表全部写0)*/
static inline void prepare_page_table(void)
{
unsigned long addr;
/*
* Clear out all the mappings below the kernel image.
*/
/*MODULES_VADDR(模块区)值为(PAGE_OFFSET - 16*1024*1024)即内核下16MB,其实就是内核以下的映射,
前两个for循环将清除内核空间以下的映射
pmd_clear是将()中的页表项地址中的Cache无效,以及清除段开始的两个字节
pmd_off_k为获取addr在一级页表中的位置
要注意pmd_clear的实现,注意传进来的地址是什么,为什么操作其后两个成员*/
for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
#ifdef CONFIG_XIP_KERNEL
/* The XIP kernel is mapped in the module area -- skip over it */
addr = ((unsigned long)_etext + PGDIR_SIZE - 1) & PGDIR_MASK;
#endif
/*再完成模块区MODULES_VADDR到内核起始的这16MB的清除*/
for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
/*
* Clear out all the kernel space mappings, except for the first
* memory bank, up to the end of the vmalloc region.
*/
/*从物理内存对应的虚拟起始地址开始,直到VMALLOC区结尾,清除*/
for (addr = __phys_to_virt(bank_phys_end(&meminfo.bank[0]));
addr < VMALLOC_END; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
}
***************************************************************************
/*为主(物理)内存创建映射;建立bootmem分配器;初始化重要全局变量*/
void __init bootmem_init(void)
{
/*对于marvell,此时meminfo已初始化,start = 0,size = 128MB,nr_banks = 1,其他应该是均为0*/
struct meminfo *mi = &meminfo;
unsigned long min, max_low, max_high;
int node, initrd_node;
sort(&mi->bank, mi->nr_banks, sizeof(mi->bank[0]), meminfo_cmp, NULL);
/*
* Locate which node contains the ramdisk image, if any.
*/
/*绝大多数情况下,无用*/
initrd_node = check_initrd(mi);
max_low = max_high = 0;
/*
* Run through each node initialising the bootmem allocator.
*/
/*查看for_each_node的实现,也就理解了底下的代码是什么意思,如果有多于一个的内存node,
如何判断内存node个数?就是宏NODES_SHIFT(其引申宏MAX_NUMNODES,单node时这两个宏值均为1),
在单node情况下,就是个for(node = 0; node == 0;node = 1){底下代码}的结果,即底下代码只执行一次*/
for_each_node(node) {
unsigned long node_low, node_high;
/*由该bank参数,给min、node_low、node_high参数赋值start、end、end,正常情况下:
min: start,物理的页地址起始,0
node_low: end,物理的页地址结尾,0x8000
node_high: end,,物理的页地址结尾,0x8000*/
find_node_limits(node, mi, &min, &node_low, &node_high);
/*max_low = max_high = node_low = node_high = 0x8000*/
if (node_low > max_low)
max_low = node_low;
if (node_high > max_high)
max_high = node_high;
/*
* If there is no memory in this node, ignore it.
* (We can't have nodes which have no lowmem)
*/
/*这个node里面实际没有物理内存,返回*/
if (node_low == 0)
continue;
/*为主(物理)内存的指定节点创建映射,
node = 0,mi是meminfo的地址,min = 0,node_low = 0x8000*/
/*为主(物理)内存的指定节点node创建映射,start_pfn是物理起始地址,end_pfn是物理结尾地址
这时mi即meminfo的node成员等于0,highmem为0,nr_banks为1
node = 0,mi是meminfo的地址,start_pfn = 0,end_pfn = 0x8000
此外,最重要的是建立了bootmem分配器,在mm_init初始化伙伴系统之前用于分配物理内存的工具,
这里将所有node的每个bank都做了映射并设为可用,并且把bootmem自身的bitmap分配表设为已用*/
bootmem_init_node(node, mi, min, node_low);
……………………后面的是和bootmem分配器相关,下一专题讨论
***************************************************************************
/*为主(物理)内存的指定节点node创建映射,start_pfn是物理起始地址,end_pfn是物理结尾地址
这时mi即meminfo的node成员等于0,highmem为0,nr_banks为1
node = 0,mi是meminfo的地址,start_pfn = 0,end_pfn = 0x8000
此外,最重要的是建立了bootmem分配器,在mm_init初始化伙伴系统之前用于分配物理内存的工具,
这里将所有node的每个bank都做了映射并设为可用,并且把bootmem自身的bitmap分配表设为已用*/
static void __init bootmem_init_node(int node, struct meminfo *mi,
unsigned long start_pfn, unsigned long end_pfn)
{
unsigned long boot_pfn;
unsigned int boot_pages;
pg_data_t *pgdat;
int i;
/*
* Map the memory banks for this node.
*/
/*就是对该node的每个bank进行遍历循环*/
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
/*为一个Bank创建映射*/
if (!bank->highmem)
map_memory_bank(bank);
}
………………………………后面的是bootmem分配器相关,下一专题讨论
***************************************************************************
/*arm体系的创建页表,为一个Bank创建映射*/
static inline void map_memory_bank(struct membank *bank)
{
#ifdef CONFIG_MMU
struct map_desc map;
/*物理页地址,对于marvell,为0*/
map.pfn = bank_pfn_start(bank);
/*虚拟地址,对于marvell,为0xc0000000*/
map.virtual = __phys_to_virt(bank_phys_start(bank));
/*长度,对于marvell,为128MB*/
map.length = bank_phys_size(bank);
/*类型*/
map.type = MT_MEMORY;
/*实际开始创建页表,为一个物理存储空间创建映射*/
create_mapping(&map);
#endif
}
***************************************************************************
/*实际开始创建页表,为一个物理存储空间创建映射*/
void __init create_mapping(struct map_desc *md)
{
unsigned long phys, addr, length, end;
const struct mem_type *type;
pgd_t *pgd;
/*虚拟地址不是中断表地址并且在用户区(0~3G),出错*/
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for "
"0x%08llx at 0x%08lx in user region\n",
__pfn_to_phys((u64)md->pfn), md->virtual);
return;
}
/*内存类型为IO型或ROM并且虚拟地址为低端内存申请区(3G~3G + 768MB),出错*/
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) {
printk(KERN_WARNING "BUG: mapping for 0x%08llx at 0x%08lx "
"overlaps vmalloc space\n",
__pfn_to_phys((u64)md->pfn), md->virtual);
}
/*mem_types的type成员在前面配置(build_mem_type_table),
说明这个区间所属的域,以及是否可读、写、可高速缓存等属性
得到映射类型和属性
根据内存类型获取内存信息,主要是CP15中的内存操作权限*/
type = &mem_types[md->type];
/*
* Catch 36-bit addresses
*/
/*页表号>1M,超过4G了,实际上这个情况绝大多数情况下不会出现*/
if (md->pfn >= 0x100000) {
create_36bit_mapping(md, type);
return;
}
/*对于marvell,内核的虚拟起始地址addr为0xc0000000,PAGE_MASK = 0xfffff000 = ~(4095)*/
addr = md->virtual & PAGE_MASK;
/*对于marvell,phys为0*/
phys = (unsigned long)__pfn_to_phys(md->pfn);
/*按页对齐计算长度(主要是补齐未按页对齐的多余的部分),对于marvell,为128MB*/
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
/*段类型为0(意为不是段映射)但却能按1MB对齐,前后矛盾,出错*/
if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not "
"be mapped using pages, ignoring.\n",
__pfn_to_phys(md->pfn), addr);
return;
}
/*下面等于 = pgd_offset(&init_mm, addr) = (init_mm)->pgd + pgd_index(addr)
= (init_mm)->pgd + ((addr) >> 21)
= (pgd_t *)0xc0004000 + (addr >> 21)
(init_mm)->pgd = swapper_pg_dir,该变量在head.S中定义,定义为KERNEL_RAM_VADDR - 0x4000 = 0xc0004000
含义是:得到虚拟地址addr在一级页表PGD中的位置,即addr对应的段在init_mm->pgd的下标*/
pgd = pgd_offset_k(addr);
/*end是结尾地址*/
end = addr + length;
do {
/*只要不超过end,就获得下一段(2MB)的虚拟起始地址
所以传给alloc_init_section的next参数,要么与addr相差一整段(2MB),要么是end则不足一整段(2MB)*/
unsigned long next = pgd_addr_end(addr, end);
/*申请并初始化一个段,参数分别是:
一级页表(段页表)地址、虚拟起始地址、虚拟结尾地址(要么是addr + 2MB,要么是end)、物理起始地址、内存类型*/
alloc_init_section(pgd, addr, next, phys, type);
/*物理地址累加*/
phys += next - addr;
/*更新addr为下一段的虚拟起始地址*/
addr = next;
} while (pgd++, addr != end); /*下一段,判断条件就是虚拟起始地址addr不等于虚拟结尾地址end*/
}
***************************************************************************
/*一级页表(段页表)地址、虚拟起始地址、虚拟结尾地址(要么是addr + 2MB,要么是end)、物理起始地址、内存类型*/
static void __init alloc_init_section(pgd_t *pgd, unsigned long addr,
unsigned long end, unsigned long phys,
const struct mem_type *type)
{
/*这里就要真正的进行映射了,可能是直接段映射(end - addr = 2MB),也有可能是二级映射(end - addr < 2MB),
但无论哪种映射,都要实际写一级页表的表项,由于pgd的处理是一个表项实际带两个条目(2个ulong),但最终表项必须每个都要写入,
所以到了这里,要用pmd(1个ulong)定位一级页表表项了,具体说来如下:
对于段映射,底下的"if (addr & SECTION_SIZE)",就是为了区分是哪个1MB段(奇数段or偶数段);
对于二级映射,在函数alloc_init_pte中会把这2个1MB的段都写入二级页表地址*/
pmd_t *pmd = pmd_offset(pgd, addr);
/*
* Try a section mapping - end, addr and phys must all be aligned
* to a section boundary. Note that PMDs refer to the individual
* L1 entries, whereas PGDs refer to a group of L1 entries making
* up one logical pointer to an L2 table.
*/
/*当大小和地址1MB(掩码0x000fffff)对齐时,使用段映射*/
if (((addr | end | phys) & ~SECTION_MASK) == 0) {
pmd_t *p = pmd;
/*奇数段,段的处理总是一次处理2段(2MB),SECTION_SIZE = 0x00100000
之所以一次处理2段,是因为数据结构pgd_t是两个ulong成员的数组的类型,即它大小是8个字节,
上面的pmd直接取pgd的值,但它是ulong型即4个字节大小
//重要知识: arm硬件实际的一级页表共4096个条目,其index从0-0xfff(4095),每个条目占4个字节,
对于marvell来说,这个一级页表自身的虚拟地址范围是[0xc0004000,0xc0007fff]共占用16K空间,形象的表示如下图:
pgd_t pmd-t index vaddr value
0xc0004000 0xc0004000 0x000 0xc0004000 XXXXX
0xc0004000 0xc0004004 0x001 0xc0004004 XXXXX
0xc0004008 0xc0004008 0x002 0xc0004008 XXXXX
0xc0004000 0xc000400c 0x003 0xc000400c XXXXX
0xc0004010 0xc0004010 0x004 0xc0004010 XXXXX
0xc0004000 0xc0004014 0x005 0xc0004014 XXXXX
..........
对于pgd,它就是按2个段(2MB)的幅度递增,这样将无法只能定位到偶数的段,
而pmd则在这里通过判断第21位判断是否是奇数,来定位到奇数的段
按说这种情况发生概率不大,因为它发生的条件是: pgd参数是某个两段(2MB)的起始,但addr参数却是这2MB的后1MB,
这时确实需要后移pmd,但应该发生概率较低*/
if (addr & SECTION_SIZE)
pmd++;
/*段页表的值value(即一级页表的值,代码中的*pmd)如果是按段映射则是物理地址的值,如果是按二级映射则是二级映射表地址,
在if这里,先看段映射的情况,pmd指向的地址虽然和pgd一样,但它是ulong型
二级映射的情况将在下面的alloc_init_pte中注释*/
do {
/*将(物理地址 |段属性(prot_sect))的值填入段描述符,可见,作为段映射的一级页表,写入的是物理地址(及其属性)
这就是在写段页表(一级页表)的表项内容了!!! */
*pmd = __pmd(phys | type->prot_sect);
/*下一段(1MB)*/
phys += SECTION_SIZE;
} while (pmd++, addr += SECTION_SIZE, addr != end);
/*填充一层页表的对应项,本质是将内容强行刷进RAM中
实际写段映射(一级映射)表项了!!!*/
flush_pmd_entry(p);
}
/*对于不满1MB的部分,不能直接段映射(一级映射),需要二级映射,
alloc_init_pte就是完成二级映射!!!
下面的注释是在说为什么这里不用循环,因为能整段映射的已经在上面的if中映射了,需要进入这里的肯定是不足1MB的区段
需要注意的是,在初始化为物理内存node分配页表(bootmem_init_node->map_memory_bank->create_mapping)时,
不会调用到这里,因为那些都是段映射!!!
*/
else {
/*
* No need to loop; pte's aren't interested in the
* individual L1 entries.
*/
alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);
}
}
***************************************************************************
static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
unsigned long end, unsigned long pfn,
const struct mem_type *type)
{
pte_t *pte;
/*这里是做二级页表映射,即让一级页表表项pmd中写入二级页表的地址值
这里是先检查这个一级页表表项是否已写入了东西,如果没有内容(NULL),则说明需要创建一个二级页表
可见,二级页表不同于一级页表,它是动态创建释放的,在slab分配器可用之前,使用bootmem分配器创建*/
if (pmd_none(*pmd)) {
/*为二级页表pte分配4KB空间,参数值为4096*/
pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));
/*写一级页表表项pmd,值是二级页表表项的物理地址及其属性*/
__pmd_populate(pmd, __pa(pte) | type->prot_l1);
}
/*定位到二级页表pte的虚拟地址*/
pte = pte_offset_kernel(pmd, addr);
do {
set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
pfn++;
} while (pte++, addr += PAGE_SIZE, addr != end);
}
***************************************************************************
/*为设备I/O内存创建映射*/
static void __init devicemaps_init(struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void *vectors;
/*
* Allocate the vector page early.
*/
/*从bootmem分配器中获取一页(4K)物理空间,赋给vectors,这是用于中断向量的!!!
注意下获取到的已经转换为虚拟地址*/
vectors = alloc_bootmem_low_pages(PAGE_SIZE);
/*这里还有一步重要内容,就是彻底清除一级页表项,前面的prepare_page_table已经将0-VMALLOC_END的页表项全部删除,
这里把VMALLOC_END到4G的页表项全部清除
为什么在这里才清除?因为这里要搞的arm中断向量映射,是处在高端内存区域,所以这里清除比较恰当*/
for (addr = VMALLOC_END; addr; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
/*
* Map the kernel if it is XIP.
* It is always first in the modulearea.
*/
#ifdef CONFIG_XIP_KERNEL
map.pfn = __phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK);
map.virtual = MODULES_VADDR;
map.length = ((unsigned long)_etext - map.virtual + ~SECTION_MASK) & SECTION_MASK;
map.type = MT_ROM;
create_mapping(&map);
#endif
/*
* Map the cache flushing regions.
*/
#ifdef FLUSH_BASE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS);
map.virtual = FLUSH_BASE;
map.length = SZ_1M;
map.type = MT_CACHECLEAN;
create_mapping(&map);
#endif
#ifdef FLUSH_BASE_MINICACHE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M);
map.virtual = FLUSH_BASE_MINICACHE;
map.length = SZ_1M;
map.type = MT_MINICLEAN;
create_mapping(&map);
#endif
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
/*实际开始创建页表,为一个物理存储空间创建映射*/
/*先获取vectors的物理页地址*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
/*0xffff0000是arm默认的中断向量所在页,长度为1页,不可轻易修改!
注意,这里映射大小为4K,小于1M,将使用二级映射!*/
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map);
if (!vectors_high()) {
map.virtual = 0;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/*
* Ask the machine support to map in the statically mapped devices.
*/
/*这个很重要,是移植相关重要内容,执行machine_desc结构变量中定义的map_io成员,这里的marvell为函数mv_map_io,分为7个部分*/
if (mdesc->map_io)
mdesc->map_io();
/*
* Finally flush the caches and tlb to ensure that we're in a
* consistent state wrt the writebuffer. This also ensures that
* any write-allocated cache lines in the vector page are written
* back. After this point, we can start to touch devices again.
*/
/*刷新内存MMU的页表*/
local_flush_tlb_all();
flush_cache_all();
}
***************************************************************************
/*对于marvell,没有高端内存,无需关心,
对于高端内存映射初始化,也是从bootmem获取页表表项占用的空间,
大小为1页(4K),并在表项中填入表项地址值和属性内容即可*/
static void __init kmap_init(void)
{
/*对于marvell,没有高端内存,无需关心,
对于高端内存映射初始化,也是从bootmem获取页表表项占用的空间,
大小为1页(4K),并在表项中填入表项地址值和属性内容即可*/
#ifdef CONFIG_HIGHMEM
pmd_t *pmd = pmd_off_k(PKMAP_BASE);
pte_t *pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));
BUG_ON(!pmd_none(*pmd) || !pte);
__pmd_populate(pmd, __pa(pte) | _PAGE_KERNEL_TABLE);
pkmap_page_table = pte + PTRS_PER_PTE;
#endif
}
最后,附上在源码加入调试打印的调试结果,调试方式是在段式映射函数alloc_init_section和二级映射函数alloc_init_pte中加入打印信息,记录都有哪些东西被映射,level 1标识段式映射,level 2标识二级映射:
//前面有些没有打印出来,下面是从物理内存映射半截开始:
level 1: pmd: c00072ec, addr: cbb00000, phy: 0bc00000, end: cbc00000, addr+secsize: cbc00000
level 1: pmd: c00072f0, addr: cbc00000, phy: 0bd00000, end: cbe00000, addr+secsize: cbd00000
level 1: pmd: c00072f4, addr: cbd00000, phy: 0be00000, end: cbe00000, addr+secsize: cbe00000
level 1: pmd: c00072f8, addr: cbe00000, phy: 0bf00000, end: cc000000, addr+secsize: cbf00000
level 1: pmd: c00072fc, addr: cbf00000, phy: 0c000000, end: cc000000, addr+secsize: cc000000
level 1: pmd: c0007300, addr: cc000000, phy: 0c100000, end: cc200000, addr+secsize: cc100000
level 1: pmd: c0007304, addr: cc100000, phy: 0c200000, end: cc200000, addr+secsize: cc200000
level 1: pmd: c0007308, addr: cc200000, phy: 0c300000, end: cc400000, addr+secsize: cc300000
level 1: pmd: c000730c, addr: cc300000, phy: 0c400000, end: cc400000, addr+secsize: cc400000
level 1: pmd: c0007310, addr: cc400000, phy: 0c500000, end: cc600000, addr+secsize: cc500000
level 1: pmd: c0007314, addr: cc500000, phy: 0c600000, end: cc600000, addr+secsize: cc600000
level 1: pmd: c0007318, addr: cc600000, phy: 0c700000, end: cc800000, addr+secsize: cc700000
level 1: pmd: c000731c, addr: cc700000, phy: 0c800000, end: cc800000, addr+secsize: cc800000
level 1: pmd: c0007320, addr: cc800000, phy: 0c900000, end: cca00000, addr+secsize: cc900000
level 1: pmd: c0007324, addr: cc900000, phy: 0ca00000, end: cca00000, addr+secsize: cca00000
level 1: pmd: c0007328, addr: cca00000, phy: 0cb00000, end: ccc00000, addr+secsize: ccb00000
level 1: pmd: c000732c, addr: ccb00000, phy: 0cc00000, end: ccc00000, addr+secsize: ccc00000
level 1: pmd: c0007330, addr: ccc00000, phy: 0cd00000, end: cce00000, addr+secsize: ccd00000
level 1: pmd: c0007334, addr: ccd00000, phy: 0ce00000, end: cce00000, addr+secsize: cce00000
level 1: pmd: c0007338, addr: cce00000, phy: 0cf00000, end: cd000000, addr+secsize: ccf00000
level 1: pmd: c000733c, addr: ccf00000, phy: 0d000000, end: cd000000, addr+secsize: cd000000
level 1: pmd: c0007340, addr: cd000000, phy: 0d100000, end: cd200000, addr+secsize: cd100000
level 1: pmd: c0007344, addr: cd100000, phy: 0d200000, end: cd200000, addr+secsize: cd200000
level 1: pmd: c0007348, addr: cd200000, phy: 0d300000, end: cd400000, addr+secsize: cd300000
level 1: pmd: c000734c, addr: cd300000, phy: 0d400000, end: cd400000, addr+secsize: cd400000
level 1: pmd: c0007350, addr: cd400000, phy: 0d500000, end: cd600000, addr+secsize: cd500000
level 1: pmd: c0007354, addr: cd500000, phy: 0d600000, end: cd600000, addr+secsize: cd600000
level 1: pmd: c0007358, addr: cd600000, phy: 0d700000, end: cd800000, addr+secsize: cd700000
level 1: pmd: c000735c, addr: cd700000, phy: 0d800000, end: cd800000, addr+secsize: cd800000
level 1: pmd: c0007360, addr: cd800000, phy: 0d900000, end: cda00000, addr+secsize: cd900000
level 1: pmd: c0007364, addr: cd900000, phy: 0da00000, end: cda00000, addr+secsize: cda00000
level 1: pmd: c0007368, addr: cda00000, phy: 0db00000, end: cdc00000, addr+secsize: cdb00000
level 1: pmd: c000736c, addr: cdb00000, phy: 0dc00000, end: cdc00000, addr+secsize: cdc00000
level 1: pmd: c0007370, addr: cdc00000, phy: 0dd00000, end: cde00000, addr+secsize: cdd00000
level 1: pmd: c0007374, addr: cdd00000, phy: 0de00000, end: cde00000, addr+secsize: cde00000
level 1: pmd: c0007378, addr: cde00000, phy: 0df00000, end: ce000000, addr+secsize: cdf00000
level 1: pmd: c000737c, addr: cdf00000, phy: 0e000000, end: ce000000, addr+secsize: ce000000
level 1: pmd: c0007380, addr: ce000000, phy: 0e100000, end: ce200000, addr+secsize: ce100000
level 1: pmd: c0007384, addr: ce100000, phy: 0e200000, end: ce200000, addr+secsize: ce200000
level 1: pmd: c0007388, addr: ce200000, phy: 0e300000, end: ce400000, addr+secsize: ce300000
level 1: pmd: c000738c, addr: ce300000, phy: 0e400000, end: ce400000, addr+secsize: ce400000
level 1: pmd: c0007390, addr: ce400000, phy: 0e500000, end: ce600000, addr+secsize: ce500000
level 1: pmd: c0007394, addr: ce500000, phy: 0e600000, end: ce600000, addr+secsize: ce600000
level 1: pmd: c0007398, addr: ce600000, phy: 0e700000, end: ce800000, addr+secsize: ce700000
level 1: pmd: c000739c, addr: ce700000, phy: 0e800000, end: ce800000, addr+secsize: ce800000
level 1: pmd: c00073a0, addr: ce800000, phy: 0e900000, end: cea00000, addr+secsize: ce900000
level 1: pmd: c00073a4, addr: ce900000, phy: 0ea00000, end: cea00000, addr+secsize: cea00000
level 1: pmd: c00073a8, addr: cea00000, phy: 0eb00000, end: cec00000, addr+secsize: ceb00000
level 1: pmd: c00073ac, addr: ceb00000, phy: 0ec00000, end: cec00000, addr+secsize: cec00000
level 1: pmd: c00073b0, addr: cec00000, phy: 0ed00000, end: cee00000, addr+secsize: ced00000
level 1: pmd: c00073b4, addr: ced00000, phy: 0ee00000, end: cee00000, addr+secsize: cee00000
level 1: pmd: c00073b8, addr: cee00000, phy: 0ef00000, end: cf000000, addr+secsize: cef00000
level 1: pmd: c00073bc, addr: cef00000, phy: 0f000000, end: cf000000, addr+secsize: cf000000
level 1: pmd: c00073c0, addr: cf000000, phy: 0f100000, end: cf200000, addr+secsize: cf100000
level 1: pmd: c00073c4, addr: cf100000, phy: 0f200000, end: cf200000, addr+secsize: cf200000
level 1: pmd: c00073c8, addr: cf200000, phy: 0f300000, end: cf400000, addr+secsize: cf300000
level 1: pmd: c00073cc, addr: cf300000, phy: 0f400000, end: cf400000, addr+secsize: cf400000
level 1: pmd: c00073d0, addr: cf400000, phy: 0f500000, end: cf600000, addr+secsize: cf500000
level 1: pmd: c00073d4, addr: cf500000, phy: 0f600000, end: cf600000, addr+secsize: cf600000
level 1: pmd: c00073d8, addr: cf600000, phy: 0f700000, end: cf800000, addr+secsize: cf700000
level 1: pmd: c00073dc, addr: cf700000, phy: 0f800000, end: cf800000, addr+secsize: cf800000
level 1: pmd: c00073e0, addr: cf800000, phy: 0f900000, end: cfa00000, addr+secsize: cf900000
level 1: pmd: c00073e4, addr: cf900000, phy: 0fa00000, end: cfa00000, addr+secsize: cfa00000
level 1: pmd: c00073e8, addr: cfa00000, phy: 0fb00000, end: cfc00000, addr+secsize: cfb00000
level 1: pmd: c00073ec, addr: cfb00000, phy: 0fc00000, end: cfc00000, addr+secsize: cfc00000
level 1: pmd: c00073f0, addr: cfc00000, phy: 0fd00000, end: cfe00000, addr+secsize: cfd00000
level 1: pmd: c00073f4, addr: cfd00000, phy: 0fe00000, end: cfe00000, addr+secsize: cfe00000
level 1: pmd: c00073f8, addr: cfe00000, phy: 0ff00000, end: d0000000, addr+secsize: cff00000
level 1: pmd: c00073fc, addr: cff00000, phy: 10000000, end: d0000000, addr+secsize: d0000000
//下面这个二级映射是中断映射:
level 2: pmd: c0007ff8, addr:ffff0000, phys: 00c37000
//下面是machine_desc的map_io成员,即硬件IO映射:
level 1: pmd: c0007c40, addr: f1000000, phy: f1100000, end: f1100000, addr+secsize: f1100000
level 1: pmd: c0007c80, addr: f2000000, phy: f2100000, end: f2100000, addr+secsize: f2100000
level 1: pmd: c0007c84, addr: f2100000, phy: f2200000, end: f2200000, addr+secsize: f2200000
level 1: pmd: c0007d80, addr: f6000000, phy: f6100000, end: f6200000, addr+secsize: f6100000
level 1: pmd: c0007d84, addr: f6100000, phy: f6200000, end: f6200000, addr+secsize: f6200000
level 1: pmd: c0007d88, addr: f6200000, phy: f6300000, end: f6400000, addr+secsize: f6300000
level 1: pmd: c0007d8c, addr: f6300000, phy: f6400000, end: f6400000, addr+secsize: f6400000
level 1: pmd: c0007d90, addr: f6400000, phy: f6500000, end: f6600000, addr+secsize: f6500000
level 1: pmd: c0007d94, addr: f6500000, phy: f6600000, end: f6600000, addr+secsize: f6600000
level 1: pmd: c0007d98, addr: f6600000, phy: f6700000, end: f6800000, addr+secsize: f6700000
level 1: pmd: c0007d9c, addr: f6700000, phy: f6800000, end: f6800000, addr+secsize: f6800000
level 1: pmd: c0007da0, addr: f6800000, phy: f6900000, end: f6a00000, addr+secsize: f6900000
level 1: pmd: c0007da4, addr: f6900000, phy: f6a00000, end: f6a00000, addr+secsize: f6a00000
level 1: pmd: c0007da8, addr: f6a00000, phy: f6b00000, end: f6c00000, addr+secsize: f6b00000
level 1: pmd: c0007dac, addr: f6b00000, phy: f6c00000, end: f6c00000, addr+secsize: f6c00000
level 1: pmd: c0007db0, addr: f6c00000, phy: f6d00000, end: f6e00000, addr+secsize: f6d00000
level 1: pmd: c0007db4, addr: f6d00000, phy: f6e00000, end: f6e00000, addr+secsize: f6e00000
level 1: pmd: c0007db8, addr: f6e00000, phy: f6f00000, end: f7000000, addr+secsize: f6f00000
level 1: pmd: c0007dbc, addr: f6f00000, phy: f7000000, end: f7000000, addr+secsize: f7000000
level 1: pmd: c0007dc0, addr: f7000000, phy: f7100000, end: f7200000, addr+secsize: f7100000
level 1: pmd: c0007dc4, addr: f7100000, phy: f7200000, end: f7200000, addr+secsize: f7200000
level 1: pmd: c0007dc8, addr: f7200000, phy: f7300000, end: f7400000, addr+secsize: f7300000
level 1: pmd: c0007dcc, addr: f7300000, phy: f7400000, end: f7400000, addr+secsize: f7400000
level 1: pmd: c0007dd0, addr: f7400000, phy: f7500000, end: f7600000, addr+secsize: f7500000
level 1: pmd: c0007dd4, addr: f7500000, phy: f7600000, end: f7600000, addr+secsize: f7600000
level 1: pmd: c0007dd8, addr: f7600000, phy: f7700000, end: f7800000, addr+secsize: f7700000
level 1: pmd: c0007ddc, addr: f7700000, phy: f7800000, end: f7800000, addr+secsize: f7800000
level 1: pmd: c0007de0, addr: f7800000, phy: f7900000, end: f7a00000, addr+secsize: f7900000
level 1: pmd: c0007de4, addr: f7900000, phy: f7a00000, end: f7a00000, addr+secsize: f7a00000
level 1: pmd: c0007de8, addr: f7a00000, phy: f7b00000, end: f7c00000, addr+secsize: f7b00000
level 1: pmd: c0007dec, addr: f7b00000, phy: f7c00000, end: f7c00000, addr+secsize: f7c00000
level 1: pmd: c0007df0, addr: f7c00000, phy: f7d00000, end: f7e00000, addr+secsize: f7d00000
level 1: pmd: c0007df4, addr: f7d00000, phy: f7e00000, end: f7e00000, addr+secsize: f7e00000
level 1: pmd: c0007df8, addr: f7e00000, phy: f7f00000, end: f8000000, addr+secsize: f7f00000
level 1: pmd: c0007dfc, addr: f7f00000, phy: f8000000, end: f8000000, addr+secsize: f8000000
level 1: pmd: c0007c88, addr: f2200000, phy: f2300000, end: f2400000, addr+secsize: f2300000
level 1: pmd: c0007c8c, addr: f2300000, phy: f2400000, end: f2400000, addr+secsize: f2400000
level 1: pmd: c0007c90, addr: f2400000, phy: f2500000, end: f2600000, addr+secsize: f2500000
level 1: pmd: c0007c94, addr: f2500000, phy: f2600000, end: f2600000, addr+secsize: f2600000
level 1: pmd: c0007d40, addr: f5000000, phy: f5100000, end: f5100000, addr+secsize: f5100000
//这个还没有找到!
level 2: pmd: c0007f00, addr:fc000000, phys: fc000000
=============================================arm-linux之bootmem分配器 =======================
http://blog.csdn.net/u010246947/article/details/9928249
所谓bootmem分配器,是内核页表创建后的临时使用的内存分配器,它将在后面的mem_init函数初始化伙伴系统时被伙伴系统取代,即它是一个过渡手段。理解bootmem,一方面有利于对内存管理尤其内核启动阶段阶段内存管理的理解,另一方面也有利于伙伴系统的理解。
在函数bootmem_init中,对每一个node执行bootmem_init_node,bootmem_init_node又对该node的每一个bank执行map_memory_bank即创建内存映射后,接下来的内容是创建bootmem分配器,如下:
static void __init bootmem_init_node(int node, struct meminfo *mi,
unsigned longstart_pfn, unsigned long end_pfn)
{
unsigned longboot_pfn;
unsigned intboot_pages;
pg_data_t *pgdat;
int i;
/*
* Map the memory banks for this node.
*/
for_each_nodebank(i,mi, node) {
structmembank *bank = &mi->bank[i];
if(!bank->highmem)
map_memory_bank(bank);
}
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
boot_pfn = find_bootmap_pfn(node, mi, boot_pages);
node_set_online(node);
pgdat = NODE_DATA(node);
init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);
for_each_nodebank(i, mi, node) {
struct membank*bank = &mi->bank[i];
if(!bank->highmem)
free_bootmem_node(pgdat,bank_phys_start(bank), bank_phys_size(bank));
}
reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,
boot_pages << PAGE_SHIFT,BOOTMEM_DEFAULT);
}
下面是详述:
1、 boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
bootmem分配器是用bitmap即比特位图的方式标识每一个物理页的占用未占用的情况,这里就是计算这么些物理页需要多少个字节的bitmap,如128M物理内存,相当于32768个物理页,需要32768/8 = 4096个字节的bitmap,即4K的空间(一页)用于bitmap;
2、 boot_pfn = find_bootmap_pfn(node, mi, boot_pages);
找出内核镜像之后(即_end之后)的第一个物理页地址,找它是为了确定这个bitmap应该放在哪,它应该放在内核镜像之后;
3、 node_set_online(node);
设置这个node为online,这是个软件的伎俩,知道意思即可;
4、 pgdat = NODE_DATA(node);
取得该node的pg_data_t数据结构变量,每个node都有一个pg_data_t变量描述;今后会经常用到;
5、 init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);
它将直接调用init_bootmem_core (pgdat->bdata, freepfn, startpfn, endpfn);
这是直接创建bootmem分配器了,第一参数pgdat->bdata是bootmem_data_t类型,这个就是bootmem分配器成员:
下面是确定bootmem分配器的实体:bitmap的虚拟地址在哪里,参数mapstart就是前面第2步得到的boot_pfn即bitmap所在物理页地址,把它转换成虚拟地址;
bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));
下面是确定bootmem分配器的起始物理页地址、结尾物理地址
bdata->node_min_pfn = start;
bdata->node_low_pfn = end;
把这个bootmem分配器加入到链表bdata_list中,链表头bdata_list就在本文件定义(这步无需重视);
link_bootmem(bdata);
下面是计算出该bootmem分配器的bitmap占用多少字节mapsize;然后根据字节数长度把bitmap全都置成1,意为当前所有页都是不可用(bootmem分配器,原理是用每一比特标识每一页的使用情况,置1为已占用,置0为可用)
mapsize = bootmap_bytes(end - start);
memset(bdata->node_bootmem_map, 0xff, mapsize);
6、 for_each_nodebank(i, mi, node) {
structmembank *bank = &mi->bank[i];
if (!bank->highmem)
free_bootmem_node(pgdat,bank_phys_start(bank), bank_phys_size(bank));
}
对该node的每个bank,只要是低端内存,调用函数free_bootmem_node;
void__init free_bootmem_node(pg_data_t *pgdat, unsigned long physaddr,
unsigned long size)
{
unsigned long start, end;
kmemleak_free_part(__va(physaddr), size);
start = PFN_UP(physaddr);
end = PFN_DOWN(physaddr + size);
mark_bootmem_node(pgdat->bdata, start, end,0, 0);
}
首先由start和end获取物理起始、结尾地址start和end;然后调用函数mark_bootmem_node:
mark_bootmem_node(pgdat->bdata, start, end, 0, 0);
这个函数的用途是把该bootmem分配器标识的相关内存(由start和end标识是哪些内存),进行标记,标记为可用或已用(通过标记node的boomtown的node_bootmem_map成员为1或0,为1为已用,为0为可用)
staticint __init mark_bootmem_node(bootmem_data_t *bdata,
unsigned longstart, unsigned long end,
int reserve, intflags)
{
unsigned long sidx, eidx;
bdebug("nid=%td start=%lx end=%lxreserve=%d flags=%x\n",
bdata - bootmem_node_data, start,end, reserve, flags);
BUG_ON(start < bdata->node_min_pfn);
BUG_ON(end > bdata->node_low_pfn);
sidx = start - bdata->node_min_pfn;
eidx = end - bdata->node_min_pfn;
if (reserve)
return __reserve(bdata, sidx, eidx,flags);
else
__free(bdata, sidx, eidx);
return 0;
}
由物理页地址和该node的bootmem分配器的首尾物理页地址的差值,获取索引sidx和eidx,最后通过__reserve或__free设置某块内存区域为已占用(reserve)或已可用(free);无需再细看__reserve和__free的实现,它们都是bitmap相应的位操作;
要注意这里的意义:执行到这里,该node的低端物理内存均已在bootmem分配器中标识为可用!
7、reserve_bootmem_node(pgdat,boot_pfn << PAGE_SHIFT,
boot_pages << PAGE_SHIFT,BOOTMEM_DEFAULT);
reserve_bootmem_node和上面的free_bootmem_node功能相反,它是设置某块内存为已占用,这里把内核代码段之后(_end之后)的第一个物理页地址boot_pfn之后的一页(4K)物理内存设置为不可用,实际上这一页内容就是bootmem的bitmap表本身,所以不能被占用;
至此,除了bitmap本身所在的物理页为已占用,其他内存还是可用的;
8、从bootmem_init_node还是返回,执行到:
if (node == 0)
reserve_node_zero(NODE_DATA(node));
这里是把内核页表(0xc0004000到0xc0008000)以及内核镜像([_stext:_end])部分所处的物理页面也在bootmem分配器的bitmap中标识为已占用;
至此,bitmap本身、内存页表本身、内核镜像本身所处的物理页已被标识为已占用,其他物理内存可用;到这里,bootmem分配器已创建好并且把不应被占用的物理页面保护起来了,接下来看看bootmem分配器的使用:
对于bootmem分配器的使用,重在知道原理和大概记住都用在了哪些地方,具体细节无需细究,因为伙伴系统初始化后bootmem就不用了,没意义:
主要会用到alloc_bootmem_node函数和alloc_bootmem_low_pages函数等,它们最终使用的都是同一个函数alloc_bootmem_core(释放函数仍是free_bootmem_node,不过初始阶段都是在分配,基本没有用到释放),这个函数是从bootmem分配器的bitmap位图中申请内存,过程非常繁琐,网上也有不少关于它的注释和讲解,我个人认为无需仔细分析,因为bootmem分配器本身效率就比较低,又是临时使用的分配手段,重点看看都有哪些地方需要申请内存,至少有如下几处:
devicemaps_init中的为中断向量创建映射时需要映射在高端地址,中断向量本身需申请物理内存(1页),又因为所映射中断向量长度不足1M,不足一个段所以需要二级映射,创建二级页表又需申请1页物理内存;
此外其他IO寄存器映射中,不足1M的映射均需创建二级映射的,都需申请相应数量物理内存;
kmap_init中高端内存映射(当前这里marvell不存在高端内存),需要申请相关创建二级页表所需物理内存;
paging_init中最后需1页物理内存专门用作一个全0页empty_zero_page;
最早的初始化后面伙伴系统函数free_area_init_node中,调用函数alloc_node_mem_map为每一个物理内存页创建其struct page管理结构,所需申请的内存;
在最终start_kernel调用函数mm_init--->mem_init时,将把该node中所有可用的内存转交给伙伴系统:
for_each_online_node(node) {
pg_data_t*pgdat = NODE_DATA(node);
free_unused_memmap_node(node, &meminfo);
if(pgdat->node_spanned_pages != 0)
totalram_pages+= free_all_bootmem_node(pgdat);
}
free_unused_memmap_node函数意思是,逐个bank遍历,/*释放每个bank之间的间隙(它们之间的间隙是下一个bank的描述部分),对于这里的marvell,只有1个bank,不存在此情况;
free_all_bootmem_node是把整个bootmem分配器掌管的内存包括bitmap本身统统都释放到伙伴系统中去。下面是该函数的注释片段:
while (start < end) {
unsignedlong *map, idx, vec;
map =bdata->node_bootmem_map;
idx = start- bdata->node_min_pfn;
vec =~map[idx / BITS_PER_LONG];
/*end比start大32页时,释放内存到伙伴系统*/
if (aligned&& vec == ~0UL && start + BITS_PER_LONG < end) {
intorder = ilog2(BITS_PER_LONG);
__free_pages_bootmem(pfn_to_page(start),order);
count+= BITS_PER_LONG;
}
/*end比start大不足32页时,释放内存到冷热页框*/
else {
unsignedlong off = 0;
while(vec && off < BITS_PER_LONG) {
if(vec & 1) {
page= pfn_to_page(start + off);
__free_pages_bootmem(page,0);
count++;
}
vec>>= 1;
off++;
}
}
start +=BITS_PER_LONG;
}
/*最后把bootmem分配器的位图本身所占页也释放掉,释放给冷热页框*/
page =virt_to_page(bdata->node_bootmem_map);
pages =bdata->node_low_pfn - bdata->node_min_pfn;
pages =bootmem_bootmap_pages(pages);
count += pages;
while (pages--)
__free_pages_bootmem(page++,0);
更多推荐
所有评论(0)