(列出本次实验所使用的软件、工具;简要概括实验步骤) 【实验环境】 VirtualBox-6.1.38 Ubuntu-14.04.6 Bochs2.9.6 【实验步骤】 将本次实验代码拷贝到工作目录,运行Makefile,出现报错: undefined reference to ‘__stack_chk_fail' 查阅资料得知,使用gcc编译源码到目标文件时,一定要加“-fno-stack-protector”,否则会默认调函数“__stack_chk_fail”进行栈相关检查,没有链接到“__stack_chk_fail”所在库文件时一定会报错: undefined reference to `__stack_chk_fail'。 Makefile第20行 CFLAGS = -I include/ -c -fno-builtin 改为: CFLAGS = -I include/ -c -fno-builtin -fno-stack-protector 为了便于后续实验,我们可以写一个脚本文件build2.sh,帮助修改Makefile文件增加”-fno-stack-protector”以及更新bochsrc。(参考链接: week7 — OsEx2021 v1 documentation (whu-os-ex-2021.readthedocs.io)) 使用方法是把要替换的bochsrc文件和脚本文件放在chapter6目录下: 在命令行中运行./build2.sh X(X为目录名)即可。 例如,要在b目录下调试并启动bochs: 便能直接启动和正确显示,有效提升了我们调试的效率。 |
进程表: 在操作系统中,内核负责管理维护所有进程,为了管理进程,内核在内核空间维护了一个称为进程表(Process Table)的数据结构,这个数据结构中记录了所有进程,每个进程在数据结构中都称为一个进程表项(Process Table Entry)。进程表在进行上下文切换时,能够保存下在 CPU 中关于当前运行进程的一些重要寄存器信息。 进程结构体: 结构体s_stackframe,s_proc在include/proc.h中均有定义,与进程表的开始位置结构图刚好对应。 typedef struct s_stackframe { u32 gs; /* \ */ u32 fs; /* | */ u32 es; /* | */ u32 ds; /* | */ u32 edi; /* | */ u32 esi; /* | pushed by save() */ u32 ebp; /* | */ u32 kernel_esp; /* <- 'popad' will ignore it */ u32 ebx; /* | */ u32 edx; /* | */ u32 ecx; /* | */ u32 eax; /* / */ u32 retaddr; /* return addr for kernel.asm::save() */ u32 eip; /* \ */ u32 cs; /* | */ u32 eflags; /* | pushed by CPU during interrupt */ u32 esp; /* | */ u32 ss; /* / */ }STACK_FRAME; typedef struct s_proc { STACK_FRAME regs; /* process registers saved in stack frame */ u16 ldt_sel; /* gdt selector giving ldt base and limit */ DESCRIPTOR ldts[LDT_SIZE]; /* local descriptors for code and data */ u32 pid; /* process id passed in from MM */ char p_name[16]; /* name of the process */ }PROCESS; /* Number of tasks */ #define NR_TASKS 1 定义完后在global.c中进行声明: PUBLIC PROCESS proc_table[NR_TASKS]; 进程相关的GDT/LDT: 在进程开始之前要用到进程表中各项的值,首先将这些值进行初始化。分析得必须初始化的寄存器列表为:cs、ds、es、fs、gs、ss、esp、eip、eflags。 这里cs ds等段寄存器对应的将是LDT中而不再是GDT中的描述符。我们在初始化局部描述符表时,由于LDT是进程的一部分,可以把LDT放置在进程表中,上面已给出的代码中,s_proc结构体里就有相应的ldt_sel的定义。 在进程表里,我们初始化了3个部分:寄存器、LDT Selector 和LDT;其中,LDT Selector 被赋值为 SELECTOR_LDT_FIRST;LDT里面共有两个描述符,分别被初始化成内核代码段和内核数据段,改变了一下 DPL 以让其运行在低的特权级下;要初始化的寄存器比较多,cs 指向LDT中第一个描述符,ds, es, fs, ss 都设为指向LDT中的第二个描述符,gs 仍然指向显存,只是其 RPL 发生改变。 p_proc->ldt_sel = SELECTOR_LDT_FIRST; memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS>>3], sizeof(DESCRIPTOR)); p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; // change the DPL memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS>>3], sizeof(DESCRIPTOR)); p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; // change the DPL p_proc->regs.cs = (0 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; p_proc->regs.ds = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; p_proc->regs.es = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; p_proc->regs.fs = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; p_proc->regs.ss = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; p_proc->regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK; 同时, LDT跟GDT是联系在一起的,我们还必须在GDT中增加相应的描述符,并在合适的时间将相应的选择子加载给 ldtr: /* 填充 GDT 中进程的 LDT 的描述符 */ init_descriptor(&gdt[INDEX_LDT_FIRST], vir2ph1ys(seg2phys(SELECTOR_KERNEL_DS), proc_table[0].ldts), LDT_SIZE * sizeof(DESCRIPTOR) - 1, DA_LDT); } 相关宏定义如下: /* GDT */ /* 描述符索引 */ #define INDEX_DUMMY 0 /* \ */ #define INDEX_FLAT_C 1 /* | LOADER 里面已经确定了的 */ #define INDEX_FLAT_RW 2 /* | */ #define INDEX_VIDEO 3 /* / */ #define INDEX_TSS 4 #define INDEX_LDT_FIRST 5 /* 选择子 */ #define SELECTOR_DUMMY 0 /* \ */ #define SELECTOR_FLAT_C 0x08 /* | LOADER 里面已经确定了的 */ #define SELECTOR_FLAT_RW 0x10 /* | */ #define SELECTOR_VIDEO (0x18+3)/* /<-- RPL=3 */ #define SELECTOR_TSS 0x20 /* TSS */ #define SELECTOR_LDT_FIRST 0x28 #define SELECTOR_KERNEL_CS SELECTOR_FLAT_C #define SELECTOR_KERNEL_DS SELECTOR_FLAT_RW #define SELECTOR_KERNEL_GS SELECTOR_VIDEO /* 每个任务有一个单独的 LDT, 每个 LDT 中的描述符个数: */ #define LDT_SIZE 2 /* 选择子类型值说明 */ /* 其中, SA_ : Selector Attribute */ #define SA_RPL_MASK 0xFFFC #define SA_RPL0 0 #define SA_RPL1 1 #define SA_RPL2 2 #define SA_RPL3 3 #define SA_TI_MASK 0xFFFB #define SA_TIG 0 #define SA_TIL 4 进程相关的TSS: 由于用到了任务状态段,所以必须初始化一个TSS,我们来到 init_prot(),填充TSS以及对应描述符: /* 填充 GDT 中 TSS 这个描述符 */ memset(&tss, 0, sizeof(tss)); tss.ss0 = SELECTOR_KERNEL_DS; init_descriptor(&gdt[INDEX_TSS], vir2phys(seg2phys(SELECTOR_KERNEL_DS), &tss), sizeof(tss) - 1, DA_386TSS); tss.iobase = sizeof(tss); /* 没有I/O许可位图 */ tss结构体如下: typedef struct s_tss { u32 backlink; u32 esp0; /* stack pointer to use during interrupt */ u32 ss0; /* " segment " " " " */ u32 esp1; u32 ss1; u32 esp2; u32 ss2; u32 cr3; u32 eip; u32 flags; u32 eax; u32 ecx; u32 edx; u32 ebx; u32 esp; u32 ebp; u32 esi; u32 edi; u32 es; u32 cs; u32 ss; u32 ds; u32 fs; u32 gs; u32 ldt; u16 trap; u16 iobase; /* I/O位图基址大于或等于TSS段界限,就表示没有I/O许可位图 */ }TSS; 接下来把对应的选择子加载给 tr 这个寄存器,在kernel.asm添加这些代码。 xor eax, eax mov ax, SELECTOR_TSS ltr ax 进程表、进程体、GDT/LDT、TSS的关系如图: 进程表、进程体、GDT、TSS四个部分在程序的启动过程的时间顺序如图所示:
我们使用进程表是为了保存进程的状态,以便中断处理程序完成之后需要被恢复的进程能够被顺利地恢复。在进程表中给每一个寄存器预留了位置,把它们所有的值都保存下来。这样就可以在进程调度模块中使用这些寄存器而不会对进程产生不良影响。 以al 寄存器为例,在改变al的值前后分别保存和回复: ALIGN 16 hwint00: ; Interrupt routine for irq 0 (the clock). pushad ; `. push ds ; | push es ; | 保存原寄存器值 push fs ; | push gs ; / inc byte [gs:0] ; 改变屏幕第 0 行, 第 0 列的字符 mov al, EOI ; `. reenable out INT_M_CTL, al ; / master 8259 pop gs ; `. pop fs ; | pop es ; | 恢复原寄存器值 pop ds ; | popad ; / iretd |
orange‘s 操作系统实验 oslab7学习笔记
最新推荐文章于 2024-09-10 15:25:59 发布