ucore 操作系统实验 lab1小结(含Challenge)

Reading time ~1 minute

最近做操作系统的实验,本来挺好的,结果被那个challenge给折磨了好久,真的想说challenge的题目描述实在让人无力吐槽。

扩展proj4,增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务。需写出详细的设计和分析报告。完成出色的可获得适当加分。
提示:规范一下 challenge 的流程。 
kern_init 调用 switch_test,该函数如下:
static void switch_test(void) {
    print_cur_status(); // print 当前 cs/ss/ds 等寄存器状态
    cprintf(“+++ switch to  user  mode +++n”);
    switch_to_user();  // switch to user mode
    print_cur_status();
    cprintf(“+++ switch to kernel mode +++n”);
    switch_to_kernel();         // switch to kernel mode
    print_cur_status();
}
switch_to_* 函数建议通过 中断处理的方式实现。主要要完成的代码是在 trap 里面处理 T_SWITCH_TO* 中断,并设置好返回的状态。

就这样的描述,反正从头到尾我也没能弄清楚他是想要让我们做什么,上来就说让我们增加syscall功能,可最后却check的是内核态与用户态的切换。

然后,最让我不能理解的还是,为什么他要让我们通过中断做一个从用户态到内核态的切换,用户态自己随便调用一个中断,就能把权限提升为内核权限,这样的操作系统真的还有安全性可言么,实在是不能理解!!!(PS:后听一同学说是内核启动第一个用户程序的时候,需要从用户态切回内核态,完全初始化好后再关掉此中断即可)

不过,既然让做,咱就来做吧:

首先是内核态切到用户态的函数:

题目建议通过中断处理实现,于是乎在trap.h中查找中断号,发现:

#define T_SWITCH_TOU                120    // user/kernel switch
#define T_SWITCH_TOK                121    // user/kernel switch

然后,查看中断处理函数入口(vector.S),会发现所有中断处理函数都是trapentry.S中的__alltraps函数:

而__alltraps就是将各种寄存器压栈保存,然后调用trap.c中的trap函数,最后恢复各寄存器并返回,在trap.h中可发现压栈保存的数据结构:

/* registers as pushed by pushal */
struct pushregs {
    uint32_t reg_edi;
    uint32_t reg_esi;
    uint32_t reg_ebp;
    uint32_t reg_oesp;            /* Useless */
    uint32_t reg_ebx;
    uint32_t reg_edx;
    uint32_t reg_ecx;
    uint32_t reg_eax;
};
struct trapframe {
    struct pushregs tf_regs;
    uint16_t tf_gs;
    uint16_t tf_padding0;
    uint16_t tf_fs;
    uint16_t tf_padding1;
    uint16_t tf_es;
    uint16_t tf_padding2;
    uint16_t tf_ds;
    uint16_t tf_padding3;
    uint32_t tf_trapno;
    /* below here defined by x86 hardware */
    uint32_t tf_err;
    uintptr_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding4;
    uint32_t tf_eflags;
    /* below here only when crossing rings, such as from user to kernel */
    uintptr_t tf_esp;
    uint16_t tf_ss;
    uint16_t tf_padding5;
} __attribute__((packed));

于是可发现,我们想要做特权级的切换,就是要修改cs、ds、es等段寄存器,而通过中断修改,最好的办法就是直接将保存在栈中这些数据修改,等返回时就会将寄存器调整为我们需要的值,实现权限切换。

然后需要注意的是,在由内核态切换成用户态的时候,一开始调用中断时,由于是从内核态调用的,没有权限切换,故ss、esp没有压栈,而iret返回时,是返回到用户态,故ss、esp会出栈,于是为了保证栈的正确性,需要在调用中断前将esp减8以预留空间,中断返回后,由于esp被修改,还需要手动恢复esp为正确值。

这样之后,系统特权级已经成功切换,但是由于切换到了用户态,导致IO操作没有权限,故之后的printf无法成功输出,为了能够正常输出,我们需要将eflags中的IOPL设成用户级别,即3,同样也是通过修改栈中值来达到修改的目的。

这个IOPL确实是让人无语至极,实验给的资料里完全没有提及过这个东西,但是没有设置这个,会导致在printf的时候走入自动中断,简直看不出来是什么原因导致的!

IOPL(EFLAGS寄存器的bits 12 and 13) [I/O privilege level field]:指示当前运行任务的I/O特权级(I/O privilege level),正在运行任务的当前特权级(CPL)必须小于或等于I/O特权级才能允许访问I/O地址空间。这个域只能在CPL为0时才能通过POPF以及IRET指令修改。

接下来是从用户态切换到内核态,此时由于调用中断时,是从用户态调用的,故会将ss、esp压栈,但是iret返回时,是返回到内核态,故ss、esp不会出栈,所以此时需要手动出栈,而ss由于维持了中断内部的ss,即已经是kernel,无需修改,故直接出栈esp即可。最后,为了能够让用户态的时候可以调用此中断,需要在idt的初始化函数中将该中断的DPL改成USER级别。

最后,再实现一下syscall,获得时钟计数值,syscall number采取255,于是便可以得到:

static int get_ticks(void) {
    asm("mov $0xff, %eax");
    asm("int $0x80");
}

而对于中断内部的处理代码就更简单了,直接在trap_dispatch的switch里面增加一个分支返回ticks即可。

最后在lab1_switch_test里面的kernel切到user后,再切回kernel前调用此函数进行测试,发现可正确获取ticks。

最后,附上整个lab1的实验报告和修改的代码。

lab1_result.zip

挂载网络文件夹后网络故障时文件操作命令卡死

挂载 NFS 或者 Samba 的时候,经常会由于网络故障导致挂载好的链接断掉。此时如果尝试进行 ls、cd、df 等各种命令,只要与此目录沾上边,就会卡住。如果使用了类似 oh-my-zsh 这种配置的,只要在网络目录中,弹出命令提示符前就会直接卡住。这个时候第一反应就是...… Continue reading

路由折腾记 第四弹

Published on September 02, 2017