• bootz 启动 kernel



    活动地址:CSDN21天学习挑战赛

    思考

    uboot 启动 kernel,这是一个众所周知的事实。但是,你有没有思考过,这两个独立的镜像实体,是如何做到 A 启动 B 的呢?如果是同一份代码中的 func_a() 调用 func_b(),还好理解,因为是在同一份程序中(或者说是同一份指令中),func_a 是知道 func_b() 的地址的,直接跳转过去就行了(设置 PC 指针指向 func_b 的地址)。
    但是 uboot 和 kernel 不在同一份指令中,这是两个独立的程序,那么是怎样实现 程序 A “调用” 程序 B 的呢?

    bootz

    在 uboot 命令行下,可以使用 bootz 命令启动 kernel,我们就以此为切入点,探究 uboot 启动 kernel 的底层原理。

    表象

    使用如下命令,就能完成 uboot 启动 kernel。

    tftp 80800000 zImage
    tftp 83000000 imx6ull-14x14-evk.dtb
    bootz 80800000 - 83000000
    
    • 1
    • 2
    • 3

    这里简单介绍下命令作用。
    前两条是通过 tftp 命令,分别将 kernel 镜像 zImage 和设备树二进制文件下载到内存的 0x80800000 和 0x83000000 位置。
    第三条是使用 bootz 命令启动 kernel。

    bootz

    接下来,就分析下 bootz 启动 kernel 的原理。
    通过 uboot 命令行,向 bootz 传递了三个参数 “80800000”、“-”、“83000000”,它们分别是 “kernel 镜像在内存中的首地址”、“无效参数”、“设备树文件首地址”,
    bootz 命令对应的执行函数为 do_bootz(),在该函数中,首先设置 kernel 首地址和设备树首地址(其实这就是 uboot 能够启动 kernel 的原因:有了地址就能跳转过去运行)。

    images->ep = simple_strtoul(argv[0], NULL, 16);
    
    • 1

    变量 ep 用来存储 kernel 镜像在内存中的首地址,该参数由 argv[0] 赋值,argv[0] 就是刚刚在命令行传递给 bootz 的第一个参数 80800000

    images.ft_addr = argv[2];
    
    • 1

    变量 ft_addr 用来存储设备树在内存中的首地址,该参数由 argv[2] 赋值,为 83000000

    kernel_entry()

    do_bootz() 最终会调用 kernel_entry(),kernel_entry 并不是在 uboot 中定义的函数,它是一个地址转换来的,如下:

    kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
    				void *res2))images->ep;
    
    • 1
    • 2

    而 image->ep 就是 kernel 在内存中的首地址,这不就联系上了嘛🤞。

    uboot 将设备树传递给 kernel

    刚才分析出 images.ft_addr 存放设备树在内存中的首地址,那又是如何将这个地址传递给 kernel 呢?这里不像函数传参,当成实参就传递过去了。
    一来 uboot 和 kernel 不是同一份程序,不共用同一个堆栈;二来 kernel 一开始的代码是汇编代码,C 函数也没办法直接将自己的实参传递给汇编函数。
    那要怎么才能实现 uboot 中的一个变量传递给 kernel 呢?答案是使用寄存器。
    uboot 生命周期的最后一个函数调用如下

    r2 = (unsigned long)images->ft_addr;
    kernel_entry(0, machid, r2);
    
    • 1
    • 2

    使用 r2 寄存器存储设备树文件的首地址。

    kernel 接收设备树

    再来看 kernel 是怎么接收这个地址的呢?
    kernel 的入口函数是 head.S 中的 stext

    ENTRY(stext)
    	...
    	ldr	r13, =__mmap_switched		@ address to jump to after
    
    __mmap_switched:
    	...
    	str	r2, [r6]			@ Save atags pointer
    	cmp	r7, #0
    	strne	r0, [r7]			@ Save control register values
    	b	start_kernel
    ENDPROC(__mmap_switched)
    
    
    __mmap_switched_data:
    	.long	__data_loc			@ r4
    	.long	_sdata				@ r5
    	.long	__bss_start			@ r6
    	.long	_end				@ r7
    	.long	processor_id			@ r4
    	.long	__machine_arch_type		@ r5
    	.long	__atags_pointer			@ r6
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    总体流程是:
    stext 调用 __mmap_switched,在 __mmap_switched 中将 r2 的值赋给全局变量 __atags_pointer,然后调用 start_kernel()。

    start_kernel() 调用 setup_arch(),在该函数中解析设备树,如下:

    mdesc = setup_machine_fdt(__atags_pointer);
    
    • 1

    总结

    uboot 启动 kernel:

    uboot 拿到 kernel 在内存中的首地址,就能跳转到该地址运行。

    uboot 将设备树传递给 kernel:

    通过 r2 寄存器。

  • 相关阅读:
    doT.js模板学习笔记
    统计力学中的概率论基础(一)
    【luogu CF1286E】Fedya the Potter Strikes Back(字符串)(KMP)(势能分析)(线段树)
    openjudge 1.5.22 津津的储蓄计划
    Redis-AOF持久化是怎样实现的
    [开源项目]可观测、易使用的SpringBoot线程池
    vscode登录同步打开vscode.dev失败解决方法
    [附源码]Java计算机毕业设计SSM飞羽羽毛球馆管理系统
    win中的vscode利用ssh插件,在同一台电脑的virtualbox虚拟出来的ubuntu中编译,调试设置方法
    分布式ID系统设计(2)
  • 原文地址:https://blog.csdn.net/lyndon_li/article/details/126150965