从头开始编写操作系统(10) 第9章:开启A20

来源:互联网 发布:汽车竞猜源码 编辑:程序博客网 时间:2024/06/06 19:02

译自:http://www.brokenthorn.com/Resources/OSDe9.html

9章:开启A20

Mike, 2008

本系列文章旨在向您展示并说明如何从头开发一个操作系统。

介绍

欢迎! :)

在前一章里,我们了解到如何将处理器切换到32位模式。我们也了解到如何访问最高4 GB的内存。这很好,但,怎么做呢?

PC启动时在实模式,限制使用16 位寄存器。并且,由于16 位的段地址。这限制了你在实模式里可以访问的内存数量,因此我们还不能访问超过1GB的内存。甚至我们不能跨过1MB界限!怎么做呢?我们需要打开第20条地址线。这需要直接硬件编程,所以我们也需要了解关于硬件编程的问题。

这是我们要讨论的:

  • 直接为硬件编程 - 理论
  • 直接为硬件编程和控制器
  • 键盘控制器编程——基础
  • 开启A20

为了使用像C语言这样的高级语言,访问超过1MB的内存空间是必须的,因此打开A20 (20条地址线)是很重要的!

注意:现在我们不能访问超过1MB的地址!否则会产生三重错误。

因为我们要讨论直接为硬件编程,所以这章要比前面的章节复杂些。别担心,在我们开发内核、设备驱动时会有更多的硬件编程经历。

准备好了吗?

准备

对于那些一直读到这里的人来说,我曾提醒过你操作系统开发到底有多难。但我们还真的没有见到真正困难的地方。这里所以的感念都是最基本的,虽然都更近了一步。但是,事情只有会变得越来越复杂。

每个控制器都需要以特定的方式设定程序,以使其正确工作。例如, 要向硬盘写数据(或读数据),你需要先确定这是一台IDE设备还是SCSI设备。然后要知道驱动器号。以及要用哪个IDE控制器或是SCSI控制器等等。这些控制器都是不一样的。

再复杂些,一个“扇区”并不总是512字节,这样“读写一个扇区”也就变得不一样了。

后面还有内存的管理,碎片,分页,虚地址空间和内存管理单元 (MMU) 等等等等。

读写驱动器又和操作其他设备不同,这在引导这一层更是如此。文件系统也会随着介质的不同而不同,所以使用FAT12可以在软盘引导系统,而不能在一个CDFS 文件系统的CD ROM上工作。通过抽象对硬件特殊的代码(底层代码),我们可以使多数的代码对多数设备有效。

当我们说"将一个文件写到硬盘",我们不会定义“文件”是什么,因为我们不需要,我们不需要了解要操作的控制器,甚至也不关心写到的位置。这就是抽象很重要的原因!

这里的内容对于保护模式很重要,当然这些在实模式下也会工作的很好。要记住保护模式的规则:

  • 没有中断!不要使用中断否则就是一个三重错误

...因此,你必须做你自己的。

内核调试

调试是艺术。它提供了一种方式来跟踪问题,以便在问变得严重前修复错误。内核调试要调试内核层(环0)的程序,这从来都不是一个简单的工作。

高级语言的调试器

CC++语言的调试器,都提供了在运行时显示变量名、函数名、值和位置的功能,但是,问题是在我们的程序里还没有任何符号名。我们还在二进制这一层上。

这意味着我们得找一个直接显示内存的调试器。Bochs就有这样一个。

Bochs调试器

Bochs有个调试器叫作bochsdbg.exe。当你运行它的时候你会见到一个和Bochs.exe一样的启动界面,加载配置文件,开始模拟器。

Bochs 调试器和显示窗户就出现了,你会看到下面的显示:

[0x000ffff0] f000:fff0 (unk. ctxt): jmp f000:e05b         ; ea5be000f0
< bochs:1> _

2行,bochs告诉你正在设置的命令序号(这里是第1条命令,所以显示是1)。你可以在这里输入命令。

1行很重要。它告诉你当前指令, 绝对地址, 和段:偏移地址。也会显示机器语言的操作码 (Opcode)

HELP命令

help 命令给出一个有效命令的列表。

BREAK命令

b (break) 命令允许你在某个内存地址设置断点。例如,如果要调试我们的操作系统,我们要在引导(07c00:0)的时候开始调试。因此,我们要在0x7c00:0设置断点,并且继续执行直到到达断点:


                // BIOS 在 0xea5be000f0
    [0x000ffff0] f000:fff0 (unk. ctxt): jmp f000:e05b
        < bochs:1> b 0x7c00                     // 在 0x7c00:0设置断点
    < bochs:2> c                            //继续执行
    < 0> Breakpoint 1, 0x7c00 in ?? < >     //我们设置的断点到了
        Next at t=834339
                                //这是引导出现的第1条指令
        < 0> [0x00007c00] 0000:7c00 (unk. ctxt): jmp 7cb5         ; e9b200

 

上面的信息告诉我们,在我们的引导程序了的main()函数的地址是0x7cb5。这是因为在这条跳转指令和main()函数之间是OEM参数块。

我们知道stage2被引导加载器加载到0x500,我们再设置断点:

 < bochs:3> b 0x500
< bochs:4> c
< 0> Breakpoint 2, 0x500 in ?? < >
Next at t=934606
<0> [0x000000500] 0050:0000 (ink. ctxt): jmp 00a0 ; e99d00
< bochs:5> _

现在我们在stag2开始的地方了,这样我们就能一边调试一边参考我们的汇编文件了!酷!最好的是,你可以在显示窗口看到系统的输出正在动态更新。

单步

s (Single Step) 命令用于一次执行一条指令:

    < bochs:6> s
    Next at t=934607
    <0> [0x0000005a0] 0050:00a0 (ink. ctxt): cli             ; fa
    < bochs:7> s
    Next at t=934608
    <0> [0x0000005a1] 0050:00a1 (ink. ctxt): xor AX, AX      ; 31c0
    < bochs:8> _

dump_cpu

这条命令显示寄存器的当前值,标志寄存器,通用寄存器,测试、调试、控制和段寄存器。GDTR, IDTR, LDTR, TR,EIP也在其中。

print_stack

这条指令显示当前栈的值,当我们跟着栈时,这条命令很常用。

总结

有更多的命令没有介绍。但是上面的是最有用的,了解如何使用调试器是很重要的,尤其是像我们这样在早期工作的时候。

直接为硬件编程 - 理论

这里是系统开发中比较困难的地方。

"直接为硬件编程" 简单来说就是和直接和硬件交流。因为芯片是(在一定程度上)可编程的,我们可以控制他们。

第7,我们很详细的了解了系统是如何工作的。我们也讨论了软件端口如何工作,端口映射,INOUT 指令,,我也给出一张很大的表来说明在x86体系下的一般端口映射。

无论处理器接收到IN还是OUT 指令, 都会设置控制总线的I/O访问位。因为系统总线连接着内存控制器I/O控制器,控制器会监听特定的地址及控制总线。如果I/O访问为1 (电流通过其上)I/O控制器会处理这个地址.

然后I/O控制器会将这个端口地址发给所有连在其上的其他设备,并且等待一个返回的信号 (如果某个设备返回了信号,这表示这个端口地址属于这个设备,那么把数据交给这个设备就好了)。如果没有得到回复,I/O控制器忽略这个IN/OUT指令。

这正是端口映射所做的工作 (参考第7章以获取详细信息)

一个控制器芯片可能会映射到一个端口地址范围。端口地址由BIOS POSTBIOS加载执行之前分配。为什么呢?大多数的设备需要不同类型的信息。一些端口代表"寄存器",而其他的这代表"数据"或是"准备好"端口。这不太好,我知道,而且还会更糟。在不同的系统,端口地址的变动很大,因为x86要向后兼容,基本的设备 (键盘、鼠标等)一般使用相同的地址,而更多复杂的设备不会。

直接为硬件编程和控制器

为了更好的理解事情是怎么做的,我们看看控制器,毕竟我们要讨论很多关于他们的事儿——尤其是在保护模式.

很多PC都配置了Intel 8042 微控制器芯片。这个控制器芯片要么嵌入到一个集成电路(IC)里,要么直接安装到主板上。它一般连在南桥上。

这个控制器通过一个连接到你的键盘的芯片和键盘上的控制器芯片交流。

当你在键盘上按下一个键时, 就按下了键盘下面的一个橡胶块,橡胶块下面可以导电,当按下去的时候就联通了键盘上的电路,电流可以在上面流过,每个键对应一对电线,当信号改变时(取决于键是否按下),会产生一个编码(通过一系列电线),这个编码设置了键盘里的控制器,也就通过连接到计算机的硬件端口设置了计算机里面的那个控制器,这是通过一系列高低电脉冲实现的,根据时钟周期,每个脉冲信号被转换为位模式中的一位。

在主板上,这些通过南桥上的电信号到达8042控制器,控制器将上面的编码解码得到扫描码,并保存在内部寄存器里,就是我们说的缓冲区,这个内部的寄存器是EEPROM芯片,可以通过电擦洗,重写入我们需要的如何数据。

引导的时候,BIOS POST为每个设备(通过I/O控制器)分配端口地址。这是通过查询设备完成的。一般的,BIOS POST会将这个内部寄存器分配到端口地址 0x60. 这表示, 当我们访问端口0x60, 我们实际上是从这个内部寄存器读数据。

你知道关于端口映射和IN/ OUT 指令的其他内容,我们从这个寄存器读数据试试:

in al, 0x60; 从8042 微控制器输入寄存器读数据

你可能会猜到, 8042微控制器是键盘控制器. 通过与这个芯片的一系列寄存器交流,我们可以读到键盘的输入,映射扫描码,以及其他的一系列操作:比如开启A20.

你可能会好奇为什么需要和键盘控制器交流,来开启A20。在后面你会看到。

A20 - 理论

最后我们讨论如何开启A20,我知道,我知道,上面的内容和A20没有太多直接的关系,但是我还是想要在开启A20前,包含直接为硬件编程的基本内容,因为开启A20需要,其他控制器编程也需要。

开启A20需要为键盘控制器编程,因此,我们只是讨论为键盘控制器编程而不是为键盘编程。

一点历史

IBM设计 IBM PC AT 时,他们使用了新的Intel 80286处理器, 而这个处理器与前面的x86处理器在实模式下不兼容。什么问题呢?旧的x86 处理器不使用A20A31的地址线,也没有这么多条地址线。所有的超过1 MB的地址被回卷了。而在80286的地址空间里,需要32条地址线,也就是说全部的32条地址线都可以被访问,我们就有了回卷问题。

为了解决这个问题Intel在处理器和系统总线之间加了一个逻辑门以控制第20条地址线,这个逻辑门就叫做Gate A20。对于旧的程序可以关闭A20,对于新的则打开A20

在引导的时候,BIOS会在计算和测试内存时打开A20,而在将控制权交给操作系统之前再关上。

有很多种方法来开启A20. A20打开之后,我们就可以访问全部的32条地址总线, 这样通过使用32为的地址, 最大到0xFFFFFFFF - 4 GB内存.

Gate A20是一个逻辑或门,原来连在8042控制器(键盘控制器) P21线上。这个逻辑门对应数据输出端口的1,我们可以发送命令来接收数据或是修改它。通过检查这一位,并向输出线上些数据,我们就能控制这个逻辑门,以开启/关闭A20。我们可以直接或是间接完成这个工作。下一节里会详细介绍。

在引导时,BIOS在测试内存时会开启A20而后为了向旧的计算机兼容再关闭上,因此默认的对于我们的操作系统A20是关上的。

有多种方法来重新打开A20,这要看主板的配置。我会涉及多种开启A20的常见方法。

下面是详细内容;)

A20开启

有很多方法开启A2。如果你只是写一个只有你用的系统,你要做的只是找出一种你能用的就成,如果要求可移植性的话,就要多用几种方法了。

方法 1: 系统控制端口 A

这个很快,但是可移植不好。

Sone 系统包括 MCA EISA使用系统控制端口I/O 0x92控制A20。制造商对于端口0x92有非常好的实现。下面是常用的几位:

  • Bit 0设成1来快速重设 (用于返回实模式)
  • Bit 1 - 0: 关闭 A20; 1:开启A20
  • Bit 2制造商定义
  • Bit 3电源口令(CMOS bytes 0x38-0x3f0x36-0x3f). 0: 可访问, 1:不可访问
  • Bits 4-5 -制造商定义
  • Bits 6-7 - 00: HDD 活动LED ;其他 ""

这是用这种方法启动A20的例子:

mov    al, 2    ; 设置第2位(开启a20)
out    0x92, al

用这个端口也可以做其他是一些事情:

mov    al, 1    ; 设置第1位(快速重设)
out    0x92, al
这个方法在Bochs中可以使用。

注意!

虽然这是一个简单的方法。我发现这种方法与一些硬件会发生冲突。可能会导致系统停机,如果你想用方法,注意一些。

其它端口

一些系统允许使用其他的I/O端口来启动A20.

最常见的是I/O端口 0xEE。如果I/O 端口 0xEE ("快速 A20") 在这个系统有效,从这个端口读数据会开启A20,写数据会关闭A20。相似的端口0xEF ("快速CPU重设") 会重置系统。

其他系统会使用不同的端口(ie; AT&T 6300+需要写0x90I/O 端口 0x3f20来开启A20, 还有写0 关闭 A20). 也有些系统使用I/O端口0x65的第2位或是I/O端口0x1f8的第0位开启或关闭 A20 (0: 关, 1:开).

如你所见,A20的操作多数与硬件相关,所以你要先确定你的主板制造商。

方法 2: Bios

很多Bios中断可以开启或是关闭A20.

Bochs 支持

一些版本的Bochs可以使用这些方法而其他的版本并不支持.

INT 0x15方法2400 – 关闭A20

这个方法可以关闭A20。很容易用:

mov ax, 0x2400
int 0x15
Returns:
CF = 如果成功,为0
AH = 0
CF =如果发生错误为1
AH = 状态 (01=键盘控制器处在安全模式, 0x86=不支持这个方法)

INT 0x15 方法2401 – 开启A20

这个方法开启A20

mov ax, 0x2401
int 0x15
Returns:
CF = 如果成功,为0
AH = 0
CF =如果发生错误为1
AH = 状态 (01=键盘控制器处在安全模式, 0x86=不支持这个方法)

INT 0x15 方法2402 – A20状态

这个方法返回A20当前的状态。

mov ax, 0x2402
int 0x15
Returns:
CF = 如果成功,为0
AH = 状态 (01=键盘控制器处在安全模式, 0x86=不支持这个方法)
AL = 当前状态 (00: 关, 01: 开)
CX =如果键盘控制器在0xc000读尝试时未准备好被设为
CF = 错误时为1

INT 0x15 方法2403 – 询问A20支持

这个方法询问系统 对于A20支持.

mov ax, 0x2403
int 0x15
Returns:
CF =如果成功,为0
AH = 状态 (01=键盘控制器处在安全模式, 0x86=不支持这个方法)
BX =状态.
BX 包含一个位模式:

  • Bit01支持键盘控制器
  • Bit 11支持I/O端口0x92的第1
  • Bit s 2-14 - 保留
  • Bit 15如果附加数据有效则为1

方法 3: 键盘控制器

这可能是最常见的开启A20的方法,简单,但要求键盘控制器编程的知识。这也最有可移植性的方法。因为有键盘控制器编程的要求,我先介绍一下。

这也是我要先介绍硬件编程的原因。这是我们第一次介绍如何直接为硬件编程, 别担心,还不算难,我们会慢慢的增加难度:)

8043 键盘控制器 - 端口映射

为了与控制器交流,我们得知道这个控制器的I/O 端口映射。

这个控制器的端口映射如下:

端口映射

端口

/

描述

0x60

读输入缓存

0x60

写输出缓存

0x64

读状态寄存器

0x64

给控制器发命令

要给这个控制器发送命令,将命令写到端口0x64。 如果这个命令要接收一个参数,参数写到端口0x60。命令的所有返回值都可以从端口0x60读回。

要注意键盘控制器很慢。因为我们的代码执行要比键盘控制器快得多,我们需要一种方法来等待控制器完成它的工作,在继续我们的代码。

这经常使用检测控制器状态的方法实现。很困惑?别担心,后面会解释清楚的。

8043 键盘控制器状态寄存器

好,我们怎么得到控制器的状态呢?看看上面的表,我们知道从0x64读数据就行了。从这个寄存器读出的值有8为,格式如下:

  • Bit 0: 输出缓冲区状态
    • 0: 输出缓冲区空,不能读
    • 1: 输出缓冲区满,请读
  • Bit 1: 输入缓冲区状态
    • 0: 输入缓冲区空,可写
    • 1: 输入缓冲区慢,不能写
  • Bit 2: 系统标志
    • 0: 系统重设后设置为0
    • 1: 完成键盘控制器自检后设为1
  • Bit 3: 命令数据
    • 0: 最后写到输入缓冲区的是数据 (通过端口 0x60)
    • 1: 最后写到输入缓冲区的是命令 (通过端口 0x64)
  • Bit 4: 键盘锁
    • 0: 锁定的
    • 1: 未锁定的
  • Bit 5: 辅助输出缓冲区满
    • PS/2 系统:
      • 0: 决定是否可以从端口0x60读,0=键盘数据
      • 1:鼠标数据,只能从端口0x60
    • AT 系统s:
      • 0: OK 标志
      • 1:超时.键盘可能不存在
  • Bit 6: 超时
    • 0: OK 标志
    • 1: 超时
    • PS/2:
      • 一般超时
    • AT:
      • 超时,可能有错 (Bit 6 7 同时置1)
  • Bit 7: 奇偶错误
    • 0: OK 标志, 没错
    • 1: 奇偶错误

如你所见,有很多东西,其中重要的都在上面加粗显示了,我们要检测控制器的输出、输入缓冲区是满的还是空的。

这是一个例子。我们向控制器发命令时,命令写到控制器的输入缓冲区。因此当输入缓冲区满的时候就不能执行,就像这样:

wait_input:
        in      al,0x64        ;读状态寄存器
        test    al,2        ; 超时第2位 (输入缓冲区状态)
        jnz     wait_input    ; 如果非0(不空)跳转,继续等
在输入输出的时候都要这么做。

现在我们知道怎么等控制器了, 我们必须要告诉控制器我们要做什么。我们看看“命令字”。

8042 键盘控制器命令寄存器

再看看I/O 端口映射表,我们可以通过写命令到 I/O 端口0x64来发送命令。

键盘控制器有很多命令,因为这不是键盘编程的教材, 我没有吧它们全列出来,但是下面的表中是最重要的:

键盘控制器的命令字

键盘 命令

描述

0x20

读键盘控制器命令字

0x60

写键盘控制器命令字

0xAA

自检

0xAB

接口检测

0xAD

使键盘无效

0xAE

使键盘有效

0xC0

读输入端口

0xD0

读输出端口

0xD1

写输出端口

0xDD

开启A20地址线

0xDF

关闭 A20地址线

0xE0

读测试输入

0xFE

系统重设

Mouse 命令

描述

0xA7

使鼠标端口失效

0xA8

使鼠标端口有效

0xA9

测试鼠标端口

0xD4

写鼠标

再说一次,这不是全部的命令,我们会在后面再说。

方法 3.1: 通过键盘控制器开启A20

注意到命令0xDD0xDF用于开启/关闭A20

; 方法 3.1: 通过键盘控制器开启A20
mov al, 0xdd    ; 命令 0xdd: 开启a20
out 0x64, al    ; 发送命令到控制器
不是所有的键盘控制器都支持这个方法,如果有效的话,它确实很简单;)

方法 3.2:通过输出端口开启A20

也有使用键盘控制器的输出端口开启A20的,这样我们需要使用命令D0D1 来读、写输出端口 (参考键盘控制器的命令字)

这种方法比起其他的要复杂的多,但也不太差。基本的想法是,停止键盘,并且读键盘控制器的输出端口。80423个端口s: 一个输入,其他的是输出,第3个用于检测,这些"端口"实际上就是控制器上的硬件线路。

简单起见(因为这不是键盘编程教材), 我们只看输出端口。

好的,从输出端口读数据,简单的发送一个读输出端口的命令(0xD0)到控制器就行了: (参考键盘控制器的命令字)

; 读输出端口到al
mov     al,0xD0
out     0x64,al

现在我们得到了输出端口的数据,但他不是太有用,实际上输出端口的格式如下,一个特定的位模式。

  • Bit 0: 系统重设
    • 0:重设计算机
    • 1: 一般操作
  • Bit 1: A20
    • 0:
    • 1:
  • Bit 2-3: 未定义
  • Bit 4: 输入缓冲区满
  • Bit 5: 输出缓冲区空
  • Bit 6: 键盘时钟
    • 0: High-Z
    • 1: Pull Clock Low
  • Bit 6: 键盘数据
    • 0: High-Z
    • 1: Pull Data Low

其中的大多数位我们不希望改变,将第0为置0会重启计算机;第1位置1会开启A20,你可以通过将这个值与某一位为1的掩码做或操作设置特定的位,设好后,写回去就行了(命令字0xD1).

写数据到输出端口,从输入、输出缓冲区取得控制器的数据。

这表示,如果我们要读输出端口, 数据要从控制器的输入缓冲区寄存器读 看看I/O 端口映射表,我们知道要从端口0x60读数据。

看个例子,在读写操作时,我们要等待控制器完成操作。wait_input用于等待输入缓冲区变空,而wait_output等待输出缓冲区变空。

; 发送读输出端口命令
mov     al,0xD0
out     0x64,al
call    wait_output
 
; 读输入缓存区,并保存在栈中,数据从输出端口读入
in      al,0x60
push    eax
call    wait_input
 
; 发送写输出端口命令
mov     al,0xD1
out     0x64,al
call    wait_input
 
; 从栈中弹出输出端口的数据,并设置第1位 (A20)
pop     eax
or      al,2        // 2 = 10(二进制)
out     0x60,al        // 写数据到输出端口. 这通过输出缓冲区完成

以上是这个方法的全部:) 这个方法比其他的要复杂但有更好的可移植性。

小心

因为我们在虚拟机上运行,下面的多数情况都不会在Bochs下发生,但是对于真正的硬件就有麻烦了。

要控制器执行错误的命令

如果控制器执行了错误的命令,一般会做一些你不想去做的事儿,(比如从一个端口读数据而不是你想要的写)这可能会损害你的数据。例如, 使用in al, 0x61而不是in al, 0x60, 就会读不同的寄存器,而不是期望的状态寄存器 (端口 0x60).

未知的控制器命令

多数控制器忽视那些它不知道的命令,并把它扔掉(清空命令寄存器,可能是这样)

一些控制器可能失灵,看看失灵那一节。

控制器失灵

很少会发生,但是这是可能会发生的,有两个例子都和Pentium处理器相关,包括FDIVfoof错误。FDIV 错误是cpu设计的问题,处理器FPU会给出错误的结果。

foof更严重。当处理器处理命令0xf0 0x0f 0xc70xc8, 这是一条Hault and Catch Fire (HCF) 指令(一条没有文档定义的指令)。多数这样的指令会锁定处理器,并强制重启。在使用这样的指令时会发生不正常的边界效应。

要知道这些问题可能会发生,当他们发生的时候,控制器不会产生异常。

多数的控制器异常都是硬件"设计失误"的结果。

物理设备损坏

虽然少见,但也可能会由于软件的原因损坏硬件。一个简单例子是软盘驱动器,你可以通过软盘驱动器控制器(FDC)直接控制软盘驱动器的马达,当忘记关闭马达会导致软盘驱动器快速用旧和损坏,小心。

三重错误

当控制总线产生一个问题的时候,会导致处理器产生异常,当然可能会导致重启。

Bochs里的控制器问题

当控制器有问题,Bochs会产生三重错误,并且记录日志。

例如, 如果你发送一个未知命令 (0)给键盘控制器:

    mov        al, 0x00        ; 随便的一个命令
    out        0x64, al        ; 发送该命令给控制器
Bochs 会产生三重错误,并记录日志:
    [KBD   ] unsupported io write to key board port 64, value = 0
"KBD"表示这条日志是有键盘控制器设备记录的。

Demo

所有 A20的 代码在A20.inc中。我写了几个不同的函数实现了使用不同的方法开启A20,所以如果一种方法失效了,就换一种。

为了减少复杂性,我决定,以下载的方式提供这章的演示Demo当前的Stage2.asm 没改多少。

因为这个演示在显示上没有新东西,所以就没有截屏图片。

想看看.

这里下载(*.ZIP: 8KB).

总结

啊,这章要比我想象的更大一些。

我们了解了很多新概念。我们也介绍了硬件编程。记住:在保护模式只能和硬件直接交流! 没有中断,没有BIOS,一切要靠我们自己。

现在, 你可能开始感激Windows:)毕竟,它做了很多困难的工作。

如果你不全部了解也别太担心——这很复杂,我知道。当我们开始编写内核的时候,我们会有一整章来讨论为键盘控制器编程,并且将位它写个驱动程序,酷,不是吗?

下一张很很简单,我们对于保护模式的讨论暂告段落,我们要返回实模式。我们要增加FAT12加载代码来加载我们的内核,A20打开了,我们可以将它放在1MB!

还有我们会取一些BIOS信息,还有一些其他的东西:)等你。

下次见

原创粉丝点击