uboot启动和kernel启动流程

流程:通过u-boot.lds(各种段,刚开始有个链接地址)找到start.S的入口

(ENTRY(__start))

第一阶段:start.S(初始化soc内部的一些部件)

__start: b reset(异常向量表)->

通过cpsr设置SVC模式->

关闭MMU->读取启动介质->

设置sp(ldr #0xd0036000,这时还在sram中运行) 调用函数 bl lowlevelinit(如果只是一层调用就用bl,如果子函数里面还有调用就要使用栈)->lowlevel_init.S->push {lr}(压栈,因为后面还要调用)->

检查复位状态(冷热启动,睡眠启动(低功耗))来判断是否还要初始化DDR->

关看门狗->

判断当前代码是在SRAM还是DDR中运行(BL1(uboot前8K)在DDR和SRAM中各有一份)(adr ro, _start(运行地址或者PC) ldr r1, =_start(链接地址,自己指定) cmp r0 r1)->来判定是否时钟和DDR初始化->pop {pc}->DDR已经初始化,把栈挪到DDR中,重新设置栈(ldr sp, _TEXT_PHY_BASE sub sp, sp, ##12 mov fp, #0) 把第二阶段加载到33e00000->

重定位,将bl2从sd卡或者mmc搬运到ddr中(在sd卡中的开始扇区,扇区数,还有CFG_PHY_UBOOT_BASE:33e00000)->

串口->

使能mmu(TTB,cp15的c1的bit0置1即可开启mmu)->

ldr pc, _start_armboot

CFG_PHY_UBOOT_BASE 33e00000 uboot在DDR中的物理地址

TEXT_BASE c3e00000 uboot的虚拟地址

第二阶段:lib_arm/board.c start_armboot(初始化soc外部的一些硬件,uboot本身的一些东西(命令行,环境变量)) 最终bootcmd进入启动内核阶段

typedef int (int_func_t) (void);//这是一个函数类型,不是函数指针类型

init_func_t **init_func_ptr; //二重指针(一个用来指向一重指针,一个用来指向一个函数指针数组)

全局变量gd register volatile gd_t  *gd asm("r8")

register 修饰,这个变量尽量放到寄存器中,volatie 修饰

gd的内存分配不能用malloc

gd_base 

gd = (gd_t*)gd_base;

gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));

函数指针数组去遍历,可以在函数指针数组末尾添加一个null来作为结束标志,这样做的好处的是不用事先去指定数组大小。

挂起

void hang(void)

{

printf("error XXXXX");

for(;;);

init_sequence

}

bi_arch_number 机器码 uboot和kernel需要匹配(向uboot官方申请,国内的板子基本都不申请)

bi_boot_params uboot将cmd_line(bootarg),meminfo,mtdpartition,放在地址bi_boot_number处,uboot通过r0,r1,r2(bi_boot_params)kernel的传参

控制台(console)最终也是操作硬件(serial)操作寄存器来实现,优点是优化通信,比如说缓冲机制。

重定位:把代码从(有可能是运行地址)copy一份到链接地址

bl 短跳转

ldr pc,=0x33e00000

adr r0,=_start adr短加载,加载运行地址 (adr的反汇编是sub)

ldr r1,=_start ldr长加载,加载链接地址(ldr反汇编后还是ldr)

ldr r2, =bss_start

cmp r0 r1

beq clean_bss

copy_loop:

        ldr r3,  [r0], #4 //源

        str r3, [r1], #4 //目的

        cmp r1, r2

        bne copy_loop

clean_bss:

.word 相当于int(4字节)

__TEXT_BASE:

.word TEXT_BASE

这里标号__TEXT_BASE相当于指针,可以使用ldr __TEXT_BASE相当于加载TEXT_BASEqi

vmlinux.lds ENTRY(stext)

head.S ENTRY(stext)

head-common.S

1.校验

machid __vet_atags

2.__create_page_tables:建立粗页表 开MMU

3.创建C语言环境:

b start_kernel(都给uboot做了,就不用做太多了)

找到machine,并初始化架构相关的初始化

2.start_kernel

setup_arch(&command_line) 初始化架构相关的一些

setup_macharch_type

cmd_line 内核维护了一个默认的cmd_line

parse_early_param parse_args 解析cmd_line

各种子系统的初始化

rest_init

kernel_thread(启动了2个内核线程)

        kernel_thread(kernel_init)内核中运行一个函数就是变成内核线程  这就是init进程
        kernel_thread(kthreadd) 内核守护进程(保证内核自己本身能正常工作)

进程0:死循环 进程1:kernel_init

cpu_idle 结束内核调用 while(1)(cpu必须干活,不干活,就会跑飞,cpu每个时钟周期如果不干活就会跑飞)(没事干就死循环,有事干被调度去干活)

init进程 :一个进程2个状态

init在内核态下挂载根文件系统(mount_blocK_root)(根文件系统提供用户态init程序),并试图找到用户态的init程序,找到后,就转化为了用户态进程。通过执行kernel_execve来来执行一个用户空间编译连接的一个用户程序。内核态和用户态的init进程都是进程1。

挂载完根文件系统init_post(kernel_execve)

用户态init进程启动login进程,shell进程

内核启动流程

CPU从bootloader跳转出来后会开始启动linux kernel,内核启动完成后会将控制权转交给用户空间。

1. _start (head.S)

加载内核运行是的个数据段寄存器,重新设置中断描述符表; 开启内核争创运行是的协处理器等资源

加载全局指针

失能浮点运算部件

选择某线程运行boot sequence

清除bss段

保存线程号和DTB信息

setup_vm:

设置内存管理的分页机制;

relocate:

重定位虚拟地址(relocate),包括返回地址、stvec的虚拟地址、kernel页表的satp等,随后切换到kernel的页表首地址

parse_dtb:

解析设备树(”setup.c” : parse_dtb)

2. 跳转到start_kernel(init/main.c: start_kernel)

2.1 关中断单线程阶段

setup_arch():根据体系结构进行初始化
trap_init():异常初始化,为每个CPU配置和建立异常向量表
init_IRQ():初始化设备树描述的中断控制器
time_init():初始化计时系统部分。

2.2 开中断单线程阶段

2.2.1 local_irq_enable()

2.2.2 kmem_cache_init_late()

2.2.3 console_init()

控制台初始化,选择VTconsole(鼠标键盘显示器)、SerialConsole(串口模式)、VGAConsole或者FBConsole模式

2.3 开中断多线程阶段

3. 跳转到rest_init

在调用该函数之前主要进行与操作系统核心层相关的操作,包括进程调度、内存管理和中断系统等主要模块的初始化,而该rest_init函数将创建kernel_init进程。

4. 跳转到kernel_init

该函数主要分为两步,一步是内核线程,接管之前初始化的工作,并开始启动多核进入多核阶段,进而调用外部设备的初始化函数;下一步是装载用户态的init进程,变身为一个用户进程,成为所有其他用户态进程的鼻祖。

4.1 kernel_init_freeable():

4.1.1 准备工作

4.1.2 smp_prepare_cpus()

为启动多核做准备

4.1.3 workqueue_init()

工作队列初始化

4.1.4 do_pre_smp_initcalls()

执行__initcall_start和__initcall0_start之间的.initcall*.init()函数,实际上就是执行所有用early_initcall()定义的initcall函数

实际上linux内核中有9个级别的do_initcalls()函数,该过程只执行优先级最高的函数eraly_initcall(),其余的均在do_basic_setup阶段执行。

4.1.5 smp_init()

多核启动阶段

4.1.6 sched_init_smp()

该函数执行完成后,linux将正式进入多处理器并行状态

4.1.7 do_basic_setup()

4.1.7.1 driver_init()

4.1.7.1.1 devtmpfs_init()

该函数调用kthread_run创建线程devtmpfsd,该线程创建/dev目录下的基本设备节点,如/dev/console、/dev/zero、/dev/null等

4.1.7.1.2 device_init()

创建/sys/devices以及下级节点

4.1.7.1.3 buses_init()

4.1.7.1.4 classes_init()

4.1.7.1.5 firmware_init()

4.1.7.1.6 hypervisor_init()

4.1.7.2 中断处理

4.1.7.3 do_initcalls()

该阶段需要执行剩余的8个级别initcall函数,执行的代码量非常大。 该函数采用__define_initcall宏的形式从外部接收参数并处理
参考链接:调用initcall的机制

4.1.7.3.1 pure_initcall()

4.1.7.3.2 core_initcall()

init_hpet_clocksource():注册HPET的ClockSource

4.1.7.3.3 postcore_initcall()

4.1.7.3.4 arch_initcall()

4.1.7.3.5 subsys_initcall()

4.1.7.3.6 rootfs_initcall()

4.1.7.3.7 device_initcall()/module_init()

该函数将调用__initcall6_start()到__initcall7_start()之间的函数。
载入驱动的函数会调用platform_drc_probe()进行设备树信息提取进而调用probe(dev)指向dev的驱动文件中的_probe函数,例如lowrisc开发的sd控制器的驱动就指向lowrisc_sd_probe(),从而执行该函数体里的驱动代码。

如果linux设备驱动程序采用built-in的方式则使用_define_initcall的方式加载驱动,如果是采用Module的方式,则采用device_initcall的方式加载。
驱动加载顺序由makefile确定,顺序会打印在根目录下的System,map中。
参考链接:linux 驱动module_init

4.1.7.3.8 late_initcall()

4.1.8 打开console控制台设备

4.1.8.1 ksys_open("/dev/console")

打开控制台设备,linux中一切皆为文件,/dev/console文件就代表控制台设备

4.1.8.2 ksys_dup(0)

此时kernel_init进程获得了该文件的文件描述符,此时调用两次ksys_dup(0)复制两次,则得到三个文件描述符,这三个文件描述符分别是0、1、2,这三个文件描述符就是所谓的:标准输入、标准输出、标准错误。
此后kernel_init所有的子进程都将继承这3个文件描述符,也就是后面所有的进程一生出来,就默认有标准输入、标准输出、标准错误的文件描述符。

4.1.9 prepare_namespace()

挂载根文件系统,此时若出错,可能是bootloader阶段传递的bootargs设置不对,导致传递了错误的参数给kernel

4.2 numa_default_policy()

将1号进程自己的NUMA内存分配策略改成MPOL_DEFAULT

4.3 run_init_process()

装入用户态的init程序,变身为普通进程

4.3.1 getname_kernel()

4.3.2 do_execve()

在根文件系统中寻找init程序,具体路径由bootloader设置的环境变量bootargs提供,一旦init程序被找到,就会启动init进程(该可执行程序的文件名不一定叫init),然后操作系统正运行。

6. init()

init程序需要读取配置文件/etc/inittab,以查看下一步做什么。inittab是一个不可执行的文本文件,它有若干行指令所组成,告诉 init 要进入什么运行级别,以及在哪里可以找到该运行级别的配置文件。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值