linux0.11源码分析第一弹——bootset.s内容

🚀前言

    本系列主要参考的《linux源码趣读》,也结合之前《一个64位操作系统的设计与实现》的内容结合起来进行整理成本系列博客。在这一篇博客对应的是《linux源码趣读》第一~四回

目录

  • 🚀前言
  • 🏆启动后的第一步
    • 📃启动区
    • 📃为什么是0x07c00
    • 📃设置寄存器基地址
    • 📃设置其他寄存器
  • 🏆复制其他文件进内存
    • 📃整体流程
    • 📃一些其他细节
  • 🎯boot文件总结
  • 📖参考资料

🏆启动后的第一步

📃启动区

    操作系统启动后,BIOS将硬盘中启动区(0道0盘1磁道,以0x55aa结尾)的512字节复制到内存的0x07c00h处,并跳转至对应位置运行代码。至于为什么是这个位置,只能说是最初的BIOS定义的,记住便好。流程便如下图所示。
在这里插入图片描述

📃为什么是0x07c00

    这个问题其实包含了两个问题。
    第一个问题,为什么是0x7c00,而不是其他位置。这个问题在大疆面试我的时候就被问到了,当时我说的是这是硬件厂商之间的规定,启动区为这个位置,是约定俗成的位置,《一个64位操作系统的设计与实现》里面也是说为什么是0x7c00只有当年的BIOS工程师才知道。但显然面试官不满意我的回答,又问了我一次,最后不出意外的和大疆擦肩而过了。但是我后来还是去找了其他资料,抛开说约定俗称的,还是被我找到了真实的解释,下面正片开始:

    这个就是一个历史遗留问题,具体可以看参考资料的第三个。简单来说就是IBM早期电脑5150采用了8088芯片,而芯片本身需要占用0x0000~0x03FF用来保存各种中断处理程序的储存位置。为了把尽量多的连续内存留给操作系统,主引导记录(MBR)就被放到了内存地址的尾部。而搭载的系统为86-DOS,该操作系统最少要32KB,即0x0000~0x7FFF。因此结合前面的,加上MBR本身也要产生数据,预留512字节,一个扇区也是512字节,因此开始位置就变成了

0x7FFF - 512 - 512 = 0x7c00

    后续的操作系统为了兼容,就都采用了0x7c00作为启动地址,而现在操作系统的内存分区大致如下划分:

在这里插入图片描述

    第二个问题,明明是0x7c00,为什么变成了0x07c00呢?别小看前面多了个0,实际上是多了四位!原本只有16位寻址线,因此是0x7c00,后来x86 为了让自己在 16 位这个实模式下能访问到 20 位的地址线这个历史因素,段基地址要左移4位,那么0x07c00左移四位就正好会变成0x7c00。因此说最后是0x07c00这个内存位置。

📃设置寄存器基地址

设置ds段寄存器
    这是第一次设置ds寄存器,ds寄存器表示数据段,linux0.11中的代码如下所示:

BOOTSEG  = 0x07c0			; original address of boot-sector
start:mov	ax,#BOOTSEGmov	ds,ax

    以上这段是先将0x07c0放入ax寄存器,再将ax寄存器的值写入ds寄存器。为什么需要用一个ax寄存器作为中转,而不是直接写入ds寄存器呢?这是因为在8086 CPU架构的限制下,不能将立即数(直接给出的数值)写入段寄存器中(如ds,cs,es,ss等),因此就必须通过一个中转,这个中转就是ax寄存器。

复制到0x9000

    这一步我理解的作用是保护0x7C00位置,防止后续加载代码进行覆盖,因此将第一个磁盘的内容从0x7c00处复制到0x9000处,并将后续磁盘的内容依次复制到后面。linux0.11中的 实现源码如下:

INITSEG  = 0x9000			; we move boot here - out of the way
mov	ax,#INITSEG
mov	es,ax
mov	cx,#256
sub	si,si
sub	di,di
rep
movw
jmpi	go,INITSEG

    这段代码中同样是通过ax寄存器设置了es寄存器,同时清空了si(源地址)与di(目的地址)。rep指令表示重复执行后面的指令,后面的指令是movw,表示复制一个字(16位,两个字节),重复次数根据cx寄存器而定,cx寄存器为256,因此一共复制了512个字节。复制的位置是从ds:sies:di 也就是从0x07c00复制到0x9000位置。现在内存中是如下所示:

在这里插入图片描述

📃设置其他寄存器

    接下来还需要设置别的段寄存器,包括ds,es,ss。
    ds是数据段,表示如何访问数据;
    es是附加段,可先不管;
    ss是堆栈段,结合sp堆栈指针访问栈;
    cs是代码段,结合ip指针访问代码。

	jmpi	go,INITSEG
go:	mov	ax,csmov	ds,axmov	es,ax
; put stack at 0x9ff00.mov	ss,axmov	sp,#0xFF00		; arbitrary value >>512

    在上面复制完成之后,执行jmpi指令进行跳转,跳转的位置是:0x9000:go 而这个jmpi指令等同于

cs = 0x9000
ip = go

    因为jmpi后,cs指针已经被置为0x9000,因此后面的mov中,ds,es,ss均被置为了0x9000。至于为什么ds是数据段,cs是代码段,ss是堆栈段,但是指向同一个地址呢,这就不得不提到一个新概念了,这里刚上电还处于实模式,所有物理地址都可以被访问,因此暂时不会对这三个的内存地址做功能上的区分。
    ss指针被置为0x9000,同时sp指针被置为了0xff00。因此栈顶指针此时就是 ss:sp = 0x9ff00

🏆复制其他文件进内存

📃整体流程

    上面我们将第一个磁盘512个字节复制进了内存空间,接下来就需要将剩下的磁盘也复制进内存空间,源码如下:

load_setup:mov	dx,#0x0000		; drive 0, head 0mov	cx,#0x0002		; sector 2, track 0mov	bx,#0x0200		; address = 512, in INITSEGmov	ax,#0x0200+4	; service 2, nr of sectorsint	0x13			; read itjnc	ok_load_setup		; ok - continuemov	dx,#0x0000mov	ax,#0x0000		; reset the disketteint	0x13jmp	load_setup

    首先是设置dx,cx,bx,ax的参数,然后使用int指令调用BIOS的0x13指令,该指令对应的位置是BIOS预留的中断处理程序入口地址,会为我们处理对应的中断程序。放在此处就是从第二个扇区开始,将数据加载到0x90200处,共4个扇区。

    这之后,我们就要加载剩下的240个扇区进内存,至于这4个扇区,240个扇区各存的什么,这之后再说,代码里面实现是这样的(去除掉其他代码之后):

mov ax, #0x1000
mov es, ax			; segment of 0x10000
call read_itjmpi 0, 0x9020

    这段代码的作用就是将剩下的240个扇区加载到0x10000处。至于读取的逻辑就和上面读取的那四个扇区是一样的:设置ax,bx,cx,dx的参数,然后调用0x13中断。

    最后会跳转到0x9020位置,即第二个扇区的位置,第二个扇区开始就是setup.s的内容了。最终整个内存如下图所示

在这里插入图片描述

📃一些其他细节

    下面是read_it函数的细节,用来读取240个扇区的

SETUPLEN = 4				; nr of setup-sectorssread:	.word 1+SETUPLEN	; sectors read of current track
head:	.word 0			; current head
track:	.word 0			; current trackread_it:mov ax,es          ; 将ES寄存器的值移动到AX寄存器test ax,#0x0fff     ; 测试AX的低12位是否为0(检查ES是否在64KB边界上)
die:	jne die          ; 如果不是,跳转到标签die,形成无限循环xor bx,bx          ; 将BX寄存器清零,用作段内起始地址
rp_read:mov ax,es          ; 再次将ES寄存器的值移动到AX寄存器cmp ax,#ENDSEG      ; 比较AX和ENDSEG,检查是否已经读取了所有数据jb ok1_read        ; 如果AX小于ENDSEG,跳转到ok1_readret                 ; 如果已经读取完毕,返回
ok1_read:seg cs             ; 将下一段代码的段寄存器设置为csmov ax,sectors      ; 将sectors的值(在最后)移动到AX寄存器sub ax,sread        ; 从AX中减去sread的值,计算剩余需要读取的扇区数mov cx,ax          ; 将计算结果移动到CX寄存器shl cx,#9          ; 将CX左移9位,转换为字节偏移量add cx,bx          ; 将BX(段内起始地址)加到CX(偏移量)jnc ok2_read       ; 如果没有发生进位,跳转到ok2_readje ok2_read        ; 如果CX等于0xFFFF,也跳转到ok2_readxor ax,ax          ; 清零AX寄存器sub ax,bx          ; 计算BX的补码shr ax,#9          ; 将AX右移9位,转换回扇区数
ok2_read:call read_track     ; 调用read_track函数读取磁盘扇区mov cx,ax          ; 将返回的扇区数移动到CX寄存器add ax,sread       ; 将sread的值加到AX(已读取扇区数)seg cs             ; 再次将代码段寄存器的值移动到ES寄存器cmp ax,sectors     ; 比较AX和sectors,检查是否已经读取了所有扇区jne ok3_read       ; 如果没有,跳转到ok3_readmov ax,#1          ; 设置AX为1sub ax,head        ; 从1减去head的值,检查是否需要更新trackjne ok4_read       ; 如果不相等,跳转到ok4_readinc track          ; 如果相等,增加track的值
ok4_read:mov head,ax        ; 更新head的值xor ax,ax          ; 清零AX寄存器
ok3_read:mov sread,ax       ; 更新sread的值shl cx,#9          ; 将CX(扇区数)左移9位,转换为字节偏移量add bx,cx          ; 将偏移量加到BX(段内起始地址)jnc rp_read        ; 如果没有发生进位,跳转到rp_read继续读取mov ax,es          ; 将ES寄存器的值移动到AX寄存器add ax,#0x1000     ; 增加AX的值,移动到下一个64KB段mov es,ax          ; 更新ES寄存器的值xor bx,bx          ; 清零BX寄存器,重置段内起始地址jmp rp_read        ; 跳转到rp_read继续读取read_track:push ax            ; 保存AX寄存器的值push bx            ; 保存BX寄存器的值push cx            ; 保存CX寄存器的值push dx            ; 保存DX寄存器的值mov dx,track       ; 将track的值移动到DX寄存器mov cx,sread       ; 将sread的值移动到CX寄存器inc cx             ; 增加CX的值,准备读取下一个扇区mov ch,dl          ; 将DX的低8位(即CL)移动到CHmov dx,head        ; 将head的值移动到DX寄存器mov dh,dl          ; 将DX的低8位(即DL)移动到DHmov dl,#0          ; 清零DL寄存器and dx,#0x0100     ; 取DX的第8位,设置为0,其他位清零mov ah,#2          ; 设置AH为2,准备读取扇区int 0x13           ; 调用BIOS中断0x13,执行读取操作jc bad_rt          ; 如果读取失败,跳转到bad_rtpop dx             ; 恢复DX寄存器的值pop cx             ; 恢复CX寄存器的值pop bx             ; 恢复BX寄存器的值pop ax             ; 恢复AX寄存器的值ret                 ; 返回到调用read_track的地方
bad_rt:mov ax,#0          ; 设置AX为0mov dx,#0          ; 设置DX为0int 0x13           ; 再次调用BIOS中断0x13,执行读取操作pop dx             ; 恢复DX寄存器的值pop cx             ; 恢复CX寄存器的值pop bx             ; 恢复BX寄存器的值pop ax             ; 恢复AX寄存器的值jmp read_track     ; 跳转回read_track,尝试重新读取sectors:.word 0

   

🎯boot文件总结

    整个boot文件其实只做了两件事,一件事是设置各个段寄存器的地址,第二个就是把磁盘加载进内存中,最开始是将自己放入0x7c00位置,然后又复制了自己到0x9000。之后把后续四个磁盘中的setup编译后的文件放入到0x9020处。最后将剩下的240个扇区放入到0x10000处。然后远跳到0x9020处准备执行第二个扇区,即setup中的部分。

📖参考资料

[1] linux源码趣读
[2] 一个64位操作系统的设计与实现
[3] 为什么主引导记录的内存地址是0x7C00?
[4] 为什么 x86 操作系统从 0x7c00 处开始

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/492158.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenIPC开源FPV之Adaptive-Link天空端代码解析

OpenIPC开源FPV之Adaptive-Link天空端代码解析 1. 源由2. 框架代码2.1 消息机制2.2 超时机制 3. 报文处理3.1 special报文3.2 普通报文 4. 工作流程4.1 Profile 竞选4.2 Profile 研判4.2.1 回退策略4.2.2 保持策略 4.3 Profile 应用 5. 总结6. 参考资料7. 补充资料7.1 RSSI 和 …

【译】仅有 Text2SQL 是不够的: 用 TAG 统一人工智能和数据库

原文地址:Text2SQL is Not Enough: Unifying AI and Databases with TAG 摘要 通过数据库为自然语言问题提供服务的人工智能系统有望释放出巨大的价值。此类系统可让用户利用语言模型(LM)的强大推理和知识能力,以及数据管理系统…

【自动驾驶】单目摄像头实现自动驾驶3D目标检测

🍑个人主页:Jupiter. 🚀 所属专栏:传知代码 欢迎大家点赞收藏评论😊 目录 概述算法介绍演示效果图像推理视频推理 核心代码算法处理过程使用方式环境搭建下载权重文件pytorch 推理(自动选择CPU或GPU&#x…

什么是Modbus协议网关?

在工业自动化领域,设备间的通信与数据交换是实现高效、智能控制的关键。Modbus协议作为一种广泛应用的通信协议,自1971年由Modicon公司首次推出以来,便以其标准、开放、支持多种电气接口等特点,在工业控制系统中占据了重要地位。然…

《云原生安全攻防》-- K8s安全框架:认证、鉴权与准入控制

从本节课程开始,我们将来介绍K8s安全框架,这是保障K8s集群安全比较关键的安全机制。接下来,让我们一起来探索K8s安全框架的运行机制。 在这个课程中,我们将学习以下内容: K8s安全框架:由认证、鉴权和准入控…

如何利用Python爬虫获得1688商品详情

在这个信息爆炸的时代,数据就像是一块块美味的奶酪,而爬虫就是我们手中的瑞士军刀。今天,我要带你一起潜入1688这个巨大的奶酪洞穴,用Python爬虫捞起那些香气四溢的商品详情。别担心,我们的工具箱里有各种各样的工具&a…

CAN配置---波特率中断引脚等---autochips-AC7811-ARM-M3内核

1、配置工具 虽然不怎么好用,但比没有强多了。具体看图: 时钟选着 NVIC配置 GPIO配置 2、生成的具体配置信息 NXP的配置工具里面,具体的波特率可以直接显示,这个工具没有,怎么办? 它放到了生成的代码里面…

MySQL的并发控制与MVCC机制深度解析

目录 1. MySQL中的并发问题2. 数据库的隔离级别3. MVCC(多版本并发控制)机制3.1 MVCC的实现原理3.2 Read View详解3.3 当前读与快照读 4. MVCC在不同隔离级别下的工作方式5. MVCC解决幻读问题6. MVCC的优缺点优点:缺点: 7. MVCC在…

网络编程 02:IP 地址,IP 地址的作用、分类,通过 Java 实现 IP 地址的信息获取

一、概述 记录时间 [2024-12-18] 前置文章:网络编程 01:计算机网络概述,网络的作用,网络通信的要素,以及网络通信协议与分层模型 本文讲述网络编程相关知识——IP 地址,包括 IP 地址的作用、分类&#xff…

游戏AI实现-寻路算法(Dijkstra)

戴克斯特拉算法(英语:Dijkstras algorithm),又称迪杰斯特拉算法、Dijkstra算法,是由荷兰计算机科学家艾兹赫尔戴克斯特拉在1956年发现的算法。 算法过程: 1.首先设置开始节点的成本值为0,并将…

【第二节】Git 工作流程、概念及仓库创建

目录 一、Git 工作流程 二、Git 基本概念 2.1 工作区 2.2 暂存区 2.3 版本库 2.4 操作流程 三、Git 仓库创建 3.1 初始化仓库 3.2 克隆仓库 一、Git 工作流程 Git 的工作流程通常包括以下几个步骤: 1. **克隆 Git 资源**:将远程 Git 仓库克隆到…

概率论得学习和整理30: 用EXCEL 描述泊松分布 poisson distribution

目录 1 泊松分布的基本内容 1.1 泊松分布的关键点 1.1.1 属于离散分布 1.1.2 泊松分布的特点:每个子区间内概率相等 , λ就是平均概率 1.2 核心参数 1.3 pmf公式 1.4 期望和方差 2 例1:用EXCEL计算泊松分布的概率 3 比较λ不同值时…

【机器学习】【无监督学习——聚类】从零开始掌握聚类分析:探索数据背后的隐藏模式与应用实例

从零开始掌握聚类分析:探索数据背后的隐藏模式与应用实例 基本概念聚类分类聚类算法的评价指标(1)内部指标轮廓系数(Silhouette Coefficient)DB指数(Davies-Bouldin Index)Dunn指数 &#xff08…

灵活接入第三方接口,解析第三方json数据,返回我们想要的json格式

需求&#xff1a;我想接入任意第三方http 接口&#xff08;暂不考虑鉴权问题&#xff09;、接口返回任意json数据。 1、要求返回的json数据通过我的R< T > 返回。 2、我的R< T > 里面包含参数 data&#xff0c;code&#xff0c;msg&#xff0c;success标识。 3、…

Nginx 在不同操作系统下的安装指南

Nginx 在不同操作系统下的安装指南 一、Linux 系统下 Nginx 的安装 &#xff08;一&#xff09;基于 Ubuntu 系统 更新软件包列表 打开终端&#xff0c;首先执行sudo apt-get update命令。这一步是为了确保系统的软件包列表是最新的&#xff0c;能够获取到最新版本的 Nginx 及…

docker redis 详细教程

1. 拉取镜像 docker pull redis 2. 创建数据存储目录 cd /home/ mkdir redis cd redis mkdir data mkdir log mkdir conf 3.创建容器并且运行 docker run \ -p 6379:6379 \ --name redis \ -v /home/redis/data:/data \ -d redis 参考链接 史上最详细Docker安装Redis &am…

学技术学英文:代码中的锁:悲观锁和乐观锁

本文导读&#xff1a; 1. 举例说明加锁的场景&#xff1a; 多线程并发情况下有资源竞争的时候&#xff0c;如果不加锁&#xff0c;会出现数据错误&#xff0c;举例说明&#xff1a; 业务需求&#xff1a;账户余额>取款金额&#xff0c;才能取钱。 时间线 两人共有账户 …

云计算赋能:TSP 问题求解与创新定价机制的全景剖析

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;编程探索专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年12月18日14点02分 神秘男子影, 秘而不宣藏。 泣意深不见, 男子自持重, 子夜独自沉。 论文源地址&#xff1a; Aspiringco…

二、windows环境下vscode使用wsl教程

本篇文件介绍了在windows系统使用vscode如何连接使用wsl&#xff0c;方便wsl在vscode进行开发。 1、插件安装 双击桌面vscode&#xff0c;按快捷键CtrlShiftX打开插件市场&#xff0c;搜索【WSL】点击安装即可。 2、开启WSL的linux子系统 点击左下方图标【Open a Remote Win…

QScreen在Qt5.15与Qt6.8版本下的区别

简述 QScreen主要用于提供与屏幕相关的信息。它可以获取有关显示设备的分辨率、尺寸、DPI&#xff08;每英寸点数&#xff09;等信息。本文主要是介绍Qt5.15与Qt6环境下&#xff0c;QScreen的差异&#xff0c;以及如何判断高DPI设备。 属性说明 logicalDotsPerInch&#xff1…