以前在linux内核之旅上下的一个只有引导代码,只初始化了中断和页表的一个"OS"。。
这几天偶然翻出来了,感觉还挺好玩的,贴一下代码,加了一些注释。希望我做的这些能够帮助到大家。
sagalinux源代码可以从这里得到。
解压出之后,在根目录运行
$make
$make bootdisc
会在当前目录下生成一个kernel.img
要运行这个“OS”的话需要装一个bochs,然后使用如下的配置文件bochsrc,我使用的是ubuntu,其他系统可能会稍有差别。
bochsrc
###############################################################
# bochsrc.bxrc file for sagalinux.
###############################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latest
#, address=0xf0000
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
#VGABIOS-elpin-2.40
# what disk images will be used
floppya: 1_44=kernel.img, status=inserted
# choose the boot disk.
cpu: count=1,ips=15000000
boot: a
# where do we send log messages?
log: bochsout.txt
# disable the mouse
mouse: enabled=0
# enable key mapping, using US layout as default.
keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-us.map
使用命令
$bochs -qf bochsrc
如果一切没错的话,按c(continue)就可以运行sagalinux了。
你会看到一个假的用户界面如下,只能打字。
在根目录下运行
$ls
boot Changelog copying include kernel kernel.bin kernel.img lib Makefile System.map tags todo Troublelog
只有四个目录./boot、/kernel、/include、/lib
/include目录是头文件的一些定义。
/lib目录内是库函数的实现,比如printf。
我们主要就来分析/boot和/kernel目录。
/boot用于系统的启动,/kernel目录则是sagalinux的核心了。
这是根目录下的Makefile,先看一下以把握系统的构成。
all: boot/boot.bin kernel.bin
clean:
(cd kernel;make clean)
(cd lib;make clean)
(cd boot;make clean)
(rm *.bin *.img *~ System.map)
boot/boot.bin:
(cd boot;make)
lib/vsnprintf.o:
(cd lib;make)
kernel/page.o:
kernel/kernel.o:
(cd kernel;make)
kernel.bin: kernel/kernel.o kernel/page.o lib/vsnprintf.o
ld -Map System.map --oformat binary -emain -Ttext 0x7000 -okernel.bin kernel/kernel.o kernel/console.o kernel/page.o kernel/i8259.o kernel/idt.o kernel/printk.o lib/vsnprintf.o kernel/keyboard.o kernel/irq.o
#-Ttext 0x7000表示代码从内存段基地址0x7000开始
bootdisc: boot/boot.bin kernel.bin
cat boot/boot.bin boot/setup.bin kernel.bin > kernel.img //生成软盘映像
# boot.bin:512B 1sector; setup.bin:512*3B 2~4sector kernel.bin 5~36sector
dd if=kernel.img of=/dev/fd0 bs=512
这一篇我们来分析/boot目录。
下面是对/boot目录下文件的分析,慢点看。。
/boot目录下只有一个Makefile,boot.s和setup.s
编译出来的boot.bin和setup.bin用于初始化中断,读软盘数据至内存,初始化全局描述符表,进入保护模式,装载内核代码。最后跳到内核代码的内存地址上去。
这是/boot下的Makefile,一看就明白了
CFLAGS = -c -Wall -o
all: boot.bin setup.bin
clean:
rm -f *.bin *.o *~
boot.bin:
nasm -fbin -o boot.bin boot.s
setup.bin:
nasm -fbin -o setup.bin setup.s
boot.s代码
;这段16位代码即512B的MBR,将编译为为第一个扇区的数据
[BITS 16]
[ORG 0]
;占第0个扇区
jmp start
;EQU可以理解成宏
BOOTSEG EQU 0x07c0
INITSEG EQU 0x9000
SETUP EQU 0x9020
SYSSEG EQU 0x1000
bootmsg db 'Loading',0
dot db '.',0
;知识背景:计算机BIOS首先会将MBR(硬盘第一个扇区的数据)装入0x7c00并执行
start: ;将启动扇区从内存地址0x07c00~0x07e00处复制至0x90000~0x90200处,并跳转到0x90000,执行启动扇区程序.
mov [bootdrive], dl ;mov [bootdriver], 0,存储驱动器号
mov ax, BOOTSEG ;0x07c0,16位代码采用段:段内偏移来寻址
mov ds, ax
mov ax, INITSEG ;mov ax, 0x9000
mov es, ax
xor di, di
xor si, si
mov cx, 0x0200 ; Bootsector is 512 bytes. 2^9bytes
cld ;清标志位,使si,di增
rep ;ds:si -> es:di 移动cx次
movsb
jmp INITSEG:go ;go为段内偏移,jmp INITSEG:go这句还是在0x7c00处的段内执行,执行这句后就跳到了0x9000处的段内执行
go: ;进行必要初始化,载入第二阶段程序(setup.s),跳转,执行.
mov ax, INITSEG
mov ds, ax
mov es, ax
cli
mov ss, ax
mov sp, 0xeeee
sti
mov si, bootmsg ;bootmsg偏移地址-->si
call write_message
mov ax, INITSEG
mov es, ax
mov bx, 0x0200
call reset_drive
call start_loading ;装载软盘上的setup.s代码到内存地址0x90200处
jmp INITSEG: 0x0200 ; setup loaded at 0x90200 跳到内存地址0x90200处执行代码setup.s
reset_drive: ;重置驱动器
push ax
mov ah, 0 ;reset the disk
int 0x13
pop ax
ret
start_loading: ; 读扇区号为0x02,0x03,0x04三个扇区(setup.s代码),放入0x90200起始的内存中,0x90200~0x90800
mov ah, 0x02 ; ah = read function at int 13h, 2号功能,读扇区
mov al, 0x03 ; al = the number of sectors to read 共读扇区数的记录
mov ch, 0 ; ch = track number 柱面
mov cl, 0x02 ; cl = starting sector 起始扇区号
mov dl, [bootdrive] ; dl = drive number
mov dh, 0 ; dh = head number 磁头号
read_one_sector: ;将读出的数据放入es:bx中
push ax
mov al, 0x1 ;扇区数
mov ah, 0x2 ;int 12h 2号功能,将读出的数据放入内存es:bx中0x90200
int 0x13
push bx
mov si, dot ;读一个扇区就打印一个'.'
call write_message
pop bx
pop ax
inc cl ; increment sector value增加扇区号
dec al ; decrement the number of sectors to read减少剩下要读的扇区数
add bx, 0x200 ; increment the offset增加内存地址es:bx
cmp al, 0 ; check al if all the sectors read
je load_finished
call reset_drive ;每次读一个扇区后就复位依次软盘
jmp read_one_sector ; read next sector
load_finished:
call reset_drive
ret ;装载setup.s代码完毕
; -------------------------------------------------------------
;这里会使用ax和bx,调用前ax,bx需入栈
write_message: ;si中存放字符串偏移地址
lodsb ; ds:si is read to al读ds:si上的1B到al
cmp al, 0x0 ;字符串结束符
jz end_message
mov ah, 0x0E ; teletype Mode
mov bx, 0007 ; white on black attribute
int 0x10 ;调用BIOS中断,打印字符
jmp write_message
end_message:
ret
; -------------------------------------------------------------
bootdrive db 0
times 510-($-$$) db 0 ;填充文件 0.使最后编译出的代码为512B
dw 0xAA55 ;以AA55结束.
注意最后一句指令,这里只是简单的把剩余的空间填充为0,真正的引导扇区结构是这样的,而且,引导扇区与操作系统平台无关。图来自网络。
下面是setup.s代码
[BITS 16]
;这段代码最终编译出的二进制代码位于软盘上02 03 04 号扇区内,会被boot.s代码载入到内存0x90200处并由boot.s代码跳入执行.
SETUP EQU 0x9020
sector_end EQU 18 ; hardcoded for now
head_end EQU 1 ; drive geometry will be taken from BIOS later
start_setup:
mov ax, SETUP
mov es, ax ;装载段基址
mov ds, ax
mov ax, 0x0700 ;把内核从扇区载入到0x7000h~0x74000h
mov es, ax
mov bx, 0x0000
call reset_drive
call start_loading ;load the kernel code,内核代码位于5~36扇区共32个扇区,读至内存es:bx处
mov cx, 0xFFFF
kill_motor:
mov dx,0x3f2
mov al,0x0
out dx,al
loop kill_motor
enable_a20: ;打开A20地址位,处于兼容原因.
cli
call wait_keyboard
mov al, 0xD1
out 0x64, al
call wait_keyboard
mov al, 0xDF
out 0x60, al
call wait_keyboard
sti
set_gdt: ;将全局描述符表复制到内存0x80000处
mov ax, 0x8000
mov es, ax
mov di, 0x0
mov si, gdt
mov cx, gdt_end-gdt
cld
rep movsb ;ds:si --> es:di 移动cx次
lgdt [gdtr] ;将gdt基址和表大小装载至gdtr寄存器
mov si, a20_ok
call write_message
mov si, gdt_ok
call write_message
pic_init: ;初始化中断芯片8259,这里就是固定的操作了,不细说了。
cli
mov al, 0x11 ; initialize PICs
out 0x20, al ; 8259_MASTER
out 0xA0, al ; 8259_SLAVE
mov al, 0x20 ; interrupt start 32
out 0x21, al
mov al, 0x28 ; interrupt start 40
out 0xA1, al
mov al, 0x04 ; IRQ 2 of 8259_MASTER
out 0x21, al
mov al, 0x02 ; to 8259_SLAVE
out 0xA1, al
mov al, 0x01 ; 8086 Mode
out 0x21, al
out 0xA1, al
mov al, 0xFF ; mask all
out 0x21, al
out 0xA1, al
sti
mov si, pic_ok
call write_message
protected_mode:
mov si, pm_ok
call write_message
cli
mov ax, 0x0001 ;装入机器状态字.将CR0寄存器最低位置1,进入保护模式
lmsw ax
mov ax, 0x10 ; set selectors to data segment,置选择子指向数据段
mov ds, ax ;装入描述符表中的索引值,亦称为装入选择子
mov ax, 0x10
mov es, ax
mov ax, 0x10
mov ss, ax
mov esp, 0xFFFF
push dword forever ; 废止实模式下的预取指令.
jmp 0x8:0x7000 ;最后代码由此跳走了....选择子0x8指示的段为物理内存0~16M的只读可执行的代码段,偏移为0x7000,也就是跳入了内核代码,
forever: ; Ooops! Houstin we have a problem ,程序出错才会跳至此处
jmp forever
;;----------- READ_START------------------------|
reset_drive:
push ax
mov ah, 0
int 0x13
pop ax
ret
start_loading: ;read the kernel 5~37共32个扇区,将内核载入至0x70000~0x74000
mov ah, 0x02 ; ah = read function at int 13h
mov al, 0x20 ; al = the number of sectors to read读32个扇区
mov ch, 0 ; ch = track number
mov cl, 0x05 ; cl = starting sector
mov dl, 0x0 ; dl = drive number
mov dh, 0 ; dh = head number
read_one_sector: ;读扇区到内存es:bx,在boot.s中有类似的代码
push ax ;软盘规格:2head*80cylinder*18sector*512B=1.44M
mov al, 0x1
mov ah, 0x2
int 0x13
pop ax
inc cl ; increment sector value
dec al ; decrement the number of sectors to read
add bx, 0x0200 ; increment the offset
push ax
mov si, dot ;读一个扇区打印一个'.'
call write_message
pop ax
cmp al, 0
je load_finished
cmp cl, sector_end ;18个扇区读完,需换磁头
je next_head
call reset_drive
jmp read_one_sector ; read next sector
next_head:
push ax
mov si, dot
call write_message
pop ax
inc dh ;增加磁头号,到软盘反面去读扇区
mov cl, 0x0 ;重置扇区号为0
call reset_drive
jmp read_one_sector
load_finished:
call reset_drive
ret
;;----------- READ_END -------------------------|
write_message:
lodsb ; al = DS:[SI] and SI++
or al, al
jz end_message
mov ah, 0x0E ; teletype Mode
push bx
mov bx, 0007 ; white on black attribute
int 0x10
pop bx
jmp write_message
end_message:
ret
wait_keyboard:
in al, 0x64
test al, 2
jnz wait_keyboard
ret
gdtr: ;此处的值,将被装入gdtr寄存器
dw gdt_end - gdt - 1 ; gdt size is one less than the total descriptor sizes
dd 0x80000 ; pyhsical address of gdt全局描述符表将被存储在内存0x80000处
gdt: ;此处将被装入内存地址0x80000h处一个全局描述符占8B,每个描述符描述了一个段
dd 0x00000000 ; 第一个8B没有被使用
dd 0x00000000
dd 0x00000FFF ;段界线为16M,是只读的执行代码段.
dd 0x00C09A00
dd 0x00000FFF ;段界线为16M,是可写的数据码段.
dd 0x00C09200
gdt_end:
a20_ok db 13,10,'A20 Line [ OK ]',13,10,0
gdt_ok db 'Global Descriptor Table [ OK ]',13,10,0
idt_ok db 'Interrupt Descriptor Table [ OK ]',13,10,0
pic_ok db 'PIC Initialized [ OK ]',13,10,0
pm_ok db 'Protected Mode [ OK ]',13,10,0
dot db '.', 0
times 1536-($-$$) db 0 ;填充0,代码占3个扇区,3*512=1536
以下分别是boot.s和setup.s的流程,图来自《linux内核之旅》
基本上所有硬件相关的操作就完成了。接下来会贴出其他代码。
贴图如有冒犯请联系,谢谢。