;文件名:followking/boot/head.s
;本文件改写linux-0.11/boot/head.s,目的是为了体验整个系统构建的过程。
;我是看着赵炯《Linux内核0.11完全注释》编写的。不过,我是编写代码,有疑问再看。
;我用的nasm的语法格式。我想写一个操作系统,现在觉得最简单的方式莫过于
;先把前辈的实现的东西重新实现一遍。等到对这个问题有更深刻认识的时候,
;再重新思考,写出有自己特色的系统。
;作者:hk0625
;开始时间: 2010年03月20号星期六 20:57
;完成时间: 2010年03月21号星期天 11:16
;最后修改时间: 2010年04月07日星期三 11:47
;修改理由:
;(1) 2010年03月26号星期五 20:51, 我把原来有下划线的全局变量做了修改
;(除_start外),理由现在的编译器对c代码编译后有关标识符已经不再加下划线了。
;地点:北化1#宿舍楼426
;下面let's try
;head.s含有32位启动代码。
;注意!32位启动代码是从绝对地址0x00000000开始的,这里也同样是页目录将存在的地方
;因此这里的启动代码将被页目录覆盖掉。
[section .text]
global idt, gdt, pg_dir, tmp_floppy_area, _start
extern stack_start, main, printk
_start:
pg_dir: ;页目录将会存放在这里。
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; mov ss, ax
lss esp, [stack_start]
; mov esp, 0xff00 ;测试用的。
call setup_idt
call setup_gdt
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; mov ss, ax
lss esp, [stack_start]
; mov esp, 0xff00 ;测试用的。
;下面测试A20地址线是否已经开启。采用的方法是向内存地址0x000000处写入任意一个
;数值,然后看内存地址0x100000(1M)处是否也是这个数值。如果相同,就一直比较下去
;也即是循环。呵呵,这里不太明白为什么会是这样。
xor eax, eax
lb: inc eax
mov [0x000000], eax
cmp [0x100000], eax
je lb
;注意!在下面这段程序中,486应该将位16置位,以检查在超级用户模式下的写保护,
;此后“verify_area()”调用中就不需要了。486的用户通常也会想将NE(#5)置位,
;一边对数学协处理器的出错使用 int 16。
mov eax, cr0
and eax, 0x80000011
or eax, 2
mov cr0, eax
call check_x87
jmp after_page_tables
; 依赖ET标志的正确性来检测287/387存在与否。
check_x87:
fninit
fstsw ax
cmp al, 0
je lf
mov eax, cr0
xor eax, 6
mov cr0, eax
ret
align 2
lf: db 0xDB, 0xE4
ret
;下面这段是设置中断描述符表子程序 setup_idt
;将中断描述符表idt设置成具有256个项,并都指向ignore_int中断门。然后加载中断
;描述符表寄存器(用lidt指令)。真正实用的中断门以后在安装。当我们在其他地方认为
;一切都正常时在开启中断。盖子程序将会被页表覆盖掉。
setup_idt:
mov edx, ignore_int - $$ ;lea edx, ignore_int
mov eax, 0x00080000
mov ax, dx
mov dx, 0x8E00
mov edi, idt - $$ ;lea edx, _idt
mov ecx, 256
rp_sidt:
mov [edi], eax
mov [edi+4], edx
add edi, 8
dec ecx
jne rp_sidt
lidt [idt_descr - $$]
ret
;设置全局描述表项setup_gdt
;这个子程序设置一个新的全局描述表项gdt,并加载。此时仅创建了两个表项,
;与前面的一样。该子程序只有两行。
;该子程序将被页表覆盖。
setup_gdt:
lgdt [gdt_descr - $$]
ret
;这里将内核的内存页表直接放在页目录之后,使用了4个表来寻址16Mb的物理内存。
;如果你有多于16Mb的内存,就需要在这里进行扩充修改。
times 4096 - ($ - $$) db 0 ;resb 0x1000 ;0rg 0x1000
pg0:
times 4096 db 0 ;resb 0x1000 ;org 0x2000
pg1:
times 4096 db 0 ;resb 0x1000 ;org 0x3000
pg2:
times 4096 db 0 ;resb 0x1000 ;org 0x4000
pg3:
times 4096 db 0 ;resb 0x1000 ;org 0x5000
;当DMA(直接存储器访问)不能访问缓冲块时,下面的tmp_floppy_area内存块
;就可供软盘驱动程序使用。其地址需要对其调整,这样就不会跨越64kB边界。
tmp_floppy_area:
times 1024 db 0 ;resb 1024
after_page_tables:
push 0
push 0
push 0
push L6 - $$
push main
; push L6 - $$ ;调试用的,接下来死循环。
jmp setup_paging
; call setup_paging
; push 0
; push 0
; jmp main
L6:
jmp $ ;应该不会从main中返回这里,不过为了以防万一,
;在这里设一条指令吧。
;下面是默认的中断“向量句柄”
int_msg:
db "Unknown interrupt. in the head.s", 0xa, 0x00
align 2
ignore_int:
push eax
push ecx
push edx
push ds
push es
push fs
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
; mov ss, ax
push int_msg - $$
call printk ;在/kernel/printk.c中。
pop eax
pop fs
pop es
pop ds
pop edx
pop ecx
pop eax
jmp $
iret
;这个子程序通过设置控制寄存器cr0的标志(PG位31)来启动对内存的分页处理功能,
;并设置各个页表的内容,以恒等映射前16MB的物理内纯。分页假定不会产生非法的
;地址映射(也即在只有4Mb的机器上设置出大于4Mb的内存地址)。
align 2
setup_paging:
mov ecx, 5*1024
xor eax, eax
xor edi, edi
cld
rep
stosd
mov word [pg_dir + 0x00 - $$], pg0 + 7 - $$
mov word [pg_dir + 0x04 - $$], pg1 + 7 - $$
mov word [pg_dir + 0x08 - $$], pg2 + 7 - $$
mov word [pg_dir + 0x0c - $$], pg3 + 7 - $$
mov edi, pg3+4092 - $$
mov eax, 0xfff007
std
lb2: stosd ;这里存在问题
sub eax, 0x1000
jge lb2
xor eax, eax
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
ret
align 2
dw 0
idt_descr:
dw 256*8-1
dd idt - $$
align 2
dw 0
gdt_descr:
dw 256*8-1
dd gdt - $$
align 8
times 0x6000 - ($ - $$) db 0
idt: times 256 dd 0, 0
gdt: dd 0x00000000
dd 0x00000000
dd 0x00000fff
dd 0x00c09a00
dd 0x00000fff
dd 0x00c09200
dd 0x00000000
dd 0x00000000
times 252 dd 0, 0
length equ $ - $$
[section .data]
[section .bss]