sagalinux学习之/boot目录

以前在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内核之旅》



基本上所有硬件相关的操作就完成了。接下来会贴出其他代码。

贴图如有冒犯请联系,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值