内存管理(四):DTB的映射

ⅰ亾dé卋堺 提交于 2020-05-06 10:09:27

Linux版本:4.14.74

1 建立系统映射的过程

经过前面几章的内容,我们开启了MMU,为kernel image创建了映射,通过调用start_kernel setup_arch建立页表映射,读取kernel建立页表映射的代码和流程如下图所示,下面分章节对这三个部分进行说明

1.	void __init setup_arch(char **cmdline_p)  
2.	{  
3.	    pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());  
4.	  
5.	        .....  
6.	  
7.	    early_fixmap_init();  
8.	    early_ioremap_init();  
9.	  
10.	    setup_machine_fdt(__fdt_pointer);  
11.	        ....  
12.	    arm64_memblock_init();  
13.	  
14.	    paging_init();  
15.	  
16.	        ....  
17.	}  

在这里插入图片描述
本文主要描述映射DTB的过程

2 FIXMAP概念

虽然可以通过kernel image mapping和identity mapping来窥探物理地址空间,但终究是管中窥豹,不了解全局,那么内核是如何了解对端的物理世界呢?答案就是DTB,但是问题来了,这时候,内核还没有为DTB这段内存创建映射,因此,打开MMU之后的kernel还不能直接访问,需要先创建dtb mapping,而要创建address mapping,就需要分配页表内存,而这时候,还没有了解内存布局,内存管理模块还没有初始化,如何来分配内存呢?这要用到在内核虚拟内存布局中用到的fix map区域。
fixed map是被linux kernel用来解决一类问题的机制,这类问题的共同特点是:(1)在很早期的阶段需要进行地址映射,而此时,由于内存管理模块还没有完成初始化,不能动态分配内存,也就是无法动态分配创建映射需要的页表内存空间。(2)物理地址是固定的,或者是在运行时就可以确定的。对于这类问题,内核定义了一段固定映射的虚拟地址,让使用fix map机制的各个模块可以在系统启动的早期就可以创建地址映射,当然,这种机制不是那么灵活,因为虚拟地址都是编译时固定分配的。
下图是DTB的映射图 在这里插入图片描述
Fixmap区域分为permanent区域和temporary区域,所谓permanent表示映射关系永远都是存在的,例如FDT区域,一旦完成地址映射,内核可以访问DTB之后,这个映射关系一直都是存在的。而temporary fixmap则不然,一般而言,某个模块使用了这部分的虚拟地址之后,需要尽快释放这段虚拟地址,以便给其他模块使用,例如fixmap中为页表预留的区域PGD/PUD/PMD/PTE,在系统进行映射时,这块区域将会被反复使用。
在这里插入图片描述
上图为fixmap区域的虚拟地址布局




  • FIXADDR_START是fix map虚拟地址的起始地址
  • 蓝色区域为fixmap永久映射区域,橙色区域为temporary区域
  • FDT为映射DTB的区域,大小为4M,起始地址是2M对齐。
  • PGD/PUD/PMD/PTE用于在建立系统映射时,临时的页表虚拟地址

3 FIXMAP的初步映射

好,现在虚拟地址有了,要创建映射还需要有实际的物理地址来存放页表。对于fixed-mapped address这段虚拟地址空间,由于也是位于内核空间,因此PGD当然就是复用swapper进程的PGD了(其实整个系统就一个PGD),而其他LEVEL,如PUD/PMD/PTE的物理地址空间如何获取,此时页表未建立好,后面我们要讲的memblock也还没有建立好,此时是无法分配物理地址空间,所以PUD/PMD/PTE需要提前静态定义好

1.	static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;  
2.	static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;  
3.	static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;  

上述定义位于内核bss段,由于所有的Translation table都在kernel image mapping的范围内,因此内核可以毫无压力的访问,并创建fixed-mapped address这段虚拟地址空间对应的PUD、PMD和PTE的entry。所有中间level的Translation table都是在early_fixmap_init函数中完成初始化的。early_fixmap_init的所做工作如下图所示,建立起对FIXADDR_START的初步映射,所谓“初步”映射指的是只建立中间页表的关系,如下面的示意图,只建立了PGD–>PMD–>PTE的关系,而最后一步的映射在最终用的时候才会进行映射(实际上FIXADDR_MAP并不会用到,也就不会进行最终的映射)。
在这里插入图片描述

1.	/* 
2.	 * The p*d_populate functions call virt_to_phys implicitly so they can't be used 
3.	 * directly on kernel symbols (bm_p*d). This function is called too early to use 
4.	 * lm_alias so __p*d_populate functions must be used to populate with the 
5.	 * physical address from __pa_symbol. 
6.	 */  
7.	void __init early_fixmap_init(void)  
8.	{  
9.	    pgd_t *pgd;  
10.	    pud_t *pud;  
11.	    pmd_t *pmd;  
12.	    unsigned long addr = FIXADDR_START;  
13.	  
14.	    pgd = pgd_offset_k(addr);   /*****1****/  
15.	    if (CONFIG_PGTABLE_LEVELS > 3 &&  
16.	        !(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {  
17.	        /* 
18.	         * We only end up here if the kernel mapping and the fixmap 
19.	         * share the top level pgd entry, which should only happen on 
20.	         * 16k/4 levels configurations. 
21.	         */  
22.	        BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));  
23.	        pud = pud_offset_kimg(pgd, addr);  
24.	    } else {  
25.	        if (pgd_none(*pgd)) /******2*****/ 
26.	            __pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE);  
27.	        pud = fixmap_pud(addr);  
28.	    }  
29.	    if (pud_none(*pud))   /******3*****/  
30.	        __pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);  
31.	    pmd = fixmap_pmd(addr); /******4******/
32.	    __pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);  
33.	  …
34.	} 
  • (1)获取FIXADDR_MAP对应的pgd页表项
  • (2)以bm_pud的物理地址填充pgd
  • (3)以bm_pmd的物理地址填充pud
  • (4)以bm_pte的物理地址填充pmd
    系统对dtb的size有要求,不能大于2M,这个要求主要是要确保在创建地址映射(create_mapping)的时候不能分配其他的translation table page,也就是说,所有的translation table都必须静态定义。为什么呢?因为这时候内存管理模块还没有初始化,即便是memblock模块(初始化阶段分配内存的模块)都尚未初始化(没有内存布局的信息),不能动态分配内存。最后一个level则是在各个具体的模块进行的,对于DTB而言,这发生在fixmap_remap_fdt函数中

4 DTB的映射

DTB的实际映射位于fixmap_remap_fdt中,在前面的过程中,已经对FIXMAP区域的PGD–>PUD–>PMD–>PTE有了初步的映射,并且DTB区域的虚拟地址是2M的对齐的,这也是为了以2M为单位进行section map的映射。DTB映射的过程如下图
在这里插入图片描述
在这里插入图片描述

  • 第一次映射,PGD–>PMD的映射关系在early_fixmap_init已经建立,第一次映射是建立2M区域的映射,也就是对PMD的一个页表项进行映射,并且,相对于在early_fixmap_init中建立的映射关系,由于FIXADDR_START不是2M对齐的,而FDT是2M对齐的,所以此时PMD的页表项index会大1.
  • 第二次映射,虽然FDT的虚拟地址是2M对齐的,但是DTB的物理地址并不一定是2M对齐的,如果DTB正好位于跨2M的区域内,则需要进行第二次映射,在第一次映射中已经对DTB进行了部分映射,这一部分映射就可以读取出DTB的实际大小,在第二次映射中进行完整映射。
void *__init __fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)  
{  
  /********1*******
    const u64 dt_virt_base = __fix_to_virt(FIX_FDT);  
    int offset;  
    void *dt_virt;  
  
    /* 
     * Check whether the physical FDT address is set and meets the minimum 
     * alignment requirement. Since we are relying on MIN_FDT_ALIGN to be 
     * at least 8 bytes so that we can always access the magic and size 
     * fields of the FDT header after mapping the first chunk, double check 
     * here if that is indeed the case. 
     */  
    BUILD_BUG_ON(MIN_FDT_ALIGN < 8);  
    if (!dt_phys || dt_phys % MIN_FDT_ALIGN)  
        return NULL;  
  
    /* 
     * Make sure that the FDT region can be mapped without the need to 
     * allocate additional translation table pages, so that it is safe 
     * to call create_mapping_noalloc() this early. 
     * 
     * On 64k pages, the FDT will be mapped using PTEs, so we need to 
     * be in the same PMD as the rest of the fixmap. 
     * On 4k pages, we'll use section mappings for the FDT so we only 
     * have to be in the same PUD. 
     */  
    BUILD_BUG_ON(dt_virt_base % SZ_2M);  
  
    BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT !=  
             __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT);  
  /*******2******/
    offset = dt_phys % SWAPPER_BLOCK_SIZE;  
    dt_virt = (void *)dt_virt_base + offset;  
  /*******3******
    /* map the first chunk so we can read the size from the header */  
    create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),  
            dt_virt_base, SWAPPER_BLOCK_SIZE, prot);  
  
    if (fdt_magic(dt_virt) != FDT_MAGIC)  
        return NULL;  
  
    *size = fdt_totalsize(dt_virt);  
    if (*size > MAX_FDT_SIZE)  
        return NULL;  
  /*******4******/
    if (offset + *size > SWAPPER_BLOCK_SIZE)  
        create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,  
                   round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot);  
  
    return dt_virt;  
}   
  • (1)获取FDT的虚拟基地址
  • (2)获取实际的offset和虚拟地址
  • (3)映射第一块2M地址
  • (4)若跨2M区域,进行第二块2M映射
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!