orange‘s 操作系统实验 oslab7学习笔记

  • 实验环境及实验步骤

(列出本次实验所使用的软件、工具;简要概括实验步骤)

【实验环境】

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'

Makefile20

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 XX为目录名)即可。

例如,要在b目录下调试并启动bochs

便能直接启动和正确显示,有效提升了我们调试的效率。

  1. 描述进程数据结构的定义与含义:进程控制块(进程表)、进程结构体、进程相关的GDT/LDT、进程相关的TSS,画出数据结构的关系图

进程表:

在操作系统中,内核负责管理维护所有进程,为了管理进程,内核在内核空间维护了一个称为进程表(Process Table)的数据结构,这个数据结构中记录了所有进程,每个进程在数据结构中都称为一个进程表项(Process Table Entry)。进程表在进行上下文切换时,能够保存下在 CPU 中关于当前运行进程的一些重要寄存器信息。

进程结构体:

结构体s_stackframes_procinclude/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

在进程开始之前要用到进程表中各项的值,首先将这些值进行初始化。分析得必须初始化的寄存器列表为:csdsesfsgsssespeipeflags

这里cs ds等段寄存器对应的将是LDT中而不再是GDT中的描述符。我们在初始化局部描述符表时,由于LDT是进程的一部分,可以把LDT放置在进程表中,上面已给出的代码中,s_proc结构体里就有相应的ldt_sel的定义。

在进程表里,我们初始化了3个部分:寄存器、LDT Selector LDT;其中,LDT Selector 被赋值为 SELECTOR_LDT_FIRSTLDT里面共有两个描述符,分别被初始化成内核代码段和内核数据段,改变了一下 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;

同时, LDTGDT是联系在一起的,我们还必须在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/LDTTSS的关系如图:

进程表、进程体、GDTTSS四个部分在程序的启动过程的时间顺序如图所示:

  1. 画出以下关键技术的流程图:初始化进程控制块的过程、初始化GDTTSS、实现进程的启动

  1. 怎么实现进程的现场保护与恢复?

我们使用进程表是为了保存进程的状态,以便中断处理程序完成之后需要被恢复的进程能够被顺利地恢复。在进程表中给每一个寄存器预留了位置,把它们所有的值都保存下来。这样就可以在进程调度模块中使用这些寄存器而不会对进程产生不良影响。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值