读setup.s

原文出处 《linux内核0.11完全注释》

 

! bootsect.s 2011-04-10
! setup.s负责从BIOS中获取系统数据,并将这些数据放到系统内存的适当位置。
! 此时setup.s和system已经由bootsect引导加载到内存中

! 这段代码询问bios有关内存/磁盘/其他参数,并将这些参数放到一个"安全的"地方:
! 0x90000-0x901FF, 也即原来bootsect代码块曾经在的地方,然后在被缓冲块覆盖掉之前
! 由保护模式的system读取

INITSEG = 0x9000
SYSSEG = 0x1000
SETUPSEG = 0x9020

.global begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data:
begdata:
.bss
begbss:
.text


entry start
start:

! ok,整个读磁盘过程都正常,现在将光标位置保存以备今后使用
    mov ax, #INITSEG
    mov ds, ax
    mov ah, #0x03            ! BIOS中断0x10的读光标功能号ah=0x03
                             ! 输入: bh = 页号
                             ! 返回: ch = 扫描开始线       cl = 扫描结束线
                             !       dh = 行号(0x00是顶端) dl = 列号(0x00是左边)
    xor bh, bh
    int 0x10
    mov [0], dx              ! 将光标位置信息存放在0x90000处,控制台初始化时会来取

! 以下三行代码是取扩展内存的大小值(KB)
! 调用中断号0x15,功能号ah=0x88
! 返回ax=从0x100000(1M)处开始的扩展内存大小(KB)
! 若出错则CF置位,ax=出错码
    mov ah, #0x88           
    int 0x15                
    mov [2], ax              ! 将扩展内存数值存在0x90002处(1个字)

! 下面这段用于取显示卡当前显示模式
! 调用BIOS中断0x10,功能号ah=0x0f
! 返回:ah=字符数列 al=显示模式 bh=当前显示页
! 0x90004(1字)存放当前页,0x90006显示模式 0x90007字符数列
    mov ah, #0x0f
    int 0x10
    mov [4], bx
    mov [6], ax


! 检查显示方式(EGA/VGA)并取参数
! 调用BIOS中断0x10,附加功能选择-取方式信息
! 功能号:ah=0x12 bl=0x10
! 返回:bh=显示状态
!         (0x00 - 彩色模式,I/O端口=0x3dX)
!         (0x01 - 单色模式,I/O端口=0x3bx)
! bl = 安装的显示内存
! (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 - 256k)
! cx = 显示卡特性参数
    mov ah, #0x12
    mov bl, #0x10
    int 0x10
    mov [8], ax
    mov [10], bx             ! 0x9000A=安装的显示内存 0x9000B=显示状态(彩色/单色)
    mov [12], cx             ! 0x9000C=显示卡特性参数


! 取第一个硬盘的信息(复制硬盘参数表)
! 第1个硬盘参数表的首地址竟然是中断向量0x41的向量值
! 第2个硬盘参数紧接着第1个表的后面,中断向量0x46的向量值也指向第2个硬盘的参数表首地址
! 表的长度是16个字节(0x10)
! 下面两段程序分别复制BIOS有关两个硬盘的参数表:
! 0x90080处存放第1个硬盘的表   0x90090处存放第2个硬盘的表
    mov ax, #0x0000
    mov ds, ax
    lds si, [4*0x41]         ! 取中断向量0x41的值,也即hd0参数表的地址->ds:si
    mov ax, #INITSEG
    mov es, ax
    mov di, #0x0080          ! 传输的目的地址: 0x9000:0x0080 ->es:di
    mov cx, #0x10            ! 共传输0x10字节
    rep
    movsb

    mov ax, #0x0000
    mov ds, ax
    lds si, [4*0x46]
    mov ax, #INITSEG
    mov es, ax
    mov di, #0x0090
    mov cx, #0x10
    rep
    movsb


! 检查系统是否存在第2个硬盘,如果不存在则第2个表清零
! 利用BIOS中断调用0x13的取盘类型功能
! 功能号 ah = 0x15:
! 输入: dl=驱动器号(0x8X是硬盘 0x80指第1个硬盘  0x81第2个硬盘)
! 输出: ah=类型码;00--没有这个盘,CF置位
!                  01--软驱,没有changge-line支持
!                  02--软驱(或其他可移动设备),有change-line支持
!                  03--硬盘
    mov ax, #0x01500
    mov dl, #0x81
    int 0x13
    jc no_disk1
    cmp ah, #3           ! 是硬盘吗
    je is_disk1
no_disk1:
    mov ax, #INITSEG     ! 第2个硬盘不存在,则对第2个硬盘表清零
    mov es, ax
    mov di, #0x0090
    mov cx, #0x10
    mov ax, #0x00
    rep
    stosb
is_disk1:


! 现在我们要进入保护模式了...
    cli   ! 此时不允许中断

! 我们将system模块移动到正确位置
! bootsect引导程序将system模块读到0x10000开始的位置。由于当时假设system模块
! 最大长度不会超过0x80000,也即其末端不会超过内存地址0x90000,所以bootsect会将
! 自己移动到0x90000开始的地址,并把setup加载到它的后面。
! 下面这段程序的用户是再把整个system模块移动到0x0000位置,即把0x10000到0x8ffff的
! 内存块(512k),整块的向内存低端移动了0x10000(64k)的位置
   
    mov ax, #0x0000
    cld
do_move:
    mov es, ax                ! es:di -->目的地址(初始为0x0000:0x0)
    add ax, #0x1000
    cmp ax, #0x9000           ! 已经把从0x8000段开始的64k代码移动完
    jz end_move
    mov ds, ax                ! ds:si -->源地址(初始为0x1000:0x0)
    sub di, di
    sub si, si
    mov cx, #0x8000           ! 移动0x8000字(64k字节)
    rep
    movsw
    jmp do_move


! 此后,我们要加载段描述符
! lidt指令用于加载中断描述符表(idt)寄存器,它的操作数为6个字节:
! 0-1字节是描述表的长度值; 2-5字节是描述表的32位线性基地址(首地址)。
! 中断描述表中的每一个表项(8字节)指出发生中断时需要调用的代码的信息


! lgdt指令用于加载全局描述附表(gdt)寄存器,其操作数格式与lidt指令的相同。
! 全局描述符表中的每个描述符项(8字节)描述了保护模式下数据和代码段的信息。
! 其中包括段的最大长度限制(16bit)、段的线性基址(32bit)、段的特权级、段是否在内存、
! 读写许可以及其他一些保护模式运行的标志


end_move:
    mov ax, #SETUPSEG
    mov ds, ax
    lidt idt_48           ! 加载中断描述符表(idt)寄存器,idt_48是6字节操作数的位置
    lgdt gdt_48           ! 加载全局描述符表(gdt)寄存器,gtd_48是6字节操作数的位置


! 以上操作很简单,现在我们开启A20地址线
    call empty_8042       ! 等待输入缓冲器空
                          ! 只有当输入缓冲器为空时才可以对其进行写命令
    mov al, #0xD1         ! 0xD1命令码-表示要写数据到8042的P2端口。
                          ! P2端口的位1用于A20线的选通
                          ! 数据要写到0x60口
    call empty_8042       ! 等待输入缓冲器空,看命令是否被接受
    mov al, #0xDF         ! 选通A20地址线的参数
    out #0x60, al
    call empty_8042       ! 输入缓冲器为空,则表示A20线已经选通


! 如果以上一切正常,现在我们必须重新对中断进行编程
! 我们将它们放在正好处于intel保留的硬件中断后面,在int 0x20-0x2F。
! 在那里它们不会引起冲突。不幸的是IBM在原PC机中搞糟了,以后也没有纠正过来。
! PC机得BIOS将中断放在了0x08-0x0f,这些中断也被用于内部硬件中断。
! 所以我们就必须重新对8259中断控制器进行编程,这一点都没劲!
    mov al, #0x11         ! 0x11表示初始化命令开始,是ICW1命令字
                          ! 表示边沿触发、多片8259级联、最后要发送ICW4命令字
    out #0x20, al         ! 发送到8259A主芯片
! 下面定义的两个字是直接使用机器码表示的两条相对跳转指令,起延时作用
! 0xeb是直接近跳转指令的操作码,带1个字节的相对位移值。因此跳转范围是-127-127.
! CPU通过把这个相对位移值加到EIP寄存器中就形成了一个新的有效地址。此时EIP指向
! 下一条被执行的指令。执行时所花费的CPU时钟周期数是7-10个。
! 0x00eb表示跳转值为0的一条指令,因此还是直接执行下一条指令。这两条指令共可提供
! 14-20个CPU时钟周期的延时。在as86中没有表示相应指令的助记符,因此Linus在setup.s
! 等一些汇编程序中就直接使用机器码来表示这种指令。
! 另外,每个空操作指令NOP的时钟周期数是3个,因此若要达到相同的延迟效果就需要6-7个NOP指令
    .word 0x00eb, 0x00eb
    out #0xA0, al         ! 再发送到8259A从芯片
    .word 0x00eb, 0x00eb
    mov al, #0x20
    out #0x21, al         ! 送主芯片ICW2命令字,起始中断号,要送奇地址
    .word 0x00eb, 0x00eb
    mov al, #0x28
    out #0xA1, al         ! 送从芯片ICW2命令字,从芯片的起始中断号
    word 0x00eb, 0x00eb
    mov al, #0x04         ! 8259-1 is master
    out #0x21, al         ! 送主芯片ICW3命令字,主芯片的IR2连从芯片INT
    .word 0x00eb, 0x00eb
    mov al, #0x02         ! 8259-2 is slave
    out #0xA1, al         ! 送从芯片ICW3命令字,表示从芯片的INT连主芯片的IR2引脚上
    .word 0x00eb, 0x00eb
    mov al, #0x01         ! 8086 mode for both
    out #0x21, al         ! 送主芯片ICW4命令字。8086模式: 普通EOI方式
                          ! 需发送指令来复位。初始化结束,芯片就绪

    .word 0x00eb, 0x00eb
    out #0xA1, al         ! 送从芯片ICW4命令字,内容同上。
    .word 0x00eb, 0x00eb
    mov al, #0xFF
    out #0x21, al         ! 屏蔽主芯片所有中断请求
    .word 0x00eb, 0x00eb
    out #0xA1, al         ! 屏蔽从芯片所有中断请求

! 这里设置进入32位保护模式运行。首先加载机器状态字,也称控制寄存器CR0,其比特位0
! 置1将导致CPU工作在保护模式
    mov ax, #0x0001
    lmsw ax               ! 就这样加载机器状态字
    jmpi 0, 8             ! 跳转至cs段8,偏移0处
! 我们已经将system模块移动到0x00000开始的地方,所以这里的偏移是0。这里的段值的8
! 已经是保护模式的段选择符了,用于选择描述符表和描述符表项以及所要求的特权级。
! 段选择符长度为16位(2字节);位0-1表示请求的特权级0-3,linux操作系统只用到:
! 0级(系统级)和3级(用户级);位2用于选择全局描述符表(0)还是局部描述符表(1);
! 位3-15是描述符表项的索引,指出选择第几项描述符。
! 所以段选择符8(0b0000, 0000, 0000, 1000)表示请求特权级0、使用全局描述符表中的第1项
! 该项指出代码的基地址是0,因此这里的跳转指令就会直接执行system的代码。


! 下面子程序检查键盘命令队列是否为空。
! 只有当输入缓冲器为空时(状态寄存器2 = 0)才可以对其进行写命令
empty_8042:
    .word 0x00eb, 0x00eb  ! 这里两个跳转指令的机器码,相当于延时空操作
    in al, #0x64          ! 读AT键盘控制器状态寄存器
    test al, #2           ! 测试位2,输入缓冲器满?
    jnz empty_8042        ! yes -- loop
    ret


! 全局描述符表开始处。描述符表由多个8字节长的描述符项组成
! 这里给出3个描述符项。
! 第1项无用,单必须存在。第2项是系统代码段描述符。第3项是系统数据段描述符。
gdt:
    .word 0,0,0,0         ! dummy 第1个描述符,不用
! 这里在gdt表中的偏移量为0x08,当加载代码段寄存器(段选择符)时,使用的是这个偏移量   
    .word 0x07FF          ! 8Mb-limit=2047 (2048*4096=8Mb)
    .word 0x0000          ! base address=0
    .word 0x9A00          ! code read/exec
    .word 0x00C0          ! granularity=4096, 386
! 这里在gdt表中的偏移量为0x10,当加载数据段寄存器(如ds等)时,使用的是这个偏移量   
    .word 0x07FF          ! 8Mb-limit=2047 (2048*4096=8Mb)
    .word 0x0000          ! base address=0
    .word 0x9200          ! data read/write
    .word 0x00C0          ! granularity=4096, 386

idt_48:
    .word 0               ! idt limit=0
    .word 0,0             ! idt base=0L

gdt_48:
    .word 0x800           ! gdt limit=2048, 256 GDT entries
                          ! 全局表长度为2k字节,因为每8字节组成一个段描述符项
                          ! 所以表中共可有256项
    .word 512+gdt,0x9     ! gdt base = 0X9xxxx
                          ! 4个字节构成的内存线性地址: 0x0009<<16+0x0200+gdt
                          ! 也即0x90200+gdt(即在本程序段中的偏移地址)
.text
endtext:
.data
enddata:
.bss
endbss:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

robbie1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值