以前在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
基本上所有硬件相关的操作就完成了。接下来会贴出其他代码。
贴图如有冒犯请联系,谢谢。