“ 看书犯困,那是梦开始的地方。”
处理器逻辑结构
实模式、保护模式
处理器主要有两种运行模式,实模式和保护模式
处理器在上电或重启后自动进入到实模式,控制寄存器 CR0 中的 PE 标志控制着处理器是在实模式下还是在保护模式下
操作系统一启动,就通过修改PE标志,将处理器从实模式切换到保护模式
下面的知识都是保护模式下的知识
全局和局部描述符表(GDT和LDT)
在保护模式下操作时,所有的内存访问要么通过全局描述符表(GDT)要么通过局部(可选)描述符表(LDT)。
在这些描述符表里是段描述符,段描述符里包含了段的基 地址、访问特权、类型和用法信息。
每个段描述符都有一个与之相关的段选择符。
段选择符包含
- GDT 或 LDT(与它相关的段描述符)里的一个索引
- 一个全局/局部标志(决定段选 择符是指向 GDT 还是指向 LDT)
- 访问特权等信息。
要想访问段中的内容,必须同时提供段选择符和偏移地址,段选择符为段(在 GDT 或者 LDT 中)的描述符提供了一个访问途径。
对于段描述符,处理器包含了线性地址空间里的基地址,偏移量确定了相对于基地址的字节地址。
如果在处理器运行的当前特权级(CPL-Current Privilege Level)上可以访问段的话 (CPL 被定义为当前执行代码段的保护级),那么就可以通过这种机制来访问在 GDT 或 LDT 中的各种合法代码、数据或者堆栈段
除了代码、数据和堆栈段是构成程序运行环境之外,系统架构还定义了两个系统段
- 任务状态段(TSS)
- LDT。(GDT不被看作段因为它不能通过段选择符和段描述符访问)。
这些段类型都有一个专门为它们定义的描述符。
中断描述符表(IDT)
逻辑地址、线性地址、物理地址
逻辑地址由段选择符和偏移量组成(实模式下,段选择符和偏移量直接得到物理地址)
即使最小程度的使用段机制,处理器地址空间内的每一个字节都是通过逻辑地址访问的。
一个逻辑地址由一个 16 位的段选择符和一个 32 位的偏移量组成。
段选择符确定该字节位于哪个段,偏移量确定这个字节相对于段基址在这个段中的位置。
处理器将逻辑地址转换为线性地址。线性地址是处理器线性地址空间内的 32 位的地址。
线性地址与物理地址一样,是平坦的(不分段的),空间大小为232字节,从地址 00000000H 到FFFFFFFFH。
线性地址空间包含了所有的段以及为系统而定义的各种系统表。
处理器通过如下几个步骤将逻辑地址转换为线性地址:
- 通过段选择符中的偏移量,在 GDT 或者 LDT 中定位该段的段描述符。(仅当一个新的段选择符被读入段寄存器时才执行这一步)
- 检查段描述符中的访问权限和段的地址范围以确保该段是可访问的,偏移量是在段限长范围内的。
- 将段描述符中的段基址与偏移量相加以构成线性地址。
物理地址就是真正的内存中的地址,线性地址通过操作系统的分页机制得到物理地址
分段、分页
分段是处理器的机制,分页是操作系统实现的机制
下面是4k页通过分页机制将线性地址转物理地址的过程
下面是4M页通过分页机制将线性地址转物理地址的过程
特权级
当前特权级CPL是当前正在运行的进程或任务的特权级。它存在CS和SS段寄存器的bit0和bit1
请求特权级RPL存储在段选择符的 bit0 和 bit1。
处理器会检验(通过LAR等权限检查指令) RPL 和 CPL 以及段描述符中的描述符特权级DPL来决定对这个段的访问是否是被允许的。
即使访问该段的进程或任务有足够的特权级(CPL已经够了),如果 RPL 不够,访问也会被拒绝的RPL是有软件控制的,一个CPL为3的应用进程可以将数据段的段选择符的RPL置为0
DPL >= CPL && DPL >= RPL 访问才会被允许
操作系统内核的代码处于特权级0的段上,其他的都处于特权级3的段上,CPU不允许直接在特权级不一样的代码段之间跳转(比如某进程中一个JMP指令或者CALL指令想跳到内核某行代码上接着执行,是不允许的),要实现跳转必须使用门
每个特权级下都存在各自的栈,每个栈都在一个单独的段中,特权级切换就必然要进行栈切换,栈切换时也会进行必要的参数拷贝
特权级为 3 的栈的栈选择符和栈指针放在 SS 寄存器中和 ESP 寄存器中,特权级为 0,1 和 2 的段选择符和栈指针存在 TSS 中
特权级为 3 的进程,即用户进程,没有任何直接访问IO设备的能力,一出生这种能力就会被操作系统剥夺
操作系统要负责创建栈和所有特权级使用的栈段的描述符,还要负责将那些栈的初始指针载入 TSS 中
栈切换过程:
- 使用目标代码段的 DPL(也就是新的 CPL)从 TSS 中选中一个指向新栈的指针(段选择符和栈指针)。
- 从当前 TSS 中读取将要切换到的栈的段选择符和栈指针。在读入栈段选择符,栈指针或栈段描述符时,如果检查到任何限制违例,都会产生一个非法 TSS(#TS)异常
- 检查栈段描述符的特权级和类型,如果发现违例,就会产生一个非法 TSS 异常
- 暂时保存当前 SS 和 ESP 寄存器的值
- 将新栈的段选择符和栈指针载入 SS 和 ESP 寄存器
- 将暂时保存的 SS 和 ESP 寄存器的值(调用例程的)压入新栈。
- 将调用门的参数计数域所指定个数的参数从调用例程的栈拷贝到新栈。如果参数个数域为 0,则一个参数也不拷贝。
- 将返回指令指针(当前 CS 和 EIP 寄存器)压入新栈
- 将新代码段的段选择符和调用门的新指令指针分别载入 CS 和 EIP 寄存器,然后开始指向调用例程。有关通过调用门进行远调用时的特权级检验和其他保护检验的相关信息,请参看 第二卷 第三章中关于 调用指令的描述。
特权指令(CPL 为 0 时才能执行它们):
- LGDT-装载GDT寄存器
- LLDT-装载LDT寄存器
- LTR-装载任务寄存器
- LIDT-装载IDT寄存器
- MOV(控制寄存器)-装载和存储控制寄存器 ? LMSW-装载机器状态字
- CLTS-清除寄存器CR0中的任务切换标志
- MOV(调试寄存器)-装载和存储调试寄存器
- INVD-使缓存失效,无写回
- WBINVD-使缓存失效,有写回
- INVLPG-使TLB项失效
- HLT-使处理器停止
- RDMSR-Read Model-Specific Registers.
- WRMSR—Write Model-Specific Registers.
- RDPMC—读取性能监视计数器
- RDTSC-读时间戳计数器
门
系统架构也定义了一套称为门的描述符
- 调用门
- 中断门
- 陷阱门
- 任务门
这些门提供了一种访问运行在不同于应用程序特权级的系统过程和处理程序的方法。
例如一个对调用门的调用可以访问与当前代码段特权相同或者数字更低(特权更高)的代码段中的过程。
通过调用门访问,调用程序必须提供调用门的选择符。
执行访问特权检查的处理器比较调用门的特权和调用门指向的目的代码的 CPL。
如果允许访问,处理器从调用门得到目标代码段的选择符和偏移地址。
如果调用需要进行特权级的改变,处理器也切换到那个级别的堆栈(新堆栈的段选择符是通过当前运行任务的 TSS 获得的)。
调用门也使得 16 位和 32 位之间的转换变得更加容易,反之亦然。
段内的跳转称为近调用,段间的跳转称为远调用,通过调用门切换特权级也是远调用,使用CALL调用 调用门
调用门描述符在 GDT 或 LDT 中:
- 确定将要访问的代码段
- 定义在指定代码段中的一个例程入口
- 指明了调用该例程的所应当有的特权级
- 如果有栈切换,要确定在栈之间拷贝的可选参数的个数。
- 定义压入目标栈的值的尺寸:16bit的门执行16bit的压栈,32bit的门执行32bit的压栈
- 确定该调用门描述符是否有效
陷阱门、中断门以及任务门描述符都在中断向量表IDT中。门描述符中的段选择符指向位于 GDT 或当前 LDT 中的可执行代码段的段描述符。门描述符中的偏移字段指向异常或中断处理例程的入口
中断门和陷阱门同调用门非常相似,是特殊的调用门。只不过调用门是使用CALL指令,而中断门和陷阱门是使用INT指令
它们包含一个远指针(段选择符和位移),处理器用其来将执行流转移至异常或中断处理代码段中的处理例程
通过任务门进行特权级切换时,要进行任务切换,相对中断门和陷阱门更加耗时。在某些操作系统中根本没有用到,比如Linux
响应异常和中断时,处理器将异常或中断向量作为 IDT 中描述符的索引。若该索引指向一个中断门或陷阱门,那么处理器会象处理 CALL 指令引用调用门一样,引用异常或中断例程。若该索引指向的是任务门,处理器会执行任务切换,切换到异常或中断处理例程,与用 CALL 指令调用一个任务门相近
硬件产生的中断和处理器检测到的异常,处理器会忽略掉中断或陷阱门中的DPL检查
总线锁
LOCK汇编指令可以实现锁总线,来自其他处理器或总线代理的总线控制请求将被阻塞,下一行指令执行完自动释放锁
下面的操作会自动的带有 LOCK 语义:
- 执行引用内存的XCHG(交换两个变量的内容)指令
- 设置TSS描述符的B(busy忙)标志。在进行任务切换时,处理器检查并设置TSS描述符的 busy 标志。为了保证两个处理器不会同时切换到同一个任务。处理器会在检查和设置这个标志的时遵循 LOCK 语义。
- 更新段描述符时。在装入一个段描述符时,如果段描述符的访问标志被清除,处理器会设置这个标志。在进行这个操作时,处理器会遵循 LOCK 语义,因此这个描述符不会 在更新时被其他的处理器修改。
- 更新页目录(page-directory)和页表(page-table)的条目。在更新页目录和页表 的条目时,处理器使用加锁的周期(locked cycles)来设置访问标志和脏标志(dirty flag)。
- 响应中断。发生中断后,中断控制器可能会使用数据总线给处理器传送中断向量。处理器必须遵循 LOCK 语义来保证传送中断向量时数据总线上没有其他数据
如果加锁的内存区域已经缓存在处理器中, 处理器可能并不对总线发出LOCK#信号,而是仅仅修改缓存中的数据,然后依赖缓存一致性 机制来保证加锁操作的自动执行。这个操作称为“缓存加锁”。缓存一致性机制会自动阻 止两个或多个缓存了同一区域内存的处理器同时修改数据。
串行化指令
串行化指令强制处理器完先前指令对标志、寄存器以及内存的修改,并且在执行下一条指令之前将所有缓冲区里的数据写入内存
串行化指令自带LOCK属性
下面的指令是串行化指令:
- 特权串行化指令——MOV(目标操作数为控制寄存器),MOV(目标操作数为调试存器),WRMSR, INVD, INVLPG, WBINVD, LGDT, LLDT, LIDT, LTR。
- 非特权串行化指令——CPUID, IRET, RSM。
- 非特权访存排序指令——SFENCE, LFENCE, MFENCE。
超线程技术
超线程技术是对IA-32体系的一个扩展,它使一个物理处理器可以并发执行两个或更多代码流 (称作线程,thread)
超线程技术的实现包含了两个逻辑处理器(每个都使用单独的IA-32体系状态来表示)。它们共享一个处理器执行引擎和系统总线。每个逻辑处理器都有它自己的高级可编程中断控制器(APIC)。
BIOS
在主板的ROM中存放着一段程序,称为BIOS(基本输入输出系统),可以成为极度精简的操作系统
包含几个部分:
- 硬件的检测与初始化程序
- 一些中断处理函数。比如显示器显示、载入操作系统等等
BIOS中断:
- INT 00h CPU:除零错,或商不合法时触发。
- INT 01h CPU:单步陷阱,TF标记为打开状态时,每条指令执行后触发。
- INT 02h CPU:非可屏蔽中断,如引导自我测试时发生内存错误。
- INT 03h CPU:第一个未定义的中断向量,约定俗成仅用于调试程序。
- INT 04h CPU:算数溢出。通常由INTO指令在置溢出位时触发。
- INT 05h 在按下Shift-Print Screen或BOUND指令检测到范围异常时触发。
- INT 06h CPU:非法指令。
- INT 07h CPU:没有数学协处理器时尝试执行浮点指令触发。
- INT 08h IRQ0:可编程中断控制器每 55 毫秒触发一次,即每秒 18.2 次。
- INT 09h IRQ1:每次键盘按下、按住、释放。
- INT 0Ah IRQ2:
- INT 0Bh IRQ3:COM2/COM4。
- INT 0Ch IRQ4:COM1/COM3。
- INT 0Dh IRQ5:硬盘控制器(PC/XT 下)或 LPT2。
- INT 0Eh IRQ6:需要时由软盘控制器调用。
- INT 0Fh IRQ7:LPT1。
- INT 10h 显示服务 - 由BIOS或操作系统设定以供软件调用。AH=00h 设定显示模式;AH=01h 设定游标形态;AH=02h 设置游标位置;AH=03h 获取光标位置与形态;AH=04h 获取光标位置;AH=05h 设置显示页;AH=06h 清除或滚动栏画面(上);AH=07h 清除或滚动栏画面(下);AH=08h 读取游标处字符与属性;AH=09h 更改游标处字符与属性;AH=0Ah 更改游标处字符;AH=0Bh 设定边界颜色;AH=0Eh 在TTY模式下写字符;AH=0Fh 获取当前显示模式;AH=13h 写字符串。
- INT 11h 返回设备列表。
- INT 12h 获取常规内存容量。
- INT 13h 低级磁盘服务。AH=00h 复位磁盘驱动器;AH=01h 检查磁盘驱动器状态;AH=02h 读扇区;AH=03h 写扇区;AH=04h 校验扇区;AH=05h 格式化磁道;AH=08h 获取驱动器参数;AH=09h 初始化硬盘驱动器参数;AH=0Ch 寻道;AH=0Dh 复位硬盘控制器;AH=15h 获取驱动器类型;AH=16h 获取软驱中盘片的状态。
- INT 14h 串口通信例程;AH=00h 初始化串口;AH=01h 写出字符;AH=02h 读入字符;AH=03h 状态。
- INT 15h 其它(系统支持例程)。AH=4FH 键盘拦截;AH=83H事件等待;AH=84H读游戏杆;AH=85HSysRq 键;AH=86H等待;AH=87H块移动;AH=88H获取扩展内存容量;AH=C0H获取系统参数;AH=C1H获取扩展 BIOS 数据区段;AH=C2H指针设备功能;AH=E8h, AL=01h (AX = E801h)获取扩展内存容量(自从 1994 年引入的新功能),可获取到 64MB 以上的内存容量;AH=E8h, AL=20h (AX = E820h)查询系统地址映射;该功能取代了 AX=E801h 和 AH=88h。
- INT 16h 键盘通信例程。AH=00h 读字符。AH=01h读输入状态;AH=02h读 Shift 键(修改键)状态;AH=10h读字符(增强版);AH=11h读输入状态(增强版);AH=12h读 Shift 键(修改键)状态(增强版)。
- INT 17h 打印服务;AH=00h 打印字符;AH=01h 初始化打印机;AH=02h 检查打印机状态。
- INT 18h 执行磁带上的 BASIC 程序:“真正的”IBM 兼容机在 ROM 里内置 BASIC 程序,当引导失败时由 BIOS 调用此例程解释执行。(例:打印“Boot disk error. Replace disk and press any key to continue...”这类提示信息)
- INT 19h 加电自检之后载入操作系统。
- INT 1Ah 实时钟服务。AH=00h读取实时钟;AH=01h设置实时钟;AH=02h读取实时钟时间;AH=03h设置实时钟时间;AH=04h读取实时钟日期;AH=05h设置实时钟日期;AH=06h设置实时钟闹铃;AH=07h重置实时钟闹铃。
- INT 1Bh Ctrl+Break,由 IRQ 9 自动调用。
- INT 1Ch 预留,由 IRQ 8 自动调用。
- INT 1Dh 不可调用:指向视频参数表(包含视频模式的数据)的指针。
- INT 1Eh 不可调用:指向软盘模式表(包含关于软驱的大量信息)的指针。
- INT 1Fh 不可调用:指向视频图形字符表(包含从 80h 到 FFh 的 ASCII 字符的数据)的信息。
- INT 41h 地址指针:硬盘参数表(第一硬盘)。
- INT 46h 地址指针:硬盘参数表(第二硬盘)。
- INT 4Ah 实时钟在闹铃时调用。
- INT 70h IRQ8:由实时钟调用。
- INT 74h IRQ12:由鼠标调用
- INT 75h IRQ13:由数学协处理器调用。
- INT 76h IRQ14:由第一个 IDE 控制器所调用
- INT 77h IRQ15:由第二个 IDE 控制器所调用
BIOS会为某些简单的外围设备提供初始化代码以及中断处理函数,但是有些设备是由自己的设备驱动提供初始化代码,自己安装中断函数
那么如何自己安装的呢?
每个外部设备,比如显卡、网卡、键盘等都有自己的ROM,这些ROM中提供了自己的初始化代码和功能调用函数(就是驱动)
按照约定规范,ROM中前两个单元是0x55、0xAA,第三个单元是ROM的长度,第四个单元开始就是驱动代码
从内存物理地址A0000开始,到FFFFF,有一部分空间是留给外围设备的,如果设备存在,那么它自带的ROM会映射到分配给它的内存空间中
BIOS会2kb单位搜索C0000-E0000之间的内存区域,但发现头两个字节是0x55、0xAA,意味着找到了一个设备,于是就跳转到这个设备第四个单元开始执行代码,这个ROM中的代码就会向IDT为自己注册中断向量
开机过程
- 主板通电,CS=0FFFFH,IP=0,自动从FFFF:0开始执行程序。FFFF:0处一条跳转指令,转到BIOS
- BIOS进行系统检测,然后初始化
- 建立BIOS所支持的中断向量(就是将BIOS提供的中断处理函数登记到IDT中,BIOS常驻内存,只需要把入口地址登记到IDT即可)
- 调用INT 19h中,加载操作系统所在的引导扇区引导代码并执行
- 引导扇区引导代码
- 使用int 13h中断加载loader
- 跳转到loader执行
- loader(com文件)
- LGDT指令加载GDT
- 使用int 13h中断加载ELF格式的操作系统内核,并根据ELF中的Program header table中的值把内核中相应的段放到正确的位置
- 切到保护模式
- 实现分页机制
- 跳转到操作系统内核
- 操作系统内核执行
- 重新安装GDT
- 安装LGT
- 安装IDT
- 等等
戳↙【阅读原文】