下面这段程序定义了两个任务,每个任务都有自己的TSS,LDT。任务0打印字符‘a’,任务1打印字符'b'.任务切换通过时钟中断处理过程完成。时钟到达时,判断当前任务号,切换到另一个任务,并用jmp tss:0的方式实现任务切换。任务中通过0x80系统调用打印字符。任务切换时,cpu会自动把当前任务的上下文(主要是各种寄存器,ldt)等保存当当前任务的TSS段中,并加载目标任务的TSS段相关信息,切换到新任务。
LATCH equ 11930 ;时钟中断
VIDEO_DS equ 0x18 ;显存段
TSS0_CS equ 0x20 ;任务0 TSS段
TSS0_LDT equ 0x28 ;任务0 LDT段
TSS1_CS equ 0x30 ;任务1 TSS段
TSS1_LDT equ 0x38 ;任务1 LDT段
[SECTION text]
[BITS 32]
startup_32:
mov eax,0x10
mov ds,ax
lss esp,[init_stack] ;mem low->reg,mem high->ss
call setup_idt ;设置默认IDT
call setup_gdt ;设置GDT
mov eax,0x10 ;设置完GDT后,重新加载ds,es,fs,gs,ss,esp
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
lss esp,[init_stack]
;设置8253定时芯片,把计数器通道0设置成每隔10ms向中断控制器发送一个中断请求
mov al,0x36
mov edx,0x43
out dx,al
mov eax,LATCH
mov edx,0x40
out dx,al
mov al,ah
out dx,al
;在IDT表第8和0x80项处分别设置定时中断门和系统调用陷阱门描述符
; 31 ------------------------- 16 ---------------------8 --------- 0 中断门描述符格式
; 过程入口点偏移高位 P DPL 01110 高32位
; 段 选 择 符 过程入口点偏移低位 低32位
mov eax,0x00080000 ;eax高位为内核代码段选择符,低位为时钟中断程序偏移
mov ax,time_interrupt
mov dx,0x8E00 ;中断门类型;
mov ecx,0x08 ;中断向量号,BIOS设置的时钟中断为8,暂用它
lea esi,[idt+ecx*8] ;取第8号中断向量地址
mov dword [esi],eax ;设置低32位
mov dword [esi+4],edx ;设置高32位
; 31 ----------------------- 16 ---------------------8 --------- 0 陷阱门描述符格式
; 过程入口点偏移高位 P DPL 01111 高32位
; 段 选 择 符 过程入口点偏移低位 低32位
mov eax,0x00080000
mov ax,system_interrupt ;过程入口点偏移
mov dx,0xEF00 ;DPL=3,特权级3可执行
mov ecx,0x80
lea esi,[idt+ecx*8] ;0x80中断向量描述符地址
mov dword [esi],eax ;设置低32位
mov dword [esi+4],edx ;设置高32位
;END 设置IDT 0x08和0x80
;移动到任务0
pushf
and dword [esp],0xffffbfff
popf
mov eax,TSS0_CS
ltr ax ;加载任务0 TR
mov eax,TSS0_LDT
lldt ax ;加载任务LDT
mov dword [current],0
sti ;开中断
;jmp $
push dword 0x17 ;任务0局部数据段选择符
push dword init_stack ;堆栈指针
pushf ;标志寄存器
push dword 0x0f ;任务0局部代码段
push dword task0 ;任务0 eip
iret ;中断返回指令,切换到特权级3任务0执行
setup_gdt:
lgdt [lgdt_opcode]
ret
;begin setup_idt fun
setup_idt:
lea edx,[ignore_int] ;中断入口偏移放入edx
mov eax,0x00080000 ;eax高位为内核代码段选择子
mov ax,dx ;eax低位为中断入口偏移低位
mov dx,0x8E00 ;edx低位为中断门定义.DPL=0
lea edi,[idt] ;得到idt表位置,准备初始化
mov ecx,256 ;初始化256 IDT描述符
rp_idt:
mov dword [edi],eax
mov dword [edi+4],edx
add edi,8
dec ecx
jne rp_idt
lidt [lidt_opcode]
ret
;end setup_idt fun
;默认中断处理函数
ignore_int: ;默认中断处理函数
push ds
push eax
mov eax,0x10 ;内核数据段
mov ds,ax
mov eax,'C' ;设置显示'c'字符
call write_char
pop eax
pop ds
iret
;END 默认中断处理函数
;时钟中断处理函数
align 2
time_interrupt:
push ds
push eax
mov eax,0x10 ;ds->内核数据段
mov ds,ax
mov al,0x20
out 0x20,al ;立即设置允许其它中断,向8259A发送EOI命令
mov al,'S'
call write_char
mov eax,1
cmp eax,dword [current]
je goto_tss0 ;当前任务号为1,切换任务0
mov dword [current],eax
jmp TSS1_CS:0
jmp exit_time
goto_tss0:
mov dword [current],0
jmp TSS0_CS:0
exit_time:
pop eax
pop ds
iret
;END 时钟中断处理程序
;系统调用0x80,显示字符串
system_interrupt:
push ds
push edx
push ecx
push ebx
push eax
mov edx,0x10 ;内核数据段
mov ds,edx
call write_char
pop eax
pop ebx
pop ecx
pop edx
pop ds
iret ;软中断返回
;END 系统调用
;Begin write_char
write_char:
push gs
push ebx
mov ebx,VIDEO_DS
mov gs,bx
mov ebx,dword [scr_loc]
shl ebx,1
mov [gs:ebx],al
shr ebx,1
inc ebx
cmp ebx,2000
jb exit
mov ebx,0
exit:
mov dword [scr_loc],ebx
pop ebx
pop gs
ret
;End write_char
;内存控制变量
color: dw 0 ;显示颜色
current: dd 0 ;当前执行的任务号
scr_loc: dd 0 ;屏幕位置
;BEGIN lidt
align 2
lidt_opcode:
dw 256*8-1 ;长度
dd idt ;线性基址
;END lidt
lgdt_opcode:
dw (end_gdt - gdt)-1
dd gdt
align 3
gdt:
dd 0x00000000 ;NULL
dd 0x00000000
dd 0x000007ff
dd 0x00c09a00 ;内核代码段0x08
dd 0x000007ff
dd 0x00c09200 ;内核数据段0x10
dd 0x80000002
dd 0x00c0920b ;显存数据段描述符0x18
; 31 ----------------24------------19 ---------------- 16 ---------11-------8 ---------------- 0 系统段描述符之TSS,LDT
; 基地址31...24 G D 0 AVL 段限长19...16 P DPL 0 TYPE 基地址23...16
; 1001 tss
; 0010 ldt
; 基地址 15......0 段限长 15.....0
dw 0x68,tss0,0xe900,0x0 ;TSS0段选择符0x20
dw 0x40,ldt0,0xe200,0x0 ;LDT0 0x28
dw 0x68,tss1,0xe900,0x0 ;TSS1段选择符 0x30
dw 0x40,ldt1,0xe200,0x0 ;LDT1 0x38
end_gdt:
idt:
times 512 dd 0 ;初始化中断描述表
kernel_stack:
times 128 dw 0 ;512B内核堆栈
init_stack:
dd init_stack ;堆栈段偏移位置
dw 0x10 ;内核数据段选择符
ldt0:
dd 0x00000000,0x00000000
dd 0x000003ff,0x00c0fa00 ;局部代码段描述符 0x0f
dd 0x000003ff,0x00c0f200 ;局部数据段描述符 0x17
tss0:
dd 0 ;back link
dd krn_stk0,0x10 ;内核esp0,ss0
dd 0,0,0,0,0 ;esp1,ss1,esp2,ss2,cr3
dd 0,0,0,0,0 ;eip,eflags,eax,ecx,edx
dd 0,0,0,0,0 ;ebx,esp,ebp,esi,edi
dd 0,0,0,0,0,0 ;es,cs,ss,ds,fs,gs
dd TSS0_LDT,0x80000000 ;ldt,trace bitmap
times 128 dd 0
krn_stk0: ;任务0内核栈esp
ldt1:
dd 0x00000000,0x00000000
dd 0x000003ff,0x00c0fa00 ;局部代码段描述符 0x0f
dd 0x000003ff,0x00c0f200 ;局部数据段描述符 0x17
tss1:
dd 0 ;back link
dd krn_stk1,0x10 ;内核esp0,ss0
dd 0,0,0,0,0 ;esp1,ss1,esp2,ss2,cr3
dd task1,0x200,0,0,0 ;eip,eflags,eax,ecx,edx
dd 0,usr_stk1,0,0,0 ;ebx,esp,ebp,esi,edi
dd 0x17,0x0f,0x17,0x17,0x17,0x17 ;es,cs,ss,ds,fs,gs
dd TSS1_LDT,0x80000000 ;ldt,trace bitmap
times 128 dd 0
krn_stk1: ;任务1内核栈
task0:
mov eax,0x17
mov ds,ax ;用户数据段
mov al,65
int 0x80 ;软中断,系统调用显示字符'A'
mov ecx,0xffff
loop $
jmp task0
task1:
mov al,66
int 0x80 ;软中断,系统调用显示字符'A'
mov ecx,0xffff
loop $
jmp task1
times 128 dd 0
usr_stk1: