linux 0.12引导启动程序

引导启动程序

91年16bit实模式的引导代码是Minix上的as86编译器编译的,现在改为as编译,进入32bit保护模式下后就是gas(现as)编译

linux 0.12引导启动程序_第1张图片

bootsect.S

linux 0.12引导启动程序_第2张图片
功能:

将自己从0x7c00:0移动到0x9000:0处,并且读入setup.s(4*512=2kb)到自己后面

将system模块读入到0x1000:0处,然后控制转移给setup.s

  • 在第一个扇区的倒数第6个字节开始分别是SWAP设备号和根文件系统设备号

setup.S

  1. 通过BIOS中断得到磁盘和内存的信息,以及设置当时的显示卡,并且将数据放在bootsect.s处(0x9000:0)

  2. 移动system模块到0x0000:0开始,

  3. 加载临时的GDT和IDT(head.s中重新设置)

  4. 通过键盘控制器端口开启A20地址线,

  5. 设置中断控制器8259A,

    硬件中断从0x20开始,从片接IR2引脚

    设置完后屏蔽所有中断,置位ISR寄存器

  6. CR0,第0位置位(进入保护模式),jmp刷新流水线,并且将控制转移到system模块的head.s

    关于显示卡设置略过,没意义

8259A中断控制器(设置太复杂

ICW1:设置成中断沿边沿触发,多片级联

ICW2:设置中断0-7对应中断号0x20-0x27,从片0x28-0x2f

ICW3:设置中断优先级0->(8-15)->(3-7),因为IR2连从片

ICW4:设置中断非自动结束,需要中断处理过程发送EOI

  • 大致原理:

    1. 设置初始化命令字
    2. 写入操作命令字

    优先级请求被选中,8259A发出INT信号给CPU,CPU发出的INTA信号响应,当CPU发出第2个INTA信号,通知8259A在数据总线上给出中断号

    1. 自动结束中断
    2. 手动(发送EOI命令),来复位ISR(正在服务寄存器)对应的中断位,从片也要发
#include "../include/linux/config.h"

INITSEG = DEF_INITSEG
SYSSEG  = DEF_SYSSEG
SETUPSEG= DEF_SETUPSEG

.globl begtext,begdata,begbss,endtext,enddata,endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

entry start
start:
	mov	ax,#INITSEG
	mov	ds,ax
	
!int 0x15功能0x88取得扩展内存存在0x90002处
	mov	ah,#0x88
	int	0x15
	mov	[2],ax

!0x12功能,获取EGA配置信息
	mov	ah,#0x12
	mov	bl,#0x10
	int 	0x10
	mov	[8],ax
	mov	[10],bx 
	mov	[12],cx
!
	mov	ax,#0x5019
	cmp	bl,#0x10
	je	novga
	call	chsvga
novga:
	mov	[14],ax
	mov	ah,#0x03
	xor	bh,bh
	int	0x10
	mov	[0],dx
	
!取显卡当前显示模式
	mov 	ah,#0x0f
	int	0x10
	mov	[4],bx
	mov	[6],ax

!取第一个硬盘的参数
	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]		!中断向量表中参数表地址hd0
	mov 	ax,#INITSEG
	mov	es,ax
	mov 	di,#0x0080		!存放到0x9000:0080
	mov 	cx,#0x10
	rep
	movsb
!hd1参数表地址
	mov 	ax,#0x0000
	mov 	ds,ax
	lds	si,[4*0x46]
	mov 	ax,#INITSEG
	mov 	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb
	
!检查系统是否有第2个硬盘
	mov 	ax,#0x1500
	mov 	dl,#0x81		!驱动器号0x81第一个硬盘
	int 	0x13
	jc	no_disk1
	cmp	ah,#3			!是否是硬盘
	je 	is_disk1
no_disk1:
	mov 	ax,#INITSEG		!2个存储介质不存在,
	mov 	es,ax
	mov 	di,#0x0090
	mov 	cx,#0x10
	mov 	ax,#0x00
	rep
	stosb
is_disk1:
	cli				!禁止外部中断
	
!首先将system移动到从内存地址0x0000开始
	mov 	ax,#0x0000
	cld
do_move:
	mov	es,ax
	add	ax,#0x1000
	cmp	ax,#0x9000
	jz	end_move
	mov	ds,ax			!初始化为0x1000:0x0
	sub	di,di
	sub	si,si
	mov	cx,#0x8000		!
	rep
	movsw
	jmp 	do_move

!加载段描述符
end_move:
	mov	ax,#SETUPSEG
	mov	ds,ax			!指向本程序
	lidt	idt_48			!加载IDT寄存器
	lgdt	gdt_48			!加载GDT寄存器

!开启A20地址线
	call	empty_8042		!键盘芯片,等待输入缓冲区为空
	mov	al,#0xD1
	out 	#0x64,al		
	call 	empty_8042
	mov	al,#0xDF
	out	#0x60,al
	call 	empty_8042		!

!主片端口0x20,0x21,从片端口0xA0-0xA1,	
	mov	al,#0x11
	out	#0x20,al		!ICW1=0x11
	.word	0x00eb,0x00eb
	out 	#0xA0,al		!从片
	.word	0x00eb,0x00eb
	
!Liux系统硬件中断从0x20开始
	mov	al,#0x20
	out	#0x21,al		!ICW2=0x20(对应中断号从0x20开始
	.word	0x00eb,0x00eb
	mov	al,#0x28
	out	#0xA1,al		!从片ICW2
	.word	0x00eb,0x00eb
	mov	al,#0x04		!主片ICW3=0x04
	out	#0x21,al		!
	
	.word	0x00eb,0x00eb
	mov	al,#0x02
	out	#0xA1,al		!从片ICW3

	.word	0x00eb,0x00eb
	mov	al,#0x01		
	out	#0x21,al		!主片ICW4=0x01

	.word	0x00eb,0x00eb
	out	#0xA1,al		!从片ICW4
	.word	0x00eb,0x00eb
	mov	al,#0xFF		!屏蔽所有的中断(+从
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al

!修改cr0,进入保护模式
	mov	ax,#0x0001
	lmsw	ax
	jmpi	0,8			!第一个段选择子=代码段描述符
!从上面开始机器去执行system中的代码
		
!当输入缓冲器为空时,执行写命令,(键盘控制器的端口作为与A20地址线,
empty_8042:
	.word	0x00eb,0x00eb
	in	al,#0x64		!读取AT键盘控制器状态寄存器
	test	al,#2			!
	jnz	empty_8042
	ret
	
!显示卡部分未写


gdt:
	.word	0,0,0,0
!内核代码段选择符0x08
	.word	0x07FF
	.word	0x0000
	.word	0x9A00
	.word	0x00C0
!内核数据段	0x10
	.word	0x07FF
	.word	0x0000
	.word	0x9200
	.word	0x00C0
idt_48:
	.word	0
	.word	0,0
gdt_48:
	.word	0x800
	.word	512+gdt,0x9		!0x9000:512+gdt

msg1:
	.ascii	"Press  to see SVGA-modes available or any other key to continue."
	db	0x0d,0x0a,0x0a,0x00
msg2:
	.ascii	"Mode:	COLSxROWS:"
	db	0x0d,0x0a,0x0a,0x00
msg3:
	.ascii	"Choose mode by pressing the corresponding number."
	db	0x0d,0x0a,0x00

!显卡特征数据串
idati:
	.ascii	"761295520"
idcandt:
	.byte	0xa5
idgenoa:
	.byte	0x77,0x00,0x66,0x99
idparadise:
	.ascii	"VGA="
!..................

.text
endtext:
.data
enddata:
.bss
endbss:

linux 0.12引导启动程序_第3张图片

head.s

后面的都是AT&T格式,采用gas编译,第6个扇区开始,

重新设置IDT,IDT用简单中断处理过程填满IDT表,避免找不到中断处理历程出错,(后续在改IDT)

重新设置GDT,仍是一个内核代码段一个数据段描述符,段长修改为16MB,和IDT一起放在head.s末尾

内核栈定义在/kernel/sched.c中(此时我还没看main.c后面代码)

初始化协处理器(浮点运算)

转跳到0x0540:0x0000处执行页目录和页表初始化

0x0000:0x0到0x0500:0作为1个页目录和4个页表的内存空间,紧跟1024个字节作为软盘缓冲区,(原来是head.s代码,现在接下来代码写覆盖了)

通过设置前16MB的线性地址和物理地址是一样的(经过分页部件转换后也一样)

将main参数和main地址入栈,控制转移到main中

.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:

startup_32:
	movl	$0x10,%eax	#数据段选择子
	mov	%ax,%ds
	mov	%ax,%es
	mov	%ax,%fs
	mov	%ax,%gs
#设置内核的栈,_stack_start定义在 kernel/sched.c中
	lss	_stack_start,%esp

#重新设置IDT和GDT
	call	setup_idt
	call	setup_gdt
	movl	$0x10,%eax
	mov	%ax,%ds
	mov	%ax,%es
	mov	%ax,%fs
	mov	%ax,%gs
	#重新设置内核栈
	lss	_stack_start,%esp
	
	xorl	%eax,%eax
1:
	incl	%eax
	movl	%eax,0x000000		#向0x0地址写入eax值
	cmpl	%eax,0x100000		#判断地址1M是否也是这个值,
	je	1b
	
	movl	%cr0,%eax
	andl	$0x80000011,%eax	#PG,PE,ET位
	orl	$2,%eax			#set MP位
	movl	%eax,%cr0
	call	check_x87
	jmp	after_page_tables	

	
#
check_x87:
	fninit				#初始化协处理器
	fstsw	%ax
	cmpb	$0,%al
	je	1f
	movl	%cr0,%eax
	xorl	$6,%eax
	movl	%eax,%cr0
	ret

.align 2
1:	.byte	0xDB,0xE4
	ret

#设置中断门描述符,然后填入IDT表中256项
setup_idt:
	lea	ignore_int,%edx
	movl	$0x00080000,%eax	#GDT代码段选择符
	movw	%dx,%ax			#低位是过程入口点0-15bit
	movw	$0x8E00,%dx		#中断门描述符属性
	lea	_idt,%edi
	mov	$256,%ecx
rp_sidt:
	movl	%eax,(%edi)		#
	movl	%edx,4(%edi)
	addl	$8,%edi
	dec	%ecx
	jne	rp_sidt			#!=0就转跳
	
	lidt	idt_descr		#加载IDTR
	ret
	
setup_gdt:
	lgdt	gdt_descr
	ret

.org	0x1000
pg0:
.org	0x2000
pg1:
.org	0x3000
pg2:
.org	0x4000
pg3:	

.org	0x5000
_tmp_floppy_area:
	.fill	1024,1,0
	
after_page_tables:
	pushl	$0
	pushl	$0
	pushl	$0
	pushl	$L6
	pushl	$_main
	jmp	setup_paging
L6:
	jmp	L6

int_msg:
	.asciz	"Unknown interrupt\n\r"	#每个字符串后面会添加NULL字符

.align 2	#2^2=4字节对齐
ignore_int:
	pushl	%eax
	pushl	%ecx
	pushl	%edx
	push	%ds
	push	%es
	push	%fs
	movl	$0x10,%eax	#数据段选择子
	mov	%ax,%ds
	mov	%ax,%es
	mov	%ax,%fs
	pushl	$int_msg	#int_msg地址入栈
	call	_printk		#此函数在/kernel/printk.c中
	popl	%eax		#int_msg地址给eax寄存器
	pop	%fs
	pop	%es
	pop	%ds
	popl	%edx
	popl	%ecx
	popl	%eax
	iret			#通用中断返回

.align 2
setup_paging:
	movl	$1024*5,%ecx	#1页目录+4页表清零
	xorl	%eax,%eax
	xorl	%edi,%edi
	
	cld;rep;stosl		#将EAX填入ES:EDI中

#填写页目录前4,分配所有的16Mb物理内存空间
	movl	$pg0+7,_pg_dir		#+7表示31的属性
	movl	$pg1+7,_pg_dir+4
	movl	$pg2+7,_pg_dir+8
	movl	$pg3+7,_pg_dir+12
#
	movl	$pg3+4092,%edi
	movl	$0xfff007,%eax		#16Mb-4096+7

	std
1:
	stosl
	subl	$0x1000,%eax
	jge 1b

#设置页目录表基础地址
	xorl	%eax,%eax
	movl	%eax,%cr3		#设置CR3,页目录基地址
	movl	%cr0,%eax
	orl	$0x80000000,%eax
	movl	%eax,%cr0
	ret				#转跳到main函数

.align 2
	.word	0		#4字节对齐,和下面组合起来=4字节
idt_descr:
	.word	256*8-1
	.long	_idt
.align 2
	.word	0
gdt_descr:
	.word	256*8-1
	.long	_gdt
.align 3
_idt:
	.fill	256,8,0
_gdt:
	.quad	0x0000000000000000
	.quad	0x00c09a0000000fff	#修改代码段段长度=16MB(下同
	.quad	0x00c0920000000fff	
	.quad	0x0000000000000000
	.fill	252,8,0

linux 0.12引导启动程序_第4张图片


关于A20地址线和8259A编程大致了解就好

参考<>和<>

seg指定段超越前缀
BIOS磁盘服务int 0x13
汇编jXX转移指令参考
BIOS int 0x10中断
Intel 8042键盘控制器详细介绍
IA-32指令手册

你可能感兴趣的:(编写操作系统之路)