Linux——操作系统启动——linux启动概述

 本文的Linux内核分析将以risc-v体系结构为例。

1. linux代码

  • Linux压缩版内核和非压缩版内核

在启动Linux时,linux内核的elf可执行文件可以是非压缩版的原始内核,也可以是压缩后再加上一个elf头的压缩版内核(这个elf头就是解压缩的代码)。这样区分是考虑到嵌入式系统的存储空间容量一般都比较小,内核要常驻内存,采用压缩版可以占用较少的存储空间,但是会牺牲一些性能,加载linux内核时间也更长,所以一般嵌入式系统均采用压缩的内核映像文件。

对于非压缩版内核,内核的执行入口点是arch//kernel/head.S的kernel_entry;对于压缩版内核,在解压前的真正的执行入口是arch//boot/compressed/head.S中的start标号,压缩版内核在start标号处开始执行的时候会通过decompress_kernel()进行自解压,解压内容释放到内存里形成一个原始内核,解压完毕后,执行跳转到原始内核的kernel_entry入口执行。

  • Linux kernel中使用文件系统的部分
  1. 高速缓冲区的管理程序
    buffer.c
  2. 文件系统的底层通用函数,包括对硬盘的读写、分配、释放等,对目录的节点管理、内存与磁盘的映射
  3. 对文件数据进行读写操作的模块,即VFS
    虚拟文件系统,硬件驱动和文件系统的关系
  • /dev目录

Linux内核使得设备作为文件显示在/dev文件夹下,该目录很重要,linux会通过根目录下的init进入用户程序

  • Linux运行级别

常用的运行级别为3和5,该设置保存在/etc/inittab配置文件或/etc/init文件夹中;每个级别对应的启动服务保存在/etc/rc.d/rv[0123456].d中。

  1. 0:关机
  2. 1:单用户模式:该模式下不启动任何服务,且直接以root用户登录,不需要密码(可以用来修改root密码~_~)
  3. 2:不带网络的多用户模式
  4. 3:多用户模式
  5. 4:未使用
  6. 5:X11图形化模式
  7. 6:重启
  • linux中的系统调用

在include/uapi/asm-generic/unistd.h中可以找到通用的系统调用号(有些体系结构等有可能有另外单独的实现,这里是generic的系统调用),在这里可以找到系统调用号,但是不能找到函数实现的入口。

“SYSCALL_DEFINEn(xyzzy, ...)`` for the entry point”这句话其实提示了找到系统调用入口位置关键,通常 调用会有一个SYSCALL_DEFINEn的关键词,用来把系统调用和相应的系统调用号关联起来。因此找系统调用函数的实现可以找SYSCALL_DEFINEn(call_name,..)这种格式的东西。此时可以通过文本检索来查找。

  • linux代码中一些通用函数的含义
  • 设备树解析

设备树用于描述硬件的信息,包含节点各类属性,在dts文件中定义,最终会被编译成dtb文件加载到内存中,linux内核会在启动过程中去解析dtb文件,解析成device_node描述的Device Tree;再根据device_node节点,创建platform_device结构,并最终注册进系统

参考连接:Linux驱动篇(六)——设备树之DTS规则(一) - 知乎

2. 基本启动过程

此文讲的很清晰:Linux 系统启动过程 | 菜鸟教程

2.1 bootloader

上电引导程序,一般保存在主板上的存储芯片中,是计算机启动的第一个代码。不同类型的bootloader程序有不同的执行过程,但总的来说,大致功能都是硬件设备的初始化和后续代码的加载,具体有:

分析设备树文件,检查硬件并且查找可启动设备,确定启动设备后一般会加载启动设备的第一段512KB的数据到内存中(上述功能基本使用汇编语言实现);后续加载的完整引导程序,整段程序的功能是持续不断的加载后续引导代码和linux内核代码到内存中(该部分功能基本使用C语言实现)

大型linux代码的引导则更加复杂,该代码的引导会通过GRUB进行

最终将跳转到操作系统(head.S)的首地址

uboot的执行过程:一个IC设计人员理解的Boot - 讨论区 - EETOP 创芯网论坛 (原名:电子顶级开发网) -

2.2 linux启动

  • kernel的启动
    参考连接:Linux——操作系统启动——kernel的启动_KGback的博客-CSDN博客
  • 执行init()

 init是linux系统中执行的第一个进程,即0号进程

  1. setup(&drive_info)
    获取硬件驱动信息
  2. open("/dev/ttyS0",O_RDWR)
    打开标准输入控制台
  3. dup(0)
    打开标准输出控制台、打开标准错误控制台
  4. 创建1号进程,如果在0号父进程船舰进程成功,fork函数返回0,如果在子进程中fork则返回父进程PID;
    在1号进程中执行,关闭0号进程标准输入输出,打开系统配置文件,挂载文件系统,执行sh脚本
  5. 在0号进程中等待子进程退出
  6. 创建新的子进程,在该进程中执行,关闭之前所有的控制台,打开新的控制台,执行sh脚本;
    如果该进程创建失败,将重新创建子进程
     

调用/etc/rc.d/rv.sysinit负责对系统进行初始化,挂载文件系统并且根据运行级别启动相应服务
 

3. 相关案例

1 以x86加载ucore为例

  • 上电初始化,操作系统加载
    硬件加电后,首先进入实模式(intel为了想下兼容)
  1. cs和EIP结合形成启动后执行的第一个地址,即BIOS的EPROM所在地
  2. BIOS进行底层的硬件初始化、外设的自检(包括CPU、内存、硬盘等),设置主板参数
  3. 任务2完成后,BIOS加载存储设备(硬盘、关盘、USB盘等任一)上的第一个扇区的512 Byte 到内存的0x7c00开始
  4. IP地址跳转到0x7c00开始执行,执行上述扇区的代码(该代码即bootloader,对ucore进一步加载)
  • OS内核启动
    bootloader开始引导操作系统:
  1. 使能保护模式,段机制开始工作
  2. 从硬盘中读取kernel(elf格式)读到内存中的固定位置
  3. 跳转到ucore OS的入口点执行(此时控制权到了ucore OS中)

4 以龙芯3号MIPS架构加载Linux5.4为例

下图是龙芯电脑及其操作系统启动过程(图片摘自“基于龙芯的linux内核探索解析”一书)

龙芯的BIOS有两种,分别是legacy的PMON和UEFI的昆仑固件,本文以昆仑固件说明。boot.cfg和grub.cfg是BIOS的启动配置文件,这两个文件内容大同小异,均是由多个启动项组成,每个启动项都会指定一个内核文件的路径名、一个可选的初始化内存盘的路径名、以及一串启动参数(由设备数文件传递)。可以这么理解,不同的启动项对应不同的操作系统

龙芯3号是一款4核的CPU,但是在linux启动过程中,编号为0的核最先启动,在它启动完成后才会唤醒其他核

  • 上电初始化并加载
  1. 上电执行BIOS初始化硬件(入口地址固定为0xbfc00000,物理地址为0x1fc00000)
  2. 初始化完成后昆仑固件使用Grub作为启动加载器(bootloader),在Grub缺省的情况下会读取启动磁盘第一个分区上的启动配置文件grub.cfg
  • 内核启动
    选定一个启动项后,就开始了操作系统的启动过程,即bootloader加载操作系统内核到内存,此时内核全面接管电脑的控制权
  1. 首先启动linux内核(0号进程),CPU的执行入口点(PC寄存器的初始值)是编译内核时决定的,由BIOS或bootloader传递给内核。对于非压缩版内核来说,kernel_entry是其执行入口点。该部分为与体系结构相关的汇编代码,主要是将设备树信息传递给内核,初始化GP,SP和一些特殊寄存器等。
  2. 随后跳转到start_kernel(),进行一系列CPU体系架构相关的初始化。分三个阶段进行:
    首先需要关中断单线程(中断处理的基础设施尚未准备好)
    1)setup_arch():根据体系结构进行初始化
    2)trap_init():根据体系结构进行异常初始化,为每个CPU配置和建立异常向量表
    3)init_IRQ():根据体系结构初始化设备树描述的中断控制器
    4)time_init():初始化体系结构中计时系统部分。
    其次是开中断单线程
    5)console_init():控制台初始化,与SoC架构相关,选择VTconsole(鼠标键盘显示器)、SerialConsole(串口模式)、VGAConsole或者FBConsole模式
    最后是开中断多线程
    6)rest_init():余下的初始化工作,包括kernel_init()和kthreadd两个进程
    kernel_init()的进程分为两步,一步是内核线程,接管之前初始化的工作,并开始启动多核进入多核阶段,下一步是装载用户态的init进程,变身为一个用户进程,成为所有其他用户态进程的鼻祖
  3. 内核根据有没有配置初始化内存盘来决定是直接启动跟文件系统里的init程序还是先启动内存盘里的init程序
  4. init进程会启动各种系统服务,然后根据不同的操作模式(命令行模式或图形模式)来启动不同的登录管理服务,此后计算机的控制权都将转交给根文件系统的init进程(1号进程)
  5. 一旦init程序装入并执行,内核启动就结束了,后续就是核外系统的启动。

你可能感兴趣的:(Linux,linux,bootloader)