赞
踩
上一篇文章,我们讨论了以下内容:
org
指令来完成寻找 Boot Sector 前两个字节内容的任务代码仓库。
今天的目标,我们要告别 16-bit Real Mode,进入 32-bit Protected Mode。意味着我们离内核只有一步之遥了。
这篇文章中,我们即将要学习
我将前两篇文章及今天这篇文章中涉及的代码,整理了一下,列在 这里。
我们开始吧。
硬盘数据的读取,需要在寄存器设置一系列的参数。有一些参数,有关硬盘的工作方式。所以,我们先来简单了解一下硬盘的必要知识。
硬盘由盘片,和读写磁头组成。为了扩大容量,几张盘片,重叠在一起,由磁头来读写数据。由于盘片是有两面的,因此,一张盘片就有两个磁头,分别负责读取/写入该磁盘面上的数据。
引用书上的图片为例。
这是典型的机械硬盘的内部构造。
硬盘的盘片,是可以磁化的,一个比特的数据,磁化即为 1,非磁化即为 0。数据在盘片高速旋转时,由读写头读取和写入。
推荐大家看一下希捷的关于硬盘的视频。里面提到了 1个 bit 的实际物理大小,是 84 纳米(nanometer,也称毫微米)。
硬盘的物理构造,由专门的名词来描述。盘片叠加在一起,盘片上的每一圈,我们称之为磁道(Track),因为硬盘由多个盘片叠加组成,这些磁道,构成如一个圆柱体,我们称之为柱面(Cylinder)。读写装置,我们称之为磁头(Head)。每个盘面,被逻辑分成多个扇区(Sector),每个扇区通常是 512 个字节。
那么,在这样的物理构造下,我们要读取特定一个位置上的数据,就需要 3 个参数来确定。哪一个柱面(磁道),哪一个磁头,哪一个扇区。
这个 3D 坐标被称为 Cylinder-Head-Sector (CHS)地址。更多关于 CHS 的信息,可以阅读这篇 Wiki。
应用书上的图片作为例子。将 CHS 地址视觉化。
如同之前的文章中,我们要调用显示设备,在屏幕上输出字符,就要在 ah
中写入 0x0e
,并触发中断。硬盘读写,也需要我们将相应的指令写入到寄存器,来告诉 BIOS 我们要读取的数据的位置和长度。
硬盘与与 CPU 有多种不同的连接总线,如 ATA/IDE,SATA,SCSI,USB。BIOS 为这些常见的设备提供了统一的指令。
我们将这些指令(包括寄存器中的参数和中断)列举在下面:
读取操作完成之后,CPU 会设置几个返回值到寄存器,说明读取操作是否成功,我们可以做错误处理:
更多关于每个参数的索引起始,以及索引范围的信息,可以阅读这篇 Wiki。
记得上一篇中,我们给出的 boot sector 在内存中的位置,我们将选取 0x8000
作为加载我们磁盘数据的内存地址。它在我们的 boot sector 之后的空闲空间里。
我们将读取两个扇区的测试数据。
来看代码,磁盘读取的参数设置,在 read_from_disk.asm 中。
read_from_disk.asm
read_from_disk: pusha checking push bx ; 之后打印出测试数据被加载到内存的位置 push dx ; 各个参数 mov ah, 0x02 ; 读取模式 mov al, dh ; 读取两个扇区的数据 (dead...beef...) mov ch, 0x00 ; 从第 1 柱面开始读 mov cl, 0x02 ; 从第 2 扇区开始读 (第 1 个扇区是 boot sector) mov dh, 0x00 ; 从第 1 磁头开始读 ;mov dl, 0x00 ; 从第1 块存储介质开始读 int 0x013 ; 触发中断 mov bx, READ_START call print call print_nl jc op_error ; 如果操作失败, CF(Carry Bit) 寄存器会被设置为 1, 如果 CF 寄存器被设置为 1,jc 就会跳转 pop dx cmp al, dh jne read_error mov bx, READ_COMPLETE call print ; 打印出测试数据被加载到哪里 pop bx mov dx, bx call print_hex call print_nl popa ret op_error: ; 错误信息 mov bx, OP_ERROR call print call print_nl ; 如果有错误发生,我们打印出错误码信息 mov dh, ah call print_hex ; just hang the cpu on error jmp disk_loop read_error: mov bx, READ_ERROR call print jmp disk_loop disk_loop: jmp $ READ_START: db "Reading start...", 0 READ_COMPLETE: db "Reading complete, data loaded to ", 0 OP_ERROR: db "Disk read error...", 0 READ_ERROR: db "Incorrect number of sectors read...", 0
print_hex.asm
print_hex: pusha mov cx, 0 ; our indbx variable ; Strategy: get the last char of 'dx', then convert to ASCII ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N. ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40 ; Then, move the ASCII byte to the correct position on the resulting string hex_loop: cmp cx, 4 ; loop 4 times je end ; 1. convert last char of 'dx' to ascii mov ax, dx ; we will use 'ax' as our working register and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros add al, 0x30 ; add 0x30 to N to convert it to ASCII "N" cmp al, 0x39 ; if > 9, add extra 7 to represent 'A' to 'F' jle step2 add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7 step2: ; 2. get the correct position of the string to place our ASCII char ; bx <- base address + string length - indbx of char mov bx, HEX_OUT + 5 ; base + length, starts last last char of HEX_OUT sub bx, cx ; our indbx variable mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx' ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234 ; increment indbx and loop add cx, 1 jmp hex_loop end: ; prepare the parameter and call the function ; remember that print receives parameters in 'bx' mov bx, HEX_OUT call print popa ret HEX_OUT: db '0x0000',0 ; reserve memory for our new string
print.asm
print: pusha start: mov al, [bx] ; 'bx' is the base address for the string cmp al, 0 je done mov ah, 0x0e int 0x10 ; 'al' already contains the char add bx, 1 ; print next char jmp start done: popa ret ; print new line print_nl: pusha mov ah, 0x0e mov al, 0x0a ; newline char int 0x10 mov al, 0x0d ; carriage return int 0x10 popa ret
boot_sect_main.asm
[org 0x7c00] ; 我们将利用栈保存一些寄存器的值,所以将栈的内存位置设置在空闲区域 mov ax, 0x8000 mov bp, ax mov sp, bp ; 测试数据会被加载到 [ES:BX] => 0x8000 mov ax, 0x0 mov es, ax mov bx, 0x9000 ; 读取两个扇区的数据,dh 这里用来传递 0x2 这个数据 mov dh, 0x2 ; 开始读取 call read_from_disk ; 打印第 2 扇区第一个字 => 0xdead mov dx, [es:bx] call print_hex ; 打印第 3 个扇区第一个字 => 0xbeef mov dx, [es:bx + 512] call print_hex jmp $ %include "print.asm" %include "print_hex.asm" %include "read_from_disk.asm" times 510 - ($ - $$) db 0x0 dw 0xaa55 ; 写入 512 个字节到第 2 扇区(第 1 扇区是 boot sector) times 256 dw 0xdead ; 写入 512 个字节到第 3 扇区 times 256 dw 0xbeef
在 boot_sect_main.asm 中,我们分别读取了第二和第三个扇区的前两个字节,可以看到结果如下。
我们可以用 od 命令查看 bin 文件中的内容,看到紧接着我们的 boot sector,写入了我们的测试数据。
大家可以尝试修改代码,加载测试数据到不同的内存地址,观察程序的变化。
现在,我们已经具备加载内核的能力。下面,我们告别 16-bit Real Mode,开启 32-bit Protected Mode。
16-bit Real Mode 以下称 16 位模式, 32-Bit Protected Mode 以下称 32 位模式。
首先,经过前两篇文章的学习,我们已经很熟悉 16 位模式了。现在,我们要思考一下为什么还需要切换到 32 位模式,它和 16 位模式有什么区别。
接着,我们要学习 32 位模式中最重要的概念,全局描述符(Global Descriptor Table)。
最后,我们学习怎么在汇编中定义 GDT,并切换到 32 位模式。
在切换操作之前,我们必须先了解一下 32 位模式。
Protected Mode,保护模式,是自 80286 以来的现代 CPU 的主要工作模式。
32 位模式加入了虚拟内存的概念,并且加强了内存读写保护,提供了通过 Rings 限制可用指令的能力。
总而言之,32 位模式向着更加高级,更加安全的方向发展,为现代操作系统提供了一个更好的运行环境。
我们从 16 位模式切换到 32 位模式,有两个最主要的目的。
我们不能容忍那可怜的 1MB 内存,不能容忍我们程序的内存毫无保护,所以,32 位模式势在必行。
到了 32 位模式之后所发生的变化总结如下:
e
,意思是 extended
,例如:mov eax, 0x80808080在继续下面的内容之前,我们必须先做一点代码上的调整。能够打印字符对于程序的调试是很重要的,所以,我们现在要将 16 位模式下的打印字符的代码,调整到 32 位可用。在调整代码之前,我们需要先对 32 位模式下的底层调用有所了解,才能顺利在 32 位模式下打印字符。
BIOS 下的中断和系统调用,是专门为 16 位模式设计的,因此,在 32 位模式下不可用。书中提到,有办法可以暂时切换回 16 位模式去使用 BIOS 的系统调用,但是这没有意义,十分复杂,也违背我们要切换到 32 位的初衷。
那么,我们必须丢弃 BIOS,重新调整我们的思路去适应 32 位模式。
这里要说明的是 32 位模式下,关于显示设备调用需要理解的一些概念。
计算机的外围设备,分为 Memory-Mapped(内存映射) 和 Port-Mapped(端口映射)两种。我们这里讨论的显示设备,是 Memory-Mapped Device(暂译为内存映射设备)的一种。
计算机外围设备,都以某种方式连接至 CPU,他们都与 CPU 有输入输出的操作。因此,外围设备的输入输出操作被统称为 Memory/Port-Mapped I/O。
内存映射设备使用同一内存空间来记录数据内存地址与设备内存地址。这是我总结的,原文是 “Memory-mapped I/O uses the same address space to address both memory and I/O devices.”。可以这样理解, CPU 访问设备内存上的数据时,其实就是在访问设备本身。有这样特征的设备,就被称为 Memory-Mapped Device。
例如显示设备,我们只需要往设备内存中写入数据,就可以在屏幕上展示这些数据。
所以,接下来要讲到的在 32 位模式下打印字符,我们只需要 CPU 去访问特定的显示设备内存(Video Memory),即可完成显示设备调用,打印字符到屏幕。
显示设备有两种模式可以设置:
在计算机启动的时候,无论计算机上有多么高级的显示设备(RTX 2080Ti
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。