Akaban操作系统(2)-----Loader,进入64位模式

64位模式听起来就很厉害呢,看它是32位的整整一倍呢,寻址能力也由4G到达了16EB呢!

太天真了,没有16EB(没有面包但是有土地),它要有我自己吃了它,满意了吧!

但是,有最大2^52=1M*1MB=1T*1000B=1PB(这还是好的,虽然是线性地址,据说地址线有的也有52位的,2^40次方就只有1TB寻址大小呢)大小的寻址能力呢,比32位好就是!

What`s up!神马玩意说好的64位呢不应该是2^64=16EB么怎么整整缩了2^12,啊,假如intel欺骗了你,不要悲伤,不要心急,忧郁的日子将会过去,希望......

吐槽完毕,回归正题intel这些生产CPU的公司估计为了节省经费少12根地址线少了整整多少寻址大小,额,自己算去吧敲敲计算机的事,有句话好叫积小流,得以成江海。intel积小钱得以成巨款(不错,还挺押韵的),总之让个位失望了,它没有2^64=16EB的寻址能力所以内存条插再多也没用(实际上你也插不到16EB)所以别问以后会不会有128位CPU,现在连会不会有64位完全寻址能力的CPU都没有,就别想那么多了。

那么究竟怎么进入64位模式呢?首先一个4级页表是“必不可少”的,what`s up!32位时还只有两层呢,咋直接变4层了,不要慌,如果你懒你可以整一个1GB大小的页,YES,1GB懒人的福利,当然前提是要CPUID告诉你CPU支持,瞬间就放弃了有没有

首先你需要亿些些表,就像你出生时要办的各种证明一样,进入64位你必须要提交各种表格,其中最必要的两项就是GDT表和页表(不知道是不是不开分页机制就可以不要,但是最好要),但说这么多虚的不实在,还是要从实践说起,先讲讲64位模式支持检测,上代码!

;=======================================================检查是否支持IA32E
check_ia32e_support:
	xor	edx,edx
	mov	eax,80000001h
	cpuid
	bt	edx,30	;没想到吧,叕改了一点点东西
	jnc	not_support
	ret
not_support:
	mov	cx,size_cpu_nos
	mov	bl,red
	mov	bp,str_cpu_nos
	call	print
	jmp	sys_halt

看了这份代码懂的人一定会说,不是要先用cpuid的80000000h功能号先检查它是否支持80000001h及以上的cpuid功能吗?oh,不必多次一举,看到xor edx,edx了吗?先把这个要返回参数的寄存器清零,之后等它返回,如果不支持,它的edx寄存器值应该还是0,而此时bt edx,30(检测edx的第30位是否为1,如果为1则carry表示支持IA32E模式<-貌似也叫IA64)一定是会not carry,所以一句jnc not_support给这个不支持64位操作系统的用户来一句灵魂慰问让TA彻底放弃装64位操作系统的想法,连最大扩展功能号都不需要检测,少了好几行汇编指令,是不是很有逻辑!

而具体CPUID返回的参数我就不多花字数来写了,在许多blog都有到说呢,查查就能找到,好像叫cpuid扩展功能号吧, 在书的296面也有讲到,当然我还是要推荐一篇文章,因为书上296面edx的30位被作者直接抹黑了,实际上那个位是检测是否支持IA64的关键一位,在这篇文章上有讲到:

cpuid指令

既然CPU支持了64位那么怎么进入呢?先看成功进入的图

由图可知进入64位模式需要一张页表,一张gdt表,当然还要更改一些cr的值,在loader加载期间,会在1.4MB的物理内存中0x9a000的位置构建页表,其代码如下所示

;构建页目录表
	mov	ax,SelData32
	mov	es,ax
	mov	ds,ax

	xor	eax,eax
	xor	ebx,ebx
	xor	ecx,ecx
	xor	edx,edx

	mov	dword[PML4_ADDR_ST],PDPT_ADDR_ST|0x07
	
	mov	dword[PDPT_ADDR_ST],PDT_ADDR_ST|0x07

	mov	dword[PDT_ADDR_ST],0x00000|0x83
	mov	dword[PDT_ADDR_ST+8],0x200000|0x83
	mov	dword[PDT_ADDR_ST+16],0x400000|0x83
	mov	dword[PDT_ADDR_ST+24],0x600000|0x83
	mov	dword[PDT_ADDR_ST+32],0x800000|0x83
;据计算1024*768*3Byte=0x240000byte	所以大概也就2MB多一点
;映射帧缓存
	mov	eax,[VBE_MODE_ADDR+40]
	or	eax,0x83
	mov	dword[PDT_ADDR_ST+0x1000-72],eax	;0MB

	add	eax,200000h
	mov	dword[PDT_ADDR_ST+0x1000-64],eax	;2MB

	add	eax,200000h
	mov	dword[PDT_ADDR_ST+0x1000-56],eax	;4MB

当然,这么操作必须要在1.4MB以下的干净的内存(貌似在真机上1.4MB内存空间默认是干净的,我在两台不同平台的电脑上实验过,当然,也在一台笔记本上确认过,结果的确如此)空间,否则嘛...,直接崩溃,为什么?看清楚上面的是dword是32位变量,而64位下的页表是64位的,如果内存是脏的(也就是说已经写过了数据),那么其高32位的基址可能不为零,如果用这样的页表则会出现我之前所遇到的情况,虚拟机上运行一切正常,而一放到真机上就直接重启了,查了几周没查出来(当然这几周没有专门去整这件事)

至于帧缓存的映射则是靠VBE_MODE_INFO_BLOCK里头的物理地址指针来映射的,为什么不按书《一个64位操作系统的设计与实现》上的0xe0000000来映射呢,这个原因也很简单,因为我真机测试时电脑叕重启了,what`s up.再仔细一看书上第279面表7-27下面的文字(也亏得我能找到):

这个地址可以不是0xe0000000,甚至在同一平台下它的位置都可以是0xc0000000

绝,绝了,于是便只好乖乖用物理平台提供的值去映射了,但具体为什么映射在0x3ee00000,其实我随便挑的,反正bochs会告诉我在哪,我只需要直接填到代码里就好了

随后是GDT表,这个GDT表一看就知道不是神马简单的玩意,写这玩意好像都花了我几天时间去学,一方面学了如何用汇编表示二进制数(后来发现ubuntu自带计算器的编程模式居然可以直接将二进制转16进制),另一方面嘛...,另一面去网上搜了大把关于GDT表的资料,当然后来看了一眼这本书的208和228瞬间感觉前面搜的资料都白费了,因为一眼就看懂了

于是,我就写了如下代码的32位GDT表和64位的GDT表

;======================================================================临时GDT表
GDT32_ST:
GDT32_NULL:
		dq	0x0000000000000000
GDT32_CODE:
		dw	0xffff;limit_low低16位的限长
		dw	0x0000;base_low 低16位的基址
		db	0x00  ;base_mid 中8 位的基址
		db	10011010b	;fst_flag,type_flag 第一个标志与类型标志
		db	11001111b	;scd_flag,limit_hig 第二个标志与高4位的限长
		db	0x00	;base_high 高8 位的基址	
GDT32_DATA:
		dw	0xffff;limit_low低16位的限长
		dw	0x0000;base_low 低16位的基址
		db	0x00  ;base_mid 中8 位的基址
		db	10010010b	;fst_flag,type_flag 第一个标志与类型标志
		db	11001111b	;scd_flag,limit_hig 第二个标志与高4位的限长
		db	0x00	;base_high 高8 位的基址	
GDT32_END:

GDT64_ST:
GDT64_NULL:
		dq	0x0000000000000000
GDT64_CODE:

		dw	0x0000;limit_low低16位的限长
		dw	0x0000;base_low 低16位的基址
		db	0x00  ;base_mid 中8 位的基址
		db	10011000b	;fst_flag,type_flag 第一个标志与类型标志
		db	00100000b	;scd_flag,limit_hig 第二个标志与高4位的限长
		db	0x00	;base_high 高8 位的基址	
GDT64_DATA:	
		dw	0x0000;limit_low低16位的限长
		dw	0x0000;base_low 低16位的基址
		db	0x00  ;base_mid 中8 位的基址
		db	10010010b	;fst_flag,type_flag 第一个标志与类型标志
		db	00000000b	;scd_flag,limit_hig 第二个标志与高4位的限长
		db	0x00	;base_high 高8 位的基址	

GDT64_END:

GDT32_DESC	dw	GDT32_END - GDT32_ST - 1
		dd	GDT32_ST

SelCode32	equ	GDT32_CODE - GDT32_ST
SelData32	equ	GDT32_DATA - GDT32_ST

GDT64_DESC	dw	GDT64_END - GDT64_ST - 1
		dd	GDT64_ST

SelCode64	equ	GDT64_CODE - GDT64_ST
SelData64	equ	GDT64_DATA - GDT64_ST

随后,再用lgdt将GDT的基址和长度加载到GDTR寄存器里头去(不知道为什么不直接把GDT加载到CPU的缓存里,不是说CPU有L1L2L3这么多级缓存吗,明明加载进去运行应该会更快的才对),之后再将GDT表的段选择子加载到段寄存器中设置栈顶,代码如下所示

;重载gdt表
	;db	0x66	;没想到吧,我把这玩意注释掉了,看了眼书发现这是声明32位宽的前缀
	;反正前面已经有了[bits 32]这里可以不要,顺便做个笔记
	lgdt	[GDT64_DESC]
	mov	ax,SelData64
	mov	ds,ax
	mov	es,ax
	mov	fs,ax
	mov	gs,ax
	mov	ss,ax
	mov	esp,7c00h	;设置栈顶

当然,如上代码通过了运行检验(指虚拟机上)

之后我们需要置位一些cr(controll register)寄存器的值,让我们的系统进入64位模式,代码如下所示

;置位PAE标志位,即CR4的第5位
	mov	eax,cr4
	bts	eax,5
	mov	cr4,eax
;将页目录表的地址指向PML4_ADDR_ST,
	mov	eax,PML4_ADDR_ST
	mov	cr3,eax
;置位IA32_EFER寄存器以启动IA-32e模式
	mov	ecx,0x0c0000080
	rdmsr
	bts	eax,8
	wrmsr
;启动分页机制,以及保护模式使能位
	mov	eax,cr0
	bts	eax,0
	bts	eax,31
	mov	cr0,eax
;转跳至paw_loader
	
	jmp	SelCode64:PawLoaderADDR

虽然不会讲到每一个标志位,但是我会把上述代码开启的标志位说明一下:

PAE允许访问32位以上的物理地址,好像也叫物理地址扩展标志位,部分32位CPU也可以将此位置位,并置位CR4.PSE-36(前提是CPUID eax=80000001h时edx的17位被置位了),这样32位CPU可以访问36位,甚至是40位的物理地址,并且可以开启4MB分页模式,然而线性地址的寻址能力保持32位不变(这可能是为什么32位CPU这么操作会降低运行速度的原因,毕竟当所有低4GB内存用完访问高于4GB内存时就需要频繁切换页表了)

cr3寄存器不是控制功能的,而是用来记录页表基址的

在32位下:

其为32位寄存器,不可以直接赋值,必须要靠其它寄存器间接赋值,同时,它的低12位不可用,保持为0,这意味着页表必须保持4K对齐

在64位下:

其为64位寄存器,同样要间接赋值,其高32位无效(开玩笑的,这得看你CPU最大支持的物理地址),低12位用于PCID功能,这里我没用这个功能,只是将它单纯地指向了一个物理地址

MSR寄存器组是开启IA32E模式的关键:

通过rdmsr和wrmsr指令更改msr寄存器组中的IA32_EFER.LME置位,使用rdmsr和wrmsr之前不要忘记给ecx(rax)寄存器写上msr寄存器地址,这里IA32_EFER则是在MSR寄存器组地址0x0c0000080的位置,之后将它的第八位IME(IA32-E Mode Enable)置位,该寄存器功能是这样的

最后是cr0:

这个寄存器控制着分页(当然,据说不置位PG也可以,好像线性地址会与物理地址保持一致)和保护模式(PE,不是体育,是Protection Enable,置位可以运行32位模式的程序,当然需要一个32位的GDT选择子)的开启,其中PE和PG分别位于cr0的0位和31位,如下图所示

不容易,终于写完了,我还是收回上一章的话吧,这玩意,不简单。下一章就要开始将pawloader了,不仅要讲整个操作系统的启动流程,还要要挂新的源码链接了,说实话,内存分配,不好写啊,PCI驱动没有内存驱动,不好写啊!

当然如果文中有错误,请各位不吝赐教,在下方留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值