x86下使用硬件实现的任务切换(TSS表)---使用代码讲解

实现任务切换(使用TSS)

视频讲解可以看这一个课程

• The current program, task, or procedure executes a JMP or CALL instruction to a TSS descriptor in the GDT.

• The current program, task, or procedure executes a JMP or CALL instruction to a task-gate descriptor in the GDT or the current LDT.
这两段的意思大概是可以使用jmp或者使用call指令指向GDT或者LDT表里面的TSS描述符就可以进行跳转
GDT表讲解

所以实际使用TSS进行跳转的过程是

  1. 初始化一个TSS表
  2. 加入描述符到GDT表里面
  3. 使用jmp或call进行跳转

第一个任务需要手动告知CPU自己的TSS在哪一个描述符

TSS表

在进行任务切换的时候最重要的记录当前的状态, x86提供了一个TSS表用来记录这一个任务的状态
x86下使用硬件实现的任务切换(TSS表)---使用代码讲解_第1张图片

x86使用一个这样的结构进行保存任务的状态

这里面存放的是当前任务的状态, CPU会自动把寄存器的状态存放在这里面

这里面的SS, ESP等有多个是给不同特权级的时候使用不同的段, 在处理终中断的时候使用不同的栈

Previous Task Link: 任务的连接, 没有用到

之后是几个不同优先级的时候使用的不同的额栈, ss是栈的段寄存器

cr3记录的是页表的值(用于虚拟内存)

再往后是寄存器的信息

LDT, 记录LDT的选择子(给任务使用的GDT表)

IO图, SSP没有使用

在使用的时候可以使用一个jmp指令进行跳转

在产生中断的时候会从特权级0的位置找到栈, 之后执行中断相关的内容

需要在GDT里面使用段来记录任务的TSS, 通过GDT的描述符进行区分

x86下使用硬件实现的任务切换(TSS表)---使用代码讲解_第2张图片

TSS表在GDT里面的描述符
Base: 起始地址

Segment Limit: 界限-1

DPL: 段的访问权限, 0-3

P: 这一个段是否有效

G: 指定limit的单位是byte还是4KB

AVL: 保留

type: 段的类型

B: 忙标志

代码实现

//任务的TSS保存位置
#define TASK0_TSS_SEG           ((5 * 8))
#define TASK1_TSS_SEG           ((6 * 8))

//GDT表里面加两个TSS表描述符
struct {uint16_t limit_l, base_l, basehl_attr, base_limit;}gdt_table[256] __attribute__((aligned(8))) = {
	....
    //TSS表, 由于直接使用一个数组作为TSS会导致报错,这里基地址初始化为0, 后面在C语言里面加地址
    [TASK0_TSS_SEG /8] = {0x68, 0, 0xe900, 0},
    [TASK1_TSS_SEG /8] = {0x68, 0, 0xe900, 0},
};
....
//初始化几个32位的栈, 使用模式为特权级3
uint32_t task0_dpl3_stack[1024];//用户级的栈
uint32_t task1_dpl3_stack[1024];
uint32_t task0_dpl0_stack[1024];//系统级的栈
uint32_t task1_dpl0_stack[1024];
    
//定义一个TSS结构
//任务切换的时候栈之类的寄存器不会保存, 需要初始化设置
uint32_t task0_tss[] = {
	//依次填入TSS表里面的信息
};
//设置要切换的任务的栈以及任务的入口
uint32_t task1_tss[] = {
	//依次填入TSS表里面的信息
};

.....
	//把表地址记录进入
    gdt_table[TASK0_TSS_SEG / 8].base_l = (uint16_t)(uint32_t)task0_tss;
    gdt_table[TASK1_TSS_SEG / 8].base_l = (uint16_t)(uint32_t)task1_tss;
  • 使用TR寄存器保存当前的任务的TSS对应的GDT位置, 需要使用汇编指令设置这一个值
	//告诉CPU正在运行的任务
	mov $TASK0_TSS_SEG, %ax
	ltr %ax

实际的任务切换

void task_sched(void){
    static int task_tss = TASK0_TSS_SEG;
    //选一个TSS表
    task_tss = (task_tss == TASK0_TSS_SEG) ? TASK1_TSS_SEG : TASK0_TSS_SEG;
    uint32_t addr[] = {0, task_tss};//偏移以及选择子
    __asm__ __volatile__("ljmpl *(%[a])"::[a]"r"(addr));
}

*() 是通过间接寻址方式访问内存中的内容。在这段代码中,*(%[a]) 表示将 %[a] 所代表的地址作为一个指针,然后访问这个指针指向的内存位置。

在远跳指令 ljmp 中,需要跳转的目标地址是通过一个指针来指定的。所以,使用 *() 来对指针进行间接寻址,以获取指针所指向的内容(即跳转目标地址),并将该地址作为参数传递给 ljmp 指令。

在这段代码中,*(%[a]) 中的 %[a] 是一个占位符,用来表示汇编代码中的输入变量 [a],即地址数组 addr 的地址。所以 *(%[a]) 表示获取 addr 数组的地址所对应的内容,即跳转的目标地址。

你可能感兴趣的:(手写操作系统,linux,服务器,经验分享,笔记,windows,ubuntu,centos)