5.1 任务切换使用jmp半自动切换任务

核心代码 使用jmp来切换任务

增加一个switch_task 过程手动切换任务,使用jmp切换

  • 注:

  • 无论是call/jmp/iret在切换前都会保存当前任务的所有状态(上下文)

  • 也就是把当前所有寄存器的值保存到TR寄存器指向的TSS结构

  • 以及隐含执行指令(ltr,lldt)

  • 为每个任务创建一个信息控制块存放在链表中,维护当前任务的状态

  • 在链表中根据状态(0=待运行,1=正在运行,2=结束)找到待运行的任务, 使用JMP指令执行此新任务

  • jmp指令:

    1. 保存当前上下文到TR指向的TSS结构中
    1. 将当前任务的TSS描述符B=0, 新任务的TSS描述符B=1
    1. 加载新任务的TSS描述符到TR中
    1. 开始执行新任务
  • 信息控制块的状态字段与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  
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值