首页 > 其他 > 详细

Linux内核设计的艺术-从开机加电到执行main函数之前的过程

时间:2014-02-18 00:32:59      阅读:476      评论:0      收藏:0      [点我收藏+]

        我们假定本书所用的计算机是基于 IA—32 系列 CPU, 安装了标准单色显示器、 标准键 盘、一个软驱、一块硬盘、16 MB 内存,在内存中开辟了 2 MB 内存作为虚拟盘,并在 BIOS 中设置软驱为启动设备。后续所有的讲解都以此为基础。

      

        目前处于实模式下,内存地址为0x00000~0xFFFFF,共1MB,20位地址线,BIOS所占地址为0xFE000~0xFFFFF,在最末尾。开机加电,CS为0xF000,IP为0xFFF0,所以程序从0xFFFF0处开始执行。BIOS程序在0x00000~0x003FF放置了中断向量表,共4*256=1023个字节,所以共有256个中断向量。在0x00400~0x004FF放置了BIOS数据区,在0x0E05B~0x0FFFE处放置了中断服务程序。如下图所示:

bubuko.com,布布扣


        计算机硬件体系结构的设计与BIOS联手操作,会让CPU接收一个int 0x19的中断,CPU指向0x0E6F2,开始执行中断处理程序,将软驱0 号磁头对应盘面的 0 磁道 1 扇区的内容复制至内存0x07C00 ~0x07E00处。


       程序从0x07C00处开始执行bootsect.s

entry _start
_start:
	mov	ax,#BOOTSEG
	mov	ds,ax    !起始段寄存器
	mov	ax,#INITSEG
	mov	es,ax    !目的段寄存器
	mov	cx,#256 !移动的次数
	sub	si,si    !起始段偏移
	sub	di,di    !目的段偏移
	rep
	movw

       BOOTSEG为0x07C0,INITSEG为0x9000,将ds:si内存地址的内容,移动到es:di内存地址处,一共移动256次,一次是一个字,最后一共移了256*2=512字节。把0x07C00 ~0x07E00移动到0x90000~0x90200


        跳转,并重新设置段寄存器

jmpi	go,INITSEG
go:	mov	ax,cs
	mov	ds,ax
	mov	es,ax
! put stack at 0x9ff00.
	mov	ss,ax
	mov	sp,#0xFF00		! arbitrary value >>512
      当时 CS 的值为 0x07C0,执行完 这个跳 转后,CS 值变为 0x9000(INITSEG),IP 的值为从 0x9000(INITSEG)到 go: mov ax, cs 这一行对应指令的偏 移。所以程序就转到执行 0x90000 这边的代码了。

      上述代码的作用是通过 ax,用 CS 的值 0x9000 来把数据段寄存器(DS)、附加段寄存器 、栈基址寄存器(SS)设置成与代码段寄存器(CS)相同的位置,并将栈顶指针 SP 指 (ES) 向偏移地址为 0xFF00 处。



       读入第二扇区开始的4个扇区

load_setup:
	mov	dx,#0x0000		! drive 0, head 0
	mov	cx,#0x0002		! sector 2, track 0
	mov	bx,#0x0200		! address = 512, in INITSEG
	mov	ax,#0x0200+SETUPLEN	! service 2, nr of sectors
	int	0x13			! read it
	jnc	ok_load_setup		! ok - continue
	mov	dx,#0x0000
	mov	ax,#0x0000		! reset the diskette
	int	0x13
	j	load_setup

ok_load_setup:

       上面的int 0x19中断是机器自动产生的,现在是我们自己手动配置int 0x13参数,将软盘第二扇区开始的 4 个扇区,即 setup.s 对应的程序加载至内存的0x90200~0x90A00处。


        然后又调用int 0x13终端,软盘第六扇区开始的 约 240 个扇区(共120KB)的 system 模块加载至内存的 0x10000~0x2E000中。


      获取根设备号

	seg cs                          
	mov	ax,root_dev             !cs:root_dev地址内容付给ax
	cmp	ax,#0                   !此时并不为0,为0x306,所以跳转到root_defined
	jne	root_defined
	seg cs
	mov	bx,sectors              !如果没有设置,那么由扇区数去决定
	mov	ax,#0x0208		! /dev/ps0 - 1.2Mb
	cmp	bx,#15
	je	root_defined
	mov	ax,#0x021c		! /dev/PS0 - 1.44Mb
	cmp	bx,#18
	je	root_defined
undef_root:
	jmp undef_root
root_defined:
	seg cs
	mov	root_dev,ax

.org 508               !偏移是508
root_dev:
	.word ROOT_DEV
boot_flag:
	.word 0xAA55   !引导盘最后两个字节必须是0xAA55

        

       bubuko.com,布布扣


        跳转到setup.s执行

     jmpi	0,SETUPSEG
       

       setup.s首先做的第一件事情就是利用 BIOS 提供的中断服务程序从设备 上提取内核运行所需的机器系统数据,这些机器系统数据被加载到内存的0x90000 ~ 0x901FC (覆盖了0x90000~0x90200)位置


       关中断

     cli;
       如果没有 cli,又恰好发生中断,如用户不 小心碰了一下键盘,中断就要切进来,就不得不面对实模式的中断机制已经废除、保护模式 的中断机制尚未完成的尴尬局面,结果就是系统崩溃。


      移动system模块

	mov	ax,#0x0000
	cld			! ‘direction‘=0, movs moves forward
do_move:
	mov	es,ax		! destination segment
	add	ax,#0x1000
	cmp	ax,#0x9000      !末尾是0x80000~0x8FFFF
	jz	end_move
	mov	ds,ax		! source segment
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000      !每次共移动32764*2=64KB
	rep
	movsw                   !移动一个字
	jmp	do_move
         将ds:si内存地址的内容,移动到es:di内存地址处,一共移动32764*8次,一次是一个字,最后一共移了32764*8*2=512KB。把0x10000 ~0x8FFFF(共512KB)移动到0x00000~0x7FFFF(共512KB)。在本例中实际上就是0x10000~0x2E000移动到0x00000~0x1E000处。
         这样做能取得“一箭三雕”的效果 : 

         1)废除 BIOS 的中断向量表,等同于废除了 BIOS 提供的实模式下的中断服务程序。 

         2)收回刚刚结束使用寿命的程序所占内存空间。 

         3)让内核代码占据内存物理地址最开始的、天然的、有利的位置。


         对中断描 述符表寄存器(IDTR)和全局描述符表寄存器(GDTR)进行初始化设置

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

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		! 每个是8个字节,一共256个,所以是2048字节,一共可以有256个gdt
	.word	512+gdt,0x9	! 偏移为0x9<<16+0x200+gdt
bubuko.com,布布扣
        Segment Limit 0x07FF            2048*4096=8Mb(因为界限粒度为4K 字节

        Segment Base(23-0) 0x000000

        Arributes 0xC09A

        G=1 表示界限粒度为4K 字节

        D=1 表示是32位

        P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中

        DPL为00,表示内核态

        TYPE为A ,表示代码段,执行/读

        Segment Base(31-24) 0x00

       此部分请参考http://blog.csdn.net/jltxgcy/article/details/865610

       所以0x8选择子对应代码段描述符,0x10选择子对应数据段描述符,选择子的格式请参考http://blog.csdn.net/jltxgcy/article/details/8656101


       打开A20地址线

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

         

      设置中断,参考http://blog.csdn.net/jltxgcy/article/details/8661959

	mov	al,#0x11		!往端口20h(主片)写入ICW1
	out	#0x20,al		
	.word	0x00eb,0x00eb		
	out	#0xA0,al		!往端口A0h(从片)写入ICW1
	.word	0x00eb,0x00eb

	mov	al,#0x20		!往端口21h(主片)写入ICW2
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x28	   	!往端口A1h(从片)写入ICW2
	out	#0xA1,al
	.word	0x00eb,0x00eb

	mov	al,#0x04		!往端口21h(主片)写入ICW3
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x02		!往端口A1h(从片)写入ICW3
	out	#0xA1,al
	.word	0x00eb,0x00eb

	mov	al,#0x01		!往端口21h(主片)写入ICW4
	out	#0x21,al
	.word	0x00eb,0x00eb		
	out	#0xA1,al		!往端口A1h(从片)写入ICW4
	.word	0x00eb,0x00eb

	mov	al,#0xFF		!遮蔽主8259所有中断,写入OCW1
	out	#0x21,al
	.word	0x00eb,0x00eb		!遮蔽从8259所有终端,写入OCW1
	out	#0xA1,al
      

       将 CR0 寄存器第 0 位 (PE)置 1,即设定处理器工作方式为保护模式

mov	ax,#0x0001	! protected mode (PE) bit
	lmsw	ax		! This is it!


       跳转到程序开始处,0x0,此时已经开启了保护模式,要采用保护模式下的寻址方式
jmpi	0,8		! jmp offset 0 of segment 8 (cs)


       开始执行head.s

bubuko.com,布布扣





        

Linux内核设计的艺术-从开机加电到执行main函数之前的过程

原文:http://blog.csdn.net/jltxgcy/article/details/19324619

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!