手写操作系统(三十七)-定义和初始化TSS

代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。

1.概述

每个任务关联一个任务状态段,这就是TSS (Task State Segment),用它来表示任务。

TSS是程序员为任务单独定义的一个结构体变量,维护是指CPU自动用此结构体变量保存任务的状态(任务的上下文环境,寄存器组的值)和自动从此结构体变量中载入任务的状态。

当加载新任务时,CPU自动把当前任务(旧任务)的状态存入当前任务的TSS,然后将新任务TSS中的数据载入到对应的寄存器中,这就实现了任务切换。TSS就是任务的代表,CPU用不同的TSS区分不同的任务,因此任务切换的本质就是TSS的换来换去。

因此每个任务都必须有单独的 TSS,所以 TSS 就是任务的代表。

在CPU中用TR寄存器存储TSS信息,它始终指向当前正在运行的任务,任务切换的实质就是TR寄存器指向不同的TSS。

TR寄存器结构如下:

TSS是通过选择子来访问的,将tss加载到寄存器TR的指令是ltr,其指令格式为:

ltr  "16位通用寄存器"或"16位内存单元"

TSS本质上是一片存储数据的内存区域,需要用TSS描述符结构来描述它。

TSS描述符也要在GDT中注册,这样才能“找到它。

TSS描述符属于系统段描述符,因此 S 为0,在 S为0的情况下,TYPE的值为10B1。我们这里关注一下 B 位,B 表示 busy 位,B 位为 0 时,表示任务不繁忙,B 位为 1 时,表示任务繁忙。其他字段的意义与普通数据段类似,不再赘述。

当任务刚被创建时,此时尚未上CPU执行,此时的B位为0,TYPE的值为1001。当任务开始上CPU执行时,处理器自动地把B位置为1,此时TYPE的值为1011。当任务被换下CPU时,处理器把 B位置 0。CPU 利用 B 位来判断被调用的任务是否是当前任务,若被调用任务的 B 位为1,这就表示当前任务自己在调用自己。B位主要是用来给CPU做重入判断用的。

TSS结构如下:

TSS中的字段基本上全是寄存器名称,这些寄存器就是任务运行中的最新状态。

Linux 只用到了 0 特权级和 3 特权级,用户进程处于 3 特权级,内核位于0特权级,因此对于Linux来说只需要在TSS中设置SSO和esp0,我们也效仿它,只设置SS0和esp0的值就够了。

任务在被换下CPU时,由CPU自动地把当前任务的资源状态(所有寄存器、必要的内存结构,如栈等)保存到该任务对应的TSS中(由寄存器TR指定),CPU通过新任务的TSS选择子加载新任务时,会把该TSS中的数据载入到CPU的寄存器中,同时用此TSS描述符更新寄存器TR。以上动作是CPU自动完成的。不过话第一个任务的TSS是需要手工加载的。

TSS 和 LDT 都只能且必须在 GDT 中注册描述符,TR寄存器中存储的是TSS的选择子,LDTR寄存器中存储的是 LDT 的选择子,GDTR 寄存器中存储的是GDT的起始地址及界限偏移(大小减1)。

如图所示:

我们使用 TSS 唯一的理由是为 0 特权级的任务提供栈。效仿 Linux 的任务切换方法的, CPU要求用TSS这是硬指标,Linux为每个CPU创建一个TSS,在各个CPU上的所有任务共享同一个TSS,各CPU的TR寄存器保存各CPU上的TSS,在用ltr指令加载TSS后,该TR寄存器永远指向同一个TSS,之后再也不会重新加载TSS。在进程切换时,只需要把TSS中的SS0及esp0更新为新任务的内核栈的段地址及栈指针。

实际上 Linux对 TSS 的操作是一次性加载 TSS 到 TR,之后不断修改同一个 TSS 的内容,不再进行重复加载操作。

 

2.定义并初始化TSS

我们要定义并初始化TSS

修改/kernel/global.h

#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H
#include "stdint.h"

#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3
#define DESC_G_4K   1
#define DESC_D_32   1
#define DESC_L      0
#define DESC_AVL    0
#define DESC_P      1
#define DESC_DPL_0  0
#define DESC_DPL_1  1
#define DESC_DPL_2  2
#define DESC_DPL_3  3
/*代码段和数据段属于存储段,tss和各自门描述符都属于系统段,
s为1时表示存储段,为0时表示系统段 */
#define DESC_S_CODE 1
#define DESC_S_DATA DESC_S_CODE
#define DESC_S_SYS  0
#define DESC_TYPE_CODE  8
/*x=1,c=0,r=0,a=0 代码段是可执行的、非依从的、不可读的,已访问位a清零*/
#define DESC_TYPE_DATA  2
//x=0.e=0,w=1,a=0 数据段是不可执行的、向上扩展的、可读写,已访问位清零
#define DESC_TYPE_TSS   9

#define TI_GDT 0
#define TI_LDT 1

#define SELECTOR_K_CODE ((1 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_DATA ((2 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_STACK SELECTOR_K_DATA
#define SELECTOR_K_GS   ((3 << 3) + (TI_GDT << 2) + RPL0)
//第三个段描述符是显存,第四个是tss
#define SELECTOR_U_CODE ((5 << 3) + (TI_GDT << 2) + RPL3)
#define SELECTOR_U_DATA ((6 << 3) + (TI_GDT << 2) + RPL3)
#define SELECTOR_U_STACK SELECTOR_U_DATA

#define GDT_ATTR_HIGH ((DESC_G_4K << 7) + (DESC_D_32 << 6) + (DESC_L << 5) + (DESC_AVL << 4))
#define GDT_CODE_ATTR_LOW_DPL3 ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_CODE << 4) + DESC_TYPE_CODE)
#define GDT_DATA_ATTR_LOW_DPL3 ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_DATA << 4) + DESC_TYPE_DATA)

/*TSS描述符属性*/
#define TSS_DESC_D 0
#define TSS_ATTR_HIGH ((DESC_G_4K << 7) + (TSS_DESC_D << 6) + (DESC_L << 5) + (DESC_AVL << 4) + 0x0)
#define TSS_ATTR_LOW  ((DESC_P << 7) + (DESC_DPL_0 << 5) + (DESC_S_SYS << 4) + DESC_TYPE_TSS)
#define SELECTOR_TSS  ((4 << 3) + (TI_GDT << 2) + RPL0)

/*定义GDT中描述符的结构*/
struct gdt_desc{
    uint16_t limit_low_word;
    uint16_t base_low_word;
    uint8_t base_mid_byte;
    uint8_t attr_low_byte;
    uint8_t limit_high_attr_high;
    uint8_t base_high_byte;
};

/*IDT描述符属性*/
#define IDT_DESC_P      1
#define IDT_DESC_DPL0   0
#define IDT_DESC_DPL3   3
#define IDT_DESC_32_TYPE 0xE    //32位的门
#define IDT_DESC_16_TYPE 0x6    //16位的门,不会用到

#define IDT_DESC_ATTR_DPL0 ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)
#define IDT_DESC_ATTR_DPL3 ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)

#define NULL ((void*)0)
#define bool int
#define true 1
#define false 0

#define PG_SIZE 4096

#endif

定义相关的属性

 

新建userprog目录,有关用户进程的代码文件都将存放在此目录中。

userprog/tss.c

#include "tss.h"
#include "stdint.h"
#include "global.h"
#include "string.h"
#include "print.h"
#include "thread.h"

/* 任务状态段 tss 结构 */
struct tss {
    uint32_t  backlink;
    uint32_t* esp0;
    uint32_t  ss0;
    uint32_t* esp1;
    uint32_t  ss1;
    uint32_t* esp2;
    uint32_t ss2;
    uint32_t cr3;
    uint32_t (*eip) (void);
    uint32_t eflags;
    uint32_t eax;
    uint32_t ecx;
    uint32_t edx;
    uint32_t ebx;
    uint32_t esp;
    uint32_t ebp;
    uint32_t esi;
    uint32_t edi;
    uint32_t es;
    uint32_t cs;
    uint32_t ss;
    uint32_t ds;
    uint32_t fs;
    uint32_t gs;
    uint32_t ldt;
    uint32_t trace;
    uint32_t io_base;
};

static struct tss tss;


/* 更新 tss 中 esp0 字段的值为 pthread 的 0 级线 */
void update_tss_esp(struct task_struct* pthread) {
    tss.esp0 = (uint32_t*) ((uint32_t) pthread + PG_SIZE);
}


/* 创建 gdt 描述符 */
static struct gdt_desc make_gdt_desc(uint32_t* desc_addr,
                                     uint32_t limit,
                                     uint8_t attr_low,
                                     uint8_t attr_high) {

    uint32_t desc_base = (uint32_t) desc_addr;
    struct gdt_desc desc;
    desc.limit_low_word = limit & 0x0000ffff;
    desc.base_low_word = desc_base & 0x0000ffff;
    desc.base_mid_byte = ((desc_base & 0x00ff0000) >> 16);
    desc.attr_low_byte = (uint8_t) (attr_low);
    desc.limit_high_attr_high = (((limit & 0x000f0000) >> 16) + (uint8_t) (attr_high));
    desc.base_high_byte = desc_base >> 24;
    return desc;
}


/* 在 gdt 中创建 tss 并重新加载 gdt */
void tss_init() {
    put_str("tss_init start\n");
    uint32_t tss_size = sizeof(tss);
    memset(&tss, 0, tss_size);
    tss.ss0 = SELECTOR_K_STACK;
    // 当 io位图偏移地址 大于等于 tss大小 - 1 时表示没有 io位图
    tss.io_base = tss_size;

    // gdt 段基址为 0x900, 把 tss 放到第 4 个位置, 也就是 0x900 + 0x20 的位置
    // 在 gdt 中添加 DPL 为 0 的 TSS 描述符
    *((struct gdt_desc*) 0xc0000920) = make_gdt_desc((uint32_t*) &tss, tss_size - 1, TSS_ATTR_LOW, TSS_ATTR_HIGH);
    // 在 gdt 中添加 DPL 为 3 的数据段和代码段描述符
    *((struct gdt_desc*)0xc0000928) = make_gdt_desc((uint32_t*)0, 0xfffff, GDT_CODE_ATTR_LOW_DPL3, GDT_ATTR_HIGH);
    *((struct gdt_desc*)0xc0000930) = make_gdt_desc((uint32_t*)0, 0xfffff, GDT_DATA_ATTR_LOW_DPL3, GDT_ATTR_HIGH);

    // gdt 16 位的 limit + 32 位的段基址 = 48 位(0~15 = limit, 16~47 = GDT起始地址)
    // limit: 7个描述符(包括第0个描述符), 所以 limit = 8 * 7 - 1
    // 将 0xc0000900 先转位32位再转为64位(不可一步转为64位)
    uint64_t gdt_operand = ((8 * 7 - 1) | ((uint64_t) (uint32_t) 0xc0000900 << 16));
    asm volatile("lgdt %0" : : "m"(gdt_operand));
    asm volatile("ltr %w0" : : "r"(SELECTOR_TSS));
    put_str("tss_init and ltr done\n");
}

struct tss就是TSS 的结构体

update_tss_esp函数用来更新TSS中的esp0,这是Linux任务切换的方式,只修改TSS中的特权级0对应的栈。此函数将TSS中esp0修改为参数pthread的0级栈地址,也就是线程pthread的PCB所在页的最顶端–(uint32-t)pthread + PG_SIZE,此栈地址是用户进程由用户态进入内核态时所用的栈,这和之前咱们的内核线程地址是一样的,用户进程进入内核态后,除了拥有单独的地址空间外,其他方面和内核线程是一样的。

我们的GDT是在loader.S中进入保护模式时实现的,那时候用汇编语言直接生成的GDT,进入内核后,我们已经用C语言编程了,现在我们还需要GDT中增加新的描述符。

函数make_gdt_desc专门生成描述符结构,并不是直接在GDT中安装好描述符,而是返回生成的描述符。 此函数的实现是按照段描述符的格式来拼数据,在内部生成一局部描述符结构体变量 struct gdt_desc desc,后面把此结构体变量中的属性填充好后通过return返回其值。

函数tss_init用来初始化tss并将其安装到GDT中外,还另外在GDT中安装两个供用户进程使用的描述符,一个是DPL为 3 的数据段,另一个是DPL为3的代码段。 memset(&tss,0,tss_size)将全局变量 tss 清 0 后,tss.ss0 = SELECTOR_K_STACK为其 ss0 字段赋0级栈段的选择子 SELECTOR_K_STACK. “tss. io_base= tss_size”将tss 的 io_base字段置为tss 的大小tss_size,这表示此 Tss 中并没有IO位图。当IO位图的偏移地址大于等于TSS大小减1时,就表示没有IO位图。

然后我们在GDT中安装TSS 描述符。 在调用make_gdt_desc后,其返回的描述符是安装在0xc0000920的地址,即*((struct gdt_desc*)0xc0000920),,其实此处用0x920也是可以的,我们把低端1MB空间的页表映射为同物理地址相同,并且把内核开始使用的第768个页表指向了同低端1MB空间相同的物理页,因此此时的0xc0000920可以用0x920代替。

为什么把 TSS 描述符放在 Oxc0000920 的地址呢? 32位保护模式下的描述符大小都是8字节,在GDT中第0个段描述符不可用,第1个为代码段,第2个为数据段和栈,第3个为显存段,因此把tss放到第4个位置,也就是0xc0000900+0x20的位置。

接下来安装了两个DPL为 3 的段描述符,分别是代码段和数据段,这是为用户进程提前做的准备,它们在GDT中的位置基于TSS描述符顺延,,分别是偏移GDT0x28和0x30的位置。

由于已经变更了GDT,需要用Igdt指令重新加载GDT,定义变量gdt_operand作为lgdt指令的操作数。

lgdt的指令格式其操作数是”16位表界限&32位表的起始地址”,这里要求表界限要放在前面,也就是操作数中前2字节的低地址处。在原有描述符的基础上我们又新增了3个描述符,加上第0个不可用的哑描述符,GDT中现在一共是7个描述符,因此表界限值为8*7-1。 操作数中的高32 位是GDT起始地址,在这里我们把GDT线性地址0xc0000900先转换成uint32_t后,再将其转换成uint64_t位(不可一步到位转为uint64_t),最后通过按位或运算符拼合在一起。

然后通过内联汇编asm volatile(“lgdt %0” : : “m”(gdt_operand))将新的GDT重新加载,asm volatile(“ltr %w0” : : “r”(SELECTOR_TSS))将tss加载到TR。 至此,新的GDT和TSS已经生效

 

对应userprog/tss.h

#ifndef __USERPROG_TSS_H
#define __USERPROG_TSS_H
#include "stdint.h"
#include "thread.h"

/*更新tss中esp0字段的值为pthread的0级栈*/
void update_tss_esp(struct task_struct* pthread);
/*在gdt中创建tss并将其安装到gdt中*/
void tss_init(void);

#endif

 

将tss_init加入init_all。

kernel/init.c

#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "timer.h"
#include "memory.h"
#include "thread.h"
#include "console.h"
#include "keyboard.h"
#include "tss.h"

/*负责初始化所有模块*/
void init_all(){
    put_str("init_all\n");
    idt_init();     //初始化中断
    mem_init();
    thread_init();
    timer_init();   //初始化PIT   
    console_init(); //控制台初始化最好放在开中断之前
    keyboard_init();
    tss_init();
}

 

3.执行结果

makefile同样更改

LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
    $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o $(BUILD_DIR)/debug.o \
    $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
    $(BUILD_DIR)/switch.o $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o  \
    $(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
                   lib/stdint.h lib/string.h kernel/global.h  \
                   lib/kernel/print.h thread/thread.h
    $(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o:kernel/init.c kernel/init.h lib/kernel/print.h lib/stdint.h \
    kernel/interrupt.h device/timer.h kernel/memory.h thread/thread.h device/console.h \
    userprog/tss.h
    $(CC) $(CFLAGS) $< -o $@

 

执行结果如下:

 

 

4.参考

郑钢著操作系统真象还原

田宇著一个64位操作系统的设计与实现

丁渊著ORANGE’S:一个操作系统的实现

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇