Linux内核启动(2,0.11版本)内核启动前的苦力活与内核启动

内核启动前的工作

在上一章的内容中,我们跳转到了setup.s的代码部分,这章我们先讲一讲setup做了什么吧

entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.

	mov	ax,#INITSEG	! this is done in bootsect already, but...
	mov	ds,ax
	mov	ah,#0x03	! read cursor pos
	xor	bh,bh
	int	0x10		! save it in known place, con_init fetches
	mov	[0],dx		! it from 0x90000.
! Get memory size (extended mem, kB)

	mov	ah,#0x88
	int	0x15
	mov	[2],ax

! Get video-card data:

	mov	ah,#0x0f
	int	0x10
	mov	[4],bx		! bh = display page
	mov	[6],ax		! al = video mode, ah = window width

! check for EGA/VGA and some config parameters

	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax
	mov	[10],bx
	mov	[12],cx

! Get hd0 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0080
	mov	cx,#0x10
	rep
	movsb

! Get hd1 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb

! Check that there IS a hd1 :-)

	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

这是这个程序最一开始的几行代码,他们做了什么呢,首先获取了光标位置,然后作为一个字存到了0x90000。同理,获取了扩展内存的大小、一些显示类的信息和硬盘参数列表。

硬盘参数表是什么?在PC机中BIOS设定的中断向量表中int 0x41的中断向量位置存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。对于BIOS来说,这里存放着硬盘参数表阵列的首地址0xFE401。第二个硬盘的基本参数表入口地址存于int 0x46中断向量位置处。每个硬盘参数表有16个字节大小。这些是硬件的相关知识,了解明白即可。

这些内核运行所需的机器系统数据,其中包括光标的位置,显示页面等数据,被加载到了内存的 0x90000 - 0x901FC的位置上,咦,这段位置熟悉不,熟悉吧,这不就是bootsect在的位置嘛,没错,他已经没用了,所以就被覆盖掉了。。。。好惨 :-),没用了就被覆盖了,而且你计算一下,这个空间一共也就512字节,新来的数据覆盖了510字节,也就两个字节幸免于难了,所以说,操作系统对于内存的使用是非常严谨的。

从实模式到保护模式

操作系统想要在32位保护下工作,这期间需要做大量的重建工作,并且持续到操作系统的main函数的执行过程

关中断并将system移动到内存地址起始位置(0x00000
! now we want to move to protected mode ...

	cli			! no interrupts allowed !

! first we move the system to it's rightful place

	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

! then we load the segment descriptors

首先使用cli指令屏蔽中断,准备开始乾坤大挪移,将system模块移动到想要的位置(内存0地址处)。但是我们还记得0处存放着什么嘛,没错不是还放着BIOS的中断向量表和中断响应程序嘛,是的,被覆盖了,这也是为什么要关中断的原因,毕竟这个时候不关中断突然来一个中断不就死机了嘛。

EFLAGS寄存器是标志寄存器,32位,包含状态标志,系统标志以及控制标志,cli就是改变了第9位IF中断允许标志位。

设置中断描述符表和全局描述符表

此时还需要通过setup程序自身提供的数据信息对中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR)进行初始化设置。

GDT,即全局描述表(GDT Global Descriptor Table)

IDT,即中断描述符表(IDT interrupt Descriptor Table)

在实模式下,对内存地址的访问是通过Segment:Offset的方式来进行的,其中Segment是段的Base Address,一个Segment的最大长度是64 KB,这是16-bit系统所能表示的最大长度。在实际编程的时,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)来指定Segment,CPU将段寄存器中的数值向左偏移4-bit,放到20-bit的地址线上就成为20-bit的Base Address。

到了保护模式,内存的管理模式分为两种:段模式和页模式,其中页模式也是基于段模式的。也就是说,保护模式的内存管理模式事实上是:纯段模式和段页式。进一步说,段模式是必不可少的,而页模式则是可选的——如果使用页模式,则是段页式;否则是纯段模式。

既然是这样,我们就先不去考虑页模式。对于段模式来讲,访问一个内存地址仍然使用Segment:Offset的方式。由于保护模式运行在32位系统上,那么Segment的两个因素:Base Address和Limit也都是32位的。另外,保护模式,顾名思义,又为段模式提供了保护机制,也就说一个段的描述符需要规定对自身的访问权限(Access)。所以,在保护模式下,对一个段的描述则包括3方面因素:[Base Address, Limit, Access],即基地址,偏移地址与权限,它们加在一起被放在一个64-bit长的数据结构中,被称为段描述符。这种情况下,我们如果要通过一个64-bit段描述符来引用一个段的时候,就必须使用一个64-bit长的段寄存器装入这个段描述符。但Intel为了保持向后兼容,将段寄存器仍然规定为16-bit(尽管每个段寄存器事实上有一个64-bit长的不可见部分,但对于程序员来说,段寄存器就是16-bit的),那么很明显,我们无法通过16-bit长度的段寄存器来直接引用64-bit的段描述符。

解决的方法就是把这些长度为64-bit的段描述符放入一个数组中,而将段寄存器中的值作为下标索引来间接引用(事实上,是将段寄存器中的高13-bit的内容作为索引)

GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。

有关具体的分段与分页的信息可以参考我的另一篇博客,理论篇

! then we load the segment descriptors

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

我们来看看这所谓的idt_48gdt_48到底是啥:

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

其实就是几个字,将idtr寄存器设置为了全0,将gdtr寄存器的值设置为了如下形式

请添加图片描述

那GDT是个什么地址呢,程序内也给出了,也就是gdtr寄存器指向了这个位置

gdt:                                                                     ! 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

GDT是保护模式下管理段描述符的数据结构,对操作系统自身的运行以及管理调度进程有着重大意义。此时由于内核尚未真正运行起来,所以还没有进程,所以现在创建的GDT第一项为空,第二项为内核代码段描述符,第三项为内核数据段描述符其余为空

开启 A20 地址线
! that was painless, now we enable 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

这里得注意一下:A20地址线并不是打开保护模式的关键,只是在保护模式下,不打开A20地址线,你将无法访问到所有的内存。 这个又是为了保持兼容性出的幺蛾子。empty_8042这个函数的作用是测试8042状态寄存器

! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.

	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

这一段是对中断的重新编程,涉及到 intel 的硬件问题。这些代码看起来是真的晦涩难懂,其实就是将 int 0x00 - int 0x1F的中断重新映射了,因为这部分中断在保护模式下被 intel 保存用作内部中断和异常中断,不过所幸我们进入到保护模式了,Linus都说 that certainly wasn't fun

! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.
	mov	ax,#0x0001	! protected mode (PE) bit
	lmsw	ax		! This is it!
	jmpi	0,8		! jmp offset 0 of segment 8 (cs)

CR0的最后一位PE,控制着是否开启保护模式,如果置1,则么表示开启,此时CPU将开始进入全新的模式。但为什么用lmsw ax加载程序状态字的形式进行而不直接用mov cr0,ax呢?又是历史的包袱,仅仅是为了兼容罢了。

虽然此时的Linux还是只能支持16MB物理内存,但是其线性寻址空间已经是不折不扣的4GB了。

CPU转换为保护模式的一个重要特征就是根据GDT决定后续执行哪里的程序,我们在setup.s这个程序中也就结束了,不过怎么跳转到head.s还是需要讲一下的

	jmpi	0,8	

这一行代码中的0是段内偏移地址,8是保护模式下的段选择符,用于选择描述符表和描述符表项以及所要求的的特权级,我们将8表示为二进制1000,这里的最后两个00表示的是内核特权级,与之对应的就是用户特权级11,第三位0表示的是GDT,如果是1则表示LDT,1000的1表示所选表的1项,(GDT想好排序为0项,1项,2项。。)来确定代码段的段基址和段限长等,

段描述符结构

在保护模式下,对一个段的描述则包括3方面因素:[Base Address, Limit, Access],它们加在一起被放在一个64-bit长的数据结构中,被称为段描述符,段描述符的结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVpcHVBr-1676125525000)(C:\Users\LoveSS\Desktop\linux内核\picture\20160212173325995)]

我们还记得我们在gdt中放了什么嘛

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

我们用第一项举例,第一项翻译成二进制为

0000 0000 1100 0000 1001 0010 0000 0000 0000 0000 0000 0000 0000 0111 1111 1111

根据上面的图我们可以看出,我们的段基地址 0x00000000

段限长为 0x007FF * 4KB = 8MB

特权级为 0x00 内核特权级

最终的内存分布

为了理清头绪,我们需要再写一下我们的内核内存分布

内存位置内容
0x00000 - 0x064B8内核
0x064B9 - 0x8FFFF
0x90000 - 0x901FCsetup在内存中保存的信息
0x901FD - 0x901FF
0x90200 - 0x90A00setup程序
0x90A00 - 0x9FF00栈(栈指向0x9FF00),并未占完
0x9FF01 - 0xFDFFF
0xFE000 - 0xFFFFFBIOS启动块
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LyaJpunov

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值