操作系统--局部段描述符表的使用
Q:什么是LDT
A.局部段描述符表
1.本质是一个段描述符表,用于定义段描述符
2.与GDT类似,可以看作"段描述符的数组"
3.通过定义选择子访问局部描述符表中的元素
局部段描述符选择子
局部段描述符表
需要注意的是
1.局部段描述符表需要在全局段描述符表中注册(增加描述符)
2.通过对应的选择子加载局部段描述符(lldt)
3.局部段描述符从第0项开始使用
在这里会出现一个问题-LDT具体用来干什么?同时为什么还需要一个"额外的"段描述符?
LDT的意义主要体现在两个方面:代码层次的意义-分级管理功能相同意义不同的段(多个代码段);系统层次的意义-实现多任务的基础要素(每个任务对应一系列不同的段)
B.LDT的定义与使用
1.定义独立功能相关的段(代码段,数据段,栈段)
2.将目标段描述符组成局部段描述表(LDT)
3.为各个段描述符定义选择子(SA_TIL)
4.在GDT中定义LDT的段描述符,并定义选择子
C.LDT的定义与使用
代码实现-要在inc.asm中对DA_LDT equ 0x82进行定义要不然会在运行时出现错误
%include "inc.asm"org 0x9000jmp ENTRY_SEGMENT[section .gdt]; GDT definition; 段基址, 段界限, 段属性GDT_ENTRY : Descriptor 0, 0, 0CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DR + DA_32STACK32_DESC : Descriptor 0, TopOfStack32, DA_DRW + DA_32CODE16_DESC : Descriptor 0, 0xFFFF, DA_C UPDATE_DESC : Descriptor 0, 0xFFFF, DA_DRWTASK_A_LDT_DESC : Descriptor 0, TaskALdtLen - 1, DA_LDT; GDT endGdtLen equ $ - GDT_ENTRYGdtPtr: dw GdtLen - 1 dd 0; GDT SelectorCode32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0Code16Selector equ (0x0005 << 3) + SA_TIG + SA_RPL0UpdateSelector equ (0x0006 << 3) + SA_TIG + SA_RPL0TaskALdtSelector equ (0x0007 << 3) + SA_TIG + SA_RPL0; end of [section .gdt]TopOfStack16 equ 0x7c00[section .dat][bits 32]DATA32_SEGMENT: DTOS db "D.T.OS!", 0 DTOS_OFFSET equ DTOS - $$ HELLO_WORLD db "Hello World!", 0 HELLO_WORLD_OFFSET equ HELLO_WORLD - $$Data32SegLen equ $ - DATA32_SEGMENT[section .s16][bits 16]ENTRY_SEGMENT: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, TopOfStack16 mov [BACK_TO_REAL_MODE + 3], ax ; initialize GDT for 32 bits code segment mov esi, CODE32_SEGMENT mov edi, CODE32_DESC call InitDescItem mov esi, DATA32_SEGMENT mov edi, DATA32_DESC call InitDescItem mov esi, STACK32_SEGMENT mov edi, STACK32_DESC call InitDescItem mov esi, CODE16_SEGMENT mov edi, CODE16_DESC call InitDescItem mov esi, TASK_A_LDT_ENTRY 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 ; initialize GDT pointer struct mov eax, 0 mov ax, ds shl eax, 4 add eax, GDT_ENTRY mov dword [GdtPtr + 2], eax ; 1. load GDT lgdt [GdtPtr] ; 2. close interrupt cli ; 3. open A20 in al, 0x92 or al, 00000010b out 0x92, al ; 4. enter protect mode mov eax, cr0 or eax, 0x01 mov cr0, eax ; 5. jump to 32 bits code jmp dword Code32Selector : 0BACK_ENTRY_SEGMENT: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, TopOfStack16 in al, 0x92 and al, 11111101b out 0x92, al sti mov bp, HELLO_WORLD mov cx, 12 mov dx, 0 mov ax, 0x1301 mov bx, 0x0007 int 0x10 jmp $; esi --> code segment label; edi --> descriptor labelInitDescItem: push eax mov eax, 0 mov ax, cs shl eax, 4 add eax, esi mov word [edi + 2], ax shr eax, 16 mov byte [edi + 4], al mov byte [edi + 7], ah pop eax ret[section .s16][bits 16]CODE16_SEGMENT: mov ax, UpdateSelector mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, cr0 and al, 11111110b mov cr0, eaxBACK_TO_REAL_MODE: jmp 0 : BACK_ENTRY_SEGMENTCode16SegLen equ $ - CODE16_SEGMENT[section .s32][bits 32]CODE32_SEGMENT: mov ax, VideoSelector mov gs, ax mov ax, Stack32Selector mov ss, ax mov eax, TopOfStack32 mov esp, eax mov ax, Data32Selector mov ds, ax mov ebp, DTOS_OFFSET mov bx, 0x0C mov dh, 12 mov dl, 33 call PrintString mov ebp, HELLO_WORLD_OFFSET mov bx, 0x0C mov dh, 13 mov dl, 31 call PrintString mov ax, TaskALdtSelector lldt ax jmp TaskACode32Selector : 0 ; jmp Code16Selector : 0; ds:ebp --> string address; bx --> attribute; dx --> dh : row, dl : colPrintString: push ebp push eax push edi push cx push dxprint: mov cl, [ds:ebp] cmp cl, 0 je end mov eax, 80 mul dh add al, dl shl eax, 1 mov edi, eax mov ah, bl mov al, cl mov [gs:edi], ax inc ebp inc dl jmp printend: pop dx pop cx pop edi pop eax pop ebp retCode32SegLen equ $ - CODE32_SEGMENT[section .gs][bits 32]STACK32_SEGMENT: times 1024 * 4 db 0Stack32SegLen equ $ - STACK32_SEGMENTTopOfStack32 equ Stack32SegLen - 1; ==========================================;; Task A Code Segment ;; ==========================================[section .task-a-ldt]; Task A LDT definition; 段基址, 段界限, 段属性TASK_A_LDT_ENTRY:TASK_A_CODE32_DESC : Descriptor 0, TaskACode32SegLen - 1, DA_C + DA_32TASK_A_DATA32_DESC : Descriptor 0, TaskAData32SegLen - 1, DA_DR + DA_32TASK_A_STACK32_DESC : Descriptor 0, TaskAStack32SegLen - 1, DA_DRW + DA_32TaskALdtLen equ $ - TASK_A_LDT_ENTRY; Task A LDT SelectorTaskACode32Selector equ (0x0000 << 3) + SA_TIL + SA_RPL0TaskAData32Selector equ (0x0001 << 3) + SA_TIL + SA_RPL0TaskAStack32Selector equ (0x0002 << 3) + SA_TIL + SA_RPL0[section .task-a-dat][bits 32]TASK_A_DATA32_SEGMENT: TASK_A_STRING db "This is Task A!", 0 TASK_A_STRING_OFFSET equ TASK_A_STRING - $$TaskAData32SegLen equ $ - TASK_A_DATA32_SEGMENT[section .task-a-gs][bits 32]TASK_A_STACK32_SEGMENT: times 1024 db 0TaskAStack32SegLen equ $ - TASK_A_STACK32_SEGMENTTaskATopOfStack32 equ TaskAStack32SegLen - 1[section .task-a-s32][bits 32]TASK_A_CODE32_SEGMENT: mov ax, VideoSelector mov gs, ax mov ax, TaskAStack32Selector mov ss, ax mov eax, TaskATopOfStack32 mov esp, eax mov ax, TaskAData32Selector mov ds, ax mov ebp, TASK_A_STRING_OFFSET mov bx, 0x0C mov dh, 14 mov dl, 29 call PrintString jmp $; ds:ebp --> string address; bx --> attribute; dx --> dh : row, dl : colTaskACode32SegLen equ $ - TASK_A_CODE32_SEGMENT
运行结果
该处的运行结果
从运行结果可以得知并没有打印出自定义的字符串,还是打印之前的结果,同时发现还存在一个错误,就是CPU进行了重置,但是取消掉call PrintString 这个操作,会发现存在的错误消失了,这个函数是保护模式下的函数在这里调用出现错误的原因是属于其它段的,不属于现在的段,代码段发生了跳转,选择子不一样了。可以进行改进的是将之前的打印函数复制粘贴一份到现在的段中,需要注意的是要将名字进行修改,不然会出现错误
改正后的代码,注意jmp Code16Selector : 0命令会返回实模式打印Hello World及打印结果
D.多任务程序设计的实现思路
从这张图可以联想出-在保护模式下的不同段之间如何进行代码复用?
小结
1.局部段描述符表用于组织功能相关的段
2.局部段描述符表需要加载后才能正常使用
3.局部段描述符表必须在全局段描述符表中注册
4.通过局部段描述符表的选择子对其进行访问
5.局部段描述符表是实现多任务的基础