在Linux移植之make uImage编译过程分析中已经提到了uImage是一个压缩的包并且内含压缩程序,可以进行自解压。自解压完成之后内核代码从物理地址为0x30008000处开始运行。下面分析在进入C之前内核做的一些工作,以下是内核启动过程中打印出来的信息,其中Uncompressing Linux就是在自解压代码。make uImage编译的最后也给出了链接脚本arch/arm/kernel/vmlinux.lds,以及链接的顺序arch/arm/kernel/head.o 是第一个。

分析arch/arm/kernel/vmlinux.lds可以知道程序入口的地址是stext,并且是.text.head段

277    OUTPUT_ARCH(arm)
278    ENTRY(stext)291    . = (0xc0000000) + 0x00008000;
292
293    .text.head : {
294    _stext = .;
295    _sinittext = .;
296    *(.text.head)
297    }

打开arch/arm/kernel/head.s。可见内核运行的第一条代码就是第79行的代码,从这条开始分析,首先将CPU设置为管理模式,并且关闭所有中断;然后获得CPU的id。

76        .section ".text.head", "ax" //.text.head段
77        .type    stext, %function
78    ENTRY(stext)                    //入口地址stext
79        msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode//确保进入了管理模式
80                            @ and irqs disabled                                     //并且禁止中断
81        mrc    p15, 0, r9, c0, c0        @ get processor id                               //获得处理器的CPU id,并且存入 r9中
82        bl    __lookup_processor_type        @ r5=procinfo r9=cpuid           //调用函数,输入参数r9=cpuid。返回值r5=procinfo
83        movs    r10, r5                @ invalid processor (r5=0)?//如果不支持当前CPU,则r5=0
84        beq    __error_p            @ yes, error 'p'                              //如果r5=0,则打印错误
85        bl    __lookup_machine_type        @ r5=machinfo          //调用函数,r5=返回值machinfo
86        movs    r8, r5                @ invalid machine (r5=0)?  //如果不支持当前单板,则返回r5=0
87        beq    __error_a            @ yes, error 'a'               //如果r5=0,则打印错误
88        bl    __create_page_tables//创建一级页表以建立虚拟地址到物理地址的映射关系,后面再研究

接着调用__lookup_processor_type,它位于arch\arm\kernel\head-common.S。它的功能是比较当前CPU的id与内核支持的CPU的id是否相符合。这段代码在.proc.info.init段中从__proc_info_begin开始到__proc_info_end结束,寻找符合当前CPU的ID号的proc_info_list结构

145        .type    __lookup_processor_type, %function
146    __lookup_processor_type:
147        adr    r3, 3f                        //r3 = 第178行代码的物理地址
148        ldmda    r3, {r5 - r7}      //将r3地址开始的3个地址的内容赋给 r5、r6、r7 ;r5=__proc_info_begin,r6=__proc_info_end
149        sub    r3, r3, r7            @ get offset between virt&phys//r3=r3-r7,即物理地址与虚拟地址的差值
150        add    r5, r5, r3            @ convert virt addresses to//r5=__proc_info_begind对应的物理地址
151        add    r6, r6, r3            @ physical address space   //r6=__proc_info_end对应的物理地址
152    1:    ldmia    r5, {r3, r4}            @ value, mask//r3、r4等于proc_info_list结构中的cpu_val、cpu_mask
153        and    r4, r4, r9            @ mask wanted bits//r4=r4&r9=cpu_mask&传入的cpuid
154        teq    r3, r4                                                    //比较
155        beq    2f                                                        //如果相等,则找到对应的proc_info_list结构,跳到160行
156        add    r5, r5, #PROC_INFO_SZ        @ sizeof(proc_info_list)//r5指向下一个proc_info_list结构
157        cmp    r5, r6                                                   //是否已经比较完所有proc_info_list
158        blo    1b                                                       //没有则继续比较
159        mov    r5, #0                @ unknown processor//比较完毕,但是没有找到匹配的proc_info_list结构,r5=0
160    2:    mov    pc, lr//返回,返回的值为r5=proc_info_list176        .long    __proc_info_begin
177        .long    __proc_info_end
178    3:    .long    .//.表示当前这条代码链接后的虚拟地址
179        .long    __arch_info_begin
180        .long    __arch_info_end

其中__proc_info_begin、__proc_info_end被定义在arch\arm\kernel\vmlinux.lds中,它的意思是内核源码中有被定义为.proc.info.init的内容,它的起始地址是__proc_info_begin,结束地址为__proc_info_end。

299     .init : { /* Init code and data        */
230       *(.init.text)
231      _einittext = .;
232      __proc_info_begin = .;
233       *(.proc.info.init)
234      __proc_info_end = .;

接着看到proc_info_list结构的内容,它被定义在include\asm-arm\Procinfo.h中

29    struct proc_info_list {
30        unsigned int        cpu_val;
31        unsigned int        cpu_mask;
32        unsigned long        __cpu_mm_mmu_flags;    /* used by head.S */
33        unsigned long        __cpu_io_mmu_flags;    /* used by head.S */
34        unsigned long        __cpu_flush;        /* used by head.S */
35        const char        *arch_name;
36        const char        *elf_name;
37        unsigned int        elf_hwcap;
38        const char        *cpu_name;
39        struct processor    *proc;
40        struct cpu_tlb_fns    *tlb;
41        struct cpu_user_fns    *user;
42        struct cpu_cache_fns    *cache;
43    };

接着找到对于当前内核支持的proc_info_list 定义,它在arch\arm\mm\proc-arm920.S 中。对于S3C2410、S3C2440芯片来说CPU ID都是0x41129200。cpu_val的值为0x41009200、cpu_mask的值为0xff00fff0,刚好匹配。

    .section ".proc.info.init", #alloc, #execinstr448        .type    __arm920_proc_info,#object
449    __arm920_proc_info:
450        .long    0x41009200//cpu_val值
451        .long    0xff00fff0//cpu_mask值
452        .long   PMD_TYPE_SECT | \
453            PMD_SECT_BUFFERABLE | \
454            PMD_SECT_CACHEABLE | \
455            PMD_BIT4 | \
456            PMD_SECT_AP_WRITE | \
457            PMD_SECT_AP_READ
458        .long   PMD_TYPE_SECT | \
459            PMD_BIT4 | \
460            PMD_SECT_AP_WRITE | \
461            PMD_SECT_AP_READ
462        b    __arm920_setup
463        .long    cpu_arch_name
464        .long    cpu_elf_name
465        .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
466        .long    cpu_arm920_name
467        .long    arm920_processor_functions
468        .long    v4wbi_tlb_fns
469        .long    v4wb_user_fns
470    #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
471        .long    arm920_cache_fns
472    #else
473        .long    v4wt_cache_fns
474    #endif
475        .size    __arm920_proc_info, . - __arm920_proc_info

继续回到arch/arm/kernel/head.s往下分析,看到第83行,调用完__lookup_processor_type后r5的值变为执向找到的proc_info_list 结构的地址。所以第83行与第84行比较r5是否为0,如果为0说明没有找到符合当前CPU的ID号,则打印错误。接着到85行,调用__lookup_machine_type,它同样位于arch\arm\kernel\head-common.S中,它的功能是比较当前单板的id与内核支持的单板的id是否相符合。这段代码在.arch.info.init段中从__arch_info_begin开始到__arch_info_end结束,寻找符合当前单板的ID号的machine_desc结构

176        .long    __proc_info_begin
177        .long    __proc_info_end
178    3:    .long    .//.表示当前这条代码链接后的虚拟地址
179        .long    __arch_info_begin
180        .long    __arch_info_end193        .type    __lookup_machine_type, %function
194    __lookup_machine_type:
195        adr    r3, 3b                   //r3=第178行的物理地址
196        ldmia    r3, {r4, r5, r6}     //r4=r3。r5=__proc_info_end,r6=__proc_info_begin,取得的是虚拟地址
197        sub    r3, r3, r4            @ get offset between virt&phys//r3=r3-r4,取得物理地址与虚拟地址的偏差
198        add    r5, r5, r3            @ convert virt addresses to//r5=r5+r3,取得物理地址__proc_info_end
199        add    r6, r6, r3            @ physical address space   //r6=r6+r3,取得物理地址__proc_info_begin
200    1:    ldr    r3, [r5, #MACHINFO_TYPE]    @ get machine type //r3=取得单板的编号
201        teq    r3, r1                @ matches loader number?//比较r3与r1是否相等,即linux是否支持uboot传入的单板
202        beq    2f                @ found                     //如果相等,则跳到207行,找到支持的单板,返回
203        add    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc//r5执向下一个machine_desc结构
204        cmp    r5, r6                                             //是否已经比较完machine_desc结构?
205        blo    1b                                                 //如果没有比较完,则跳到200行继续比较
206        mov    r5, #0                @ unknown machine              //如果所有machine_desc都比较完了,r5=0
207    2:    mov    pc, lr                                             //返回

其中__arch_info_begin、__arch_info_end被定义在arch\arm\kernel\vmlinux.lds中,它的意思是内核源码中有被定义为.arch.info.init的内容,它的起始地址是__arch_info_begin,结束地址为__arch_info_end。

305      __arch_info_begin = .;
306       *(.arch.info.init)
307      __arch_info_end = .;

接着看到machine_desc结构的内容,它被定义在include\asm-arm\mach\Arch.h 中

17    struct machine_desc {
18        /*
19         * Note! The first four elements are used
20         * by assembler code in head-armv.S
21         */
22        unsigned int        nr;        /* architecture number    */     //单板的编号,是从内核传过来的编号  r1
23        unsigned int        phys_io;    /* start of physical io    */
24        unsigned int        io_pg_offst;    /* byte offset for io
25                             * page tabe entry    */
26
27        const char        *name;        /* architecture name    */
28        unsigned long        boot_params;    /* tagged list        *///boo传过来的tag标记的位置,也是从内核传过来的 r2
29
30        unsigned int        video_start;    /* start of video RAM    */
31        unsigned int        video_end;    /* end of video RAM    */
32
33        unsigned int        reserve_lp0 :1;    /* never has lp0    */
34        unsigned int        reserve_lp1 :1;    /* never has lp1    */
35        unsigned int        reserve_lp2 :1;    /* never has lp2    */
36        unsigned int        soft_reboot :1;    /* soft reboot        */
37        void            (*fixup)(struct machine_desc *,
38                         struct tag *, char **,
39                         struct meminfo *);
40        void            (*map_io)(void);/* IO mapping function    *///IO映射函数,移植时需要关注
41        void            (*init_irq)(void);
42        struct sys_timer    *timer;        /* system tick timer    */
43        void            (*init_machine)(void);
44    };

接着需要找到对于当前内核支持的machine_desc定义,在include\asm-arm\mach\Arch.h 中有如下宏定义,它表示在.arch.info.init段存入一个machine_desc 的结构体,名称为

__mach_desc_type,结构体内.nr、.name初始化为MACH_TYPE_type、_name
50    #define MACHINE_START(_type,_name)            \
51    static const struct machine_desc __mach_desc_##_type    \
52     __used                            \
53     __attribute__((__section__(".arch.info.init"))) = {    \
54        .nr        = MACH_TYPE_##_type,        \
55        .name        = _name,
56
57    #define MACHINE_END                \
58    };

接着找调用MACHINE_START这个宏的文件,在arch\arm\mach-s3c2440\Mach-smdk2440.c 找到了,所以单板的ID为MACH_TYPE_S3C2440,它被定义在include\asm-arm\Mach-types.h中

#define MACH_TYPE_S3C2440              362。与UBOOT传入的参数相符合。

339    MACHINE_START(S3C2440, "SMDK2440")
340        /* Maintainer: Ben Dooks <ben@fluff.org> */
341        .phys_io    = S3C2410_PA_UART,
342        .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
343        .boot_params    = S3C2410_SDRAM_PA + 0x100,
344
345        .init_irq    = s3c24xx_init_irq,
346        .map_io        = smdk2440_map_io,
347        .init_machine    = smdk2440_machine_init,
348        .timer        = &s3c24xx_timer,
349    MACHINE_END

继续来看MACHINE_START(S3C2440, "SMDK2440")这个宏,在里面有许多和开发板相关的设置,比如说smdk2440_map_io,它被定义在arch\arm\mach-s3c2440\Mach-smdk2440.c中,在Linux移植之移植步骤中提到过想要移植成功,必须修改327行代码,将晶振的设置改为12000000。还有其它的一些配置就不一一列举了。

324    static void __init smdk2440_map_io(void)
325    {
326        s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
327        s3c24xx_init_clocks(12000000);//根据开发板合适的晶振配置
328        s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
329    }

回到arch/arm/kernel/head.s接着往下看,86、87行判断__lookup_machine_type是否成功找到支持单板的machine_desc结构,如果没找到则打印错误,88行是用来创建一级页表以建立虚拟地址到物理地址的映射关系,这里不详细分析。

继续往下看,看到100行,其中r10的值为__arm920_proc_info所在地址,PROCINFO_INITFUNC为proc_info_list结构体的偏移量,具体为__cpu_flush,对应到__arm920_proc_info结构体内,pc的值就是b __arm920_setup这条语句所在地址,即执行b __arm920_setup这条指令,__arm920_setup做一些MMU相关的初始化,在arch\arm\mm\proc-arm920.S中,这里不做细究。

97    ldr    r13, __switch_data        @ address to jump to after//r13是堆栈寄存器sp
98                            @ mmu has been enabled
99    adr    lr, __enable_mmu        @ return (PIC) address //100行设置完成之后在使能MMU
100   add    pc, r10, #PROCINFO_INITFUNC//调用__arm920_setup函数,应该跟MMU相关,后面再研究

b __arm920_setup执行完毕返回之后执行的是arch/arm/kernel/head.s下的__enable_mmu 。

152        .type    __enable_mmu, %function
153    __enable_mmu:
.....
174        b    __turn_mmu_on187        .align    5
188        .type    __turn_mmu_on, %function
189    __turn_mmu_on:
190        mov    r0, r0
191        mrc    p15, 0, r3, c0, c0, 0        @ read id reg
192        mov    r3, r3
193        mov    r3, r3
194        mov    pc, r13//设置完MMU之后跳转到__switch_data执行

__enable_mmu 执行完之后进入__switch_data执行,注意这时候的运行地址已经是初始化MMU之后的虚拟地址了。从15-24行可以看出pc=__mmap_switched,__mmap_switched的主要工作是将processor_id与__machine_arch_type初始化为当前MCU的编号与单板的编号

14        .type    __switch_data, %object
15    __switch_data:
16        .long    __mmap_switched
17        .long    __data_loc            @ r4
18        .long    __data_start            @ r5
19        .long    __bss_start            @ r6
20        .long    _end                @ r7
21        .long    processor_id            @ r4//之前找到的符合当前MCU的__arm920_proc_info结构体
22        .long    __machine_arch_type        @ r5//之前找到的符合单板的__mach_desc_S3C2440结构体
23        .long    cr_alignment            @ r6
24        .long    init_thread_union + THREAD_START_SP @ sp
25
26    /*
27     * The following fragment of code is executed with the MMU on in MMU mode,
28     * and uses absolute addresses; this is not position independent.
29     *
30     *  r0  = cp#15 control register
31     *  r1  = machine ID
32     *  r9  = processor ID
33     */
34        .type    __mmap_switched, %function
35    __mmap_switched://虚拟地址已经可以使用
36        adr    r3, __switch_data + 4//r3=__data_loc所在的地址
37
38        ldmia    r3!, {r4, r5, r6, r7}//r4=__data_loc所在地址;r5=__data_start所在地址依次类推 r3=__switch_data+4*4
39        cmp    r4, r5                @ Copy data segment if needed //检查是否有__data_loc段r4=r5说明没有__data_loc
40    1:    cmpne    r5, r6
41        ldrne    fp, [r4], #4
42        strne    fp, [r5], #4
43        bne    1b
44
45        mov    fp, #0                @ Clear BSS (and zero fp)//清0BSS段
46    1:    cmp    r6, r7
47        strcc    fp, [r6],#4
48        bcc    1b
49
50        ldmia    r3, {r4, r5, r6, sp}//r4=processor_id、r5=__machine_arch_type、r6=cr_alignment、sp=init_thread_union + THREAD_START_SP
51        str    r9, [r4]            @ Save processor ID//processor_id=r9 = proc_info_list.cpu_val = 0x41009200
52        str    r1, [r5]            @ Save machine type//__machine_arch_type=r1 = machine_desc .nr =  MACH_TYPE_S3C2440 = 362
53        bic    r4, r0, #CR_A            @ Clear 'A' bit
54        stmia    r6, {r0, r4}            @ Save control register values
55        b    start_kernel//跳转到start_kernel C函数

最终执行b start_kernel,跳到C函数,这是第二阶段的内容。

转载于:https://www.cnblogs.com/andyfly/p/9404198.html

Linux移植之内核启动过程引导阶段分析相关推荐

  1. linux文件系统启动流程,linux 内核启动过程以及挂载android 根文件系统的过程

    转载 作者:汕头大学-黄珠唐 时间:2009 年10 月29 日 主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源 ...

  2. linux启动过程中内核拷贝,轻松识破linux内核启动过程中的“”套路“”

    内核启动流程相关的内容让很多热爱linux的小伙伴既爱又恨,因为这是了解linux系统基本构造的良好过程同时由于其本身复杂且底层,脑子中的脉络不是很清晰,本文就总结了一些优秀博文,以自己的理解来解构一 ...

  3. 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...

  4. linux内核启动过程2:保护模式执行流程

    上一篇<<linux内核压缩制作bzImage>>分析了bzImage制作流程,本篇继续分析内核启动过程,从实模式跳转到保护模式及后续执行流程. protected_mode_ ...

  5. Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述     一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader ...

  6. Linux内核4.14版本:ARM64的内核启动过程(二)——start_kernel

    目录 1. rest_init 2. init 进程(kernel_init) 2.1 kernel_init_freeable 2.1.1 do_basic_setup 2.1.2 prepare_ ...

  7. linux内核启动过程3:内核初始化阶段

    上一篇<<linux内核启动过程2:保护模式执行流程>>分析了保护模式启动过程以及bzImage的解压入口函数,本篇继续分析内核启动过程,从保护模式到C代码初始化. start ...

  8. Linux内核启动过程概述

    Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创造出自己的内核^_^ Linux的启动代码真的挺大,从汇 ...

  9. linux内核启动过程5:启动用户空间

    上一篇<<linux内核启动过程4:内核运行时>>分析到了内核进入运行时状态(不退出),本篇分析用户空间(用户层)的加载过程. 启动应用空间 进入kernel_init函数,在 ...

最新文章

  1. Android Bluetooth hci 命令分析
  2. C++类型转换总结【转】
  3. apk ionic 破损_cordova – ionic build android不生成任何.apk文件或错误
  4. SpringMVC的数据响应-页面跳转-返回ModelAndView形式2(应用)
  5. 深入理解.NET Core的基元(二) - 共享框架
  6. C语言实验——数组逆序
  7. 最热开源静态网站生成器 TOP 20
  8. 敲7(升级版约瑟夫)
  9. C++小白课本练习3
  10. 零基础学python-从0开始学Python,0基础小白
  11. 2021-06-28操作表单
  12. 如何用Java的Robot完成模拟鼠标移动和键盘输入(可应用刷网课)
  13. 增值税报税显示服务器返回,增值税发票综合服务平台常见问题
  14. conan入门(二十六):使用make编译erpc/erpcgen(makefile)
  15. html页面整体隐藏,三种隐藏 HTML 元素的方式
  16. BitBucket介绍以及基础使用
  17. 【详解】标识符命名规则及命名规范
  18. 大专毕业后在富士康当生技
  19. 真正会赚钱的人,都有什么样的思维?
  20. HI3518 CV100 远程监控云台摄像头拆解

热门文章

  1. 射灯安装方法图解_江苏天筑不锈钢雕塑厂家格栅射灯安装方法,格栅射灯安装注意事项...
  2. python测试用例管理模块_python-selenium并发执行测试用例(方法一 各模块每一条并发执行)...
  3. oracle00109,ORA-01034: 、ORA-01078: 和 LRM-00109: 的解决方法,ora-01034ora-01078
  4. 国企程序员是一种怎样的体验?
  5. 收集53个程序员段子
  6. Linux有关Shell中if用法笔记
  7. Linux文件操作实用笔记
  8. Linux 与 Unix 到底有什么不同?
  9. 浅说 XSS和CSRF
  10. SQL Server各个版本功能比较