局部段描述符表基本概念
- 描述符表有 GDT 和 LDT 两种,在保护模式的代码实现中我们用的都是 GDT
- 现在我们就来学习一下 LDT
- LDT 全称: Local Descripter table
- 同 GDT 一样,LDT 本质上也是段描述符表,可以看成段描述符数组
- 也是通过选择子的方式来访问局部段描述符表中的元素
- 局部段描述符和选择子与全局段描述符和选择子格式一致
LDT 与 GDT 使用的差异
- 局部段描述符表从第 0 项开始使用,而全局段描述符表是从第 1 项开始使用
- 加载局部段描述符表使用的是 lldt 指令,而加载全局段描述符表使用的是 lgdt 指令
- 局部段描述符表需要在全局段描述符表中注册(增加描述项)
LDT 的意义
- 它是 CPU 厂商为在硬件一级原生支持多任务而创造的表,按照 CPU 的设想,一个任务对应一个 LDT
- CPU 厂商建议每个任务的私有内存段都应该放到自己的段描述符表中,该表就是 LDT
- LDT 是实现多任务的基本要素
- 当然,现在可能很难理解 LDT 对多任务的意义,可以暂时先放着,心中有个概念就行,后面再逐步具体体会
LDT 的定义与使用
- 先上完整代码:loader.asm
- 参考 GDT 的实现,LDT 实现与 GDT 非常相似
- 首先是局部段描述符表和局部段选择符的定义
; Task A 局部段描述符表定义,段基址先默认为 0 ,后面填充 TASK_A_LDT_BASE : TASK_A_CODE32_DESC : Descriptor 0, TASK_A_CODE32_SEG_LEN - 1, DA_C + DA_32 TASK_A_DATA32_DESC : Descriptor 0, TASK_A_DATA32_SEG_LEN - 1, DA_DR + DA_32 TASK_A_STACK32_DESC : Descriptor 0, TASK_A_STACK32_SEG_LEN - 1, DA_DRW + DA_32 ; ... TASK_A_LDT_LEN equ $ - TASK_A_LDT_BASE ; 长度 = 当前地址 - TASK_A_LDT_BASE 地址
; Task A 局部段选择符定义,RPL = 0; TI = 1 TASK_A_CODE32_SELECTOR equ (0x0000 << 3) + SA_RPL0 + SA_TIL TASK_A_DATA32_SELECTOR equ (0x0001 << 3) + SA_RPL0 + SA_TIL TASK_A_STACK32_SELECTOR equ (0x0002 << 3) + SA_RPL0 + SA_TIL
- 注意 lldt 指令加载的是选择子,与 lgdt 不同
mov ax, TASK_A_LDT_SELECTOR lldt ax
- 别忘了段基址需要填充
CODE16_START ... mov esi, TASK_A_LDT_BASE mov edi, TASK_A_LDT_DESC call InitDescItem mov esi, TASK_A_CODE32_SEGMENT mov edi, TASK_A_CODE32_DESC call InitDescItem mov esi, TASK_A_DATA32_SEGMENT mov edi, TASK_A_DATA32_DESC call InitDescItem mov esi, TASK_A_STACK32_SEGMENT mov edi, TASK_A_STACK32_DESC call InitDescItem
- 注意,局部段描述符表需要在全局段描述符表中注册(增加描述项),即把局部段描述符表当做一个段,然后给这个段定义一个全局描述符和选择符
; 全局描述符表定义 ... TASK_A_LDT_DESC : Descriptor 0, TASK_A_LDT_LEN-1, DA_LDT
; 全局段选择符定义,RPL = 0; TI = 0 ... TASK_A_LDT_SELECTOR equ (0x0005 << 3) + SA_RPL0 + SA_TIG
- Task A 的数据段,代码段,栈段实现主体自己查看源码分析。
- 在原 32 位代码段程序后面添加加载局部段描述符表和跳转到局部代码段执行的代码
mov ax, TASK_A_LDT_SELECTOR lldt ax jmp TASK_A_CODE32_SELECTOR:0
- 于是,程序跳转到 TASK_A_CODE32_SEGMENT 代码处执行
- TASK_A_CODE32_SEGMENT 处的程序即为局部代码段,实现功能:打印 “Task A” 字符串
- 注意了:为啥又重新实现了打印函数 TaskAPrintString ,这个函数不是跟 print_str_32 一模一样的吗?
- 因为 TaskAPrintString 和 print_str_32 不属于同一个代码段,段界限规定了每个程序段的范围,A 程序当然无法调用 B 程序中的代码。这正体现了保护模式的保护二字
- 最后看一下代码的运行效果
- 问题:TaskAPrintString 和 print_str_32 实现的功能一模一样,那么,如何实现一个函数,可供不同的代码段调用呢?
- 请看下一章节讲解