用AT&T 汇编实现第二个bootloader
作者 斯人 | 发布于 2014 年 2 月 25 日
BIOS 操作系统

上一篇走出了第一步,写了第一个bootloader,本文将继续模拟linux bootloader第二部前面比较重要的部分,

把自己从内存0x07c0处移动到0x9000处,并从0x9000处继续执行。

那为什么要将自己移动到0x9000处呢?

Bootloader除了要加载setup模块,还要加载system模块,为了把这两个模块加载到适当的位置,就需要先规

划内存,用c或其他语言写程序的时候,我们通常都不用担心代码和数据在运行的时候存放在什么位置,

是否会相互覆盖,因为操作系统和编译器已经为我们做好了看护准备以确保不会出错,而现在我们做的是操

作系统,所有内存安排都需要设计者想清楚,确保无论操作系统如何运行,都不会出现 代码与代码,数据与

数据,代码与数据之间相互覆盖的情况。

linux/boot/bootseg.s文件中有以下定义

SETUPLEN = 4                           //SETUP扇区数
BOOTSEG  = 0x07c0                    //启动扇区被加载到的位置
INITSEG  = 0x9000                     //启动扇区将要移动到的位置
SETUPSEG = 0x9020                     //setup开始执行的位置
SYSSEG   = 0x1000                      //system内核加载到的位置
ENDSEG   = SYSSEG + SYSSIZE           //内核末尾位置

之所以要设置好,就是为了启动时加载各个模块的时候保证每个模块各在其位,互不干扰,并且各自都有足

够的空间,不会有某个模块内存不够用的情况,这些都要要求操作系统设计者整体、安全的考虑内存的规

划。具体以后讲到的时候在细讲,回到本文之处,移动启动启动扇区到0x9000处,并继续执行。

那目的还是很简单,将自己移动到0x9000处继续执行,然后输出 “Hello My First OS!”。
代码如下:

#-------------------------------
# 实验:
# 模拟将自己移动到0x9000处并跳转执行
# Author:斯人
#Blog:imsiren.com
#-------------------------------

BOOTSEG         =0x07c0
INITSEG         =0x9000
.code16
.global _start

_start:
        #设置源寄存器地址:0x07c0
        movw    $BOOTSEG        ,%ax
        movw    %ax             ,%ds

        #设置目标寄存器地址:0x9000
        movw    $INITSEG        ,%ax
        movw    %ax             ,%es
        #移动多少个字256*2=512字节
        movw    $256            ,%cx
        subw    %si             ,%si
        subw    %di             ,%di
        rep
        movsw
        ljmp $INITSEG,$_go
_go:
        movw    %cx             ,%ax
        movw    %ax             ,%ds
        movw    %ax             ,%es

        movw    $bootMsg        ,%bp
        movw    $bootMsgLen     ,%cx
        movw    $0xac           ,%bx
        movw    $0x1301         ,%ax
        movb    $0              ,%dl
        movb    $0x14           ,%dh
        int     $0x10
bootMsg:
        .string "Hello My First OS!"
bootMsgLen=.- bootMsg

.org 510
.word 0xaa55

还是先来看效果图:

跟上一篇没什么区别,哈哈。

虽然效果上没有区别,但是确实将启动扇区移动到0x9000处了。
返回来看代码,看过上一篇的人会觉得跟上面很相似,只不过输出放到了_go segment里,_start segment是

新增的,那么我们重点就来看看这段代码:

BOOTSEG         =0x07c0
INITSEG         =0x9000
........
_start:
        #设置源寄存器地址:0x07c0
        movw    $BOOTSEG        ,%ax
        movw    %ax             ,%ds

        #设置目标寄存器地址:0x9000
        movw    $INITSEG        ,%ax
        movw    %ax             ,%es
        #移动多少个字256*2=512字节
        movw    $256            ,%cx
        subw    %si             ,%si
        subw    %di             ,%di
        rep
        movsw
        ljmp $INITSEG,$_go
......

这里面有两个比较重要的寄存器,%ds和%es
%ds:源地址寄存器(从哪个地址复制)
%es:目的地址寄存器(复制到哪个地址)
%cs:rep循环多少次,前面说了,启动项有512个自己,movsw每次移动2个字节,所以移动256次就是512

字节。

ljmp $INITSEG,$_go

Long jump,跳转到$INITSEG:$_go执行(0x9000:$_go),因为$_go的偏移只有运行的时候才知道,所以这

里要用 segment 名称实现跳转。
Ljmp指令会影响CS和IP寄存器,CS指向段$0x9000,IP指向$_go第一句汇编语言的偏移(movw %cs,%ax)
下面的图,显示了跳转时CS和IP的状态:

在复制完后,0x07c0处和0x9000会有两段完全相同的代码,我们的目的是跳转到0x9000处后接着执行movw

%cs ,%ax,而不是死循环。
Bootseg复制到新的地址后,整体代码位置都发生了变化,前面修改了%cs,其他的寄存器也要相应的修改。

在复制完成之后,有一个重要意义就是 bootseg能根据自己的需要来设置内存地址了,而不是开始通过BIOS

被迫加载到0x07c0处了。

现在写了两个简单的bootloader,下一篇将会模拟 bootseg读取磁盘 setup的操作。

原文出处:http://www.imsiren.com/archives/923