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

news2024/12/19 5:41:46

🚀前言

    本系列主要参考的《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,#BOOTSEG
	mov	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,cs
	mov	ds,ax
	mov	es,ax
; put stack at 0x9ff00.
	mov	ss,ax
	mov	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 0
	mov	cx,#0x0002		; sector 2, track 0
	mov	bx,#0x0200		; address = 512, in INITSEG
	mov	ax,#0x0200+4	; service 2, nr of sectors
	int	0x13			; read it
	jnc	ok_load_setup		; ok - continue
	mov	dx,#0x0000
	mov	ax,#0x0000		; reset the diskette
	int	0x13
	jmp	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_it

jmpi 0, 0x9020

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

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

在这里插入图片描述

📃一些其他细节

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

SETUPLEN = 4				; nr of setup-sectors

sread:	.word 1+SETUPLEN	; sectors read of current track
head:	.word 0			; current head
track:	.word 0			; current track

read_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_read
	ret                 ; 如果已经读取完毕,返回
ok1_read:
	seg cs             ; 将下一段代码的段寄存器设置为cs
	mov 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_read
	je ok2_read        ; 如果CX等于0xFFFF,也跳转到ok2_read
	xor 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_read
	mov ax,#1          ; 设置AX为1
	sub ax,head        ; 从1减去head的值,检查是否需要更新track
	jne ok4_read       ; 如果不相等,跳转到ok4_read
	inc 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)移动到CH
	mov dx,head        ; 将head的值移动到DX寄存器
	mov dh,dl          ; 将DX的低8位(即DL)移动到DH
	mov dl,#0          ; 清零DL寄存器
	and dx,#0x0100     ; 取DX的第8位,设置为0,其他位清零
	mov ah,#2          ; 设置AH为2,准备读取扇区
	int 0x13           ; 调用BIOS中断0x13,执行读取操作
	jc bad_rt          ; 如果读取失败,跳转到bad_rt
	pop dx             ; 恢复DX寄存器的值
	pop cx             ; 恢复CX寄存器的值
	pop bx             ; 恢复BX寄存器的值
	pop ax             ; 恢复AX寄存器的值
	ret                 ; 返回到调用read_track的地方
bad_rt:
	mov ax,#0          ; 设置AX为0
	mov dx,#0          ; 设置DX为0
	int 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.coloradmin.cn/o/2261967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式之桥接模式:抽象与实现之间的分离艺术

~犬📰余~ “我欲贱而贵,愚而智,贫而富,可乎? 曰:其唯学乎” 桥接模式概述与角色组成 想象一下你家里的电视遥控器,无论是索尼还是三星的电视机,遥控器的按键功能都差不多&#xff1…

【从零开始入门unity游戏开发之——C#篇17】C#面向对象的封装——类(Class)和对象、成员变量和访问修饰符、成员方法

文章目录 一、类和对象1、什么是类和对象?2、例子说明2.1 例子1:(1) **类的定义:**(2) **创建对象:**(3) **类和对象的关系:** 2.2 例子2:**类的比喻:****对象的比喻:**代码实例&…

在Ubuntu 22.04 LTS中使用PyTorch深度学习框架并调用多GPU时遇到indexSelectLargeIndex相关的断言失败【笔记】

在Ubuntu 22.04 LTS系统中,已安装配置好CUDA 12.4、cuDNN 9.1.1以及PyTorch环境 export CUDA_VISIBLE_DEVICES0,1,2,3,4,5,6,7 在PyTorch深度学习框架训练调用多GPU时,提示 indexSelectLargeIndex: block: [x, 0, 0], thread: [x, 0, 0] Assertion src…

FutureCompletableFuture实战

1. Callable&Future&FutureTask介绍 直接继承Thread或者实现Runnable接口都可以创建线程,但是这两种方法都有一个问题就是:没有返回值,也就是不能获取执行完的结果。因此java1.5就提供了Callable接口来实现这一场景,而Fu…

[论文阅读笔记]-PalmTree: 学习一个用于指令嵌入的汇编语言模型

深度学习已在众多二进制分析任务中展示了其优势,包括函数边界检测、二进制代码搜索、函数原型推理、值集分析等。现有方案忽略了复杂的指令内结构,主要依赖于控制流,其中上下文信息是嘈杂的,并且可能受到编译器优化的影响。为了解…

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB 是一款基于南京沁恒(WCH)推出的高性能、低功耗无线通信芯片CH582F的开发板。以下是该开发板的功能和参数详细介绍: 主要特性 双模蓝牙支持: 支持蓝牙5.0标准&#xff0…

数字IC后端设计实现篇之TSMC 12nm TCD cell(Dummy TCD Cell)应该怎么加?

TSMC 12nm A72项目我们需要按照foundary的要求提前在floorplan阶段加好TCD Cell。这个cell是用来做工艺校准的。这个dummy TCD Cell也可以等后续Calibre 插dummy自动插。但咱们项目要求提前在floorplan阶段就先预先规划好位置。 TSCM12nm 1P9M的metal stack结构图如下图所示。…

《网络对抗技术》Exp9 Web安全基础

实验目标 理解常用网络攻击技术的基本原理。 实验内容 Webgoat实践下相关实验。 实验环境 macOS下Parallels Desktop虚拟机中(网络源均设置为共享网络模式): Kali Linux - 64bit(攻击机,IP为10.211.55.10)…

Chrome 132 版本开发者工具(DevTools)更新内容

Chrome 132 版本开发者工具(DevTools)更新内容 一、使用 Gemini 调试 Network、Source 和 Performance Chrome 131 可以使用 Gemini 调试 CSS,现在可以调试更多模块了 与元素面板中的右键菜单类似,要打开 AI 辅助面板并开始与 …

消息系统之 Kafka

什么是消息系统 消息系统是专用的中间件,负责将数据从一个应用传递到另外一个应用。使应用只需关注于数据,无需关注数据在两个或多个应用间是如何传递的。 消息系统一般基于可靠的消息队列来实现,使用点对点模式或发布订阅模式。数据实时在…

Intel-ECI之Codesys PLC + Ethercat 远端IO + Codesys IDE编程

目录 一、 准备工作 二、安装Codesys 软件 PLC 三、 使用Codesys IDE 编程测试 CODESYS* 是领先的独立于制造商的 IEC 61131-3 自动化软件,适用于工程控制系统。它用于 Intel Edge Controls for Industrial(Intel ECI 或 ECI),…

[2015~2024]SmartMediaKit音视频直播技术演进之路

技术背景 2015年,因应急指挥项目需求,我们实现了RTMP推送音视频采集推送(采集摄像头和麦克风数据)模块,在我们做好了RTMP推送模块后,苦于没有一个满足我们毫秒级延迟诉求的RTMP播放器,于是第一…

Ubuntu24.04 安装 visual studio code

# 导入软件包密钥 wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg# 添加官方库 echo "deb [arch…

docker 搭建自动唤醒UpSnap工具

1、拉取阿里UpSnap镜像 docker pull crpi-k5k93ldwfc7o75ip.cn-hangzhou.personal.cr.aliyuncs.com/upsnap/upsnap:4 2、创建docker-compose.yml文件,进行配置: version: "3" services:upsnap:container_name: upsnapimage: crpi-k5k93ldwf…

已解决:elasticsearch创建索引失败

报错信息 具体报错: org.elasticsearch.ElasticsearchStatusException: Elasticsearch exception [typeillegal_argument_exception, reasonunknown setting [index.mappings.properties.category.analyzer] please check that any required plugins are installed…

PHPstudy中的数据库启动不了

法一 netstat -ano |findstr "3306" 查看占用该端口的进程号 taskkill /f /pid 6720 杀死进程 法二 sc delete mysql

计算机视觉中的特征提取算法

摘要: 本文聚焦于计算机视觉中的特征提取算法,深入探讨尺度不变特征变换(SIFT)算法。详细阐述 SIFT 算法的原理,包括尺度空间构建、关键点检测、方向分配与特征描述子生成等核心步骤。通过 C#、Python 和 C 三种编程语…

Linux USB开发整理和随笔

目录 1 概述 2 硬件原理基础 2.1 USB发展 2.2 USB的拓扑 2.3 硬件接口 2.4 USB总线协议 2.4.1 通信过程 2.4.2 概念关系 2.4.3 管道PIPE 2.4.4 传输 2.4.5 事务 2.4.6 包结构与类型 2.4.6.1 令牌包 2.4.6.2 数据包 2.4.6.3 握手包 2.5 描述符 2.5.1 设备描述符…

从0开始深入理解并发、线程与登台通知机制

1、从0开始深入理解并发、线程与等待通知机制 为什么开发中需要并发编程? 从阿里、美团的岗位JD其实就能看出来,并发编程和性能优化是密切相关的,使用并发编程可以做到: (1)加快响应用户的时间 比如我们经常用的迅雷下载,都喜欢…

简易记事本项目(基于Vue 3 + Element Plus + SSM 个人事件管理系统)

项目简介 点滴365是一个基于 Vue 3 Element Plus SSM 开发的个人事件管理系统,旨在帮助用户高效管理 个人日程 和 待办事项。系统支持日记撰写、待办事项管理、数据统计分析、图片上传、定时提醒、实时天气等功能,让用户可以更好地记录生活点滴、规划工作任务。 核心技术栈…