Exynos4412 内核移植(三)—— 内核启动过程分析

内核启动所用函数如下:

        与移植U-Boot 的过程相似,在移植Linux 之前,先了解它的启动过程。Linux 的过程可以分为两部分:架构/开发板相关的引导过程、后续的通用启动过程。对于uImage、zImage ,它们首先进行自解压得到vmlinux ,然后执行 vmlinux 开始“正常的”启动流程。

       引导阶段通常使用汇编语言编写,它首先检查内核是否支持当前架构的处理器,然后检查是否支持当前开发板。通过检查后,就为调用下一阶段的start_kernel函数作准备了。这主要分如下两个步骤:

1)-- 连接内核时使用的虚拟地址,所以要设置页表、使能MMU;

2)调用C 函数 start_kernel 之前的常规工作,包括复制数据段、清除BSS段、调用start_kernel 函数。

        第二阶段的关键代码主要使用C语言编写。它进行内核初始化的全部工作,最后调用 rest_init 函数启动init 过程,创建系统第一个进程:init 进程。在第二阶段,仍有部分架构/开发板相关的代码,比如重新设置页表、设置系统时钟、初始化串口等。

下面是详细解析:

一、第一阶段

        与Uboot 一样,我们在连接文件中查看函数入口点,内核编译完成后会在arch/arm/kernel/下生成 vmlinux.lds 文件,打开:

        stext 在 linux/arch/arm/kernel/head.S 中被定义,做为函数入口点,linux/arch/arm/kernel/head.S是linux内核映像解压后执行的第一个文件。

代码只是部分,但可以看到这一阶段究竟做了些什么:

a -- 设定为SVC模式,关闭IRQ、FIQ;

b -- 确定CPU的ID号,判定其是否有效;

c -- 确定machine的ID号,检查合法性;

d -- 检查bootloader传入的参数列表atags的合法性

e -- 创建初始页表

下面对上面遇到的程序段展开分析:

a -- 确保处于SVC模式

这没什么好讲的,就是设置CPSR 模式位,并屏蔽中断;

b -- 检查CPU ID 是否匹配

获取ID并放到 r9 寄存器中,调用_lookup_processor_type 函数, 函数主要用来判定内核是否和当前的CPU匹配,如果不匹配,r5寄存器的值应为0,此时会调用 _error_p函数,它用来打印错误信息,即内核和当前的CPU不匹配,此时内核时不能启动的;如果两者匹配,会返回一个描述处理器结构的地址(在r5寄存器中),然后调用下面的函数。

      下面看_lookup_processor_type 函数,在arch/arm/kernel/head-common.S 中定义:

        上面的代码其实就是一个地址转换过程,因为在判定CPU架构时未开启系统的MMU功能,所以均使用物理地址,而内核代码在连接时是以虚拟地址来实现的,因此要想用proc_info_list 结构体,就要先找到proc_info_list 结构的物理地址,这样必须使用上面的转换代码。

        proc_info_list 结构体很重要。在Linux 内核映像中定义了很多个proc_info_list 结构,该结构表示的是内核所支持的CPU架构,这部分下面会讲到,先分析上面的代码:

153 行:r3 存储的是_lookup_processor_type_data 的物理地址 ;

155 行:得到虚拟地址和物理地址之间的offset;

156 - 157 行:利用offset,将 r5 和 r6 中保存的虚拟地址转变为物理地址,主要是获得_proc_info_begin 及_proc_info_end 的物理地址,分别放到r5 和 r6 中;

159 行:r9 中存放的是先前读出的 processor ID,此处屏蔽不需要的位;

160 行:查看代码和CPU硬件是否匹配,如果匹配就返回,此时 r5 存放的是该CPU类型对应的结构体_proc_info_list 的基地址 ;不成功,则查看下一个 proc_info_list 结构体;

163行:如果直到 _proc_info_end ,都没有匹配,则定为未知CPU,向 r5 赋 0,然后返回 ;

      下面来看看 proc_info_list 结构体 ,这个结构体在 arch/arm/include/asm/procinfo.h 中定义:

对于 Cortex-A9 来说,其结构体在文件 arch/arm/mm/proc-v7.S 中初始化:

       .section ".proc.info.init"表明了该结构在编译后存放的位置。在链接文件arch/arm/kernel/vmlinux.lds中:

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;


      上面两个变量 _proc_info_begin  与 _proc_info_end 用于计算 proc_info_list 结构的物理地址。

      如果CPU ID匹配,在编译内核文件时,会编译 proc-v7.S 这个文件,可以在arch/arm/mm/Makefile 中看到这个文件

c -- 检测 机器ID是否匹配

      主要用到_lookup_machine_type 函数,其与_lookup_processor_type 函数实现代码很相似,这里不予阐述;

d -- 检查bootloader传入的参数列表atags的合法性

        _vet_atags 函数用于检测参数列表atags的合法性

        内核参数链表的格式和说明可以从内核源代码目录树中的 中找到,参数链表必须以ATAG_CORE 开始,以ATAG_NONE结束。这里的 ATAG_CORE,ATAG_NONE是各个参数的标记,本身是一个32位值,例如:ATAG_CORE=0x54410001。其它的参数标记还包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参数链表。参数结构体的定义如下:

struct tag {
      struct  tag_header  hdr;
      union {
             struct tag_core  core;
       struct tag_mem32   mem;
          struct tag_videotext videotext;
struct tag_ramdisk  ramdisk;
struct tag_initrd     initrd;
          struct tag_serialnr     serialnr;
struct tag_revision  revision;
          struct tag_videolfb  videolfb;
          struct tag_cmdline  cmdline;
 struct tag_acorn       acorn;
struct tag_memclk    memclk;
        } u;
};

参数结构体包括两个部分,一个是 tag_header结构体,一个是u联合体。
tag_header结构体的定义如下: 
   struct tag_header { 
                 u32 size;   
                 u32 tag; 
}; 

其中 size:表示整个 tag 结构体的大小(用字的个数来表示,而不是字节的个数),等于tag_header的大小加上 u联合体的大小,例如,参数结构体 ATAG_CORE 的 size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数 tag_size(struct * tag_xxx)来获得每个参数结构体的 size。其中 tag:表示整个 tag 结构体的标记,如:ATAG_CORE等。 

__vet_atags:
tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐
bne 1f
 
ldr r5, [r2, #0] //获取第一个tag结构的size
//#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判断该tag的长度是否合法
subs r5, r5, #ATAG_CORE_SIZE
bne 1f
ldr r5, [r2, #4]  //获取第一个tag结构体的标记,
ldr r6, =ATAG_CORE 
cmp r5, r6 //判断第一个tag结构体的标记是不是ATAG_CORE
bne 1f  
 
mov pc, lr //正常退出
 
1: mov r2, #0
mov pc, lr  //参数连表不正确
ENDPROC(__vet_atags)


e -- 创建初始页表


其在下面被执行:

下面是详细分析:

/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
 *
 * Returns:
 *  r0, r3, r5-r7 corrupted
 *  r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h)
 */
__create_page_tables:
    pgtbl    r4, r8                @ page table address
 
    /*
     * Clear the swapper page table
     */
    mov    r0, r4
    mov    r3, #0
    add    r6, r0, #PG_DIR_SIZE
1:    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    teq    r0, r6
    bne    1b
 
#ifdef CONFIG_ARM_LPAE
    /*
     * Build the PGD table (first level) to point to the PMD table. A PGD
     * entry is 64-bit wide.
     */
    mov    r0, r4
    add    r3, r4, #0x1000            @ first PMD table address
    orr    r3, r3, #3            @ PGD block type
    mov    r6, #4                @ PTRS_PER_PGD
    mov    r7, #1 << (55 - 32)        @ L_PGD_SWAPPER
1:
#ifdef CONFIG_CPU_ENDIAN_BE8
    str    r7, [r0], #4            @ set top PGD entry bits
    str    r3, [r0], #4            @ set bottom PGD entry bits
#else
    str    r3, [r0], #4            @ set bottom PGD entry bits
    str    r7, [r0], #4            @ set top PGD entry bits
#endif
    add    r3, r3, #0x1000            @ next PMD table
    subs    r6, r6, #1
    bne    1b
 
    add    r4, r4, #0x1000            @ point to the PMD tables
#ifdef CONFIG_CPU_ENDIAN_BE8
    add    r4, r4, #4            @ we only write the bottom word
#endif
#endif
 
    ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
 
    /*
     * Create identity mapping to cater for __enable_mmu.
     * This identity mapping will be removed by paging_init().
     */
    adr    r0, __turn_mmu_on_loc
    ldmia    r0, {r3, r5, r6}
    sub    r0, r0, r3            @ virt->phys offset
    add    r5, r5, r0            @ phys __turn_mmu_on
    add    r6, r6, r0            @ phys __turn_mmu_on_end
    mov    r5, r5, lsr #SECTION_SHIFT
    mov    r6, r6, lsr #SECTION_SHIFT
 
1:    orr    r3, r7, r5, lsl #SECTION_SHIFT    @ flags + kernel base
    str    r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping
    cmp    r5, r6
    addlo    r5, r5, #1            @ next section
    blo    1b
 
    /*
     * Map our RAM from the start to the end of the kernel .bss section.
     */
    add    r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
    ldr    r6, =(_end - 1)
    orr    r3, r8, r7
    add    r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:    str    r3, [r0], #1 << PMD_ORDER
    add    r3, r3, #1 << SECTION_SHIFT
    cmp    r0, r6
    bls    1b
 
#ifdef CONFIG_XIP_KERNEL
    /*
     * Map the kernel image separately as it is not located in RAM.
     */
#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
    mov    r3, pc
    mov    r3, r3, lsr #SECTION_SHIFT
    orr    r3, r7, r3, lsl #SECTION_SHIFT
    add    r0, r4,  #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
    str    r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
    ldr    r6, =(_edata_loc - 1)
    add    r0, r0, #1 << PMD_ORDER
    add    r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:    cmp    r0, r6
    add    r3, r3, #1 << SECTION_SHIFT
    strls    r3, [r0], #1 << PMD_ORDER
    bls    1b
#endif
 
    /*
     * Then map boot params address in r2 if specified.
     * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
     */
    mov    r0, r2, lsr #SECTION_SHIFT
    movs    r0, r0, lsl #SECTION_SHIFT
    subne    r3, r0, r8
    addne    r3, r3, #PAGE_OFFSET
    addne    r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
    orrne    r6, r7, r0
    strne    r6, [r3], #1 << PMD_ORDER
    addne    r6, r6, #1 << SECTION_SHIFT
    strne    r6, [r3]
 
#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
    sub    r4, r4, #4            @ Fixup page table pointer
                        @ for 64-bit descriptors
#endif
 
#ifdef CONFIG_DEBUG_LL
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
    /*
     * Map in IO space for serial debugging.
     * This allows debug messages to be output
     * via a serial console before paging_init.
     */
    addruart r7, r3, r0
 
    mov    r3, r3, lsr #SECTION_SHIFT
    mov    r3, r3, lsl #PMD_ORDER
 
    add    r0, r4, r3
    mov    r3, r7, lsr #SECTION_SHIFT
    ldr    r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
    orr    r3, r7, r3, lsl #SECTION_SHIFT
#ifdef CONFIG_ARM_LPAE
    mov    r7, #1 << (54 - 32)        @ XN
#ifdef CONFIG_CPU_ENDIAN_BE8
    str    r7, [r0], #4
    str    r3, [r0], #4
#else
    str    r3, [r0], #4
    str    r7, [r0], #4
#endif
#else
    orr    r3, r3, #PMD_SECT_XN
    str    r3, [r0], #4
#endif
 
#else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
    /* we don't need any serial debugging mappings */
    ldr    r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif
 
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
    /*
     * If we're using the NetWinder or CATS, we also need to map
     * in the 16550-type serial port for the debug messages
     */
    add    r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)
    orr    r3, r7, #0x7c000000
    str    r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
    /*
     * Map in screen at 0x02000000 & SCREEN2_BASE
     * Similar reasons here - for debug.  This is
     * only for Acorn RiscPC architectures.
     */
    add    r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)
    orr    r3, r7, #0x02000000
    str    r3, [r0]
    add    r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)
    str    r3, [r0]
#endif
#endif
#ifdef CONFIG_ARM_LPAE
    sub    r4, r4, #0x1000        @ point to the PGD table
    mov    r4, r4, lsr #ARCH_PGD_SHIFT
#endif
    mov    pc, lr
ENDPROC(__create_page_tables)


f -- 使能MMU,跳转到start_kernel

文件linux/arch/arm/kernel/head.S中

文件linux/arch/arm/kernel/head.S中


在前面有过这样的指令操作ldr r13, __switch_data ,

mov pc, r13 就是将跳转到__switch_data处。

在文件linux/arch/arm/kernel/head-common.S中:


.type __switch_data, %object  //定义一个对象
__switch_data:
.long __mmap_switched  //由此可知上面程序将跳转到该程序段处。
.long __data_loc @ r4
.long _data @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
 
 
 . = PAGE_OFFSET + TEXT_OFFSET;
 #else
 . = ALIGN(THREAD_SIZE);
 __data_loc = .;
 #endif
 
 .data : AT(__data_loc) {  //此处数据存储在上面__data_loc处。
 _data = .;
 
 *(.data.init_task)
…………………………
 
.bss : {
__bss_start = .;
*(.bss)
*(COMMON)
_end = .;
}
………………………………

init_thread_union 是 init进程的基地址. 在 arch/arm/kernel/init_task.c 中:
 
00033: union thread_union init_thread_union
00034:         __attribute__((__section__(".init.task"))) =
00035:                 { INIT_THREAD_INFO(init_task) };        
 
    对照 vmlnux.lds.S 中,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的
*/
 
__mmap_switched:
adr r3, __switch_data + 4
 
ldmia r3!, {r4, r5, r6, r7}  
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6  //将 __data_loc处数据搬移到_data处
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
 
mov fp, #0 //清除BSS段内容
1: cmp r6, r7    
strcc fp, [r6],#4
bcc 1b
 
ldmia r3, {r4, r5, r6, r7, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel  //程序跳转到函数start_kernel进入C语言部分。

--------------------- 
作者:zqixiao_09 
来源:CSDN 
原文:https://blog.csdn.net/zqixiao_09/article/details/50821995 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值