setup.s是Intel x86架构下的汇编语言代码,用于系统设置和初始化。它的主要功能是从BIOS获取系统数据,如内存大小、磁盘信息等,并将这些数据保存到一个安全的位置,以便后续的保护模式系统读取。代码还负责将系统从实模式切换到保护模式。
- 设置数据段和代码段寄存器:通过将初始化段寄存器(INITSEG)设置为0x9000,将系统段寄存器(SYSSEG)设置为0x1000,将设置段寄存器(SETUPSEG)设置为0x9020,来定义数据和代码段的位置。
- 保存光标位置:通过调用BIOS中断0x10的0x03功能号,获取当前光标位置,并将其保存在内存地址0x90000处。
- 获取内存大小:通过调用BIOS中断0x15的0x88功能号,获取扩展内存的大小(以KB为单位),并将其保存在内存地址0x90002处。
- 获取视频卡信息:通过调用BIOS中断0x10的0x0f功能号,获取视频卡的相关信息,如显示页、视频模式、窗口宽度等,并将其保存在内存的特定位置。
- 获取硬盘数据:从BIOS中读取硬盘0和硬盘1的数据,并将其保存在内存地址0x90080和0x90090处。
- 检查硬盘1是否存在:通过向硬盘1发送命令并检查返回值,来确定硬盘1是否存在。如果存在,则继续执行;否则,在内存地址0x90090处填充0x00。
- 移动到保护模式:关闭中断,将系统移动到其正确的位置,并加载段描述符。然后,启用A20地址线,重新编程中断控制器,并最终跳转到保护模式下的地址0x00000。
- 这段代码是操作系统引导加载器的一部分,用于在Intel x86架构的计算机上初始化系统并准备加载操作系统内核。
详细解析
1. 初始化与数据保存区域定义
- INITSEG: 定义为0x9000,这是初始化代码的段基址。
- SYSSEG: 定义为0x1000,这是系统代码的段基址。
- SETUPSEG: 定义为0x9020,这是设置代码的段基址。
2. 保存光标位置
- 调用BIOS中断
0x10
的0x03
功能号,读取光标位置并保存在0x90000
处,供后续使用。
3. 获取内存信息
- 使用BIOS中断
0x15
的0x88
功能号,获取扩展内存大小(单位:千字节),并将结果保存在0x90002
处。
4. 获取视频卡数据
- 通过BIOS中断
0x10
的0x0f
功能号,获取视频卡信息,包括显示页面、视频模式及窗口宽度,分别保存在0x90004
和0x90006
处。
5. 获取硬盘数据
- 读取硬盘0和硬盘1的BIOS参数块,保存在
0x90080
和0x90090
处。
6. 检查硬盘1状态
- 发送命令到硬盘1,根据返回值判断硬盘1是否存在,若不存在则清空
0x90090
处的数据。
7. 切换至保护模式
- 关闭中断,开始将系统代码从
0x10000
移动到0x90000
,为进入保护模式做准备。 - 加载全局描述符表(GDT)和中断描述符表(IDT),并使能A20地址线。
- 重置中断控制器,将硬件中断起点设为
0x20
。 - 最终通过修改机器状态字(LMSW)进入保护模式,并跳转到地址
0x00000
,开始执行32位保护模式代码。
8. 硬件初始化
- 包括A20地址线的使能和中断控制器的重新配置,确保硬件中断的正确处理。
9. 描述符表设置
- GDT: 全局描述符表,包含代码段和数据段描述符。
- IDT: 中断描述符表,初始化为空。
10. 结束标记
endtext
,enddata
,endbss
: 标记文本段、数据段和未初始化数据段的结束位置。
总结
此段代码是Linux内核引导过程中的关键部分,负责从实模式过渡到保护模式,同时收集必要的硬件信息,为后续的系统初始化和内核加载做准备。
!
! setup.s (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
! NOTE! These had better be the same as in bootsect.s!
INITSEG = 0x9000 ! we move boot here - out of the way
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020 ! this is the current segment
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
entry start
start:
! ok, the read went well so we get current cursor position and save it for
! posterity.
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx ! it from 0x90000.
! Get memory size (extended mem, kB)
mov ah,#0x88
int 0x15
mov [2],ax
! Get video-card data:
mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width
! check for EGA/VGA and some config parameters
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx
! Get hd0 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41]
mov ax,#INITSEG
mov es,ax
mov di,#0x0080
mov cx,#0x10
rep
movsb
! Get hd1 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46]
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb
! Check that there IS a hd1 :-)
mov ax,#0x01500
mov dl,#0x81
int 0x13
jc no_disk1
cmp ah,#3
je is_disk1
no_disk1:
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
is_disk1:
! now we want to move to protected mode ...
cli ! no interrupts allowed !
! first we move the system to it's rightful place
mov ax,#0x0000
cld ! 'direction'=0, movs moves forward
do_move:
mov es,ax ! destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax ! source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
jmp do_move
! then we load the segment descriptors
end_move:
mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax
lidt idt_48 ! load idt with 0,0
lgdt gdt_48 ! load gdt with whatever appropriate
! that was painless, now we enable A20
call empty_8042
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042
mov al,#0xDF ! A20 on
out #0x60,al
call empty_8042
! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2
out #0xA0,al ! and to 8259A-2
.word 0x00eb,0x00eb
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x01 ! 8086 mode for both
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.
mov ax,#0x0001 ! protected mode (PE) bit
lmsw ax ! This is it!
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.
empty_8042:
.word 0x00eb,0x00eb
in al,#0x64 ! 8042 status port
test al,#2 ! is input buffer full?
jnz empty_8042 ! yes - loop
ret
gdt:
.word 0,0,0,0 ! dummy
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9A00 ! code read/exec
.word 0x00C0 ! granularity=4096, 386
.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
.word 512+gdt,0x9 ! gdt base = 0X9xxxx
.text
endtext:
.data
enddata:
.bss
endbss: