从头写一个操作系统 09 (create an OS from scratch 09)
lesson 10
这一课,就不简单的翻译课文,因为作者只写了一点点,不过一切都在代码里。
让我们把代码拆开,看看16位实模式是如何跳转到32位保护模式的。
分析代码前,先想想,为什么会有16位实模式呢?
很久以前,大概1985年左右,那时的intel的CPU只有16位,16位就是代表CPU有16根电线接收数据(其实是16根电线发送数据,另外还有32根电线分两组,每组16根各自接受一组数据),DOS就是那个时代的操作系统,很多年过去了,intel的cpu进化成为32位,但intel为了保证硬件的向前兼容,统一计算机启动的第一步是进入16位模式,然后由引导区决定下一步的动作,这样如果是必须16位模式的DOS系统,一样可以在32位机器上工作。如果你玩树莓派,就会发现完全没有16位实模式这个说法,不过树莓派的启动也是很奇怪的,它先启动GPU,让GPU先读两个配置文件,然后才让ARMcpu工作,这是后话,以后讲到树莓派的时候再说。
32位保护模式与16位实模式是有本质区别的,cpu一次可以寻址32位的地址,也就是最大能够寻址到4G,怎么算的?
2^16 = 65536 约等于65K
2^20 = 1048575 约等于1M
2^32 = 4294967295 约等于4G
现在咱们都用64位的操作系统,还记得当年换64位操作系统的原因吗?大概2010年后,电脑内存越来越大,很快超过了8G,可尴尬的是32位操作系统无法寻址超过4G的内存地址,因为就算给CPU的32根电线
都传递高电平,也只有0XFFFF FFFF 这么几个F,内存是有8G,多出4G的空间,CPU的指头都不够数。64位操作系统是可以调动CPU所有64根电线的,如果让64根电线都是高电平,那么可以寻址到160亿G的内存地址。
16位与32位的区别就在于寻址的方式,也就是CPU如何把自己要什么地址告诉内存,16位是用段地址 X 16+偏移地址
的方式寻址,能够寻址20位。到了32位CPU,使用更加安全虚拟内存的技术,上一节课已经说过。同一个32位CPU在执行16位模式时,通过调整一个开关,就能进入32位寻址能力的模式,这个开关就是代码中的cr0,当cr0的最低位的bit被置为1时,CPU进入32位保护模式。
原代码[^1]
switch_to_pm:
cli ; 1. 关闭中断
lgdt [gdt_descriptor] ; 2. 加载 GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. 将cr0设置为32位模式
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump
lgdt
加载 gdt_descriptor
的作用就是把GDT
载入到GDTR寄存器中,其实就是载入了一个地址。
16位时CS
寄存器里存的的段地址,进入32位保护模式
后,CS
中存的是GDT
这个结构中的偏移量,比如本例中的GDT[代码][^2]为:
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
可以看到 CODE_SEG
的值是0x08 (gdt-code -gdt_start)
,所以此时的CS
寄存器中就存着0x08
,
在保护模式下,CS:IP
取指令地址的流程就成为了, CPU计算GDTR+CS 得到code段的真实base地址,然后以IP
作为offset,得到最终指令的地址。当然在载入指令前会判断code
段的limit
是否小于IP
,如果小于,则报告段错误
,写C语言的人谁没碰到过段错误
?当然C语言中的段错误,应该都是超出了LDTR的limit,LDTR中的L是local,GDTR中的G是global。
一旦进入到32位保护模式,立马天高地阔,不过首先要初始化所有的寄存器,因为寄存器在实模式时,只用了16位,现在可以让寄存器所有32位的能力都能发挥出来。由far jump 到BEGIN_PM lable执行32位下初始化寄存器的指令。下面就该进入kernel了!
THE ORIGIN ARTICALE IN GITHUB:[^3]
Concepts you may want to Google beforehand: interrupts, pipelining
Goal: Enter 32-bit protected mode and test our code from previous lessons
To jump into 32-bit mode:
Disable interrupts
Load our GDT
Set a bit on the CPU control register cr0
Flush the CPU pipeline by issuing a carefully crafted far jump
Update all the segment registers
Update the stack
Call to a well-known label which contains the first useful code in 32 bits
We will encapsulate this process on the file 32bit-switch.asm. Open it and take a look at the code.After entering 32-bit mode, we will call BEGIN_PM which is the entry point for our actual useful code (e.g. >kernel code, etc). You can read the code at 32bit-main.asm. Compile and run this last file and you will see >the two messages on the screen.
Congratulations! Our next step will be to write a simple kernel
[^1]:https://github.com/cfenollosa/os-tutorial/blob/master/10-32bit-enter/32bit-switch.asm
Copyright:BSD 3-Clause License Copyright (c) 2018, Carlos Fenollosa
[^2]:https://github.com/cfenollosa/os-tutorial/tree/master/09-32bit-gdt/32bit-gdt.asm
Copyright:BSD 3-Clause License Copyright (c) 2018, Carlos Fenollosa
[^3]:https://github.com/cfenollosa/os-tutorial/blob/master/10-32bit-enter
Copyright:BSD 3-Clause License Copyright (c) 2018, Carlos Fenollosa
版权注明:见备注
不错。腿开始粗了。加油😂
Posted using Partiko Android
哇噻,我都没搞清楚什么情况,谢谢拉哥鼓励!
欢迎联系@team-cn和@ericet加入新手村参与讨论哦~
推荐阅读《Steem指南》,学习更多关于Steem的知识和经验。
谢谢分享!
本文存在如下问题:
谢谢,已修改
This post has been voted on by the SteemSTEM curation team and voting trail. It is elligible for support from @curie.
If you appreciate the work we are doing, then consider supporting our witness stem.witness. Additional witness support to the curie witness would be appreciated as well.
For additional information please join us on the SteemSTEM discord and to get to know the rest of the community!
Please consider setting @steemstem as a beneficiary to your post to get a stronger support.
Please consider using the steemstem.io app to get a stronger support.