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段

    OUTPUT_ARCH(arm)
ENTRY(stext) . = (0xc0000000) + 0x00008000; .text.head : {
_stext = .;
_sinittext = .;
*(.text.head)
}

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

        .section ".text.head", "ax" //.text.head段
.type stext, %function
ENTRY(stext) //入口地址stext
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode//确保进入了管理模式
@ and irqs disabled //并且禁止中断
mrc p15, , r9, c0, c0 @ get processor id //获得处理器的CPU id,并且存入 r9中
bl __lookup_processor_type @ r5=procinfo r9=cpuid //调用函数,输入参数r9=cpuid。返回值r5=procinfo
movs r10, r5 @ invalid processor (r5=)?//如果不支持当前CPU,则r5=0
beq __error_p @ yes, error 'p' //如果r5=0,则打印错误
bl __lookup_machine_type @ r5=machinfo //调用函数,r5=返回值machinfo
movs r8, r5 @ invalid machine (r5=)? //如果不支持当前单板,则返回r5=0
beq __error_a @ yes, error 'a' //如果r5=0,则打印错误
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结构

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

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

     .init : { /* Init code and data        */
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;

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

    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;
};

接着找到对于当前内核支持的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, #execinstr

        .type    __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200//cpu_val值
.long 0xff00fff0//cpu_mask值
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.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结构

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

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

      __arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;

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

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

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

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

接着找调用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传入的参数相符合。

    MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> ) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END

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

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

回到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中,这里不做细究。

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

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

        .type    __enable_mmu, %function
__enable_mmu:
.....
b __turn_mmu_on .align
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
mrc p15, , r3, c0, c0, @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13//设置完MMU之后跳转到__switch_data执行

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

        .type    __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4//之前找到的符合当前MCU的__arm920_proc_info结构体
.long __machine_arch_type @ r5//之前找到的符合单板的__mach_desc_S3C2440结构体
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp /*
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 */
.type __mmap_switched, %function
__mmap_switched://虚拟地址已经可以使用
adr r3, __switch_data + //r3=__data_loc所在的地址 ldmia r3!, {r4, r5, r6, r7}//r4=__data_loc所在地址;r5=__data_start所在地址依次类推 r3=__switch_data+4*4
cmp r4, r5 @ Copy data segment if needed //检查是否有__data_loc段r4=r5说明没有__data_loc
: cmpne r5, r6
ldrne fp, [r4], #
strne fp, [r5], #
bne 1b mov fp, # @ Clear BSS (and zero fp)//清0BSS段
: cmp r6, r7
strcc fp, [r6],#
bcc 1b ldmia r3, {r4, r5, r6, sp}//r4=processor_id、r5=__machine_arch_type、r6=cr_alignment、sp=init_thread_union + THREAD_START_SP
str r9, [r4] @ Save processor ID//processor_id=r9 = proc_info_list.cpu_val = 0x41009200
str r1, [r5] @ Save machine type//__machine_arch_type=r1 = machine_desc .nr = MACH_TYPE_S3C2440 = 362
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel//跳转到start_kernel C函数

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

Linux移植之内核启动过程引导阶段分析的更多相关文章

  1. Linux移植之内核启动过程start_kernel函数简析

    在Linux移植之内核启动过程引导阶段分析中从arch/arm/kernel/head.S开始分析,最后分析到课start_kernel这个C函数,下面就简单分析下这个函数,因为涉及到Linux的内容 ...

  2. Linux内核启动过程概述

    版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...

  3. 跟踪内核启动过程CONFIG_DEBUG_LL【转自】

    转自:http://bbs.chinaunix.net/thread-3642079-1-1.html 最近在调试Linux内核,跟踪启动过程.发现在没有turn on mmu之前,可以使用物理地址, ...

  4. [oracle] oracle的三种密码验证机制以及在windows和linux下的不同启动过程

    oracle数据库的密码验证机制: ① 操作系统验证 拥有SYSDBA和SYSOPER的用户用该方式验证此时数据库无需启动,也无需开启监听和实例服务. 要求:本地组ora_dba中有该操作系统的登录用 ...

  5. linux内核启动过程

    作者:严哲璟 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 通过qemu以 ...

  6. 1.移植3.4内核-分析内核启动过程,重新分区,烧写jffs2文件系统

    1.在上章-移植uboot里.我们来分析下uboot是如何进入到内核的 首先,uboot启动内核是通过bootcmd命令行实现的,在我们之前移植的bootcmd命令行如下所示: bootcmd=nan ...

  7. linux系统配置之开机启动过程(centos)

    1.开机流程如下: 2.BIOS BIOS是英文"Basic Input Output System"的缩略词,直译过来后中文名称就是"基本输入输出系统".其实 ...

  8. Linux:系统的启动过程

    Linux系统的启动过程 过程 通电-> BIOS-> LILO/GRUB-> Kernel Boot-> init->rc.sysinit init->rc -& ...

  9. Linux内核启动过程start_kernel分析

    虽然题目是start_kernel分析,但是由于我在ubuntu环境下配置实验环境遇到了一些问题,我觉得有必要把这些问题及其解决办法写下来. 首先我使用的是Ubuntu14.04 amx64,以下的步 ...

随机推荐

  1. C++学习二继承

    转载自https://www.cnblogs.com/33debug/p/6666939.html 1.继承与派生  继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一.简单的说,继承 ...

  2. k近邻算法(KNN)

    k近邻算法(KNN) 定义:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别. from sklearn.model_selection ...

  3. Django2.0 path和re_path使用

    Django2.0发布后,很多人都拥抱变化,加入了2的行列.但是和1.11相比,2.0在url的使用方面发生了很大的变化,下面介绍一下: 一.实例 先看一个例子: from django.urls i ...

  4. 怎么在idea中新建package包,只有directory选项

    http://blog.csdn.net/liyanlei5858/article/details/77320063

  5. 学习QT——GUI的基础用法(2)

    1.listWidget列表 在构造函数里面添加: ; i<; i++) { ui->listWidget->addItem(QString::number(i)+"ite ...

  6. ivew 表格中的input数据改变就会失去焦点

    主要有两种解决办法: 1.创建一个临时空数组创建一个临时空数组,render内操作的是这个空数组内的对象,然后监听这个临时空数组,在赋值给table组件的data,render内操作的是这个空数组内的 ...

  7. Python 继承与多继承

    相关知识点: __class__.__name__的用法. >>> class ABC: def func(self): print('打印类名:',__class__.__name ...

  8. 【MongoDB】关于无法连接mongo的问题

    今天使用MongoDB的时候发现直接输入mongo提示连接失败 首先想到的可能是服务还没启动 当我随便打开一个cmd输入net start MongoDB 提示:net start mongodb 发 ...

  9. kafka消息队列的简单理解

    kafka在大数据.分布式架构中都很流行.kafka可以进行流式计算,也可以做为日志系统,还可以用于消息队列. 本篇主要是消息队列相关的知识. 零.kafka作为消息队列的优点: 分布式的系统 高吞吐 ...

  10. cf-Global Round2-C. Ramesses and Corner Inversion(思维)

    题目链接:http://codeforces.com/contest/1119/problem/C 题意:给两个同型的由0.1组成的矩阵A.B,问A能否经过指定的操作变成B,指定操作为在矩阵A中选定一 ...