百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

XV6操作系统入门系列-02-详解启动过程

liebian365 2025-03-18 23:47 2 浏览 0 评论

第零步-心理上的准备工作

任何事物都有其关键的窍门,当我们抓住了关键,事情会变得简单起来;当我们没有抓住要领,事情就会变得异常困难。

本文的主题是操作系统的启动过程。那么,理解它的关键是什么呢?

首先肯定不是 xv6 源代码使用的汇编语言或者C语言,如果哪条语法规则看不懂,我们问一下搜索引擎,ChatGPT,基本就能获得答案。学习语法是必要的工作,但不是关键。 临时抱佛脚、遇到问题再查找,甚至猜代码的作用都没有问题。

其次也不是 xv6 源代码本身。要将操作系统启动起来,涉及的代码也不少,分散在多个文件中。尤其是对于第一次接触它的人,彻底理解它的启动非常困难。当然网上有不少文章将涉及的代码一行一行注释写下来,理解成本很高。而我认为,阅读代码是必要的工作,但也不是关键。

必要的工作可以慢慢做,关键的工作要时刻做。我认为,要搞明白操作系统的启动过程,最核心的是要搞明白操作系统和CPU处理器之间的关系,也就是软件和硬件的关系

在本文以及后面的文章中,我们会碰到很多概念。学习的关键是要搞清楚,操作系统中哪些概念是由CPU来实现的(硬件电路),哪些概念是操作系统实现的(抽象的代码运行过程)。

我们时刻要绷紧这根弦。硬件电路是一种规范性的事物,在这操作系统的视角,它不能被改变,但可以被利用。软件是我们发挥主观能动性的战场,我们需要不断反思,有没有更好的设计。

我在学习操作系统的时候,犯过这样的错误。我花了很多时间在研究硬件为什么这么设计,三极管、逻辑电路、以及CPU的各个部分的硬件是如何实现的?人的精力是有限的,要想学习操作系统,就应该把注意力放在操作系统本身。既要了解它的基本概念,也要了解它的实现细节。而关于硬件部分,我们只需要知道它的基本概念即可。当然,既不要陷入硬件电路的细节,也不要忽略硬件电路背后的逻辑概念

第一步:分析源码寻找第一行代码

源代码根目录下的 Makefile 文件就是用来配置代码编译顺序的。当然我们也不需要精通 Makefile,只需要关注它的前面5行:

  • 第一行是声明一个字符串变量 K ,它的值是 kernel
  • 第五行是一个文件路径 kernel/entry.o

答案就在眼前!操作系统的第一行代码对应的文件是 kernel/entry.o。 啊哦!糟糕!我们打开 kernel 文件夹,结果发现源代码里根本没有 kernel/entry.o。 但是我们看到有一个非常类似的文件 kernel/entry.Skernel/entry.S 就是我们要找的第一行代码,而 kernel/entry.o 则是编译后的可执行文件。

我们打开这个文件 entry.S ,学习一下它。文件开头有这样的注释:entry.S 被放在 0x80000000 的位置,并且这个位置在 kernel.ld 中配置。

接着我们打开 kernel.ld 源码来看看它是怎么配置的吧。在它的开头,就出现了我们要找的字符串 0x80000000。我们不了解链接器的语法,也能猜出来这是用来配置内核第一行代码放置的位置。

第二步-梳理程序逻辑

上一小节中,我们通过探索源码,基本上搞清楚了内核的第一行代码在0x8000_0000。涉及的所有概念都属于软件层面。

  • o 第一步,根据Makefile的配置,内核源码依次被编译成以.o 结尾的可执行文件;
  • o 第二步,根据kernel.ld文件的配置,这些可执行文件被堆叠成一个可执行文件。这个可执行文件分为四个部分 .text.rodata.databss

我需要补充说明一下,中间阶段生成的每一个.o文件本身也由这四个部分组成,kernel.ld就是把所有的.o文件的四个部分拆分重组。

kernel.ld 里出现的这几个段的含义:

o .text: 代码段,存放程序的可执行指令;

o .rodata: Read-Only Data,只读数据段,存放常量和字符串字面量等只读数据;

o .data: 数据段,存放初始化的全局变量和静态变量;

o .bss: Block Started by Symbol,符号块段,存放未初始化的全局变量和静态变量。

第三步-Debug验证结果

在第一篇文章,我详细介绍了如何配置环境,以及如何调试代码。我再简述一下调试的方式。

  • 在命令行里进入 xv6-riscv 文件夹,输入make qemu-gdb ,编译并在qemu模拟器上运行xv6
  • 保持上一个命令行不变,另外开启一个命令行;
  • 如果使用windows或者linux,在新命令行输入 gdb-multiarch;如果使用macos,在新命令行输入 riscv64-elf-gdb

你将看到这个画面:左半边是make qemu-gdb 的结果,右半边是 riscv64-elf-gdb 的结果。

非常非常地不幸,通过Debug,我们发现qemu启动时的第一条指令地址在 0x1000, 根本不是前面分析的 0x8000_0000

问题出在哪里呢?本质是我们触及到了硬件部分的概念——引导程序,我们只需要了解一下Risc-V的引导程序,问题就迎刃而解了。

为了获取引导程序,我教大家一点GDB的技巧。

  1. 输入 p/x $pc 打印当前指令的地址,它存在cpu的PC寄存器里。PC全称是Program Counter,中文叫做程序计数器;
  2. 输入 x/10i $pc 打印PC寄存器指向的内存地址以及下面10行汇编指令。

从GDB的输出结果可以发现:在内存中,CPU的每条指令的地址间隔4,这里指的是4个字节,因为每个字节8位,所以每条指令占据32位

这段代码就是是存在RiscV处理器只读存储器(ROM,Read Only Memory)上的引导程序,用于初始化系统或跳转到操作系统的入口点。容我来逐行解释一下这段代码,给你节省一下时间:

  1. auipc t0,0x0 将当前PC(程序计数器)的值加上立即数(这里是0)的结果存储到t0寄存器中,t0=0x1000
  2. addi a2,t0,40 将t0中的值加上40,结果存入a2寄存器。 a2=t0+40=0x1000+0x28=0x1028
  3. csrr a0,mhartid 读取控制状态寄存器(CSR) mhartid 的值到a0寄存器。mhartid 存储了当前硬件线程(hart)的ID。
  4. ld a1,32(t0) 从内存地址(t0+32=0x1000+0x0020=0x1020)处加载64位数据到a1寄存器。
    因为我们启动的是一个64位的Risc-V处理器,所以CPU的硬件电路会读取 0x10200x1027 这8个字节64位数据。
    因为Risc-V的数据遵循小端数据(Little Endian) 的标准,所以数据从左到右,地址从大到小。
    在GDB中输入x/4i 0x1020 ,可以查到当前对应的内存布局。
    综上,寄存器a1的数值为0x87e0_0000
  1. ld t0,24(t0) 从内存地址(t0+24=0x1000+0x0018=0x1018)出加载64为数据到t0寄存器。类似的,寄存器t0的数值为ox8000_0000
  1. jr t0 跳转到t0寄存器中存储的地址,也就是 0x8000_0000

我们通过GDB来验证一下上面的内容。这是一些新的GDB的调试技巧

  • 输入break *0x1014,在跳转指令前添加断点;
  • 输入 continue,让程序继续运行到断点处;
  • 输入x/10i 0x1000,打印程序运行状态,发现程序正确运行至跳转指令 jr t0
  • 输入 p/x $a1 确认寄存器 a1 的值为 0x87e0_0000,符合预期;
  • 输入 p/x $t0 确认寄存器 t0 的值为 0x8000_0000,符合预期;
  • 输入 si(step instruction, 单步指令),运行下一条机器指令,程序跳转至0x8000_0000,符合预期。

谜底揭开,引导程序在初始化一些内部寄存器后,程序计数器PC寄存器将跳转到 0x8000_0000,也就是我们前面分析的内核的第一行代码在内存中的位置。

因为这段跳转程序是固定在硬件电路中的,所以操作系统内核第一行代码在内存中的位置由处理器硬件决定,xv6的源码进行了适配

第四步-梳理硬件逻辑

上一小节中, 我们补全了xv6的整个启动流程。 首先是 Risc-V 在通电后,首次读取它内部的只读寄存器的引导程序(硬件部分),引导程序会初始化处理器内部的寄存器,然后跳转到地址 0x8000_0000 处继续运行。因为操作系统的代码就放在 0x8000_0000 处,所以操作系统的代码开始执行。

总结

本文详细梳理了XV6的启动过程。处理器通电后,先运行引导程序,然后引导程序跳转到操作系统代码继续运行。其中引入了很多概念,有的属于硬件部分,有的属于软件部分。学习XV6的过程中,大家一定要把硬件和软件区分清楚。

相关推荐

几句代码实现搜索内存、解密数据库

本文只分享编程技术,不涉及具体软件。涉及具体软件的文章或工具出现很多年了,到处都是。头条上也有很多,这里我们不讨论。有用户问我:登录后才能解密,输入密码后才能备份出数据库,这些本来就是我自己可以查看的...

JDK 11 新特性总结(jdk最新特性)

一、语言特性增强局部变量类型推断升级支持在Lambda表达式参数中使用var关键字,编译器自动推断类型,简化代码编写并保持类型安全。...

和爷爷一起学Arduino:四位七段数码显示(学习面向对象编程)

2018年,我们买了个七段四位数码显示LED组件,如下图。经试验,它是与TM1637兼容的。右侧的引脚从上到下依次是,G(GND)、D(Data,数据)、C(Clock,时钟)、V(Vcc)。有两种,...

Linux 技巧:重定向 stderr 和 stdout 输出到 gdb 窗口

简介本文介绍了一个实用gdb调试技巧。它结合实际例子,一步一步示意如何重定向stderr和stdout到gdb窗口,使得查看应用程序的输出信息更为方便,从而提高调试者的工作效率。问题为...

CLion 1.0发布,C/C++跨平台集成开发环境

日前,知名开发者工具厂商JetBrains(捷克的一家软件开发公司)正式发布了一款跨平台的C/C++集成开发环境CLion1.0。这款强大的IDE旨在让你基于Linux、OSX、Windows系...

「运维经」第25章——gdb最实用的那几条命令

实用调试操作1setscheduler-lockingoff|on...

XV6操作系统入门系列-02-详解启动过程

第零步-心理上的准备工作任何事物都有其关键的窍门,当我们抓住了关键,事情会变得简单起来;当我们没有抓住要领,事情就会变得异常困难。...

GDB德国格德宝|OEM|奔驰车厂认证(德宝格机械)

MBMercedes油规格MB规范的名称源自奔驰蓝皮书计划,除以编号的段落和页面。经销商使用它来识别制造商认证的产品及其在发动机上的正确应用。...

o1已不是聊天模型了!SpaceX前工程师公开全新使用秘籍

梦晨发自凹非寺量子位|公众号QbitAI苹果&SpaceX前工程师分享o1使用心得,奥特曼、Brockman都转发了。...

ARM平台如何玩转GDB远程调试?(arm gdbserver)

前言关于GDB工具GDB工具是GNU项目调试器,基于命令行使用。和其他的调试器一样,可使用GDB工具单步运行程序、单步执行、跳入/跳出函数、设置断点、查看变量等等,它是UNIX/LINUX操作系统下...

ChatGPT击败50名人类医生!疾病诊断准确率达90%,OpenAI总裁:人机合作还得加强

...

GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译

友情提醒:本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!引言程序调试时,你是否遇到过下面几种情况:1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到...

实现多态必须满足什么条件(实现多态的两种方式)

虚函数机制virtualmechanism先看代码:classA{public:virtualvoidprint(){cout<<"A.."<<endl;}...

gdb查看寄存器及内存数据与函数调用栈分析

在分析kdump生成的vmcore文件时,有时会需要分析函数调用栈及函数参数与局部变量的情况,这里以使用gdb为例调试分析一下函数调用的栈帧创建与销毁。操作系统:centos73.10.0-862...

C++语言求数组元素最大值及其下标例程(指针学习与运用)

C++语言编写求数组元素最大值及其下标例程(指针学习与运用)文章logo#include"stdafx.h"...

取消回复欢迎 发表评论: