Linux bootmem (4)

本文档详细介绍了Linux内核如何映射kernel地址空间,包括使用swapper_pg_dir代表kernel地址空间,创建page table并映射kernel代码段。接着,讲解了cpu设置,包括启用MMU前的准备工作,如设置CPACR和MDSCR寄存器。最后,描述了内存属性定义,MAIR寄存器的配置,以及TCR寄存器的设置,以启用MMU并进入虚拟地址时代。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

映射kernel地址空间

swapper_pg_dir其实就是swapper进程(pid等于0的那个,其实就是idle进程)的地址空间,用它来代表kernel地址空间。和恒等映射的区别是,这段空间映射没有扩展页表的需求,当然它也是使用section mapping。和前面的恒等映射一样,swapper_pg_dir也是需要3个页面。

映射kernel空间

adrp  x0, swapper_pg_dir   kernel页表基地址

mov_q        x5, KIMAGE_VADDR + TEXT_OFFSET      // compile time __va(_text) 代码段开始虚拟地址

add   x5, x5, x23                    // add KASLR displacement

create_pgd_entry x0, x5, x3, x6   建立各个中间leveltable描述符

adrp  x6, _end                        // runtime __pa(_end) 代码段结束物理地址

adrp  x3, _text                       // runtime __pa(_text) 代码段开始物理地址

sub    x6, x6, x3                      // _end - _text        代码段长度

add   x6, x6, x5                      // runtime __va(_end)  代码段结束虚拟地址

create_block_map x0, x7, x3, x5, x6 创建PTE

adrp  x0, idmap_pg_dir

ldr     x1, =(IDMAP_DIR_SIZE + SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE)

dmb  sy

bl      __inval_dcache_area 再次清理cache

ret     x28

ENDPROC(__create_page_tables)

上面调用的函数在前面都介绍过,不再次介绍了。至此内核地址空间被全部映射。

ffffff8008080000 T _text

ffffff8009175000 A _end

sharkl3 1h10 arm64的kernel使用了17,780,736字节,即不到20MB字节。

因为level2页表采用了block型描述符,每个entry管2MB,需要10个entry就够了。

http://www-x-wowotech-x-net.img.abc188.com/content/uploadfile/201804/b8e81524313791.png 左侧是内核虚拟地址分配图

cpu设置

stext--> __cpu_setup

这部分代码主要是在使能mmu前对cpu做一些设置。

ENTRY(__cpu_setup)

         tlbi    vmalle1                                  // Invalidate local TLB

         dsb    nsh

 

         mov  x0, #3 << 20

         msr   cpacr_el1, x0                         // Enable FP/ASIMD

         mov  x0, #1 << 12                           // Reset mdscr_el1 and disable

         msr   mdscr_el1, x0                        // access to the DCC from EL0 用于debug的监控器

         isb                                          // Unmask debug exceptions now,

         enable_dbg                                     // since this is per-cpu

         reset_pmuserenr_el0 x0                          // Disable PMU access from EL0

         上面的设置和内存管理部分没有关系,看看comment就好了,暂时不去调查细节

 

下面是内存属性的定义,一共是2类:

  1. Normal型:sram或者dram那样的内存空间,一般都是过cache的(当然也可不过cache,如外设访问的地址空间,标记为NC)。

这部分主要和cache相关,可参考我的另外一篇文档《ARM Linux Cache》

  1. device型:设备寄存器那样的io空间,都不会过cache。Device属性的内存空间还有下面三种子属性,都有打开和关闭的定义。

G(gather:对多个memory的访问可以合并)  nG与之相反

R(Reordering:对内存访问指令进行重排)    nR与之相反

E(Early Write Acknowledgement hint:写操作的ack可提早应答)  nE与之相反

 

MAIR寄存器定义如下:

Linux预先定义了6种内存属性,分别存在MAIR寄存器的attr0~attr5。内存页表属性部分可以选择这个寄存器的某个index,范围(0~5)作为自己的属性。

         ldr     x5, =MAIR(0x00, MT_DEVICE_nGnRnE) | \

                        MAIR(0x04, MT_DEVICE_nGnRE) | \

                        MAIR(0x0c, MT_DEVICE_GRE) | \

                        MAIR(0x44, MT_NORMAL_NC) | \

                        MAIR(0xff, MT_NORMAL) | \

                        MAIR(0xbb, MT_NORMAL_WT)

         msr   mair_el1, x5   写入预定义的5种内存属性值

         adr    x5, crval      crval是已经定义好的sctlr寄存器的值

         .type crval, #object

crval:

         .word         0xfcffffff                       // clear  对应的bit1的会被清0

         .word         0x34d5d91d                           // set  对应的bit1的会置1

         .popsection

         ldp    w5, w6, [x5]   [x5]clear值读到w5ser值读到w6

         mrs   x0, sctlr_el1  sctlr寄存器到x0

         bic     x0, x0, x5                       // clear bits

         orr     x0, x0, x6                      // set bits

         下面是设置TCR寄存器,设置了其中8个寄存器字段

TCR寄存器定义:

  【图片加载失败】

虚拟地址VA_BITS使用39bit。

TCR_TxSZ:VA_BITS相关

TCR_CACHE_FLAGS:cacheable, inner/outer WBWA(写回/写分配)

TCR_SMP_FLAGS:共享

TCR_TG_FLAGS:页面大小为4KB

TCR_ASID16:TTBR中名字空间的bit数为16  (ASID用于OS改善tlb的刷新)

TCR_TBI0:Top Byte ignored in the address calculation. 不详,可能是查表匹配规则

TCR_A1:ASID选用TTBR1中的ASID

TCR_KASAN_FLAGS:类似TCR_TBI0的作用

         ldr     x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \

                            TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0 | TCR_A1 | \

                            TCR_KASAN_FLAGS

         tcr_set_idmap_t0sz     x10, x9  将恒等映射中VA_bits的值插入到x10

         .macro       tcr_set_idmap_t0sz, valreg, tmpreg

#ifndef CONFIG_ARM64_VA_BITS_48

         ldr_l  \tmpreg, idmap_t0sz

         bfi     \valreg, \tmpreg, #TCR_T0SZ_OFFSET, #TCR_TxSZ_WIDTH

#endif

         .endm

         mrs   x9, ID_AA64MMFR0_EL1  ID_AA64MMFR0_EL1寄存器,获得PARange

         bfi     x10, x9, #32, #3   x9寄存器的内容左移32bitcopy 3bitx10寄存器 设置物理地址宽度

#ifdef CONFIG_ARM64_HW_AFDBM

…略

#endif        /* CONFIG_ARM64_HW_AFDBM */

         msr   tcr_el1, x10   x10写入TCR寄存器

         ret                                          // return to head.S

ENDPROC(__cpu_setup)

这个函数执行完后进入__primary_switch,它会调用__enable_mmu函数

使能mmu

ENTRY(__enable_mmu)

下面4条代码判断硬件是否实际支持4K页表映射, 如果不支持, 就跳入__no_granule_support, 表明启动失败, CPU调用wfe, wfi进入idle状态

         mrs   x1, ID_AA64MMFR0_EL1

         ubfx  x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4

         cmp  x2, #ID_AA64MMFR0_TGRAN_SUPPORTED

         b.ne  __no_granule_support

         update_early_cpu_boot_status 0, x1, x2  存入0表明CPU状态正常

         adrp  x1, idmap_pg_dir

         adrp  x2, swapper_pg_dir

         msr   ttbr0_el1, x1                          // load TTBR0 将恒等映射的页表放在TTBR0

         msr   ttbr1_el1, x2                          // load TTBR1 kernel映射的页表放在TTRB1

         isb

         msr   sctlr_el1, x0     使能mmu  x0为传入参数 包含打开mmu的控制信息

         isb

         ic       iallu

         dsb    nsh

         isb

         ret     出来后跳转到__primary_switched,做最后一步的准备 进入start_kernel

ENDPROC(__enable_mmu)

上面就使能了arm的mmu,系统进入虚拟地址时代

 

__primary_switched

__primary_switched:

ARM64thread_infostack不共用, thread_info放在task_strcut里面, init_thread_union只是用于内核堆栈

         adrp  x4, init_thread_union

         add   sp, x4, #THREAD_SIZE  sp = x4+内核任务栈的大小 EL0sp放在init_task下面

         adr_l x5, init_task  init线程(idle进程)的task_struct地址

         msr   sp_el0, x5                     // Save thread_info的地址 sp_el0,内核使用sp_el0保存current信息

 

         adr_l x8, vectors                    // load VBAR_EL1 with virtual

         msr   vbar_el1, x8                           // vector table address 设置异常向量表地址

         isb

 

         stp    xzr, x30, [sp, #-16]!  x30lr寄存器 注意lr在栈中的位置

         mov  x29, sp    这两句话不清楚具体意义

 

         str_l  x21, __fdt_pointer, x5           // Save FDT pointer 把设备树物理地址存在__fdt_pointer

 

         ldr_l  x4, kimage_vaddr                  // Save the offset between

         sub    x4, x4, x0                      // the kernel virtual and

         str_l  x4, kimage_voffset, x5 // physical mappings 计算物理和虚拟地址间的偏移 存入kimage_voffset

 

         BSS区域清0

         adr_l x0, __bss_start

         mov  x1, xzr

         adr_l x2, __bss_stop

         sub    x2, x2, x0

         bl      __pi_memset

         dsb    ishst                              // Make zero page visible to PTW

 

#ifdef CONFIG_KASAN

         bl      kasan_early_init

#endif

#ifdef CONFIG_RANDOMIZE_BASE

#endif

         add   sp, sp, #16   更新sp,在进入c之前应该不需要作sp的出入栈操作

         mov  x29, #0

         mov  x30, #0

         b       start_kernel  终于进入c代码了

ENDPROC(__primary_switched)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值