今天在看《操作系统导论》时候复习了一下ucore中的上下文切换,相关代码如下:
void switch_to(struct context *from, struct context *to);
.text
.globl switch_to
switch_to: # switch_to(from, to)
# save from's registers
movl 4(%esp), %eax # eax points to from
popl 0(%eax) # save eip !popl
movl %esp, 4(%eax) # save esp::context of from
movl %ebx, 8(%eax) # save ebx::context of from
movl %ecx, 12(%eax) # save ecx::context of from
movl %edx, 16(%eax) # save edx::context of from
movl %esi, 20(%eax) # save esi::context of from
movl %edi, 24(%eax) # save edi::context of from
movl %ebp, 28(%eax) # save ebp::context of from
# restore to's registers
movl 4(%esp), %eax # not 8(%esp): popped return address already, eax now points to to
movl 28(%eax), %ebp # restore ebp::context of to
movl 24(%eax), %edi # restore edi::context of to
movl 20(%eax), %esi # restore esi::context of to
movl 16(%eax), %edx # restore edx::context of to
movl 12(%eax), %ecx # restore ecx::context of to
movl 8(%eax), %ebx # restore ebx::context of to
movl 4(%eax), %esp # restore esp::context of to
pushl 0(%eax) # push eip
ret
switch_to
里边前一半代码是将被切换进程的寄存器状态保存在栈里,后一半代码是将切换进程的相关寄存器状态从栈里恢复到寄存器中。
相关的tips
popl 0(%eax)
这条指令的含义是将栈顶的数据弹栈并赋值给eax
寄存器,等价于
movl %esp, %eax
addl $4, %esp
pushl 0(%eax)
指令的含义是将%eax寄存器中的地址压栈,即赋值给栈顶寄存器esp
,等价于
subl $4, %esp
movl %eax, %esp
- 在Linux系统中,进程的切换是在内核态完成的,也就是说此时的压栈弹栈操作用的都是内核栈,它的结构大概长这样:
内核栈是从高地址向低地址增长,如下图所示:
细看保存当前进程的寄存器状态
- 以上面(👆)的内核栈图示为例,栈顶指针
esp
指向地址为4
的位置,那么4(%esp)
指向地址为8
的位置。则movl 4(%esp), %eax
这条指令将栈顶指针加4的地址赋值给eax
,此时eax
寄存器里边存储的就是地址8
; popl 0(%eax)
这条指令的含义实际就是让eax
里边的值变为esp
此时存储的地址4
,然后esp + 4 = 8
,此时内核栈的示意图如下:
movl %esp, 4(%eax)
这条指令将当前esp
的地址8
写在eax + 4 = 8
的位置上,似乎和上一条指令的结果一样?- 随后的指令分别将
ebx
、ecx
、edx
、esi
、edi
、ebp
写在地址8
之后,如下图所示: