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
  • 首先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开始启动内核

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值