系统初始化程序 boot.s 的分析
时间:2007-02-17 来源:PHP爱好者
boot.s is loaded at 0x7c00 by the bios-startup routines, and moves itself out of the way to address 0x90000, and jumps there.
当PC机启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入0x7C00(31K;0111,1100,0000,0000),并开始执行此处的代码。这就是对内核初始化过程的一个最简单的描述。
最初,Linux核心的最开始部分是用8086汇编语言编写的。当开始运行时,核心将自己装入到绝对地址0x90000(576K;1001,0000,0000,0000,0000),再将其后的2k字节装入到地址0x90200(576.5k;1001,0000,0010,0000,0000)处,最后将核心的其余部分装入到0x10000(64k;1,0000,0000,0000,0000).
It then loads the system at 0x10000, using BIOS interrupts. Thereafter it disables all interrupts, moves the system down to 0x0000, changes to protected mode, and calls the start of system. System then must RE-initialize the protected mode in it's own tables, and enable interrupts as needed.
然后,关掉所有中断,把系统下移到0x0000(0k;0000,0000,0000,0000,0000)处,改变到保护模式,然后开始系统的运行.系统必须重新在保护模式下初始化自己的系统表格,并且打开所需的中断.
NOTE 1! currently system is at most 8*65536(8*64k=512k;1000,0000,0000,0000,0000) bytes long. This should be no problem, even in the future. I want to keep it simple. This 512 kB kernel size should be enough - in fact more would mean we'd have to move not just these start-up routines, but also do something about the cache-memory (block IO devices). The area left over in the lower 640 kB(0xA0000;1010,0000,0000,0000,0000) is meant for these. No other memory is assumed to be "physical", ie all memory over 1Mb is demand-paging. All addresses under 1Mb are guaranteed to match their physical addresses.
NOTE1 abouve is no longer valid in it's entirety. cache-memory is allocated above the 1Mb mark as well as below. Otherwise it is mainly correct.
NOTE 2! The boot disk type must be set at compile-time, by setting the following equ. Having the boot-up procedure hunt for the right disk type is severe brain-damage. The loader has been made as simple as possible (had to, to get it in 512 bytes with the code to move to protected mode), and continuos read errors will result in a unbreakable loop. Reboot by hand. It loads pretty fast by getting whole sectors at a time whenever possible.
| 1.44Mb disks: sectors = 18
| 1.2Mb disks:
| sectors = 15
| 720kB disks:
| sectors = 9
**********************************************************************************************
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
BOOTSEG = 0x07c0
|把第一个扇区装入到此处
INITSEG = 0x9000
|核心装入地址的段地址
SYSSEG = 0x1000
|system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE
|SYSSIZE在Makefile中定义的 ^_^
entry start
start:
mov ax,#BOOTSEG
| BOOTSEG = 0x07C0;现在应仍处在REAL MODE下.
mov ds,ax
| 移动自身从BOOTSEG:0000到INITSEG:0000
mov ax,#INITSEG
| 共512字节.
mov es,ax
| 那么BOOT.S处在0x90000-0x90200.
mov cx,#256
sub si,si
| 寄存器清零
sub di,di
| 寄存器清零
rep
movw
| 将由SI作为指针的源串中的一个字节或字或双字传送到由DI作为指针的目的串中,并根据标志DF值自动修
| 改这两个指针以指向串中下一项。若带有前缀REP,则重复执行这一传送,直到CX寄存器等于零为止。
| 源地址:DS=0x07C0(31K;0111,1100,0000,0000);
| 目的地址:ES=0x9000(576K;1001,0000,0000,0000,0000)
| 小结:BIOS将启动盘的第一个扇区调入到0x07C00处,然后系统把自己从0x07C00处移动到0x90000-
| 0x90200处.
jmpi go,INITSEG
go: mov ax,cs
mov ds,ax
mov es,ax
| 将DS,ES,SS均设为0x9000,所有数据都以
| 0x9000为段偏移.
mov ss,ax
| 堆栈偏移0x9000
mov sp,#0x400
| 栈顶指针0x9000:0x0400,堆栈空间512bytes??
mov ah,#0x03
| read cursor pos
xor bh,bh
int 0x10
mov cx,#24
mov bx,#0x0007
| page 0, attribute 7 (normal)
mov bp,#msg1
| 显示Loading System ...
mov ax,#0x1301
| write string, move cursor
int 0x10
| ok, we've written the message, now
| we want to load the system (at 0x10000)
| 我们已经完成了“Loading...”的在屏幕上显示,
| 以下我们将完成把核心从0x10000(64k)移到0x01000(4k)处.
mov ax,#SYSSEG
| SYSSEG = 0x1000
mov es,ax
| segment of 0x010000
call read_it
| 读内核到0x10000
call kill_motor
| 杀了软驱!? ^_^
| if the read went well we get current cursor position ans save it for
| posterity.
mov ah,#0x03
| read cursor pos
xor bh,bh
int 0x10
| save it in known place, con_init fetches
mov [510],dx
| it from 0x90510(1001,0000,0101,0001,0000).
| 功能03H,读取光标位置和类型。AH=03H,BH=页号。
| 返回:DH=当前字符行号; DL=当前字符列号
| CH=光标的起始光栅线;CL=光标的终止光栅线
| now we want to move to protected mode ...
cli
| 关掉所有中断
| first we move the system to it's rightful place
mov ax,#0x0000
cld
| 'direction'=0, movs moves forward
do_move:
mov es,ax
| ES=0x0000;destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax
| DS=0x1000;source segment
sub di,di
| 置零,地址为0x1000:0000
sub si,si
| 置零,地址为0x9000:0000
mov cx,#0x8000
| cx的作用是计数器,共0x8000=32k
rep
movsw
j do_move
| 将位于低端0x1000:0000的内核移到内存
| 高端0x9000:0000,覆盖了boot.S !?
| then we load the segment descriptors
end_move:
mov ax,cs
| right, forgot this at first. didn't work :-)
mov ds,ax
lidt idt_48
| idt_48和gdt_48都是一个3个word长的数据结构
lgdt gdt_48
| 第一个字说明(Global || Interrupt) Descriptor
| Table有多长,因为每个Table是四个字长,所以
| 可以得出整个DescriptorTable的entries.(见下)
| 后两个字指出DT的具体位置.
| idt_48是0,0,0;应表示没有中断描述符entries.
| gdt_48有256个入口,第一个是个空入口,然后
| 定义了一个code段和一个data段.基址都是
| 0x00000000, !?那里是什么东西???
| *** 0x00000000 != 0x0000:0000 ***
| 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.
| 初始化中断处理器8259i
| 初始化顺序为: 1. 向主8259A写ICW1, 0x20
| 2. 向第二块8259A写ICW1, 0xA0
| 3. 向主8259A写ICW2, 0x21
| 4. 向第二块8259A写ICW2, 0xA1
| 5. 如果ICW1指示有级联中断处理器,则初始化Master&Slave
| (在下例中只有IR2有级联8259A), 0x21, 0xA1
| 6. 向两块8259写ICW4,指定工作模式.
| 输入了适当的初始化命令之后, 8259已经准备好接收中断请求.
| 现在向他输入工作
| 命令字以规定其工作方式. 8259A共有三个工作命令字,但下例中只用过OCW1.
| OCW1将所有的中断都屏蔽掉, OCW2&OCW3也就没什么意义了.
| ** ICW stands for Initialization Command Word;
| OCW for Operation Command Word.
1. mov al,#0x11
out #0x20,al
.word 0x00eb,0x00eb | jmp $+2, jmp $+2
2. out #0xA0,al | and to 8259A-2
.word 0x00eb,0x00eb
3. mov al,#0x20 | 向主8259A写入ICW2.
out #0x21,al | 硬件中断入口地址0x20, 并由ICW1
| 得知中断向量长度 = 8 bytes.
.word 0x00eb,0x00eb
4. mov al,#0x28 | start of hardware int's 2 (0x28)
out #0xA1,al | 第二块8259A的中断入口是0x28.
.word 0x00eb,0x00eb
5. mov al,#0x04 | 8259-1 is master
out #0x21,al | Interrupt Request 2有级联处理.
.word 0x00eb,0x00eb
mov al,#0x02 | 8259-2 is slave
out #0xA1,al | 于上面对应,告诉大家我就是IR2对应
| 级联处理器.
.word 0x00eb,0x00eb
6. 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
*********************************************************************************
| This routine loads the system at address 0x10000, making sure
| no 64kB boundaries are crossed. We try to load it as fast as
| possible, loading whole tracks whenever we can.
|
| in: es - starting address segment (normally 0x1000)
|
| This routine has to be recompiled to fit another drive type,
| just change the "sectors" variable at the start of the file
| (originally 18, for a 1.44Mb drive)
|
sread: .word 1 | sectors read of current track
head: .word 0 | current head
track: .word 0 | current track
**read-it子函数********************************************************************
read_it:
mov ax,es
| ES当前应0x1000,对,是这样的!
test ax,#0x0fff
| 目的操作数与源操作数进行逻辑与操作,结果只反映在标志位上,对两个操作数无影响
| 必需确保ES处在64KB段边界上,即0x?000:XXXX.
| 要不你就会收到一个"DMA..."什么什么的ERR.
die: jne die
| jne:不相等/不等于零时转移,ZF=0
| es must be at 64kB boundary
xor bx,bx
| bx is starting address within segment
rp_read:
| **** 循环入口处 ****
mov ax,es
cmp ax,#ENDSEG
| have we loaded all yet?
jb ok1_read
| ax<#ENDSEG时转移
ret
ok1_read:
mov ax,#sectors
| 1.44M, sectors=18,linux的后续版本
| 中已改成由操作系统来探测sectors的值.
sub ax,sread
| AX内记载需要读的扇区数,初始sread为1,
| 即跳过第一道的第一扇区(BOOT区)
mov cx,ax
shl cx,#9
| CX左移9位,相当于:CX*512=17*512=8704字节
| CX算出需要读出的扇区的字节数, ax*512.
add cx,bx
| BX是当前段内偏移.
| 下面连续的两个转移指令开始还真让人莫名其妙.
jnc ok2_read
| jnc:无进位(或借位)时转移;CF=0
| 不要超过64k
| 这里先检查当前段内的空间够不够装ax个扇区
| cx算出字节数,加上当前偏移试试,够了的话,就
| 跳到ok2_read去读吧!
je ok2_read
| 这么巧的事也有,刚刚够! 读!
| 如果到了这里就确认溢出了,看下面的:
xor ax,ax
sub ax,bx
shr ax,#9
| 这段代码我觉得很精巧.
| shr指令执行逻辑右移操作。每执行一次,使目的操作数右移一位,移出的最低位送入标志CF,空出的高位| 补0;
| 它主要目的就是算出如果当前段内空间不够的话,
| 那么反算出剩余空间最多能装多少个扇区,那么
| 就读出多少个.(Hint,段内空间是扇区的整数倍)
ok2_read:
call read_track
| 读取当前磁道.
mov cx,ax
| (别忙,这里暂时不关cx什么事!)
add ax,sread
| AX是这次读出的扇区数, sread是该磁道已读出的扇区,相加更新AX的值.
cmp ax,#sectors
| 该磁道所有的扇区都读出了吗?
jne ok3_read
| 尚未,还不能移到下个磁道!
mov ax,#1
sub ax,head
| head对应软盘来说只能是0,1
jne ok4_read
| 0,1 head都读过了才准往下走!
inc track
| 终于可以读下个磁道了,真累!
ok4_read:
mov head,ax
xor ax,ax
ok3_read:
mov sread,ax
| 如果是由于还没读完所有的磁道?
| 那么ax记载当前磁道已读出的扇区,更新sread.
| 如果已读完18个扇区,ax被上一行代码置零.
shl cx,#9 <----|
| cx记载最近一次读的扇区数,*512算成字节.
add bx,cx
| bx是缓冲区的偏移.往前移!
jnc rp_read
| 看看当前段(64K)是不是已经装满了?
| 这里是不会超出当前段的,(见上的代码)
| 最多也就是刚刚装满. :-)
mov ax,es
| 装满了!移到下一段!!!
add ax,#0x1000
mov es,ax
xor bx,bx
| 偏移置零!
jmp rp_read
**read_track子函数,开始*****************************************************************
read_track:
push ax
push bx
push cx
push dx
mov dx,track
mov cx,sread
inc cx
| CL的低位0-5指示扇区号
mov ch,dl
| 磁道号(0-1023)由10位bits组成:CH 8位,CL2位.
mov dx,head
mov dh,dl
| DH=磁头号
mov dl,#0
| DL=驱动器号,0代表A:, 0x80代表C:
and dx,#0x0100
| 不明白为什么要再检验一次,也许为了确保万无一失. ???
mov ah,#2
| AX当前的值为sub as, sread, 那么
| AL的值,即扇区数为当前剩余未读的sectors.
int 0x13
| BOIS CALL, ES:BX指示缓冲区位置.
| 出错处理,见下:
jc bad_rt
pop dx
pop cx
pop bx
pop ax
ret
bad_rt: mov ax,#0
mov dx,#0
int 0x13
| 软盘复位!
pop dx
pop cx
pop bx
pop ax
jmp read_track
**read_track子函数,结束*****************************************************************
/*
* This procedure turns off the floppy drive motor, so
* that we enter the kernel in a known state, and
* don't have to worry about it later.
*/
kill_motor:
push dx
mov dx,#0x3f2
mov al,#0
outb | 向Floppy Controller端口写零,STOP!
pop dx
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 gdt,0x9 | gdt base = 0X9xxxx
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
.text
endtext:
.data
enddata:
.bss
endbss:
/**
* 黄,你好.上面的代码注释就是我读boot.S的心得. 第一次没搞清楚的SYSSIZE
* 问题已经解决,它是在Makefile在编译时定义的.定义的方法很奇怪,还用到
* Shell script. 读系统到内存我想我应该是弄清淅了,但后面的386PM和8259A
* 难度就大多了.
* 1)boot在读完system到0x10000之后,又将它这么一移到0x90000
* 岂不是把自己给覆盖了吗?我这么也不理解,我没有调试kernel的工具,不知
* 实际运行时是这样的.你上次说的单机调试的工具地址能再mail给我吗?
* 你能在你的双机上看看是怎样的吗?
* 2)在boot之后,绝对地址0x00000里面
* 到底是什么?(中断向量?!)在整个初始化过程完毕后,系统jump到那里是什么
* 意思?初始的两个GDT也是指向0x00000000.
* 3)8259A的工作原理我主要参看
* 的是清华的<<微机IBM-PC/XT原理及应用>>,周德明.后来我发现Minix的那本
* 书里也有一点东西,还没来的及看.
* 另外多谢你提供的 across reference building tool,我还没用熟,能简单
* 介绍介绍吗? ^_^
* 杜晓明 98.11.17
**/
@@ 1,你搞错了,boot在读完system到0x10000之后,又将它这么一移到0x0。:-)
@@ 2.绝对地址0x00000里面 system.实模式中断不能再用了
@@ !)在整个初始化过程完毕后,系统jump: jmpi 0,8 这是个长跳转 cs=8 eip=0
@@ cs=8不是实模式的段,而是gdt表中第一 (0开始),就是你定义的初始的两个GDT
@@ 中的第一项,所以,现在系统跳到绝对0,即head.s的startup
@@ 3.用lxr的across reference building tool先解开后,基本上按INSTLL说明
@@ make install
@@ edit $(安装目录)/http/lxr.conf
@@ baseurl 改为你的url
@@ 我是这样设的 http://192.168.1.3/lxr/
@@ 同一目录下设.htaccess INSTALL有
@@ 配置 httpd server
@@ httpd.conf 加一行 Alias /lxr $(安装目录)/http/
@@ cd $(安装目录)/source 产生标识符库 ../bin/genxref $(kernel source目录)
@@ kernel source: /linux/0.01/....
@@ /0.10/...
@@ 你还可用global http://zaphod.ethz.ch/linux/
@@ 我装过,可是最后装好后没有搜索,不然应该会更好用。?
@@ 4.内核调试我用过gdbstub,但是我发现调试好象的是gdbstub.c程序,而不是内核,只
@@ 看到gdbstub.c的原代码,没有内核的原代码,或许有个步骤我没做导致如此.
php爱好 者站 http://www.phpfans.net php基础|php进阶|php模板.
当PC机启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入0x7C00(31K;0111,1100,0000,0000),并开始执行此处的代码。这就是对内核初始化过程的一个最简单的描述。
最初,Linux核心的最开始部分是用8086汇编语言编写的。当开始运行时,核心将自己装入到绝对地址0x90000(576K;1001,0000,0000,0000,0000),再将其后的2k字节装入到地址0x90200(576.5k;1001,0000,0010,0000,0000)处,最后将核心的其余部分装入到0x10000(64k;1,0000,0000,0000,0000).
It then loads the system at 0x10000, using BIOS interrupts. Thereafter it disables all interrupts, moves the system down to 0x0000, changes to protected mode, and calls the start of system. System then must RE-initialize the protected mode in it's own tables, and enable interrupts as needed.
然后,关掉所有中断,把系统下移到0x0000(0k;0000,0000,0000,0000,0000)处,改变到保护模式,然后开始系统的运行.系统必须重新在保护模式下初始化自己的系统表格,并且打开所需的中断.
NOTE 1! currently system is at most 8*65536(8*64k=512k;1000,0000,0000,0000,0000) bytes long. This should be no problem, even in the future. I want to keep it simple. This 512 kB kernel size should be enough - in fact more would mean we'd have to move not just these start-up routines, but also do something about the cache-memory (block IO devices). The area left over in the lower 640 kB(0xA0000;1010,0000,0000,0000,0000) is meant for these. No other memory is assumed to be "physical", ie all memory over 1Mb is demand-paging. All addresses under 1Mb are guaranteed to match their physical addresses.
NOTE1 abouve is no longer valid in it's entirety. cache-memory is allocated above the 1Mb mark as well as below. Otherwise it is mainly correct.
NOTE 2! The boot disk type must be set at compile-time, by setting the following equ. Having the boot-up procedure hunt for the right disk type is severe brain-damage. The loader has been made as simple as possible (had to, to get it in 512 bytes with the code to move to protected mode), and continuos read errors will result in a unbreakable loop. Reboot by hand. It loads pretty fast by getting whole sectors at a time whenever possible.
| 1.44Mb disks: sectors = 18
| 1.2Mb disks:
| sectors = 15
| 720kB disks:
| sectors = 9
**********************************************************************************************
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
BOOTSEG = 0x07c0
|把第一个扇区装入到此处
INITSEG = 0x9000
|核心装入地址的段地址
SYSSEG = 0x1000
|system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE
|SYSSIZE在Makefile中定义的 ^_^
entry start
start:
mov ax,#BOOTSEG
| BOOTSEG = 0x07C0;现在应仍处在REAL MODE下.
mov ds,ax
| 移动自身从BOOTSEG:0000到INITSEG:0000
mov ax,#INITSEG
| 共512字节.
mov es,ax
| 那么BOOT.S处在0x90000-0x90200.
mov cx,#256
sub si,si
| 寄存器清零
sub di,di
| 寄存器清零
rep
movw
| 将由SI作为指针的源串中的一个字节或字或双字传送到由DI作为指针的目的串中,并根据标志DF值自动修
| 改这两个指针以指向串中下一项。若带有前缀REP,则重复执行这一传送,直到CX寄存器等于零为止。
| 源地址:DS=0x07C0(31K;0111,1100,0000,0000);
| 目的地址:ES=0x9000(576K;1001,0000,0000,0000,0000)
| 小结:BIOS将启动盘的第一个扇区调入到0x07C00处,然后系统把自己从0x07C00处移动到0x90000-
| 0x90200处.
jmpi go,INITSEG
go: mov ax,cs
mov ds,ax
mov es,ax
| 将DS,ES,SS均设为0x9000,所有数据都以
| 0x9000为段偏移.
mov ss,ax
| 堆栈偏移0x9000
mov sp,#0x400
| 栈顶指针0x9000:0x0400,堆栈空间512bytes??
mov ah,#0x03
| read cursor pos
xor bh,bh
int 0x10
mov cx,#24
mov bx,#0x0007
| page 0, attribute 7 (normal)
mov bp,#msg1
| 显示Loading System ...
mov ax,#0x1301
| write string, move cursor
int 0x10
| ok, we've written the message, now
| we want to load the system (at 0x10000)
| 我们已经完成了“Loading...”的在屏幕上显示,
| 以下我们将完成把核心从0x10000(64k)移到0x01000(4k)处.
mov ax,#SYSSEG
| SYSSEG = 0x1000
mov es,ax
| segment of 0x010000
call read_it
| 读内核到0x10000
call kill_motor
| 杀了软驱!? ^_^
| if the read went well we get current cursor position ans save it for
| posterity.
mov ah,#0x03
| read cursor pos
xor bh,bh
int 0x10
| save it in known place, con_init fetches
mov [510],dx
| it from 0x90510(1001,0000,0101,0001,0000).
| 功能03H,读取光标位置和类型。AH=03H,BH=页号。
| 返回:DH=当前字符行号; DL=当前字符列号
| CH=光标的起始光栅线;CL=光标的终止光栅线
| now we want to move to protected mode ...
cli
| 关掉所有中断
| first we move the system to it's rightful place
mov ax,#0x0000
cld
| 'direction'=0, movs moves forward
do_move:
mov es,ax
| ES=0x0000;destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax
| DS=0x1000;source segment
sub di,di
| 置零,地址为0x1000:0000
sub si,si
| 置零,地址为0x9000:0000
mov cx,#0x8000
| cx的作用是计数器,共0x8000=32k
rep
movsw
j do_move
| 将位于低端0x1000:0000的内核移到内存
| 高端0x9000:0000,覆盖了boot.S !?
| then we load the segment descriptors
end_move:
mov ax,cs
| right, forgot this at first. didn't work :-)
mov ds,ax
lidt idt_48
| idt_48和gdt_48都是一个3个word长的数据结构
lgdt gdt_48
| 第一个字说明(Global || Interrupt) Descriptor
| Table有多长,因为每个Table是四个字长,所以
| 可以得出整个DescriptorTable的entries.(见下)
| 后两个字指出DT的具体位置.
| idt_48是0,0,0;应表示没有中断描述符entries.
| gdt_48有256个入口,第一个是个空入口,然后
| 定义了一个code段和一个data段.基址都是
| 0x00000000, !?那里是什么东西???
| *** 0x00000000 != 0x0000:0000 ***
| 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.
| 初始化中断处理器8259i
| 初始化顺序为: 1. 向主8259A写ICW1, 0x20
| 2. 向第二块8259A写ICW1, 0xA0
| 3. 向主8259A写ICW2, 0x21
| 4. 向第二块8259A写ICW2, 0xA1
| 5. 如果ICW1指示有级联中断处理器,则初始化Master&Slave
| (在下例中只有IR2有级联8259A), 0x21, 0xA1
| 6. 向两块8259写ICW4,指定工作模式.
| 输入了适当的初始化命令之后, 8259已经准备好接收中断请求.
| 现在向他输入工作
| 命令字以规定其工作方式. 8259A共有三个工作命令字,但下例中只用过OCW1.
| OCW1将所有的中断都屏蔽掉, OCW2&OCW3也就没什么意义了.
| ** ICW stands for Initialization Command Word;
| OCW for Operation Command Word.
1. mov al,#0x11
out #0x20,al
.word 0x00eb,0x00eb | jmp $+2, jmp $+2
2. out #0xA0,al | and to 8259A-2
.word 0x00eb,0x00eb
3. mov al,#0x20 | 向主8259A写入ICW2.
out #0x21,al | 硬件中断入口地址0x20, 并由ICW1
| 得知中断向量长度 = 8 bytes.
.word 0x00eb,0x00eb
4. mov al,#0x28 | start of hardware int's 2 (0x28)
out #0xA1,al | 第二块8259A的中断入口是0x28.
.word 0x00eb,0x00eb
5. mov al,#0x04 | 8259-1 is master
out #0x21,al | Interrupt Request 2有级联处理.
.word 0x00eb,0x00eb
mov al,#0x02 | 8259-2 is slave
out #0xA1,al | 于上面对应,告诉大家我就是IR2对应
| 级联处理器.
.word 0x00eb,0x00eb
6. 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
*********************************************************************************
| This routine loads the system at address 0x10000, making sure
| no 64kB boundaries are crossed. We try to load it as fast as
| possible, loading whole tracks whenever we can.
|
| in: es - starting address segment (normally 0x1000)
|
| This routine has to be recompiled to fit another drive type,
| just change the "sectors" variable at the start of the file
| (originally 18, for a 1.44Mb drive)
|
sread: .word 1 | sectors read of current track
head: .word 0 | current head
track: .word 0 | current track
**read-it子函数********************************************************************
read_it:
mov ax,es
| ES当前应0x1000,对,是这样的!
test ax,#0x0fff
| 目的操作数与源操作数进行逻辑与操作,结果只反映在标志位上,对两个操作数无影响
| 必需确保ES处在64KB段边界上,即0x?000:XXXX.
| 要不你就会收到一个"DMA..."什么什么的ERR.
die: jne die
| jne:不相等/不等于零时转移,ZF=0
| es must be at 64kB boundary
xor bx,bx
| bx is starting address within segment
rp_read:
| **** 循环入口处 ****
mov ax,es
cmp ax,#ENDSEG
| have we loaded all yet?
jb ok1_read
| ax<#ENDSEG时转移
ret
ok1_read:
mov ax,#sectors
| 1.44M, sectors=18,linux的后续版本
| 中已改成由操作系统来探测sectors的值.
sub ax,sread
| AX内记载需要读的扇区数,初始sread为1,
| 即跳过第一道的第一扇区(BOOT区)
mov cx,ax
shl cx,#9
| CX左移9位,相当于:CX*512=17*512=8704字节
| CX算出需要读出的扇区的字节数, ax*512.
add cx,bx
| BX是当前段内偏移.
| 下面连续的两个转移指令开始还真让人莫名其妙.
jnc ok2_read
| jnc:无进位(或借位)时转移;CF=0
| 不要超过64k
| 这里先检查当前段内的空间够不够装ax个扇区
| cx算出字节数,加上当前偏移试试,够了的话,就
| 跳到ok2_read去读吧!
je ok2_read
| 这么巧的事也有,刚刚够! 读!
| 如果到了这里就确认溢出了,看下面的:
xor ax,ax
sub ax,bx
shr ax,#9
| 这段代码我觉得很精巧.
| shr指令执行逻辑右移操作。每执行一次,使目的操作数右移一位,移出的最低位送入标志CF,空出的高位| 补0;
| 它主要目的就是算出如果当前段内空间不够的话,
| 那么反算出剩余空间最多能装多少个扇区,那么
| 就读出多少个.(Hint,段内空间是扇区的整数倍)
ok2_read:
call read_track
| 读取当前磁道.
mov cx,ax
| (别忙,这里暂时不关cx什么事!)
add ax,sread
| AX是这次读出的扇区数, sread是该磁道已读出的扇区,相加更新AX的值.
cmp ax,#sectors
| 该磁道所有的扇区都读出了吗?
jne ok3_read
| 尚未,还不能移到下个磁道!
mov ax,#1
sub ax,head
| head对应软盘来说只能是0,1
jne ok4_read
| 0,1 head都读过了才准往下走!
inc track
| 终于可以读下个磁道了,真累!
ok4_read:
mov head,ax
xor ax,ax
ok3_read:
mov sread,ax
| 如果是由于还没读完所有的磁道?
| 那么ax记载当前磁道已读出的扇区,更新sread.
| 如果已读完18个扇区,ax被上一行代码置零.
shl cx,#9 <----|
| cx记载最近一次读的扇区数,*512算成字节.
add bx,cx
| bx是缓冲区的偏移.往前移!
jnc rp_read
| 看看当前段(64K)是不是已经装满了?
| 这里是不会超出当前段的,(见上的代码)
| 最多也就是刚刚装满. :-)
mov ax,es
| 装满了!移到下一段!!!
add ax,#0x1000
mov es,ax
xor bx,bx
| 偏移置零!
jmp rp_read
**read_track子函数,开始*****************************************************************
read_track:
push ax
push bx
push cx
push dx
mov dx,track
mov cx,sread
inc cx
| CL的低位0-5指示扇区号
mov ch,dl
| 磁道号(0-1023)由10位bits组成:CH 8位,CL2位.
mov dx,head
mov dh,dl
| DH=磁头号
mov dl,#0
| DL=驱动器号,0代表A:, 0x80代表C:
and dx,#0x0100
| 不明白为什么要再检验一次,也许为了确保万无一失. ???
mov ah,#2
| AX当前的值为sub as, sread, 那么
| AL的值,即扇区数为当前剩余未读的sectors.
int 0x13
| BOIS CALL, ES:BX指示缓冲区位置.
| 出错处理,见下:
jc bad_rt
pop dx
pop cx
pop bx
pop ax
ret
bad_rt: mov ax,#0
mov dx,#0
int 0x13
| 软盘复位!
pop dx
pop cx
pop bx
pop ax
jmp read_track
**read_track子函数,结束*****************************************************************
/*
* This procedure turns off the floppy drive motor, so
* that we enter the kernel in a known state, and
* don't have to worry about it later.
*/
kill_motor:
push dx
mov dx,#0x3f2
mov al,#0
outb | 向Floppy Controller端口写零,STOP!
pop dx
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 gdt,0x9 | gdt base = 0X9xxxx
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
.text
endtext:
.data
enddata:
.bss
endbss:
/**
* 黄,你好.上面的代码注释就是我读boot.S的心得. 第一次没搞清楚的SYSSIZE
* 问题已经解决,它是在Makefile在编译时定义的.定义的方法很奇怪,还用到
* Shell script. 读系统到内存我想我应该是弄清淅了,但后面的386PM和8259A
* 难度就大多了.
* 1)boot在读完system到0x10000之后,又将它这么一移到0x90000
* 岂不是把自己给覆盖了吗?我这么也不理解,我没有调试kernel的工具,不知
* 实际运行时是这样的.你上次说的单机调试的工具地址能再mail给我吗?
* 你能在你的双机上看看是怎样的吗?
* 2)在boot之后,绝对地址0x00000里面
* 到底是什么?(中断向量?!)在整个初始化过程完毕后,系统jump到那里是什么
* 意思?初始的两个GDT也是指向0x00000000.
* 3)8259A的工作原理我主要参看
* 的是清华的<<微机IBM-PC/XT原理及应用>>,周德明.后来我发现Minix的那本
* 书里也有一点东西,还没来的及看.
* 另外多谢你提供的 across reference building tool,我还没用熟,能简单
* 介绍介绍吗? ^_^
* 杜晓明 98.11.17
**/
@@ 1,你搞错了,boot在读完system到0x10000之后,又将它这么一移到0x0。:-)
@@ 2.绝对地址0x00000里面 system.实模式中断不能再用了
@@ !)在整个初始化过程完毕后,系统jump: jmpi 0,8 这是个长跳转 cs=8 eip=0
@@ cs=8不是实模式的段,而是gdt表中第一 (0开始),就是你定义的初始的两个GDT
@@ 中的第一项,所以,现在系统跳到绝对0,即head.s的startup
@@ 3.用lxr的across reference building tool先解开后,基本上按INSTLL说明
@@ make install
@@ edit $(安装目录)/http/lxr.conf
@@ baseurl 改为你的url
@@ 我是这样设的 http://192.168.1.3/lxr/
@@ 同一目录下设.htaccess INSTALL有
@@ 配置 httpd server
@@ httpd.conf 加一行 Alias /lxr $(安装目录)/http/
@@ cd $(安装目录)/source 产生标识符库 ../bin/genxref $(kernel source目录)
@@ kernel source: /linux/0.01/....
@@ /0.10/...
@@ 你还可用global http://zaphod.ethz.ch/linux/
@@ 我装过,可是最后装好后没有搜索,不然应该会更好用。?
@@ 4.内核调试我用过gdbstub,但是我发现调试好象的是gdbstub.c程序,而不是内核,只
@@ 看到gdbstub.c的原代码,没有内核的原代码,或许有个步骤我没做导致如此.
php爱好 者站 http://www.phpfans.net php基础|php进阶|php模板.
相关阅读 更多 +