实现任务切换(使用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进行跳转的过程是
- 初始化一个TSS表
- 加入描述符到GDT表里面
- 使用jmp或call进行跳转
第一个任务需要手动告知CPU自己的TSS在哪一个描述符
TSS表
在进行任务切换的时候最重要的记录当前的状态, x86提供了一个TSS表用来记录这一个任务的状态
x86使用一个这样的结构进行保存任务的状态
这里面存放的是当前任务的状态, CPU会自动把寄存器的状态存放在这里面
这里面的SS, ESP等有多个是给不同特权级的时候使用不同的段, 在处理终中断的时候使用不同的栈
Previous Task Link: 任务的连接, 没有用到
之后是几个不同优先级的时候使用的不同的额栈, ss是栈的段寄存器
cr3记录的是页表的值(用于虚拟内存)
再往后是寄存器的信息
LDT, 记录LDT的选择子(给任务使用的GDT表)
IO图, SSP没有使用
在使用的时候可以使用一个jmp指令进行跳转
在产生中断的时候会从特权级0的位置找到栈, 之后执行中断相关的内容
需要在GDT里面使用段来记录任务的TSS, 通过GDT的描述符进行区分
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, %axltr %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 数组的地址所对应的内容,即跳转的目标地址。