问题
如何通过进程上下文数据恢复进程执行?
如何使得进程运行于3特权级?
恢复上下文数据
通过任务数据结构中的寄存器值恢复上下文
借助esp寄存器以及pop指令恢复通用寄存器
注意
启动一个新任务可以看作特殊的任务切换,切换的目标任务上下文信息中通用寄存器的值为0
特权级转移 (高 => 低)
将esp指向目标内存位置 (eip,cs,eflags,esp,ss)
借助iret指令降特权级执行
再论中断与中断返回
中断发生时,可从低特权级转移到高特权级执行 (3 => 0)
中断返回时,从高特权级转移到低特权级执行 (0 => 3)
中断服务程序返回时的栈变化
任务代码执行方案
eip 指向任务代码入口
cs 指向 LDT 中的代码段描述符 (DPL = 3)
eflags 指定关键状态 (IOPL,IF,等)
esp 指向任务使用的私有栈
ss 指向 LDT 中的数据段描述符
iret 启动任务 (从任务代码入口处执行)
进程启动函数
进程的初步实现
loader.asm
%include "blfunc.asm"
%include "common.asm"
org BaseOfLoader
interface:
BaseOfStack equ BaseOfLoader
BaseOfTarget equ BaseOfKernel
Target db "KERNEL "
TarLen equ ($-Target)
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_32 + DA_C + DA_DPL0
VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_32 + DA_DRWA + DA_DPL0
CODE32_FLAT_DESC : Descriptor 0, 0xFFFFF, DA_32 + DA_C + DA_DPL0
DATA32_FLAT_DESC : Descriptor 0, 0xFFFFF, DA_32 + DA_DRW + DA_DPL0
TASK_LDT_DESC : Descriptor 0, 0, 0
TASK_TSS_DESC : Descriptor 0, 0, 0
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Code32FlatSelector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Data32FlatSelector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
[section .s16]
[bits 16]
BLMain:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, SPInitValue
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
; initalize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
call LoadTarget
cmp dx, 0
jz Output
call StoreGlobal
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt
; set IOPL to 3
cli
pushf
pop eax
or eax, 0x3000
push eax
popf
; 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 : 0
Output:
mov bp, ErrStr
mov cx, ErrLen
call Print
jmp $
; esi --> code segment labelBACK_ENTRY_SEGMENT
; edi --> descriptor label
InitDescItem:
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
;
;
StoreGlobal:
push eax
mov dword [RunTaskEntry], RunTask
mov eax, dword [GdtPtr + 2]
mov dword [GdtEntry], eax
mov dword [GdtSize], GdtLen / 8
pop eax
ret
[section .gfunc]
[bits 32]
;
; parameter ===> Task* pt
RunTask:
push ebp
mov ebp, esp
mov esp, [ebp + 8]
lldt word [esp + 200]
ltr word [esp + 202]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
iret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Data32FlatSelector
mov ds, ax
mov es, ax
mov fs, ax
mov ax, Data32FlatSelector
mov ss, ax
mov esp, BaseOfLoader
jmp dword Code32FlatSelector : BaseOfKernel
Code32SegLen equ $ - CODE32_SEGMENT
ErrStr db "No Kernel"
ErrLen equ ($-ErrStr)
Buffer:
db 0x00
RunTask函数让esp指向pt的RegValue的gs地址处,设置好任务相对应的寄存器,这里使用pop指令来设置相应的寄存器是为了不改变eflags寄存器的值;加载LDT和TSS;然后又iret指令从内核跳转到任务代码段执行。
StoreGlobal函数里将RunTask函数入口地址加载到了共享地址RunTaskEntry处。
新增了LDT和TSS段,暂时先把它们的段基址和段界限,段属性设置为0,后续再初始化。
global.c
#include "global.h"
GdtInfo gGdtInfo = {0};
void (* const RunTask) (Task* pt) = NULL;
global.h
#ifndef GLOBAL_H
#define GLOBAL_H
#include "kernel.h"
#include "const.h"
extern GdtInfo gGdtInfo;
extern void (* const RunTask) (Task* pt);
#endif
新增了一个函数指针,我们要将这个指针指向RunTask函数的入口地址,来开启一个任务。
kentry.c
%include "common.asm"
global _start
extern KMain
extern ClearScreen
extern gGdtInfo
extern RunTask
[section .text]
[bits 32]
_start:
mov ebp, 0
call InitGlobal
call ClearScreen
call KMain
jmp $
;
;
InitGlobal:
push ebp
mov ebp, esp
mov eax, dword [GdtEntry]
mov dword [gGdtInfo], eax
mov eax, dword [GdtSize]
mov dword [gGdtInfo + 4], eax
mov eax, dword [RunTaskEntry]
mov dword [RunTask], eax
leave
ret
在InitGlobal函数里,将global.h里定义的函数指针指向RunTask函数入口地址。
const.h
#ifndef CONST_H
#define CONST_H
#define NULL ((void*)0)
#define DA_DPL0 0x00
#define DA_DPL1 0x20
#define DA_DPL2 0x40
#define DA_DPL3 0x60
#define SA_RPL_MASK 0xFFFC
#define SA_RPL0 0
#define SA_RPL1 1
#define SA_RPL2 2
#define SA_RPL3 3
#define SA_TI_MASK 0xFFFB
#define SA_TIG 0
#define SA_TIL 4
#define DA_32 0x4000
#define DA_LIMIT_4K 0x8000
#define DA_DR 0x90
#define DA_DRW 0x92
#define DA_DRWA 0x93
#define DA_C 0x98
#define DA_CR 0x9A
#define DA_CCO 0x9C
#define DA_CCOR 0x9E
#define DA_LDT 0x82
#define DA_TaskGate 0x85
#define DA_386TSS 0x89
#define DA_386CGate 0x8C
#define DA_386IGate 0x8E
#define DA_386TGate 0x8F
#define GDT_DUMMY_INDEX 0
#define GDT_CODE32_INDEX 1
#define GDT_VIDEO_INDEX 2
#define GDT_CODE32_FLAT_INDEX 3
#define GDT_DATA32_FLAT_INDEX 4
#define GDT_TASK_LDT_INDEX 5
#define GDT_TASK_TSS_INDEX 6
#define GDT_DUMMY_SELECTOR ((GDT_DUMMY_INDEX << 3) + SA_TIG + SA_RPL0)
#define GDT_CODE32_SELECTOR ((GDT_CODE32_INDEX << 3) + SA_TIG + SA_RPL0)
#define GDT_VIDEO_SELECTOR ((GDT_VIDEO_INDEX << 3) + SA_TIG + SA_RPL0)
#define GDT_CODE32_FLAT_SELECTOR ((GDT_CODE32_FLAT_INDEX << 3) + SA_TIG + SA_RPL0)
#define GDT_DATA32_FLAT_SELECTOR ((GDT_DATA32_FLAT_INDEX << 3) + SA_TIG + SA_RPL0)
#define GDT_TASK_LDT_SELECTOR ((GDT_TASK_LDT_INDEX << 3) + SA_TIG + SA_RPL0)
#define GDT_TASK_TSS_SELECTOR ((GDT_TASK_TSS_INDEX << 3) + SA_TIG + SA_RPL0)
#define LDT_VIDEO_INDEX 0
#define LDT_CODE32_INDEX 1
#define LDT_DATA32_INDEX 2
#define LDT_VIDEO_SELECTOR ((LDT_VIDEO_INDEX << 3) + SA_TIL + SA_RPL3)
#define LDT_CODE32_SELECTOR ((LDT_CODE32_INDEX << 3) + SA_TIL + SA_RPL3)
#define LDT_DATA32_SELECTOR ((LDT_DATA32_INDEX << 3) + SA_TIL + SA_RPL3)
#endif
const.h定义了我们需要的全局段描述符和局部段描述符的选择子和选择子下标。
kmain.c
#include "kernel.h"
#include "screen.h"
#include "global.h"
Task t = {0};
Delay(int n)
{
while(n > 0)
{
int i = 0, j = 0;
for(i = 0; i < 1000; i++)
{
for(j = 0; j < 1000; j++)
{
asm volatile("nop\n");
}
}
n--;
}
}
void TaskA()
{
int i = 0;
SetPrintPos(0, 12);
PrintString("TaskA: ");
while(1)
{
SetPrintPos(8, 12);
PrintChar('A' + i);
i = (i + 1) % 26;
Delay(1);
}
}
void KMain()
{
PrintString("D.T.OS\n");
uint base = 0;
uint limit = 0;
uint attr = 0;
int i = 0;
PrintString("Gdt Entry: ");
PrintIntHex((uint)gGdtInfo.entry);
PrintChar('\n');
for(i = 0; i < gGdtInfo.size; i++)
{
GetDescValue(gGdtInfo.entry + i, &base, &limit, &attr);
PrintIntHex(base);
PrintString(" ");
PrintIntHex(limit);
PrintString(" ");
PrintIntHex(attr);
PrintChar('\n');
}
PrintString("RunTask Entry: ");
PrintIntHex((uint)RunTask);
PrintChar('\n');
t.rv.gs = LDT_VIDEO_SELECTOR;
t.rv.cs = LDT_CODE32_SELECTOR;
t.rv.ds = LDT_DATA32_SELECTOR;
t.rv.es = LDT_DATA32_SELECTOR;
t.rv.fs = LDT_DATA32_SELECTOR;
t.rv.ss = LDT_DATA32_SELECTOR;
t.rv.esp = (uint)(t.stack + sizeof(t.stack));
t.rv.eip = (uint)TaskA;
t.rv.eflags = 0x3002;
t.tss.ss0 = GDT_DATA32_FLAT_SELECTOR;
t.tss.esp0 = 0;
t.tss.iomb = sizeof(t.tss);
SetDescValue(t.ldt + LDT_VIDEO_INDEX, 0xB8000, 0X07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(t.ldt + LDT_CODE32_INDEX, 0x00, 0XFFFFF, DA_C + DA_32 + DA_DPL3);
SetDescValue(t.ldt + LDT_DATA32_INDEX, 0x00, 0XFFFFF, DA_DRW + DA_32 + DA_DPL3);
t.ldtSelector = GDT_TASK_LDT_SELECTOR;
t.tssSelector = GDT_TASK_TSS_SELECTOR;
SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&t.ldt, sizeof(t.ldt) - 1, DA_LDT + DA_DPL0);
SetDescValue(&gGdtInfo.entry[GDT_TASK_TSS_INDEX], (uint)&t.tss, sizeof(t.tss) - 1, DA_386TSS + DA_DPL0);
PrintString("Stack Bottom: ");
PrintIntHex((uint)t.stack);
PrintString(" Stack Top: ");
PrintIntHex((uint)t.stack + sizeof(t.stack));
RunTask(&t);
}
我们首先需要定义一个全局的Task对象t,用t来表示一个任务。注意:这个对象必须要初始化为0,否则这个结构体的其他缺省值就不为0,任务就无法正常执行。
TaskA函数用来作为一个任务函数使用,作用是在屏幕上循环打印字母A - Z。
71行 - 76行我们把局部段描述符选择子加载到对应的t的寄存器结构体中,此时对应的局部段描述符的段基址,段界限和段属性还没设置;
78行 - 80行,我们设置esp为任务的私有栈顶,eip为TaskA (我们要运行的任务入口地址),eflags设置为0x3002 (IF 为0,关闭了处理器的中断响应;IOPL 为3),
82 行- 84行,我们把ss0设置为特权级为0栈段选择子,esp设置为0是因为我们这个程序并没有从低特权级跳转到高特权级去执行,所以暂时将它设置为0。
86行 - 88行,我们初始化了这3个局部段描述符。
93行 -94行,我们设置全局段描述符LDT和TSS。
96行 - 99行,我们打印t任务的栈底和栈顶。
101行,我们开始了任务。
程序运行成功!循环在屏幕上打印字母A - Z,打印t任务的栈的范围是0xBA20 - 0xBC20。
可以看出cs后2位为11,说明当前CPL为3,程序当前运行在3特权级。
我们查看到esp寄存器的值为0xBBDC,在我们定义的私有栈的内存范围内。