devbox
算法编织者
这个屌丝很懒,什么也没留下!
热门标签
热门文章
  • 1Android之Adapter用法总结_android setadapter
  • 2报错" href="/blog/article/detail/82574" target="_blank">vue使用scss方法(报错解决方案),及变换主题色_
    当前位置:   article > 正文

    操作系统真象还原实验记录之实验十九:实现用户进程_操作系统真相还原实验

    操作系统真相还原实验

    操作系统真象还原实验记录之实验十九:实现用户进程

    1.相关基础知识

    1.1 TSS LDT TR LDTR的介绍

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    x86硬件设计原生支持的任务切换,每一个任务都有一个TSS和一个LDT
    TSS、LDT的段描述符位于GDT。
    中断发生依靠中断向量号调用任务门描述符,任务门中有TSS描述符的选择子
    LDT包括只属于当前任务的各种代码段、数据段;TSS结构见上,有各种寄存器。
    TR、LDTR为当前任务的TSS、LDT的描述符在GDT中的选择子。

    每次任务切换,先在TR中找到旧任务的TSS,保存环境,再依靠任务门描述符的TSS选择子,在TSS描述符中找到新任务的TSS地址,更新新任务环境,再更新新TR,LDTR,最后旧任务指针写入新TSS。

    这样太低效。但是高低特权级切换又必须依靠硬件规定的,从TSS获取0级栈指针。

    所以Linux采用一个CPU创建一个TSS,只更新该TSS的SS0、esp0、I/O位图字段。由于每次任务切换,一定都是在0特权级下完成,所以切换的时候,将旧任务的环境全部push入0特权级栈,其余均不变。
    LDT完全可以由GDT替代。所以Linux不涉及LDT

    一个任务分为低特权级和高特权级两部分,即用户代码段和内核代码段。
    特权级高低转移不是任务切换,但需要用当前TSS里不同等级的栈。

    Linux任务切换也不使用call / jmp

    1.4 CPU原生支持的任务切换方式

    x86硬件原本提供的任务切换方式有
    中断调用任务门,call/jmp 任务门,call/jmp TSS选择子。
    如果是嵌套调用别的任务,那么两个任务TSS的段描述符B都为1,B为1,无法再次调用该任务

    任务门:支持任务切换
    在这里插入图片描述

    两个:一个是中断+任务门、一个是call或jmp+任务门/TSS选择子和iretd

    1.中断+任务门实现任务切换过程

    通过中断获得中断向量号,该中断向量号对应的就是任务门。

    iretd有两个作用:
    eflags的第14位NT位为0,用于中断返回
    NT为1,用于从新任务返回旧任务。

    (1)中断发生时,CPU接收到中断向量号,在IDT索引到任务门描述符,
    分析S和TYPE字段,确定是门描述符,开始进行任务切换,取出要切换的新任务的TSS选择子。
    (2)再用新任务的TSS选择子在GDT索引TSS描述符
    (3)判断描述符P位是否为1,为一表示TSS位于内存
    (4)从TR获取旧任务TSS的地址,将CPU的当前任务状态(也就是寄存器,cr3,eflags等等)保存到旧任务的TSS
    (5)将新任务TSS赋值到CPU(即各种寄存器中)
    (6)使TR指向新TSS
    (7)新TSS描述符B位置1
    (8)将eflags的NT位置1
    (9)旧任务TSS选择子写入新TSS"上一个任务的TSS指针"
    (10)执行新任务
    新任务执行完成后,调用iretd指令返回到旧任务,处理器检查NT位为1,开始返回工作
    (1)eflags的NT位置0
    (2)新TSS描述符B位置0
    (3)当前任务状态写入TR指向的新TSS
    (4)将“上一个任务的TSS指针“加载到TR,恢复上一个任务的状态
    (5)执行旧任务。

    2.通过jmp/call实现任务切换过程

    任务门也可以在GDT中

    call/jmp+TSS选择子或者call/jmp+任务门选择子,忽略偏移量。
    其他步骤同理,也就是旧任务保存到旧TSS,新TSS赋值到CPU,iretd指令返回旧任务。
    唯一多的一个步骤就是特权级检查
    CPL和TSS选择子的RPL<=TSS描述符的DPL

    1.5 Linux即本实验采用的任务切换方式

    用TSS效率低

    Linux一次性加载TSS和TR,初始化0特权级栈,然后不断修改同一个TSS的内容。
    这次试验应该只是在一个任务特权级切换的时候,使用了TSS的0特权级栈,任务切换都是在同一个TSS修改,这样相比原生效率大大提升。

    2.实验代码在这里插入图片描述

    main函数创造了1个主线程,两个内核线程,两个用户进程。共5个PCB,进程PCB->pgdir为进程自己的页表首虚拟地址,线程为null。
    因此本次实验是五个进/线程在来回切换。他们切换的方式无疑都是利用自己PCB->kstack保存切换前esp,然后不断pop完成cs:eip的跳转。

    线程的调度

    关于线程的调度,和多线程调度实验是一样的。
    在切换内核线程前,就将PCB的线程栈结构设置好,PCB->kstack指向了线程栈栈顶,当主线程时间片耗尽中断,scheduel,switch上半部将自己esp保存于PCB->kstack后,switch下半部esp就指向了内核线程早已设置好的线程栈栈顶,依次弹栈,ret执行kernel_thread,在kernel_thread内调用线程函数k_thread_b,完成线程的切换。
    线程切换一直位于0特权级,一直使用的都是自己的0特权级栈
    当内核线程运行的时候,esp是指向线程栈内部的,所以内核线程的中断栈其实位于线程栈下方,而线程栈上方的预留的中断栈的空间其实被浪费了。

    进程的调度

    进程的创立不仅预留了中断栈,填写了线程栈,而且还申请了两页用作进程虚拟地址位图和页表,虚拟首地址均保存在PCB。
    依然是当主线程时间片耗尽中断,scheduel,switch上半部将自己esp保存于PCB->kstack后,switch下半部esp就指向了用户进程早已设置好的线程栈栈顶,依次弹栈,ret执行start_process(u_prog_b)。
    start_process仍然位于0特权级,该函数设置了用户进程中断栈,其中PCB->cs设置成了指向DPL等于3的代码段描述符,PCB->ds、PCB->ss设置成指向了DPL等于3的数据段,PCB->esp设置成了Linux默认的3特权级栈地址,PCB->eip指向了u_prog_b。最后将esp置为中断栈栈顶,jmp intr_exit,程序一路pop中断栈内容,最后iretd指令,用户进程切换到了特权级3,成功执行了u_prog_b,也成功访问了全局变量,相当于在3特权级用自己的页表访问了DPL为3的代码段、数据段、栈段。

    值得注意的是,Linux中,TSS的唯一作用就是保存当前用户进程0特权级栈,所以schedule确定下一个要切换的进程next后,就可以process_activate(next);
    把该进程PCB的中断栈存入TSS.esp0,此时该中断栈为空,这从上面的例子就可以看出来,jmp intr_exit,中断处理程序恢复环境,一路pop,一直pop到最后ss:esp。所以此栈就是PCB+PGSIZE。
    并且,切换cr3寄存器页表地址。

    当用户进程被中断的时候,由于需要执行中断处理程序,特权级会从3变0,故cpu会自动从TSS.esp0处取得0特权级栈,所以,在schedule决定要切换某个用户进程后,switch_to前,就要把TSS.esp0更新,这样切换后,tss.esp0才是指向当前进程0特权级栈。

    两者的区别

    进程调度与线程调度的区别在于,
    1.进程在利用提前设置好线程栈,利用switch_to切换到kernel_thread后,进一步jmp intr_exit,利用中断处理程序iretd指令,实现了cs:eip来到3特权级
    2.在schedule确定下一个要切换的用户进程后,进程更新了自己的页表和将自己PCB+PGSIZE赋值给了tss.esp0。如果schedule的是线程,那么cr3切换成0x100000,tss不变
    3.从第二点结合整个流程就可以看出,用户进程和线程都依靠中断切换,用户进程的中断栈在线程栈之上,而线程的中断栈在线程栈之下。这是由于线程在切换到自己的main.c里的函数去执行时,esp位于线程栈。而进程在切换到自己的main.c的函数执行时,0特权级栈esp位于PCB页顶。

    当然,为了实现用户进程,也设置了TR,TSS描述符,DPL为3的代码段、数据段,不过它们的段基址依然是0,还有全局变量tss结构体,所以tss的地址有编译器设定。

    2.1 main.c

    #include "interrupt.h"
    #include "init.h"
    #include "thread.h"
    #include "print.h"
    #include "process.h"
    #include "console.h"
    
    void k_thread_a(void* );
    void k_thread_b(void* );
    void u_prog_a (void);
    void u_prog_b (void);
    
    int test_var_a=0, test_var_b=0;
    
    int main(void) {
       put_str("I am kernel\n");
       init_all();
    	
    	thread_start("k_thread_a", 31, k_thread_a, "argA ");
    	thread_start("k_thread_b", 8, k_thread_b, "argB ");
    	process_execute(u_prog_a, "user_prog_a");
    	process_execute(u_prog_b, "user_prog_b");
    	
       intr_enable();
       while(1);
       return 0;
    }
    
    void k_thread_a(void* arg){
    	char* para = arg;
    	while(1){
    		console_put_str("v_a:0x");
    		console_put_int(test_var_a);
    		
    	}
    }
    
    void k_thread_b(void* arg){
    	char* para = arg;
    	while(1){
    		console_put_str("v_b:0x");
    		console_put_int(test_var_b);
    	}		
    }
    
    void u_prog_a(void) {
    	while(1) {
    		test_var_a++;	
    	}
    }
    
    void u_prog_b(void) {
    	while(1) {
    		test_var_b++;	
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    2.2 process.c

    #include "process.h"
    #include "global.h"
    #include "debug.h"
    #include "memory.h"
    #include "thread.h"    
    #include "list.h"    
    #include "tss.h"    
    #include "interrupt.h"
    #include "string.h"
    #include "console.h"
    
    extern void intr_exit(void);
    
    /* 构建用户进程初始上下文信息 */
    void start_process(void* filename_) {
       void* function = filename_;
       struct task_struct* cur = running_thread();
       cur->self_kstack += sizeof(struct thread_stack);
       struct intr_stack* proc_stack = (struct intr_stack*)cur->self_kstack;	 
       proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;
       proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0;
       proc_stack->gs = 0;		 // 用户态用不上,直接初始为0
       proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;
       proc_stack->eip = function;	 // 待执行的用户程序地址
       proc_stack->cs = SELECTOR_U_CODE;
       proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);
       proc_stack->esp = (void*)((uint32_t)get_a_page(PF_USER, USER_STACK3_VADDR) + PG_SIZE) ;
       proc_stack->ss = SELECTOR_U_DATA; 
       asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (proc_stack) : "memory");
    }
    
    /* 击活页表 */
    void page_dir_activate(struct task_struct* p_thread) {
    /********************************************************
     * 执行此函数时,当前任务可能是线程。
     * 之所以对线程也要重新安装页表, 原因是上一次被调度的可能是进程,
     * 否则不恢复页表的话,线程就会使用进程的页表了。
     ********************************************************/
    
    /* 若为内核线程,需要重新填充页表为0x100000 */
       uint32_t pagedir_phy_addr = 0x100000;  // 默认为内核的页目录物理地址,也就是内核线程所用的页目录表
       if (p_thread->pgdir != NULL)	{    // 用户态进程有自己的页目录表
          pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);
       }
    
       /* 更新页目录寄存器cr3,使新页表生效 */
       asm volatile ("movl %0, %%cr3" : : "r" (pagedir_phy_addr) : "memory");
    }
    
    /* 击活线程或进程的页表,更新tss中的esp0为进程的特权级0的栈 */
    void process_activate(struct task_struct* p_thread) {
       ASSERT(p_thread != NULL);
       /* 击活该进程或线程的页表 */
       page_dir_activate(p_thread);
    
       /* 内核线程特权级本身就是0,处理器进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0 */
       if (p_thread->pgdir) {
          /* 更新该进程的esp0,用于此进程被中断时保留上下文 */
          update_tss_esp(p_thread);
       }
    }
    
    /* 创建页目录表,将当前页表的表示内核空间的pde复制,
     * 成功则返回页目录的虚拟地址,否则返回-1 */
    uint32_t* create_page_dir(void) {
    
       /* 用户进程的页表不能让用户直接访问到,所以在内核空间来申请 */
       uint32_t* page_dir_vaddr = get_kernel_pages(1);
       if (page_dir_vaddr == NULL) {
          console_put_str("create_page_dir: get_kernel_page failed!");
          return NULL;
       }
    
    /************************** 1  先复制页表  *************************************/
       /*  page_dir_vaddr + 0x300*4 是内核页目录的第768项 */
       memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 0x300*4), (uint32_t*)(0xfffff000+0x300*4), 1024);
    /*****************************************************************************/
    
    /************************** 2  更新页目录地址 **********************************/
       uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)page_dir_vaddr);
       /* 页目录地址是存入在页目录的最后一项,更新页目录地址为新页目录的物理地址 */
       page_dir_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;
    /*****************************************************************************/
       return page_dir_vaddr;
    }
    
    /* 创建用户进程虚拟地址位图 */
    void create_user_vaddr_bitmap(struct task_struct* user_prog) {
       user_prog->userprog_vaddr.vaddr_start = USER_VADDR_START;
       uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8 , PG_SIZE);
       user_prog->userprog_vaddr.vaddr_bitmap.bits = get_kernel_pages(bitmap_pg_cnt);
       user_prog->userprog_vaddr.vaddr_bitmap.btmp_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8;
       bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);
    }
    
    /* 创建用户进程 */
    void process_execute(void* filename, char* name) { 
       /* pcb内核的数据结构,由内核来维护进程信息,因此要在内核内存池中申请 */
       struct task_struct* thread = get_kernel_pages(1);
       init_thread(thread, name, default_prio); 
       create_user_vaddr_bitmap(thread);
       thread_create(thread, start_process, filename);
       thread->pgdir = create_page_dir();
       
       enum intr_status old_status = intr_disable();
       ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
       list_append(&thread_ready_list, &thread->general_tag);
    
       ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
       list_append(&thread_all_list, &thread->all_list_tag);
       intr_set_status(old_status);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113

    包括的函数
    start_process(void* filename_)

    void process_activate(struct task_struct* p_thread)
    刚开始的主线程特权级一直是0,被中断的是0特权级,进入中断不涉及特权级改变,不会从tss取esp0
    如果是从3中断,那么就是特权级从3到0,进入中断意味着在此任务下特权级转变,cpu会自动从tss取esp0更新esp寄存器。

    void page_dir_activate(struct task_struct* p_thread)
    uint32_t* create_page_dir(void):每次创建新进程,都需要把内核页目录表中3GB以上内容复制到自己的页目录表,这样所有进程都可以访问内核代码
    void create_user_vaddr_bitmap(struct task_struct* user_prog)
    void process_execute(void* filename, char* name)
    为用户进程在内核内存池申请了一页PCB,一页页表,在用户内存池申请了一页3特权级栈

    2.3 thread.h(增加)

    /* 进程或线程的pcb,程序控制块 */
    struct task_struct {
       uint32_t* self_kstack;	 // 各内核线程都用自己的内核栈
       pid_t pid;
       enum task_status status;
       char name[TASK_NAME_LEN];
       uint8_t priority;
       uint8_t ticks;	   // 每次在处理器上执行的时间嘀嗒数
    /* 此任务自上cpu运行后至今占用了多少cpu嘀嗒数,
     * 也就是此任务执行了多久*/
       uint32_t elapsed_ticks;
    /* general_tag的作用是用于线程在一般的队列中的结点 */
       struct list_elem general_tag;				    
    /* all_list_tag的作用是用于线程队列thread_all_list中的结点 */
       struct list_elem all_list_tag;
       uint32_t* pgdir;              // 进程自己页表的虚拟地址
       
       struct virtual_addr userprog_vaddr;   // 用户进程的虚拟地址
       
       uint32_t stack_magic;	 // 用这串数字做栈的边界标记,用于检测栈的溢出
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    增加
    struct virtual_addr userprog_vaddr; // 用户进程的虚拟地址位图

    2.4 thread.c(增加)

    void schedule() {
    	next->status= TASK_RUNNING;
    	
    	process_activate(next);
    	switch_to(next);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.5 global.h(增加)

    
    // ----------------  GDT描述符属性  ----------------
    
    #define	DESC_G_4K    1
    #define	DESC_D_32    1
    #define DESC_L	     0	// 64位代码标记,此处标记为0便可。
    #define DESC_AVL     0	// cpu不用此位,暂置为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清0.  
    #define DESC_TYPE_DATA  2	// x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
    #define DESC_TYPE_TSS   9	// B位为0,不忙
    
    #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)
    /* 第3个段描述符是显存,第4个是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)
    
    
    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;
    }; 
    
    #define EFLAGS_MBS	(1 << 1)	// 此项必须要设置
    #define EFLAGS_IF_1	(1 << 9)	// if为1,开中断
    #define EFLAGS_IF_0	0		// if为0,关中断
    #define EFLAGS_IOPL_3	(3 << 12)	// IOPL3,用于测试用户程序在非系统调用下进行IO
    #define EFLAGS_IOPL_0	(0 << 12)	// IOPL0
    
    #define NULL ((void*)0)
    #define DIV_ROUND_UP(X, STEP) ((X + STEP - 1) / (STEP))
    #define bool int
    #define true 1
    #define false 0
    
    #define PG_SIZE 4096
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    2.6 tss.c

    #include "tss.h"
    #include "stdint.h"
    #include "global.h"
    #include "string.h"
    #include "print.h"
    #include "boot.inc" 
    
    /* 任务状态段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;
       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位的段基址 */
       uint64_t gdt_operand = ((8 * 7 - 1) | ((uint64_t)(uint32_t)0xc0000900 << 16));   // 7个描述符大小
       asm volatile ("lgdt %0" : : "m" (gdt_operand));
       asm volatile ("ltr %w0" : : "r" (SELECTOR_TSS));
       put_str("tss_init and ltr done\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    tss.c配合tss.h,创造了c语言的make_gdt_desc函数,之前在中断处理实验还创造了make_idt_desc。
    (注意:idt和gdt段描述符格式不同,所以硬件对他们的处理也不同。
    TSS段、代码段、数据段、任务门的段描述符都是gdt。
    中断门、任务门(含tss选择子)都是idt内的)
    接着,tss_init函数,
    在gdt第4个段描述符出增加了TSS描述符、dpl为3的数据段、代码段。
    由于gdt段界限变了,所以lgdt重新加载了gdt,
    然后ltr将TR寄存器加载了TSS选择子,Linux的任务切换方式意味着TR将永远不变。

    2.7 tss.h

    #ifndef __USERPROG_TSS_H
    #define __USERPROG_TSS_H
    #include "thread.h"
    void update_tss_esp(struct task_struct* pthread);
    void tss_init(void);
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.8 memory.c增加

    struct pool {
       struct bitmap pool_bitmap;	 // 本内存池用到的位图结构,用于管理物理内存
       uint32_t phy_addr_start;	 // 本内存池所管理物理内存的起始地址
       uint32_t pool_size;		 // 本内存池字节容量
       struct lock lock;		 // 申请内存时互斥
    };
    
    static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
       int vaddr_start = 0, bit_idx_start = -1;
       uint32_t cnt = 0;
       if (pf == PF_KERNEL) {     // 内核内存池
          bit_idx_start  = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
          if (bit_idx_start == -1) {
    	 return NULL;
          }
          while(cnt < pg_cnt) {
    	 bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
          }
          vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
       } else {	     // 用户内存池	
          struct task_struct* cur = running_thread();
          bit_idx_start  = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap, pg_cnt);
          if (bit_idx_start == -1) {
    	 return NULL;
          }
    
          while(cnt < pg_cnt) {
    	 bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
          }
          vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
    
       /* (0xc0000000 - PG_SIZE)做为用户3级栈已经在start_process被分配 */
          ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
       }
       return (void*)vaddr_start;
    }
    
    
    /* 在用户空间中申请4k内存,并返回其虚拟地址 */
    void* get_user_pages(uint32_t pg_cnt) {
       lock_acquire(&user_pool.lock);
       void* vaddr = malloc_page(PF_USER, pg_cnt);
       if (vaddr != NULL) {	   // 若分配的地址不为空,将页框清0后返回
          memset(vaddr, 0, pg_cnt * PG_SIZE);
       }
       lock_release(&user_pool.lock);
       return vaddr;
    }
    
    
    /* 将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配 */
    void* get_a_page(enum pool_flags pf, uint32_t vaddr) {
       struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
       lock_acquire(&mem_pool->lock);
    
       /* 先将虚拟地址对应的位图置1 */
       struct task_struct* cur = running_thread();
       int32_t bit_idx = -1;
    
    /* 若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图 */
       if (cur->pgdir != NULL && pf == PF_USER) {
          bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
          ASSERT(bit_idx >= 0);
          bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx, 1);
    
       } else if (cur->pgdir == NULL && pf == PF_KERNEL){
    /* 如果是内核线程申请内核内存,就修改kernel_vaddr. */
          bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
          ASSERT(bit_idx > 0);
          bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx, 1);
       } else {
          PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
       }
    
       void* page_phyaddr = palloc(mem_pool);
       if (page_phyaddr == NULL) {
          lock_release(&mem_pool->lock);
          return NULL;
       }
       page_table_add((void*)vaddr, page_phyaddr); 
       lock_release(&mem_pool->lock);
       return (void*)vaddr;
    }
    
    /* 得到虚拟地址映射到的物理地址 */
    uint32_t addr_v2p(uint32_t vaddr) {
       uint32_t* pte = pte_ptr(vaddr);
    /* (*pte)的值是页表所在的物理页框地址,
     * 去掉其低12位的页表项属性+虚拟地址vaddr的低12位 */
       return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));
    }
    
    static void mem_pool_init(uint32_t all_mem) {
       /* 将位图置0*/
       bitmap_init(&kernel_pool.pool_bitmap);
       bitmap_init(&user_pool.pool_bitmap);
    
       lock_init(&kernel_pool.lock);
       lock_init(&user_pool.lock);
    
       }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    3.实验结果总结

    3.1结果

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    3.2执行流esp寄存器的情况梳理

    了解esp的动向,也就清楚了程序在各个时刻使用的是哪个栈。

    1. 主内核线程,esp=主线程PCB+size(即主线程0特权级栈)=0x9f000
    2. 主内核线程时间片用完切换用户进程a时,主内核线程时钟中断,esp仍然是主线程0特权级栈 ,schedule检测到next是用户进程a含页表,tss.esp被赋值为next的0特权级栈。执行switch_to后,esp被赋值成了用户进程a的PCB最低地址,ret后执行start_process后iret(只有iret才能让cs:eip从高特权级到低特权级),esp被赋值成了该进程3特权级栈,cpu来到用户进程a
    3. 用户进程a时钟中断切换内核线程a,调用中断门发现特权级将从3变0,esp被赋值成tss.esp0,同时入栈保存用户进程a的3特权级栈指针,(见中断章节入栈)。switch_to中esp切换成用户线程a的kstack,pop、ret执行kernel,执行function,cpu来到用户线程a,特权级为0
    4. 内核线程a时钟中断切换内核线程b,调用中断门时特权级未转换,esp仍位于用户线程a的PCBd的那一页内存内,switch_to内esp被赋值成用户线程b的pcb中的kstack,pop、ret,执行kernel,执行function,cpu来到用户线程b。
    5. 内核线程b时钟中断切换用户进程a,中断进入schedule,检测到next为用户进程a,故tss.esp0被赋值成用户进程a的0特权级栈,switch_to内esp被切换成上次用户进程a切换时的esp(即用户进程a的0特权级栈内),iret时,根据(中断章节出栈),根据出栈的cs:eip,发现特权级将会从0到3,故esp最后又被赋值成用户进程a保存在0特权级栈的3特权级栈,同时cpu来到用户进程a。

    拓展

    思考一种情况:当某用户进程A执行while死循环的时候,突然敲击键盘引发执行键盘中断处理程序会发生什么。
    首先需要明确三点,

    1. 任何一个用户进程被创建的时候,页目录项3GB到4GB的目录项是复制过来的,所以所有用户进程访问3GB以上的虚拟地址时,得到的实际物理地址是一样的,所有进程共享3GB以上的虚拟内存。
    2. 3GB到3GB+1MB,和0MB到1MB这两段虚拟地址,访问的都是内核页目录上面的页表,实际页表映射的物理地址也是0MB到1MB。
    3. 加载内核博客,解析elf的时候,操作系统代码的所有函数符号,编译成elf格式的可执行程序后的时候都被编译成了0xC000XXXX这样的虚拟地址,其中操作系统入口函数main的main这个符号被编译成了0xC0001500,这个C代表了虚拟地址3GB,解析elf的时候main这个程序段也是被放在了虚拟地址0xC0001500对应的实际内存物理地址0x00001500处。

    有了前面各种实验基础的回顾,我们可以知道,当用户进程处理外部中断执行键盘中断处理程序的时候,键盘中断处理程序内调用的所有操作系统函数,对应的函数符号都是0xC000XXXX,当然在3GB到3GB+1MB范围。任何用户进程根据自己的页目录表寻址0xC000XXXX这个3GB到3GB+1MB的虚拟地址,映射出来的物理地址都是实际物理地址0MB到1MB,
    所以任何一个用户进程,中断后取指并执行的代码,是操作系统编译好的源码,而这些源码存储在内存的0MB到1MB处,被所有用户进程的虚拟地址3GB到3GB+1MB共享,任何进程访存操作系统函数都是通过访存虚拟地址3GB+3GB+1MB。

    这里的拓展只是分析了取指时的访存访的是哪里的内存,并没有涉及特权级的分析,也就是段寄存器cs的CPL的情况。
    特权级的分析在加载内核博客、以及中断处理博客已有。
    简单的逻辑就是,中断进入内核态后,假如需要调用键盘中断处理函数,此时eip为0xC000XXXX,cs.CPL会变成0,cs指向的代码段的DPL也是0,所以可以访问代码段,从而对应的代码段段基址(是0)以及eip这个段偏移地址计算出虚拟地址0XC000XXXX,然后这个虚拟地址会走该用户进程的页目录表、页表映射出实际的物理地址。由于所有用户进程3GB到3GB+1MB的页目录项一样,所以会访问同一份公共页表,得到同一个0MB到1MB共享内存地址,这个内存地址存储的就是操作系统指令源码。

    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/58676
    推荐阅读
    相关标签