操作系统-用户态和内核态切换细节分析

1 前言

        本文并非介绍当前使用的linux操作系统的用户态和内核态的切换细节,而是介绍ucore这个教学性质的系统,主要是为了说明用户态和内核态本质上到底是什么。

2 初始状态

        操作系统在加电启动、完成各个部分的初始化之后,处于内核态,假设当前的栈顶指针寄存器的值为x,指向内存的某个位置,如图2.1所示。

 3 内核态切换到用户态

#define T_SWITCH_TOU                120    // user/kernel switch

static void
lab1_switch_to_user(void) {
    //LAB1 CHALLENGE 1 : TODO
	asm volatile (
	    "sub $0x8, %%esp \n"
	    "int %0 \n"
	    "movl %%ebp, %%esp"
	    : 
	    : "i"(T_SWITCH_TOU)
	);
}

         (1)让esp寄存器的值减8,此时esp = x-8 。

        (2)执行软中断指令 int 120,调用120号中断处理程序。

                中断处理程序依次会往栈中压入以下寄存器值:eflags(32bit)、cs(16bit)、eip(32bit),压入之后,esp = x-20 ,栈变成如下形态。

 

 

         (3)120号中断处理程序,往栈中压入0和120(中断号),压入后,esp = x-28 。

.globl vector120
vector120:
  pushl $0
  pushl $120
  jmp __alltraps

 

        (4)跳转到 __alltraps 执行。

.globl __alltraps
__alltraps:
    # push registers to build a trap frame
    # therefore make the stack look like a struct trapframe
    pushl %ds
    pushl %es
    pushl %fs
    pushl %gs
    pushal

    # load GD_KDATA into %ds and %es to set up data segments for kernel
    movl $GD_KDATA, %eax
    movw %ax, %ds
    movw %ax, %es

    # push %esp to pass a pointer to the trapframe as an argument to trap()
    pushl %esp

    # call trap(tf), where tf=%esp
    call trap

    # pop the pushed stack pointer
    popl %esp

    # return falls through to trapret...
.globl __trapret
__trapret:
    # restore registers from stack
    popal

    # restore %ds, %es, %fs and %gs
    popl %gs
    popl %fs
    popl %es
    popl %ds

    # get rid of the trap number and error code
    addl $0x8, %esp
    iret

         a)用32位对齐(pushl)的方式将一系列数据段寄存器夺入栈中。

        b)pushal指令也会压入一系列寄存器,包括:edi、esi、ebp、oesp、ebx、edx、ecx、eax。由于本文要分析的内容与这些寄存器没有关系,因而这些东西统称为pushregs。

        c)设置ds、es寄存器,指向内核数据段描述符。

        d)将当前的esp值压栈,到此,栈的形态如图所求。

 

        (5)“call trap”调用C语言的tap函数,而trap函数直接调用了trap_dispatch函数,我们假设直接call dispatch。

static void
trap_dispatch(struct trapframe *tf) {
    case T_SWITCH_TOU:
        if (tf->tf_cs != USER_CS) {
            switchk2u = *tf;
            switchk2u.tf_cs = USER_CS;
            switchk2u.tf_ds = switchk2u.tf_es = switchk2u.tf_ss = USER_DS;
            switchk2u.tf_esp = (uint32_t)tf + sizeof(struct trapframe) - 8;
		
            // set eflags, make sure ucore can use io under user mode.
            // if CPL > IOPL, then cpu will generate a general protection.
            switchk2u.tf_eflags |= FL_IOPL_MASK;
		
            // set temporary stack
            // then iret will jump to the right stack
            *((uint32_t *)tf - 1) = (uint32_t)&switchk2u;
        }
        break;
}

        a)call指令会把当前指令的下一条指令的地址eip+4压栈, 然后跳转到函数trap_dispatch代码执行。

        b)函数的第一第二条代码是把当前ebp压栈,然后设置ebp寄存器为当前esp寄存器的值,此时栈形态如图所示

	pushl	%ebp			
	movl	%esp, %ebp		

 

 

        c)可以看到:ebp-8就是函数 trap_dispatch第一个参数的值(x-76),而x-76正好指向pushregs这一段数据,由x-76往上一段长为size_of(struct trapframe)的内存块刚好可以构成一个struct trapframe对象,这个对象刚好处于x-76到x这一段内存中!!

        (6)trap_dispatch代码

        a)首先找一个全局的变量(struct trapframe switchk2u ),在内存的某个地方,把参数tf指向的内容保存到switchk2u。

        b)把switchk2u中保存的cs寄存器的值改为用户态代码段的段选择子。

        c)把switchk2u中保存的ds、es、ss等寄存器的值改为用户态数据段的段选择子。

        d)把switchk2u中,在一开始预留的两个32位内存块的第二块设置为 x - 8 。

        e)设置switchk2u中保存的eflags寄存器的值,设置标志位 FL_IOPL_MASK 。

        f)设置x-80位置的值为switchk2u对象的地址。

 

 

        (7)trap_dispatch函数执行结束

	leave					; 恢复栈帧指针
	ret						; 返回到main

        a)恢复栈帧指针:esp = ebp, popl %ebp。

        b)返回:popl %eip

 

         (8)返回(4)中的代码执行后续指令

    popl %esp

    # return falls through to trapret...
.globl __trapret
__trapret:
    # restore registers from stack
    popal

    # restore %ds, %es, %fs and %gs
    popl %gs
    popl %fs
    popl %es
    popl %ds

    # get rid of the trap number and error code
    addl $0x8, %esp
    iret

        a)弹出栈顶值作为写入esp寄存器,此时esp=y 

 

 

        b)弹出pushregs这8个寄存器;

        c)弹出gs、fs、es和ds数据段寄存器;

        d)让esp = esp+8 ,至此,栈形态如图所示。

 

        e)iret指令弹出eip、cs和eflags;

        f)由于iret返回变更了eflags的特权级,因此还会弹出ss和sp。此时esp=x-8 。

 

4 总结

        用户态和内核态的切换本质上是eflags和cs、ds、es和ss等段寄存器值的变换。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值