内核实战之VGA你好世界
实战演练实模式启动,加入多系统启动支持,并转入保护模式
CPU运行架构
所谓指令集,就是机器码的集合,规定了机器码所能干的事情,使用打孔机/晶体管执行.指令集是具体的一套编码方式,微架构是指令集的物理实现方式
汇编代码相当于机器码的助记符,需要使用汇编编译器编译成机器码
c语言则需要使用编译器编译为汇编代码/机器代码
java/php语言则需要使用相应解释器运行在一个虚拟机里面
所谓云原生,则是不需要虚拟机,直接解释成机器码,用来提高性能
CISC复杂指令集
CISC包括一个丰富的微指令集,这些微指令简化了在处理器上运行的程序的创建。指令由汇编语言所组成,把一些原来由软件实现的常用的功能改用硬件的指令系统实现,编程者的工作因而减少许多,在每个指令期同时处理一些低阶的操作或运算,以提高计算机的执行速度,这种系统就被称为复杂指令系统。
在CISC指令集的各种指令中,其使用频率却相差悬殊,大约有20%的指令会被反复使用,占整个程序代码的80%。而余下的80%的指令却不经常使用,在程序设计中只占20%。
广泛应用于PC领域,intel的x86系列产品被自己命名为IA32指令集
amd的x64也是intel的x86授权的,但是硬件是它自己实现的,被称为x86_64或者amd64,也叫做Intel 64
Intel在之前已在Itanium处理器上使用了自家的64位IA-64技术,虽然说Intel 64也是64位,但两者并不兼容,即IA-64的软件不能直接在Intel 64上运行。Intel 64所用的x86-64是IA-32指令集的延伸,而IA-64则是另一款独立的架构,没有任何IA-32的影子。虽然IA-64可通过模拟来运行IA-32的指令,但指令在运行前需经转换,才能在IA-64上运行,导致其速度变慢。由于x86-64是从IA-32派生而来,因此运行IA-32与64位程序的表现也显得绰绰有余。
RISC精简指令集
RISC(精简指令集计算机) 设计方案,如它的名字所蕴涵的那样,有一个简化的指令集,该指令集提高处理器的效率但是需要有更复杂的外部程序。RISC结构优先选取使用频最高的简单指令,避免复杂指令;将指令长度固定,指令格式和寻地方式种类减少;以控制逻辑为主,不用或少用微码控制等措施来提高运算速度。
RISC设计方案是根据John Cocke在IBM所做的工作形成的。John Cocke发现大约20%的计算机指令完成大约80%的工作。因此,基于RISC的系统通常比CISC系统速度快。它的80/20规则促进了RISC体系结构的发展。
MIPS,ARM,Power,C6000都采用精简指令集
CPU运行模式
在16位CPU系统中,它只有4个段寄存器,所以,程序在任何时刻至多有4个正在使用的段可直接访问.传统的16位处理器启动时就是在实模式,也就是纯裸的,没有任何支持。
实模式: 前4个段寄存器CS、DS、ES和SS与CPU中的所对应的段寄存器的含义完全一致,内存单元的逻辑地址仍为”段值:偏移量”的形式。为访问某内存段内的数据,必须使用该段寄存器和存储单元的偏移量。
在32位微机系统中,它有6个段寄存器,所以,在此环境下开发的程序最多可同时访问6个段。32位CPU有两个不同的工作方式:实方式和保护方式。
32位处理器为了兼容16处理器,把开机时的实模式也兼容了,所以即使是16位的操作系统,放到32位处理器上,仍然能运行。
所以开机的时候CPU就是实模式,然后32位操作系统又将实模式切换成保护模式。如果发现是16位的操作系统,就直接运行在实模式。
模式进化
在8086/8088时代,处理器只存在一种操作模式(Operation Mode),当时由于不存在其它操作模式,因此这种模式也没有被命名。自从80286到80386开始,处理器增加了另外两种操作模式——保护模式和系统管理模式SMM(System Management Mode),因此,8086/8088的模式被命名为实地址模式RM(Real-address Mode)。
前世今生
实模式出现于早期8088CPU时期。当时由于CPU的性能有限,一共只有20位地址线(所以地址空间只有1MB),以及8个16位的通用寄存器,以及4个16位的段寄存器。所以为了能够通过这些16位的寄存器去构成20位的主存地址,必须采取一种特殊的方式。
随着CPU的发展,CPU的地址线的个数也从原来的20根变为现在的24/32根,所以可以访问的内存空间也从1MB变为现在4GB,寄存器的位数也变为32位。所以实模式下的内存地址计算方式就已经不再适合了。所以就引入了现在的保护模式,实现更大空间的,更灵活也更安全的内存访问。
CPU复位(reset)或加电(power on)的时候以实模式启动,处理器以实模式工作。在实模式下,内存寻址方式和8086相同,32位的x86 CPU用做高速的8086。在实模式下,所有的段都是可以读、写和可执行的。实模式的"实"更多地体现在其地址是真实的物理地址
保护模式是处理器的本机模式,在这种模式下,处理器支持所有的指令和所有的体系结构特性,提供最高的性能和兼容性。对于所有的新型应用程序和操作系统来说,建议都使用这种模式。为了保证PM的兼容性,处理器允许在受保护的,多任务的环境下执行RM程序。这个特性被称做虚拟8086模式(Virtual -8086 Mode),尽管它并不是一个真正的处理器模式。Virtual-8086模式实际上是一个PM的属性,任何任务都可以使用它。
实模式寻址方式
8086CPU数据总线为16位,也就是一次最多能取216=64KB数据,这个数据也解释了实模式下为什么每个段最大只有64KB。但刚才还说了其地址总线为20位,这样它能寻址的能力其实是220=1MB,这也就是实模式下CPU的最大寻址能力。既然它有1MB寻址能力,那怎么用16位的段寄存器表示呢?
这就引出了分段的概念,8086CPU将1MB存储空间分成许多逻辑段,每个段最大限长为64KB(但不一定就是64KB)。这样每个存储单元就可以用“段基地址+段内偏移地址”表示。段基地址由16位段寄存器值左移4位表达,段内偏移表示相对于某个段起始位置的偏移量
实模式下对应的寄存器一般都有固定的使命,对于寻址来说,基本只能使用上面对应的寄存器。
保护模式寻址方式
在保护模式下,可以用各种通用寄存器(除了esp不能当作变址寄存器),并且偏移量也变成了32位
保护模式下的寻址方式明显比实模式下灵活了许多。注意一下,只能使用这里面出现过的寄存器(比如EAX包括BL,但是BL无论在实模式下还是在保护模式下都没有出现,则自然不能用来进行寻址)
依照设计的规格,所有的x86 CPU都是在实模式下开机,来确保传统操作系统的向前兼容性。在任何保护模式的特性可用前,他们必须要由某些程序手动地切换到保护模式。
在现今的计算机,这种切换通常是由操作系统在开机时候必须完成的第一件任务的一个。它也可能当CPU在保护模式下运行时,使用虚拟86模式来运行设计运行在实模式下的代码。
切换保护模式
现代操作系统都是运行在保护模式下(Intel x86系列CPU)。计算机启动时,默认的工作模式是实模式,为了让内核能运行在保护模式下,Bootloader需要从实模式切换到保护模式,切换步骤如下:
- 准备好GDT(Global Descriptor Table)
- 关中断
- 加载GDT到GDTR寄存器
- 开启A20,让CPU寻址大于1M
- 开启CPU的保护模式,即把cr0寄存器第一个bit置1
- 跳转到保护模式代码
GDTR是一个6字节的寄存器,有4字节表示GDT表的基地址,2字节表示GDT表的大小,即最大65536(实际值是65535,16位最大值是65535),每个表项8字节,那么GDT表最多可以有8192项。
实模式的寻址总线是20bits,为了让寻址超过1M,需要开启A20,可以通过以下指令开启
in al, 0x92 |
把上述步骤完成之后,我们就进入保护模式了。在保护模式下我们要使用GDT通过GDT Selector完成,它是GDT表项相对于起始地址的偏移
实模式上代码
boot
还是这个表,我们找到两个可用区域,可以把setup加载到这里
起始 | 结束 | 大小 用途 |
---|---|---|
FFFF0 | FFFFF | 16B BIOS 入口地址,此地址也属于BIOS 代码,同样属于顶部的640KB 字节。只是为了 |
强调其入口地址才单独贴出来。此处16 | 字节的内容是跳转指令jmp | f000:e05b |
F0000 | FFFEF | 64KB-16B 系统 BIOS 范围是 F0000~FFFFF 共 64KB,为说明入口地址,将最上面的 16字节从此处去掉了,所以此处终止地址是 0XFFFEF |
C8000 | EFFFF | 160KB 映射硬件适配器的 ROM 或内存映射式 I/O |
C0000 | C7FFF | 32KB 显示适配器 BIOS |
B8000 | BFFFF | 32KB 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB 用于黑白显示适配器 |
A0000 | AFFFF | 64KB 用于彩色显示适配器 |
9FC00 | 9FFFF | 1KB EBDA(Extended BIOS Data Area)扩展 BIOS 数据区 |
7E00 | 9FBFF | 622080B 约 608KB 可用区域 |
7C00 | 7DFF | 512B MBR 被 BIOS 加载到此处,共 512 字节 |
500 | 7BFF | 30464B 约 30KB 可用区域 |
400 | 4FF | 256B BIOS Data Area(BIOS 数据区) |
000 | 3FF | 1KB Interrupt Vector Table(中断向量表) |
这里使用了16H中断读取键盘,让用户选择多系统启动
就是软盘和硬盘的第二扇区了
;[ORG 0x7c00] |
硬盘setup
;[ORG 0x7c00] |
软盘setup
;[ORG 0x7c00] |
实模式访问32位寄存器
当32位CPU以16位的实模式运行时,并不是变成纯粹的16位的CPU,其可以看作更为强大的16位的CPU,但其本质仍然是32位,因此仍然具备处理32位操作数的能力。
- 在16位实模式下,默认访问数据的大小是8位或者16位的;控制转移和内存访问时,偏移量也是16位的;
- 处理器在16位实模式下运行时,可以使用32位的寄存器,执行32位运算;
- 使用伪指令
[bits 16][bits 32]
设置指令前缀0x66和0x67 - 指令前缀0x66用来选择非默认值的操作数大小,0x67用来选择非默认值的地址大小
汇编代码执行nasm -f elf32 test.asm
生成二进制文件,再使用objdump -s -d
查看机器码
test0: |
由机器码可知,32位cpu操作16位时,默认会添加前缀66,这是保护模式下需要的.保护模式默认访问32位寄存器
当指定[BITS 16]
伪指令后,则结果相反,这正是实模式下需要的,实模式默认访问16位寄存器
也就是说同一个机器码40,保护模式下访问的是eax,实模式下访问的是ax;
所以,要想正确地执行指令,在启动阶段的实模式里,我们的汇编代码需要添加[BITS 16]
;进入保护模式后,我们的代码需要添加[BITS 32]
保护模式上代码
大家在github上自己看吧....
static int s_nihaoshijie[]={ |