随笔-118  评论-133  文章-4  trackbacks-0

1. 前言

前面介绍了当前启动阶段的内存分配函数memblock_alloc,该内存分配函数在本篇将要介绍paging_init中用于页表和内存的分配,paging_init函数大致流程如下图所示。

在这里插入图片描述
2. paging_init
2.1 build_mem_type_table

该函数根据具体的CPU架构对静态定义的mem_types数组中定义的属性进行调整。
2.2 prepare_page_table

该函数的作用是把页目录项清零,源码大致如下。首先是把虚拟地址范围[0, MODULES_VADDR]的页目录项清零,如果内核是模块区域以XIP方式运行的,则跳过内核部分的页目录,然后继续对区域[addr,PAGE_OFFSET]的页目录项清零,此时用户空间的页目录项已经全部清零;最后,把除了第一块内存条之外的内核空间[__phys_to_virt(end), VMALLOC_START]对应的页目录项清零。
/* arch/arm/mm/mmu.c */
static inline void prepare_page_table(void)
{
 unsigned 
long addr;
 phys_addr_t 
end;
  
/* <--(1)--> */
 
for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
  pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL
 addr 
= ((unsigned long)_etext + PMD_SIZE - 1& PMD_MASK;
#endif
  
/* <--(2)--> */
 
for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
  pmd_clear(pmd_off_k(addr));

 
end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
 
if (end >= lowmem_limit)
  
end = lowmem_limit;
  
/* <--(3)--> */
 
for (addr = __phys_to_virt(end);
      addr 
< VMALLOC_START; addr += PMD_SIZE)
  pmd_clear(pmd_off_k(addr));
}

2.3 map_lowmem

该函数将物理内存地址小于lowmem_limit的内存映射到内核空间,实际的内存映射工作在create_mapping中完成。

/* arch/arm/mm/mmu.c */
static void __init map_lowmem(void)
{
 
 for_each_memblock(memory, reg) {
  start 
= reg->base;
  
end = start + reg->size;

  
if (end > lowmem_limit)
   
end = lowmem_limit;
  
if (start >= end)
   break;

  map.pfn 
= __phys_to_pfn(start);
  map.virtual 
= __phys_to_virt(start);
  map.length 
= end - start;
  map.type 
= MT_MEMORY;

  create_mapping(
&map, false);
 }
}

 

create_mapping函数的大致流程如下图所示,这里需要提一下,linux内核使用的是四级页表,即PGD、PUD、PMD、PTE;而ARM32使用的是二级页表,即PMD、PTE。同时由于内存管理是以页为单位进行的,如果按照ARM硬件MMU的分页机制,一个PMD对应的PTE并不能完全占用完整个页,为了避免内存浪费,会在软件层面上将两个PMD对应的PTE放在一个页内,具体细节可以参考文件arch/arm/include/asm/pgtable-2level.h中的注释部分。最终会调用alloc_init_pte函数对指定范围的内存区域进行映射,其中的early_pte_alloc函数最终也会去调用memblock_alloc函数来分配内存,最后将PTE所在页写入到PMD中即可完成映射。

在这里插入图片描述

/* arch/arm/mm/mmu.c */
static void __init alloc_init_pte()
{
 pte_t 
*start_pte = early_pte_alloc(pmd);
 pte_t 
*pte = start_pte + pte_index(addr);

 
do {
  set_pte_ext(pte, pfn_pte(pfn, __pgprot(type
->prot_pte)), 0);
  pfn
++;
 } 
while (pte++, addr += PAGE_SIZE, addr != end);
 early_pte_install(pmd, start_pte, type
->prot_l1);
}

 注:

    s5p4418下,调试发现map_lowmem全走的section mapping,不然我还奇怪存放PTE用的内存如何获取(通过memblock_alloc分配的物理内存尚未映像)

2.4 devicemaps_init

该函数大致流程如下图所示,首先调用early_alloc分配一个页,然后调用early_trap_init将向量表复制到新的页内,最后调用create_mapping将这个页映射到0xffff0000处,如果mdesc->map_io存在,还会对设备相关的IO进行映射。

在这里插入图片描述

比如对于s5p4418:

内核就会把上面几段IO物理地址映射到对应的地方,最实用的莫过于内核/驱动下通过IO_ADDRESS(x)来访问寄存器了,否则就要用io_remap来临时映射就麻烦很多了。

2.5 kmap_init

这个函数非常简单,把大小为2MB的区间[PKMAP_BASE,PAGE_OFFSET]映射到内核空间。

/* arch/arm/mm/mmu.c */
static void __init kmap_init(void)
{
#ifdef CONFIG_HIGHMEM
 pkmap_page_table 
= early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),
  PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif
}

 注:

    关于kmap的介绍,可参考Linux中的kmap


3. 总结

本文主要介绍了内核启动阶段页表初始化部分的内容,其中,build_mem_type_table负责根据不同CPU架构对mem_types进行调整,prepare_page_table负责将待初始化区域的页目录项清零,然后通过map_lowmem建立低端内存区域的页表映射,最后调用devicemaps_init建立对向量表和设备IO的映射。至此,除了bootmem_init函数没有分析之外,paging_init基本算是分析完了,bootmem_init的分析将在下一篇中给出。

 

参考资料:

    1、Linux内核源码分析之setup_arch (三)



posted on 2022-12-28 19:25 lfc 阅读(182) 评论(0)  编辑 收藏 引用
只有注册用户登录后才能发表评论。