Linux0.11内核源代码(2)

INITSEG  = 0x9000	! we move boot here - out of the way
SYSSEG   = 0x1000	! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020	! this is the current segment
start:
	mov	ax,#INITSEG	
	mov	ds,ax
	mov	ah,#0x03
	xor	bh,bh
	int	0x10		! 
	mov	[0],dx		! 
	mov	ah,#0x88
	int	0x15
	mov	[2],ax		!
	mov	ah,#0x0f
	int	0x10
	mov	[4],bx		! 
	mov	[6],ax		! 
	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax
	mov	[10],bx
	mov	[12],cx
上面的代码主要对硬件进行检测,为了保证所有的段寄存器都指向正确的内存地址,代码起始位置首先重新设置一下段寄存器。由于bootsect已经不再有使用价值,所以可以重新覆盖用于保存检测到的一些硬件信息。首先利用int 0x10得到当前光标的信息,并将相应的信息存放到INITSEG段的起始位置。http://www.ctyme.com/intr/rb-0088.htm

int 0x15用于获取系统的扩展内存大小,也就是绝对地址超过1M的内存的大小。大小按照KB为单位保存在AX中,也就是扩展内存最大位64MB。http://www.ctyme.com/intr/rb-1529.htm

int 0x10用于返回当前显示模式,AX返回显示的显示的列数墓,BX则是当前显示的活动页。http://www.ctyme.com/intr/rb-0108.htm。接下来的int 0x10则用于返回显示内存,以后的显示打印可以通过这一部分内存进行直接的显示。

mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0080
	mov	cx,#0x10
	rep
	movsb
	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb
	mov	ax,#0x01500
	mov	dl,#0x81
	int	0x13
	jc	no_disk1
	cmp	ah,#3
	je	is_disk1
no_disk1:
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	mov	ax,#0x00
	rep
	stosb
is_disk1:
	cli			! no interrupts allowed !
	mov	ax,#0x0000
	cld			! 'direction'=0, movs moves forward
do_move:
	mov	es,ax		! destination segment
	add	ax,#0x1000
	cmp	ax,#0x9000
	jz	end_move
	mov	ds,ax		! source segment
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000
	rep
	movsw
	jmp	do_move
end_move:
代码开始首先测试是否存在硬盘,两个硬盘参数存放在中断向量0x41和0x46中。因为英特尔体系架构下总共可以有256个中断。其中系统保留使用的有0x0-0x31则用于保留给异常使用,而剩下的0x32-0x47则用于系统中的可编程中断,剩下的可以由用户任意分配。因此这里的0x41和0x46号中断向量存放着找到硬盘信息的地址应该是一个默认的惯例。不过这也是因为Linux中的MBR部分不规范所导致的,按照规范的可引导MBR中,应该存放BPB参数表,参数表中包含相应的磁盘信息。但是这样在安装的时候需要收集磁盘的相关信息。int 0x13用于检测磁盘的格式(这里的第二个磁盘是否是除软盘外的第二个磁盘),如果第二个盘不存在则会将它的磁盘格式表进行清零操作。在检测得到所有的硬件信息后,将system中复制到0x0000段位置处。
end_move:
	mov	ax,#SETUPSEG	! right, forgot this at first. didn't work :-)
	mov	ds,ax
	lidt	idt_48		! load idt with 0,0
	lgdt	gdt_48		! load gdt with whatever appropriate
	call	empty_8042
	mov	al,#0xD1		! command write
	out	#0x64,al
	call	empty_8042
	mov	al,#0xDF		! A20 on
	out	#0x60,al
	call	empty_8042
	mov	al,#0x11		! initialization sequence
	out	#0x20,al		! send it to 8259A-1
	.word	0x00eb,0x00eb		! jmp $+2, jmp $+2
	out	#0xA0,al		! and to 8259A-2
	.word	0x00eb,0x00eb
	mov	al,#0x20		! start of hardware int's (0x20)
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x28		! start of hardware int's 2 (0x28)
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x04		! 8259-1 is master
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x02		! 8259-2 is slave
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x01		! 8086 mode for both
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0xFF		! mask off all interrupts for now
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al
	mov	ax,#0x0001	! protected mode (PE) bit
	lmsw	ax		! This is it!
	jmpi	0,8		! jmp offset 0 of segment 8 (cs)
empty_8042:
	.word	0x00eb,0x00eb
	in	al,#0x64	! 8042 status port
	test	al,#2		! is input buffer full?
	jnz	empty_8042	! yes - loop
	ret
gdt:
对8042的设置可以防止系统内存的回卷,为了兼容以前的系统,在后续的英特尔架构下同样可以访问1M以及1M以上的内存。然而,这就不好区分是在实模式下访问1M以上内存还是在保护模式下访问1M以上内存。因此需要设置一个标志,防止系统在保护模式下访问1M以上内存出现回卷。这个标志就是设置键盘中的8042中控制寄存器的第二位为1。至于8042的操作就不具体介绍了,需要注意的是在进行这些处理时需要在关中断情况下进行处理。还有另外一个地方的0x00eb,起始是一个指令码。这个指令码表示向前跳转两个字节,主要用于同步。为了初始化8259中断控制器,总共发出了四个ICW命令控制字,除了ICW3之外用于区分8259的主从之分外,其他的都是一样的。最后将两个8259中的中断给屏蔽掉。然后跳转到32位代码处开始执行。在英特尔架构下CR0是一个控制寄存器,用于控制当前处理器的状态,比较重要的有两个位,一个是PE开启保护模式,另一个是PG开启页式内存管理。

gdt:
	.word	0,0,0,0		! dummy
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9A00		! code read/exec
	.word	0x00C0		! granularity=4096, 386
	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9200		! data read/write
	.word	0x00C0		! granularity=4096, 386
idt_48:
	.word	0			! idt limit=0
	.word	0,0			! idt base=0L
gdt_48:
	.word	0x800		! gdt limit=2048, 256 GDT entries
	.word	512+gdt,0x9	! gdt base = 0X9xxxx
进入到下一个话题之前,首先需要对上面这些结构进行分析。需要注意的第一点是英特尔是小端模式,因此高位在内存的高位地址。而GDTR中包含48位数据,32位为基地址找到对应的全局段寄存器,而16位作为GDTR的上限;IDTR也是类似的。因此GDTR给定的基地址是0x90512+gdt(也就是上面定义的gdt表在内存中的位置)。由于此时中断被禁止所以是一个无用的数值。gdt中第一个表项被设置为全部为0,是英特尔默认这一项无用。 Linux0.11内核源代码(2)_第1张图片

左图是每一个段的表项的每一个位的解释,整个结构体单元被划分的稀烂。重点看三个,基地址,上限以及DPL。gdt的第二项的基地址是0x0,而上限时0x07ff;dpl则为0。而这正好和之前的长跳转相符,因此下一步的system代码将会在段基地址位0x0,上限为0x07ff处运行,正好和复制时0x8000一致。另外,需要注意的一点是第三个gdt表项,这个表项在接下来的处理中有着隐含的含义。另外需要注意的是,在jmpi指令之前,只需要直接按照原来的存取方式进行读取就可以了。

你可能感兴趣的:(linux)