• linux-kernel 启动过程 一


    概述

    前边的章节我们分析了UBOOT是怎么加载内核的,最终是通过bootz等指令跳转到了一个地址,就开始进入到内核,要分析内核的起始点也是得先从链接脚本开始分析,因此我们先编译下内核,然后从链接脚本开始分析(注:内核版本4.1.15,后续系列均为此版本)

    1、内核目录结构及编译

    1.1 内核编译

      1 #!/bin/sh                                                                       
      2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
      3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig
      4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
      5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 首先clean下工程,删除无用配置
    • 然后选择一个配置文件来进行编译,跟uboot类似,配置文件位置在arch/arm/configs/
    • 然后make menuconfg 图形化界面进行内核配置
    • 最后make 进行编译

    1.2 内核目录结构

    在这里插入图片描述

    • arch:架构有关的目录,比如 arm、 arm64、 avr32、 x86 等等架构。
    • block:块设备目录,像 SD 卡、 EMMC、 NAND、硬盘等存储设备就属于块设备
    • COPYING:版权声明
    • crypro:加密相关,比如常见的 crc、 crc32、 md4、 md5、 hash 等加密算法
    • Documentation:此目录里面存放着 Linux 相关的文档,如果要想了解 Linux 某个功能模块或驱动架构的功能,就可以在 Documentation 目录中查找有没有对应的文档。
    • drivers:驱动相关,根据驱动类型的不同,分门别类进行整理,比如 drivers/i2c 就是 I2C相关驱动目录, drivers/gpio 就是 GPIO 相关的驱动目录,这是学习的重点
    • firmware:固件相关,用于存放固件
    • fs:文件系统相关,存放文件系统,比如 fs/ext2、 fs/ext4、 fs/f2fs 等
    • include:头文件相关
    • init:初始化相关,内核启动的时候初始化代码
    • ipc:进程间通讯相关
    • Kbuild:Makefile 会读取此文件
    • Kconfig:图形化配置界面的配置文件
    • kernel:内核相关
    • lib:库文件,一些公用的库函数
    • MAINTAINERS:维护者名单
    • Makefile:linux顶层makefile
    • make_mx6ull_alpha_emmc.sh:笔者加的编译脚本
    • mm:内存管理文件
    • modules.* Modules.* :一系列文件,和模块有关,编译生成的文件
    • net:网络相关文件
    • README:Linux 描述文件,详细讲解了如何编译 Linux 源码,以及 Linux 源码的目录信息
    • REPORTING-BUGS: BUG 上报指南
    • samples:例程相关
    • scripts:脚本相关,Linux 编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录
    • security:安全相关
    • sound:音频处理相关,音频驱动文件并没有存放到 drivers 目录中,而是单独的目录
    • System.map:符号表,编译生成的文件
    • tools:工具相关,存放一些编译的时候使用到的工具
    • usr:与 initramfs 相关的目录,用于生成initramfs
    • virt:提供虚拟机技术(KVM),存放虚拟机相关文件
    • vmlinux:编译出来的、未压缩的 ELF 格式Linux 文件,编译生成
    • vmlinux.dis:vmlinux的反汇编文件,编译生成
    • vmlinux.o:vmlinux的obj文件,编译生成

    此外还有部分隐藏文件,列举几个比较重要的:

    • .config文件:跟 uboot 一样, .config 保存着 Linux 最终的配置信息,编译 Linux 的时候会读取此文件中的配置信息。最终根据配置信息来选择编译 Linux 哪些模块,哪些功能,编译生成
    • .vmlinux.cmd:cmd 文件,用于连接生成 vmlinux

    编译生成的image文件保存在arch/arm/boot,如下
    在这里插入图片描述
    注:Image是编译生成的内核镜像二进制文件,ZImage是将Image进行了压缩的,通常使用的也是ZImage。

    2、启动流程

    2.1 链接脚本vmlinux.lds

    首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到 Linux 内核的第一行程序是从哪里执行的,如下
    在这里插入图片描述
    我们就看关键的一些信息,ENTRY指定了入口函数,即stext(链接指定入口点的几个方式,在其他的bolg中有介绍)。

    2.2 入口函数stext

    stext 定义在文件arch/arm/kernel/head.S中,如下
    在这里插入图片描述
    首先说明了在进入到kernel的入口函数之前要关闭mmu,D-cache,同时stext的入口参数有3个,第一个是0,第二个机器id,第三个是设备树的地址(此地址为物理地址)。
    在这里插入图片描述

    • 第81行:设置为大端模式,其实ARM_BE8是个宏,定义在arch/arm/include/asm/assembler.h,如下
      在这里插入图片描述
      如上图,arm是支持修改大小端的,默认是小端,因此如果需要修改成大端则会执行指令 setend be,将其设置为大端模式
    • 第83~86行:切换到thumb指令集
    • 第88~90行:若启动了虚拟化,此处未启用
    • 第92行:调用函数safe_svcmode_maskall,定义在arch/arm/include/asm/assembler.h中,确保CPU 处于 SVC 模式,并且关闭了所有的中断
    • 第94行:从cp15协处理器的c0寄存器读取硬件的CPU ID号,保存在r9中
    • 第95行:调用函数__lookup_processor_type,检查当前系统是否支持此 CPU,如果支持的就
      获 取 procinfo 信 息,会将其保存到 r5 寄存器中 。 procinfo 是 proc_info_list 类 型 的 结 构 体 , proc_info_list 在 文 件
      arch/arm/include/asm/procinfo.h 中的定义如下
      在这里插入图片描述
    • 第96~98行:若获取到的cpu信息为0,则跳转到__error_lpae,最终死循环
    • 第100~106行:若定义3级页表才需要,此处未定义

    在这里插入图片描述

    • 第108~115行:未定义XIP,需要计算出物理地址和虚拟地址的差值。
    • 第121行:校验uboot给内核的传参ATAGS(r2)格式是否正确,定义在arch/arm/kernel/head-common.S
    • 第122~124行:多核的一些检测
    • 第125~127行:给物理地址打补丁,转换成虚拟地址
    • 第128行:调用函数__create_page_tables 创建页表,以便后续打开mmu

    注:linux内核本身被链接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态,但是kernel本身工作起来后页表体系是非常复杂的,建立起来也不是那么容易的。因此kernel想了一个好办法,就是:建立页表分两步走。第一步,kernel先建立一个段式页表(和uboot之前建立的页表一样,页表以1MB为单位来区分的),这里的函数就是建立段式页表。段式页表本身比较好建立(段式页表1MB一个映射,4GB的空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),坏处是比较粗不能精细管理内存;第二步,再去建立一个细页表(以4KB为单位),然后启动新的细页表,废除第一步建立的段式映射页表。
    在内核启动的早期,建立的段式页表,并在内核启动的前期使用;在内核启动的后期,就会再次建立细页表并启用。等内核启动工作起来之后就只有细页表了。

    在这里插入图片描述

    • 第137行:取得__mmap_switched的链接地址,保存在r13中(此时mmu未开启,无法跳转到该地址运行)
    • 第139行:将1f处的地址保存到lr寄存器中,即lr地址是__enable_mmu
    • 第140行:将上文计算的r4(PHYS_OFFSET - PAGE_OFFSET)存储到r8中
    • 第141行:把cpu对应proc info中的__cpu_flush存放到r12寄存器中,r10存储的是cpuinfo(这个__cpu_flush成员存放的是cpu对应架构的setup函数的地址)
    • 第142行:与r10相加后,的得setup函数的物理地址
    • 第143行:执行setup函数,定义在arch/arm/mm/proc-v7.s中,主要是一些打开mmu之前的准备工作, 执行完之后就跳转到__enable_mmu函数

    在这里插入图片描述

    • 第429~433行:根据配置使能或禁止地址对齐错误检测
    • 第434~436行:根据配置使能或禁止数据cache
    • 第437~439行:控制位选择淘汰算法
    • 第440~442行:根据配置使能或禁止指令cache
    • 第443~448行:设置访问权限
    • 第449行:设置页表地址c2
    • 第451行:调用了__turn_mmu_on打开mmu
      在这里插入图片描述
    • 第472行:指令同步屏障
    • 第473行:根据配置设置SCTLR寄存器,打开mmu使能位,cache等
    • 第477~478行:由于mmu已经使能,因此这里可以运行虚拟地址的函数了,跳转到__mmap_switched

    __mmap_switched函数定义在arch/arm/kernel/head-common.S 中,函数代码如下:
    在这里插入图片描述

    • 第82行:加载__mmap_switched_data的地址到r3中

    • 第84行:设置寄存器值,如下在这里插入图片描述
      相当于R4=_data_loc(数据存放的位置),R5=_sdata(是数据开始的位置),R6=__bss_start(bss开始的位置),R7=_end(bss结束的位置, 也是内核结束的位置)

    • 第85~89行:比较R4和R5,判断数据存储的位置和数据的开始的位置是否相等,如果不相等,则需要搬运数据,从 __data_loc 将数据搬到 _sdata. 其中 __bss_start 是bss的开始的位置,也标志了 data 结束的位置,因而用其作为判断数据是否搬运完成

    • 第91行:初始化栈基指针fp

    • 第92~94:清空bss段数据

    • 第96行:重新设置寄存器值

    • R4=processor_id(cpu处理器ID地址,其变量定义在arch/arm/kernel/setup.c中)

    • R5=__machine_arch_type(machine id地址,其变量定义在arch/arm/kernel/setup.c中)

    • R6=__atags_pointer(dtb指针的地址,其变量定义在arch/arm/kernel/setup.c中)

    • R7=cr_alignment(cp15的c1寄存器的值的地址,也就是mmu控制寄存器的值,其变量定义在arch/arm/kernel/entry-armv.S中)

    • sp=init_thread_union + THREAD_START_SP(init_thread_union可在System.map中获取,笔者的是0x809e6000,这样一来相当于SP的初值是0x809e6000 + 0x2000 - 8)

    • 第97~98行:thumb指令的实现,意义同第86行,同时只有一处生效

    • 第99行:把cpu处理器id(r9)放到processor_id变量中

    • 第100行:把mechine id(r1)存放到__machine_arch_type变量中

    • 第101行:把dtb的地址指针(r2)存放到__atags_pointer变量中(此地址为物理地址,使用需转换)

    • 第102~103行:把cp15的c1的寄存器的值(r0)存放到cr_alignment变量中

    • 第104行:跳转到start_kernel开始启动内核

  • 相关阅读:
    docker中安装RabbitMQ(DelayExchange)插件
    SSM框架之MyBatis入门(Maven工程实现全查功能,快速入门,适合小白)
    怎么开发一个快递便携电子秤方案
    MySQL高级5-SQL优化
    公众号查题接口
    Python实现SSA智能麻雀搜索算法优化XGBoost回归模型(XGBRegressor算法)项目实战
    Typescript 的联合类型和交叉类型
    理解依赖注入DI和控制反转IOC和容器
    51单片机—智能垃圾桶(定时器)
    正则验证用户名和跨域postmessage
  • 原文地址:https://blog.csdn.net/u010681589/article/details/127590142