核心代码 使用jmp来切换任务
增加一个switch_task 过程手动切换任务,使用jmp切换
-
注:
-
无论是call/jmp/iret在切换前都会保存当前任务的所有状态(上下文)
-
也就是把当前所有寄存器的值保存到TR寄存器指向的TSS结构
-
以及隐含执行指令(ltr,lldt)
-
为每个任务创建一个信息控制块存放在链表中,维护当前任务的状态
-
在链表中根据状态(0=待运行,1=正在运行,2=结束)找到待运行的任务, 使用JMP指令执行此新任务
-
jmp指令:
-
- 保存当前上下文到TR指向的TSS结构中
-
- 将当前任务的TSS描述符B=0, 新任务的TSS描述符B=1
-
- 加载新任务的TSS描述符到TR中
-
- 开始执行新任务
-
信息控制块的状态字段与TSS描述符的B位同步更新
;上一个core.asm 通过 call TSS选择子 来调度用户程序
;本程序 为内核添加一个TCB => 增加过程 init_kernel
;修改 create_kernel_tss 过程
;通过TCB位置:0x04的状态字段 来半自动切换任务
;添加一个switch_task 过程,用于切换任务
;switch_task 使用jmp指令执行新任务
; jmp指令会将当前任务的TSS描述符B=0,新任务TSS描述符B=1
; 保存当前上下文到TSS结构中
; 加载新任务的TSS结构到TR, 开始执行新任务
;TCB状态:
;如果为1:是当前正在运行的任务
;2:任务结束,需要从链表中去除此任务
;0:可以运行的任务
;为每一个任务单独创建一个TCB,用于方便跟踪管理用户各种信息
;TCB包含了任务的所有所需的信息:
;TCB结构: (总计0x58字节)
;0x00: 下一个TCB地址
;0x04:状态 ;0:不忙 1:忙 2:任务结束
;0x08:用户程序基址
;0x0c:LDT 界限
;0x10:LDT基址
;0x14:LDT 选择子
;0x18:TSS界限
;0x1c:TSS基址
;0x20:TSS 选择子
;0x24:用户头部段选择子
;0x28:特权0 栈基址
;0x2c:特权0 栈长度,字节为单位
;0x30:特权0 esp
;0x34:特权0 栈段选择子
;0x38:特权1 栈基址
;0x3c:特权1 栈长度
;0x40:特权1 esp
;0x44:特权1 栈段选择子
;0x48:特权2 栈基址
;0x4c:特权2 栈长度
;0x50:特权2 esp
;0x54:特权2 栈段选择子
;--------------------
;常量定义
;重定位表中的每一项段信息占用12字节
REALLOC_TABLE_EACH_ITEM_BYTES EQU 12
;符号信息表每项占用16字节
SYMBOL_TABLE_EACH_ITEM_BYTES equ 16
;地址表每项占用16字节
SYMBOL_TABLE_ADDR_EACH_ITEM_BYTES EQU 16
;用户符号信息表每项占用12字节
USER_SYMBOL_TABLE_EACH_ITEM_BYTES EQU 12
;用户程序扇区号
USER_APP_SECTOR EQU 100
;分配内存的起始地址
ALLOC_MEM_BASE_ADDR EQU 0x100000
;在MBR中创建的所有描述符DPL=0, 对应的选择子RPL=0
;MBR中定义
SEL_4G_DATA equ 0x18 ;数据段
SEL_STACK EQU 0x20 ;栈
SEL_0XB8000 EQU 0x10 ;显存
SEL_MBR EQU 0X08 ;MBR段
;内核代码段和数据段所有描述符的DPL=0,选择子RPL=0
;当前程序的,由MBR创建
SEL_HEADER EQU 0x28 ; 头部段选择子
SEL_CODE EQU 0x30 ;代码段
SEL_DATA EQU 0X38 ;数据段
SEL_FUNC EQU 0X40 ;函数段
;--------------------
[bits 32]
section header vstart=0 align=16
;程序长度
app_len dd tail_end ;0x00
;入口点偏移,段地址
;当此程序被加载后,物理段地址被替换成段选择子
entry dd start ;0x04
dd section.code.start ;0x08
;重定位表有几项
realloc_table_len dd (table_end - table_start)/REALLOC_TABLE_EACH_ITEM_BYTES ;0x0c
;重定位表
;重定位表中存放了每个段的 : 段基址,段界限(段长度-1),段属性
;被加载程序处理后,所有的段基址都将被替换成段选择子
table_start:
;头部段
seg_header_len dd header_end-1 ;段界限, 0x10
seg_header_addr dd section.header.start ;段基址, 0x14
seg_header_attr dd 0x00409200 ;段属性, 0x18
;代码段
seg_code_len dd code_end-1 ;段界限,0x1c
seg_code_addr dd section.code.start ;段基址,0x20
seg_code_attr dd 0x00409800 ;段属性,0x24
;数据段
seg_data_len dd data_end-1 ; 段界限,0x28
seg_data_addr dd section.data.start ;段基址,0x2c
seg_data_attr dd 0x00409200 ;段属性,0x30
;函数段
seg_function_len dd function_end-1 ;段界限,0x34
seg_function_addr dd section.function.start ;段基址,0x38
seg_function_attr dd 0x00409800 ;属性,0x3c
table_end:
;----------------
;以下段选择子由mbr传递过来
;4G数据段
seg_4g_data dd 0 ;0x40
;栈段
seg_stack dd 0 ;0x44
;显存段
seg_0xb8000 dd 0 ;0x48
;MBR段
seg_mbr dd 0 ;0x4c
header_end:
section code vstart=0 align=16
start:
;创建调用门
push dword 3 ;调用门DPL
call SEL_FUNC:create_call_gate
;为内核创建TCB和TSS
;初始化内核使用的TCB和TSS
;并为内核TCB赋值状态字段
call SEL_FUNC:init_kernel
push USER_APP_SECTOR ;用户扇区号
call SEL_FUNC:load_app ;加载用户程序
;开始切换任务
;根据TCB中的状态来调度
;如果是1:当前正在运行的任务,2:结束的任务, 0:可以调度的任务
;调度任务使用: jmp TSS选择子
;jmp指令会将新任务的TSS描述符B=1,旧任务TSS描述符B=0
;同样的, 需要同步修改TCB中的状态
;在使用 jmp 指令时 内核代码被挂起, 上下文被保存到内核TSS结构中
;内核EIP指向 pop ds, 也就是等用户调用switch_task 后, 将执行这条指令
call SEL_FUNC:switch_task
;--------------从用户任务切换回来----------------
;在用户任务保存自己的上下文在TSS后
;加载并切换到内核任务, TR=当前内核TSS选择子
;当前所有寄存器(上下文) 都将从内核TSS结构恢复
;此时cs=当前代码段,eip=hlt指令
hlt
code_end:
section function vstart=0 align=16
;切换任务
switch_task:
push ebp
mov ebp,esp
pushad
push es
push ds
;循环链表,找到下一个TCB状态为0的任务
;设置旧TCB状态为0, 设置新TCB状态为1
;使用jmp TSS选择子,进行任务切换
mov eax,SEL_4G_DATA ;4g
mov es,eax
mov eax,SEL_DATA ;自己数据段
mov ds,eax
;从当前正在运行的TCB往后找
;先判断是不是只有内核一个任务
mov eax,[ds:tcb_header]
mov ecx,[es:eax]
cmp ecx,0
jz .switch_task_done ;只有内核一个任务,就不切换
mov ebx,[ds:tcb_curr]
.find_next_ready_task:
mov ecx,[es:ebx] ;首4个字节是下一个TCB地址
or ecx,ecx ;下一个为空
jz .find_from_begining ;从头开始
cmp dword [es:ecx+0x04],0
jz .run_task ;找到了
mov ebx,ecx ;找下一个
jmp .find_next_ready_task
;从头开始找
.find_from_begining:
mov ebx,[ds:tcb_header]
cmp dword [es:ebx + 0x04],0
cmovz ecx,ebx
jnz .find_next_ready_task
;开始运行新任务
.run_task:
mov ebx,[ds:tcb_curr] ;修改旧任务状态
mov dword [es:ebx + 0x04],0
mov [ds:tcb_curr],ecx ;修改新任务地址
mov dword [es:ecx + 0x04],1 ;修改新任务状态
;保存当前上下文到TR指向的TSS结构中
;把当前任务的TSS描述符B=0, 新任务的TSS描述符B=1
;加载新任务的TSS结构到TR中
;开始执行新任务
jmp far [es:ecx + 0x1c ] ;切换任务
.switch_task_done:
pop ds
pop es
popad
mov esp,ebp
pop ebp
retf
;初始化内核:为内核创建TCB和TSS
init_kernel:
push ebp
mov ebp,esp
push ds
push es
pushad
mov eax, SEL_DATA
mov ds,eax
push dword 0x58
call SEL_FUNC:create_tcb ;返回eax, TCB地址
mov edi,eax
push eax ;加入TCB链表
call SEL_FUNC:append_tcb_to_linklist
;为内核创建TSS
;这是一个空的TSS结构
;为了在切换到用户任务的时候,保存内核的所有寄存器到TSS结构中
mov eax,SEL_4G_DATA
mov es,eax
push es ;TCB段选择子
push edi ;TCB地址
call SEL_FUNC:create_kernel_tss
;把 kernel_tss 加载任务寄存器TR
;当前TR寄存器指向 kernel_tss描述符
;这个空的TSS结构只用于为当前正在运行的内核代码创建一个任务.
;以用来切换任务的时候能把当前状态保存到TSS结构
;ltr 只是加载并把描述符的B=1 (忙),并没有执行, 为了下面切换做准备
ltr [es:edi + 0x20]
;同时,为TCB状态字段赋值
;把内核TCB设置成:忙
mov dword [es:edi + 0x04],1 ;1:忙 , 0: 不忙
;设置当前运行的TCB
mov dword [ds:tcb_curr],edi
popad
pop es
pop ds
mov esp,ebp
pop ebp
retf
;加载程序
;参数: 用户程序扇区号
;栈中位置 : 12
load_app:
push ebp
mov ebp, esp
push es
push ds
pushad
mov eax,SEL_4G_DATA ;4G空间
mov es,eax
;创建一个TCB结构用于跟踪管理用户程序
;一个TCB结构需要0x58字节
push dword 0x58
call SEL_FUNC:create_tcb ;返回eax,TCB地址
mov edi, eax ;TCB地址保存一份
push eax
call SEL_FUNC:append_tcb_to_linklist ;加入TCB链表
;为每一个任务创建一个LDT用于单独存放描述符
;每一个任务最多存放20个描述符
push dword 0xA0 ;字节数
call SEL_FUNC:create_ldt
mov [es:edi + 0x10], eax ; TCB保存LDT基址
mov dword [es:edi + 0x0c], 0xffff ; 初始化LDT界限值, 当前共0字节,因此(0-1)=>最大界限
;开始读取用户程序
push dword [ebp + 12] ;用户起始扇区号
call SEL_FUNC:read_user_app ;读取整个用户程序, 返回eax,用户起始地址
mov [es:edi + 0x08], eax ;保存基址
;为用户程序建立段描述符, 描述符存放在自身的LDT中
push es ;访问TCB段选择子
push edi ;TCB地址
push dword 3 ;为描述符建立哪种DPL
call SEL_FUNC:create_app_descriptor
;为用户建立栈
push es ;段选择子
push edi ;TCB地址
call SEL_FUNC:create_stack_for_user
;为用户建立TSS,用于任务切换
;TSS 描述符只能存放在GDT中
push es ;段选择子
push edi ;TCB地址
push dword 0 ;TSS描述符DPL, 只能由特权0去调度
call SEL_FUNC:create_min_tss
;为用户建立额外的特权栈,用于特权转移(也就是通过调用门调用系统提供的过程)
;特权栈存放在TSS结构中
;此过程的参数 有些冗余了, 实际只需要传递 TCB地址, TCB段选择子即可,TCB中包含了TSS基址
;特权栈的描述符存放在LDT中
push es ;TCB段选择子
push edi ;TCB地址
push es ;访问TSS地址的段选择子
push dword [es:edi + 0x1c] ;TSS基址
push dword 3 ;为用户程序创建特权栈
call SEL_FUNC:create_privilege_stack
;到此, LDT的所有描述符(用户程序的段描述符,栈描述符, 特权栈描述符)全部建立完成,界限确定
;为LDT建立描述符, 存放在GDT中
mov eax,edi ;TCB地址
add eax,0x0c ;LDT界限的地址
push es ;访问LDT的段选择子
push eax ;LDT界限的地址
call SEL_FUNC:create_LDT_descriptor ; 返回 edx,eax 描述符
push edx
push eax
call SEL_FUNC:add_to_gdt ;把LDT描述符加入GDT, 返回 ax 段选择子
;保存LDT段选择子到TCB中
mov [es:edi + 0x14] , ax
;填充TSS的LDT字段
mov ebx,[es:edi + 0x1c] ;tss基址
mov [es:ebx + 96], ax
;为TSS寄存器的初始化
push es ;TCB段选择子
push edi ;TCB地址
call SEL_FUNC:init_TSS_regs
;加载局部描述符表 以使用头部段选择子
lldt [es:edi + 0x14]
;为用户处理符号表
push dword [es:edi+0x24] ;用户头部段选择子
call SEL_FUNC:realloc_user_app_symbol_table
popad
pop ds
pop es
mov esp,ebp
pop ebp
retf 4
;初始化TSS各寄存器的值
;参数: TCB地址, TCB段选择子
;栈中位置: 12 16
init_TSS_regs:
push ebp
mov ebp,esp
push es
push ebx
push eax
push esi
push edi
mov ebx,[ebp + 16] ;段选择子
mov es,ebx
mov ebx,[ebp + 12] ;TCB地址
mov esi,[es:ebx + 0x08 ] ;用户起始地址
mov edi,[es:ebx + 0x1c] ;TSS基址
;用户头部重定位表中的各个段选择子 来初始化 TSS 结构中的各寄存器
;TSS eip
mov eax,[es:esi + 0x04] ;入口点偏移
mov [es:edi + 32] , eax
;TSS cs寄存器
mov ax,[es:esi + 0x08] ;入口点段选择子
mov [es:edi + 76],ax
;TSS ds寄存器
mov ax,[es:esi + 0x14] ;用户头部段
mov [es:edi + 84],ax
;TSS ss寄存器
mov ax,[es:esi + 0x38] ;栈段
mov [es:edi + 80],ax
mov word [es:edi + 92],0 ;GS
mov word [es:edi + 88],0 ;FS
mov word [es:edi + 72],0