解决第一段登记册的历史负担问题
本系列将以一种新奇的阅读心态阅读和欣赏Linux 0.11的所有核心代码,从启动后的代码执行序列中了解操作系统的技术细节和设计思路。
你会跟着我,看着一个操作系统从无到有,最后一步一步实现它复杂而精致的设计看完这个系列,希望你能感叹原来操作系统源代码就是这个蠢东西
以下是已发表文章的列表想详细了解这个系列,可以从开篇的话开始
开场白
前两行代码
给自己挪个位置。
做好最基本的准备工作。
将硬盘的其他部分放入内存。
进入保护模式前的最后一次内存投掷
这个系列的GitHub地址如下
—正文开始—
上次,上次,我们说操作系统又折腾内存了时间久了,内存布局不会变,最后稳定下来了目前看起来是这样的
是不是操作系统的所有代码都存储在地址0的开头,也就是系统模块地址0x90000后面的几十个字节存储了一些设备的信息,方便以后使用
内存长度名称0x90002光标位置0x90022扩展内存号0x90042显示页面0x90061显示模式0x90071字符列号0x90082未知0x9000A1显示内存0x9000B1显示状态0x9000C2显卡特性参数0x9000E1屏幕号0x9000F1屏幕列号0x9008016硬盘1参数表0x9009016硬盘2参数表0x
是不是很清楚但是,不要太高兴令人耳目一新的内存布局对后续操作系统帮助很大
接下来将进行真正的第一个大项目,即模式转换,需要从目前的16位实模式转换为下一个32位保护模式。
当然,虽然是个很难啃的大项目,但是代码量少得可怜,不用太担心。
每次说起都很麻烦,因为这是x86的历史包袱现在几乎所有的CPU都支持32位模式甚至64位模式,很少有CPU只停留在16位实模式所以我们要为这个历史包袱写一个模式转换的代码如果不考虑兼容性重新设计Intel CPU,今天的代码会减少很多甚至消失
所以不用担心,懂了就懂了,不懂就忘了,放松。
我不打算直接说出实模式和保护模式的区别大家跟着代码慢慢品味吧走吧
下面仍然是setup.s文件中的代码。
idt _ 48loadidtwith0,_ 48loadgdtwithwhateverropriaeidt _ 48:. word 0,idtlimit=0.word0,0,idtbase=0L
有两行指令我看不懂别担心
要理解这两条指令,就涉及到实模式和保护模式的第一个区别我们仍处于真实模式还记得这种模式的CPU是怎么计算物理地址的吗不记得的话,第一遍看前两行代码
也就是说,段的基址左移四位,加上偏移地址。例如:
你不觉得尴尬吗更尴尬的地方来了当CPU切换到保护模式时,相同的代码和内存地址计算方式不同你觉得气人吗
它现在怎么样了刚刚存储在ds寄存器中的值在实模式下称为段基址,在保护模式下称为段选择器选择器存储段描述符的索引
通过段描述符索引,可以在全局描述符表gdt中找到段描述符,段基址存储在段描述符中。
取出基址,加上偏移地址,得到物理地址整个过程如下
就说很烦同样一段代码,在实模式和保护模式下的结果是不一样的,但是没有办法我们不得不考虑x86的历史包袱谁告诉我们没有其他CPU可以选择
综上所述,段选择器存储在段寄存器中,段选择器到全局描述符表中查找段描述符,取出段基址。
好了,那么问题自然就出来了全局描述符表看起来像什么它在哪里如何让CPU知道自己在哪里
别管它长什么样,它一定是另一个令人头疼的数据结构先说在哪里那么如何告诉CPU全局描述符表在内存中的位置呢答案是操作系统将这个位置信息存储在一个名为gdtr的寄存器中
怎么挽回刚才就是这么指示的
gdt_48
其中lgdt表示将以下值放入gdtr寄存器,gdt_48标签让我们看看它是什么样子的
gdt _ 48:. word 0x 800,gdtlimit=2048,256GDTentries.word512+gdt,0x9gdtbase=0X9xxxx
可以看出,该标签位置代表48位数据,其中高32位存储全局描述符表gdt的存储地址。
0x90200 + gdt
Gdt是一个标签,表示这个文件中的偏移量,这个文件是setup.s,编译后放在内存地址0x90200,还记得吗所以加上值0x90200
那么gdt的标签就是内存中全局描述符表的真实数据。
gdt:.word0,0,0,0,dummy . word 0x 07 ff,8Mb—limit = 2047 . word 0x 0000,base address = 0 . word 0x9a 00,coderead/exec . word 0x00c 0,粒度= 4096386 . word 0x 07 ff,8Mb—limit = 2047 . word 0x 0000,base address = 0 . word 0x 9200,data read/write . word 0x00c 0,粒度=4096,386
别担心细节,跟着我就行了。
根据刚才的段描述符格式。
可以看出,目前全局描述符表中有三个段描述符,第一个是空的,第二个是代码段描述符,第三个是数据段描述符第二个和第三个段描述符的段基址都是0,即逻辑地址转换成物理地址时,段选择器找出是代码段还是数据段,提取的段基址都是0,所以物理地址会直接等于程序员给的先记住这一点
具体的段描述符细节很多,我就不展开了例如,这里的高22位表示它是代码段还是数据段
接下来,我们来看看现在的内存布局,还是忽略规模吧。
我还在这里画了idtr寄存器这是中断描述符表,其原理与全局描述符表相同全局描述符表用于段选择器搜索段描述符,而中断描述符表用于CPU在发生中断时将中断号带到中断描述符表中查找中断处理程序的地址,找到后跳转到相应的中断程序执行我们以后再谈
好了,今天就来说说吧操作系统建立了一个全局描述符表gdt,这样在切换到保护模式后,它可以去那里找到段描述符,然后把它拼凑到最终的物理地址中就是这个功能当然还有很多段描述符,不仅仅是用来转换成最终的物理地址,这是后话了
这只是进入保护模式前的准备工作之一,还有很长的路要走欲知后事如何,且听下回分解
—这次扩展信息—
有关在保护模式下从逻辑地址到线性地址的转换,请参阅英特尔手册:
第3卷第3.4章逻辑和线性地址
有关段落描述符结构和详细描述,请参阅英特尔手册:
第3卷第3 . 4 . 5章分段描述符
比如本文提到的数据段和代码段的划分,其实是有更细分的权限控制的。
—关于这个系列—
这个系列的开篇词看这个。
快闪新系列!你把这个愚蠢的东西叫做操作系统源代码。
这个系列的延伸材料看这个还有很多有趣的素材,问答,互动参与项目,都在不断更新希望你能参与
本系列的全球视角
最后祝大家都能追到系列的最后只要你坚持追赶,理解每一次的内容,我敢说你看完系列之后,我对Linux 0.11很熟悉
郑重声明:此文内容为本网站转载企业宣传资讯,目的在于传播更多信息,与本站立场无关。仅供读者参考,并请自行核实相关内容。