Linux內存管理學習1 —— head.S中的段頁表的建立


作者

彭東林

pengdonglin137@163.com

 

平台

TQ2440

Qemu+vexpress-ca9

Linux-4.10.17

 

概述

   在Linux自解壓完畢后,開始執行arch/arm/kernel/head.S,然后跳轉到init/main.c中的start_kernel開始執行。在head.S中為了便利Linux內核啟動,會建立臨時的段頁表。這里以TQ2440和vexpress-ca9為例,其中TQ2440使用的SoC是S3C2440,ARM核心是ARM920T,指令集是ARMv4T,而vexpress-ca9是ARM核心是Cortex-A9,指令集是ARMv7。為了便於理解,在分析的時候主要以2440為主,只是順便說一下ARMv7,因為這兩個大同小異。

下面是代碼分析時的一些條件

1、以設備樹的方式啟動Linux內核

2、下面是一些宏和變量的說明:

 

說明 TQ2440(ARM920T) vxpress(Cortex-A9)
CONFIG_ARM_LPAE   No No
TEXT_OFFSET 內核代碼段相對於內核地址空間的偏移量 0x8000 0x8000
PAGE_OFFSET 內核地址空間的偏移量 0xC000_0000 0xC000_0000
 KERNEL_RAM_VADDR =PAGE_OFFSET+TEXT_OFFSET 0xC000_8000
0xC000_8000
PG_DIR_SIZE 一級頁表的大小 0x4000 (16KB) 0x4000 (16KB)
PMD_ORDER 一級頁表的每個頁表項占用的字節(2^(PMD_ORDER)) 2^2 = 4 2^2 = 4
swapper_pg_dir

一級頁表的虛擬起始地址

KERNEL_RAM_VADDR - PG_DIR_SIZE

0xC000_4000 0xC000_4000
CONFIG_ARM_VIRT_EXT   No Yes
CONFIG_XIP_KERNEL   No No
CONFIG_SMP   No Yes
CONFIG_SMP_ON_UP   No Yes
CONFIG_ARM_PATCH_PHYS_VIRT   Yes Yes
CONFIG_CPU_32v4T  ARM指令集 Yes No
CONFIG_CPU_32v7  ARM指令集 No Yes
CONFIG_CPU_V7M  ARM指令集 No No
__LINUX_ARM_ARCH__ ARM指令集 4 7
CONFIG_CPU_DCACHE_WRITETHROUGH   No No

 

 3、地址空間:

對於TQ2440,板子上面有64MB的物理內存,所以物理內存地址范圍是: 0x3000_0000 ~ 0x3400_0000

對於express板子,分配了1GB的物理內存,所以物理內存地址范圍是: 0x6000_0000 ~ 0xA000_0000

 

正文

在進入head.S是,MMU和D-Cache是關閉的,r0是0,r1的值任意,r2的值是dtb鏡像在內存中的物理起始地址。

下面是對head.S精簡后的代碼:

 1 ENTRY(stext)
 2 
 3 #ifdef CONFIG_ARM_VIRT_EXT
 4     bl    __hyp_stub_install
 5 #endif
 6     @ ensure svc mode and all interrupts masked
 7     safe_svcmode_maskall r9
 8 
 9     mrc    p15, 0, r9, c0, c0        @ get processor id
10     bl    __lookup_processor_type        @ r5=procinfo r9=cpuid
11     movs    r10, r5                @ invalid processor (r5=0)?
12     beq    __error_p            @ yes, error 'p'
13 
14     adr    r3, 2f
15     ldmia    r3, {r4, r8}
16     sub    r4, r3, r4            @ (PHYS_OFFSET - PAGE_OFFSET)
17     add    r8, r8, r4            @ PHYS_OFFSET
18 
19     /*
20      * r1 = machine no, r2 = atags or dtb,
21      * r8 = phys_offset, r9 = cpuid, r10 = procinfo
22      */
23     bl    __vet_atags
24 #ifdef CONFIG_SMP_ON_UP
25     bl    __fixup_smp
26 #endif
27 
28     bl    __fixup_pv_table
29 
30     bl    __create_page_tables
31 
32     /*
33      * The following calls CPU specific code in a position independent
34      * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
35      * xxx_proc_info structure selected by __lookup_processor_type
36      * above.
37      *
38      * The processor init function will be called with:
39      *  r1 - machine type
40      *  r2 - boot data (atags/dt) pointer
41      *  r4 - translation table base (low word)
42      *  r5 - translation table base (high word, if LPAE)
43      *  r8 - translation table base 1 (pfn if LPAE)
44      *  r9 - cpuid
45      *  r13 - virtual address for __enable_mmu -> __turn_mmu_on
46      *
47      * On return, the CPU will be ready for the MMU to be turned on,
48      * r0 will hold the CPU control register value, r1, r2, r4, and
49      * r9 will be preserved.  r5 will also be preserved if LPAE.
50      */
51     ldr    r13, =__mmap_switched        @ address to jump to after
52                         @ mmu has been enabled
53     badr    lr, 1f                @ return (PIC) address
54 
55     mov    r8, r4                @ set TTBR1 to swapper_pg_dir
56 
57     ldr    r12, [r10, #PROCINFO_INITFUNC]
58     add    r12, r12, r10
59     ret    r12
60 1:    b    __enable_mmu
61 ENDPROC(stext)
62     .ltorg
63 2:    .long    .
64     .long    PAGE_OFFSET

下面開始分析上面的代碼:

1、第4行的__hyp_stub_install在vexpress上會執行,而在2440上不執行,這里暫時忽略

2、第7行的 safe_svcmode_maskall r9 確保處理器進入SVC模式,同時關閉IRQ和FIQ中斷。對於2440,做了如下操作:

msr  cpsr_c, #(PSR_F_BIT | PSR_I_BIT | SVC_MODE)

3、第9行 mrc p15, 0, r9, c0, c0 用於獲得processor id。

對於2440, CP15的C0的值是0x4112920x,參考手冊 ARM920T Technical Reference Manual 的2.3節 CP15 register map summary

對於vexpress,CP15的C0的值是0x414FC091,參考手冊 ARM® Cortex®‑A9 Technical Reference Manual 的 4. System Control

 

比如對於2440,執行完第3行代碼后,r9的值就是0x4112920x,而對於vexpress,r9的值是0x414FC091。

4、第10到12行,遍歷kernel的".proc.info.init"段,找到與該處理器ID匹配的proc_info_list結構體,如果找到的話,r5寄存器存放的是該proc_info_list的物理地址,第11行將該地址存放到r10中,如果沒有找到的話,

寄存器r5值是0,執行完第11行的movs代碼后,第12行的beq就會成立,跳轉到__error_p處,如果配置了CONFIG_DEBUG_LL,就會打印相應的錯誤信息:

Error: unrecognized/unsupported processor variant (0xXXXXXXX)

上面括號中是實際從CP15的C0里讀到的值。

下面我們看看對於2440和vexpress這兩個板子,與之匹配的proc.info.init字段都分別是什么?

對於2440,該部分定義在arch/arm/mm/proc-arm920.S中:

 1     define_processor_functions arm920, dabort=v4t_early_abort, pabort=legacy_pabort, suspend=1
 2 
 3     .section ".rodata"
 4 
 5     string    cpu_arch_name, "armv4t"
 6     string    cpu_elf_name, "v4"
 7     string    cpu_arm920_name, "ARM920T"
 8 
 9     .align
10 
11     .section ".proc.info.init", #alloc
12 
13     .type    __arm920_proc_info,#object
14 __arm920_proc_info:
15     .long    0x41009200
16     .long    0xff00fff0
17     .long   PMD_TYPE_SECT | \
18         PMD_SECT_BUFFERABLE | \
19         PMD_SECT_CACHEABLE | \
20         PMD_BIT4 | \
21         PMD_SECT_AP_WRITE | \
22         PMD_SECT_AP_READ
23     .long   PMD_TYPE_SECT | \
24         PMD_BIT4 | \
25         PMD_SECT_AP_WRITE | \
26         PMD_SECT_AP_READ
27     initfn    __arm920_setup, __arm920_proc_info
28     .long    cpu_arch_name
29     .long    cpu_elf_name
30     .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
31     .long    cpu_arm920_name
32     .long    arm920_processor_functions
33     .long    v4wbi_tlb_fns
34     .long    v4wb_user_fns
35     .long    arm920_cache_fns
36     .size    __arm920_proc_info, . - __arm920_proc_info

第1行的define_processor_functions是一個宏,定義在arch/arm/mm/proc-macros.S中,根據傳入的參數展開后如下:

    .type    arm920_processor_functions, #object
    .align 2
ENTRY(arm920_processor_functions)
    .word    \dabort
    .word    \pabort
    .word    cpu_arm920_proc_init
    .word    cpu_arm920_proc_fin
    .word    cpu_arm920_reset
    .word    cpu_arm920_do_idle
    .word    cpu_arm920_dcache_clean_area
    .word    cpu_arm920_switch_mm
    .word    cpu_arm920_set_pte_ext
    .word    cpu_arm920_suspend_size
    .word    cpu_arm920_do_suspend
    .word    cpu_arm920_do_resume
    .size    arm920_processor_functions, . - arm920_processor_functions

第4到7行只讀,存放了一下字符串,將來在啟動階段(start_kernel --> setup_arch --> setup_processor)會被打印出來

    pr_info("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
        cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
        proc_arch[cpu_architecture()], get_cr());

如:

[    0.000000] CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c000717f

第15到35行的數據將來可以通過一個struct proc_info_list進行訪問:

struct proc_info_list {
    unsigned int        cpu_val;
    unsigned int        cpu_mask;
    unsigned long        __cpu_mm_mmu_flags;    /* used by head.S */
    unsigned long        __cpu_io_mmu_flags;    /* used by head.S */
    unsigned long        __cpu_flush;        /* used by head.S */
    const char        *arch_name;
    const char        *elf_name;
    unsigned int        elf_hwcap;
    const char        *cpu_name;
    struct processor    *proc;
    struct cpu_tlb_fns    *tlb;
    struct cpu_user_fns    *user;
    struct cpu_cache_fns    *cache;
};

第27行 initfn __arm920_setup, __arm920_proc_info 展開后是: __arm920_setup -  __arm920_proc_info,也就是這里存放了一個這兩個符號的地址偏差,將來就可以根據__arm920_proc_info輕松地找到__arm920_setup

第33和35行的分析類似第1行,都是宏展開后生成的,直接在代碼里搜索不到。

對於v4wbi_tlb_fns 定義在arch/arm/mm/tlb-v4wbi.S中:  define_tlb_functions v4wbi, v4wbi_tlb_flags ,展開如下:

    .type    v4wbi_tlb_fns, #object
ENTRY(v4wbi_tlb_fns)
    .long    v4wbi_flush_user_tlb_range
    .long    v4wbi_flush_kern_tlb_range
    .long    v4wbi_tlb_flags
    .size    v4wbi_tlb_fns, . - v4wbi_tlb_fns

對於arm920_cache_fns, 定義在arch/arm/mm/proc-arm920.S中 define_cache_functions arm920 展開后:

    .align 2
    .type    arm920_cache_fns, #object
ENTRY(arm920_cache_fns)
    .long    arm920_flush_icache_all
    .long    arm920_flush_kern_cache_all
    .long    arm920_flush_kern_cache_louis
    .long    arm920_flush_user_cache_all
    .long    arm920_flush_user_cache_range
    .long    arm920_coherent_kern_range
    .long    arm920_coherent_user_range
    .long    arm920_flush_kern_dcache_area
    .long    arm920_dma_map_area
    .long    arm920_dma_unmap_area
    .long    arm920_dma_flush_range
    .size    arm920_cache_fns, . - arm920_cache_fns

第34行,對於v4wb_user_fns 定義在arch/arm/mm/copypage-v4wb.c中:

struct cpu_user_fns v4wb_user_fns __initdata = {
    .cpu_clear_user_highpage = v4wb_clear_user_highpage,
    .cpu_copy_user_highpage    = v4wb_copy_user_highpage,
};

如果將vmlinux反匯編,可以看到__arm920_proc_info這段的內容如下:

c06adf80 <__proc_info_begin>:
c06adf80:       41009200    #cpu_val
c06adf84:       ff00fff0    #cpu_mask    
c06adf88:       00000c1e    #__cpu_mm_mmu_flags    
c06adf8c:       00000c12    #__cpu_io_mmu_flags     
c06adf90:       ff968a3c    #__cpu_flush  
c06adf94:       c04ed874    #arch_name
c06adf98:       c04ed87b    #elf_name
c06adf9c:       00000007    #elf_hwcap
c06adfa0:       c04ed87e    #cpu_name
c06adfa4:       c06b4040    #proc
c06adfa8:       c06b4034    #tlb
c06adfac:       c06b402c    #user
c06adfb0:       c00168c0    #cache

對於vexpress,對應的是proc.info.init定義在arch/arm/mm/proc-v7.S中,只留下需要關注的部分:

    define_processor_functions ca9mp, dabort=v7_early_abort, pabort=v7_pabort, suspend=1

    .section ".rodata"

    string    cpu_arch_name, "armv7"
    string    cpu_elf_name, "v7"
    .align

    .section ".proc.info.init", #alloc

    /*
     * Standard v7 proc info content
     */
.macro __v7_proc name, initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
    ALT_SMP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
            PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
    ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
            PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
    .long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
        PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
    initfn    \initfunc, \name
    .long    cpu_arch_name
    .long    cpu_elf_name
    .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
        HWCAP_EDSP | HWCAP_TLS | \hwcaps
    .long    cpu_v7_name
    .long    \proc_fns
    .long    v7wbi_tlb_fns
    .long    v6_user_fns
    .long    v7_cache_fns
.endm

    /*
     * ARM Ltd. Cortex A9 processor.
     */
    .type   __v7_ca9mp_proc_info, #object
__v7_ca9mp_proc_info:
    .long    0x410fc090
    .long    0xff0ffff0
    __v7_proc __v7_ca9mp_proc_info, __v7_ca9mp_setup, proc_fns = ca9mp_processor_functions
    .size    __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info

進一步展開后是:

 1     string  cpu_v7_name, "ARMv7 Processor"    
 2     define_processor_functions ca9mp, dabort=v7_early_abort, pabort=v7_pabort, suspend=1
 3 
 4     .section ".rodata"
 5 
 6     string    cpu_arch_name, "armv7"
 7     string    cpu_elf_name, "v7"
 8     .align
 9 
10     .section ".proc.info.init", #alloc
11 
12     /*
13      * ARM Ltd. Cortex A9 processor.
14      */
15     .type   __v7_ca9mp_proc_info, #object
16 __v7_ca9mp_proc_info:
17     .long    0x410fc090
18     .long    0xff0ffff0
19     ALT_SMP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
20             PMD_SECT_AF | PMD_FLAGS_SMP)
21     ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
22             PMD_SECT_AF | PMD_FLAGS_UP)
23     .long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
24         PMD_SECT_AP_READ | PMD_SECT_AF
25     initfn    __v7_ca9mp_setup, __v7_ca9mp_proc_info
26     .long    cpu_arch_name
27     .long    cpu_elf_name
28     .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
29         HWCAP_EDSP | HWCAP_TLS
30     .long    cpu_v7_name
31     .long    ca9mp_processor_functions
32     .long    v7wbi_tlb_fns
33     .long    v6_user_fns
34     .long    v7_cache_fns
35     .size    __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info

跟2440一樣,其中的部分標號的定義如下:

ca9mp_processor_functions:  定義在arch/arm/mm/proc-v7.S中 define_processor_functions ca9mp, dabort=v7_early_abort, pabort=v7_pabort, suspend=1

    .type    ca9mp_processor_functions, #object
    .align 2
ENTRY(ca9mp_processor_functions)
    .word    v7_early_abort
    .word    v7_pabort
    .word    cpu_ca9mp_proc_init
    .word    cpu_ca9mp_proc_fin
    .word    cpu_ca9mp_reset
    .word    cpu_ca9mp_do_idle
    .word    cpu_ca9mp_dcache_clean_area
    .word    cpu_ca9mp_switch_mm
    .word    cpu_ca9mp_set_pte_ext
    .word    cpu_ca9mp_suspend_size
    .word    cpu_ca9mp_do_suspend
    .word    cpu_ca9mp_do_resume
    .size    ca9mp_processor_functions, . - ca9mp_processor_functions

v7wbi_tlb_fns:定義在arch/arm/mm/tlb-v7.S中 define_tlb_functions v7wbi, v7wbi_tlb_flags_up, flags_smp=v7wbi_tlb_flags_smp ,展開如下:

ENTRY(v7wbi_tlb_fns)
    .long    v7wbi_flush_user_tlb_range
    .long    v7wbi_flush_kern_tlb_range
    ALT_SMP(.long    flags_smp=v7wbi_tlb_flags_smp )
    ALT_UP(.long    v7wbi_tlb_flags_up )
    .size    v7wbi_tlb_fns, . - v7wbi_tlb_fns

v6_user_fns:定義在arch/arm/mm/copypage-v6.c中:

struct cpu_user_fns v6_user_fns __initdata = {
    .cpu_clear_user_highpage = v6_clear_user_highpage_nonaliasing,
    .cpu_copy_user_highpage    = v6_copy_user_highpage_nonaliasing,
};

v7_cache_fns:定義在arch/arm/mm/cache-v7.S中 define_cache_functions v7 ,展開如下:

    .align 2
    .type    v7_cache_fns, #object
ENTRY(v7_cache_fns)
    .long    v7_flush_icache_all
    .long    v7_flush_kern_cache_all
    .long    v7_flush_kern_cache_louis
    .long    v7_flush_user_cache_all
    .long    v7_flush_user_cache_range
    .long    v7_coherent_kern_range
    .long    v7_coherent_user_range
    .long    v7_flush_kern_dcache_area
    .long    v7_dma_map_area
    .long    v7_dma_unmap_area
    .long    v7_dma_flush_range
    .size    v7_cache_fns, . - v7_cache_fns

對vmlinux反匯編后,可以看到__v7_ca9mp_proc_info部分的數據:

c06ee5fc <__v7_ca9mp_proc_info>:
c06ee5fc:       410fc090        #cpu_val
c06ee600:       ff0ffff0        #cpu_mask
c06ee604:       00011c0e        #__cpu_mm_mmu_flags
c06ee608:       00000c02        #__cpu_io_mmu_flags
c06ee60c:       ffa2e260        #__cpu_flush
c06ee610:       c0701b64        #arch_name
c06ee614:       c0701b6a        #elf_name
c06ee618:       00008097        #elf_hwcap
c06ee61c:       c011c780        #cpu_name
c06ee620:       c0958094        #proc
c06ee624:       c09081dc        #tlb
c06ee628:       c095802c        #user
c06ee62c:       c0958000        #cache

 

 

回到head.S繼續分析,上面說完proc.info.init段的內容后,下面分析__lookup_processor_type:

 1 __lookup_processor_type:
 2     adr    r3, __lookup_processor_type_data
 3     ldmia    r3, {r4 - r6}
 4     sub    r3, r3, r4            @ get offset between virt&phys
 5     add    r5, r5, r3            @ convert virt addresses to
 6     add    r6, r6, r3            @ physical address space
 7 1:    ldmia    r5, {r3, r4}            @ value, mask
 8     and    r4, r4, r9            @ mask wanted bits
 9     teq    r3, r4
10     beq    2f
11     add    r5, r5, #PROC_INFO_SZ        @ sizeof(proc_info_list)
12     cmp    r5, r6
13     blo    1b
14     mov    r5, #0                @ unknown processor
15 2:    ret    lr
16 ENDPROC(__lookup_processor_type)
17 
18 /*
19  * Look in <asm/procinfo.h> for information about the __proc_info structure.
20  */
21     .align    2
22     .type    __lookup_processor_type_data, %object
23 __lookup_processor_type_data:
24     .long    .
25     .long    __proc_info_begin
26     .long    __proc_info_end
27     .size    __lookup_processor_type_data, . - __lookup_processor_type_data

由於還沒有開啟MMU,所以虛擬地址就是物理地址,但是由於kernel代碼段的鏈接地址是從0xC0008000開始,而對於2440來說,物理內容的范圍是0x3000_0000到0x3400_0000,所以如果直接用虛擬地址訪問的話,程序一定會跑飛了。

所以在第2到第6行的代碼首先會對第25行__proc_info_begin和第26行的__proc_info_end的虛擬地址轉換,轉換成物理地址,分別存放在r5和r6中,轉換方法很簡單

第7到第14行開始從r5(也就是"proc.info.init"段的起始物理地址)開始,以#PROC_INFO_SZ為步長進行遍歷,尋找跟r9中的cpu id匹配的proc_info_list。匹配的方法很簡單:從之前的分析知道,proc_info_list的前兩個成員分別是cpu_val (r3)和cpu_mask (r4),將這兩個值讀出來,然后進行如下判斷:(r9 & cpu_mask) 是否等於 cpu_val,如果相等,意味着找到匹配項,然后返回,此時r5中存放的是找到的proc_info_list的物理地址。否則的話,繼續遍歷下一個proc_info_list,直到遍歷到最后一個proc_info_list,如果沒有找到,r5被賦值為0,然后返回。

 

 

回到head.S繼續分析。

5、第14到第17行代碼完成的任務是計算物理內存的起始地址,方法如下:

    adr    r3, 2f
    ldmia    r3, {r4, r8}
    sub    r4, r3, r4            @ (PHYS_OFFSET - PAGE_OFFSET)
    add    r8, r8, r4            @ PHYS_OFFSET
    .ltorg
2:    .long    .
    .long    PAGE_OFFSET

首先獲得2f標號的物理地址,在哪里存放的是2f標號的虛擬地址以及0xC000_0000。然后計算2f的物理地址跟虛擬地址之間的差值,再該差值加上0xC000_0000,就可以得到物理內存的起始地址。當然這里的前提是kernel被加載到(物理內存的起始地址 + 0x8000)處開始執行。

比如對於2440,執行完上面的操作后,r8的值是0x3000_0000,對於vexpress來說是,r8是0x6000_0000.

6、第23行,檢查r2中傳遞的設備樹鏡像是否合法,如果不合法的話,r2會被清0。檢查方法是:判斷r2指向的地址的前4個字節是否等於OF_DT_MAGIC,是的話,表示合法,否則不合法

7、第25和第28行暫時忽略

 

未完待續

8、第30行調用__create_page_tables建立段式頁表。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM