pintos线程转换机制

首先先大致感受下转换流程,稍后解释

原来的汇编代码:

#### This function works by assuming that the thread we're switching
#### into is also running switch_threads().  Thus, all it has to do is
#### preserve(保存) a few registers on the stack, then switch stacks and
#### restore the registers.  As part of switching stacks we record the
#### current stack pointer in CUR's thread structure.
.globl switch_threads
.func switch_threads
switch_threads:
	# Save caller's register state.
	#
	# Note that the SVR4 ABI allows us to destroy %eax, %ecx, %edx,
	# but requires us to preserve %ebx, %ebp, %esi, %edi.  See
	# [SysV-ABI-386] pages 3-11 and 3-12 for details.
	#
	# This stack frame must match the one set up by thread_create()
	# in size.
	pushl %ebx
	pushl %ebp
	pushl %esi
	pushl %edi

	# Get offsetof (struct thread, stack).
.globl thread_stack_ofs
	mov thread_stack_ofs, %edx

	# Save current stack pointer to old thread's stack, if any.
	movl SWITCH_CUR(%esp), %eax
	movl %esp, (%eax,%edx,1)

	# Restore stack pointer from new thread's stack.
	movl SWITCH_NEXT(%esp), %ecx
	movl (%ecx,%edx,1), %esp

	# Restore caller's register state.
	popl %edi
	popl %esi
	popl %ebp
	popl %ebx
        ret
.endfunc

.globl switch_entry
.func switch_entry
switch_entry:
	# Discard switch_threads() arguments.
	addl $8, %esp

	# Call thread_schedule_tail(prev).
	pushl %eax
.globl thread_schedule_tail
	call thread_schedule_tail
	addl $4, %esp

	# Start thread proper.
	ret
.endfunc
2 进入切换前 在static void
schedule (void) 函数里面

pintos线程转换机制_第1张图片

3.进入切换点

4.

保存原线程的寄存器,push入栈

pintos线程转换机制_第2张图片

5.

切换线程,本质是保存原有的esp,并将其赋值为下一个线程的esp

pintos线程转换机制_第3张图片

6

切换线程后,将它的寄存器值pop出来赋值给四个寄存器

pintos线程转换机制_第4张图片




大概就是这几步了,然后先给几个调试的常用命令


b main      :在主函数处设置断点

c          : continue 一直运行直到遇到断点停下

n          : 下一步

s         :进入函数内部

si         :到下一个汇编代码

display   /x $esp    :显示esp寄存器的值

layout src             :调试时显示c源代码

layout split             :调试时显示c源代码和汇编代码

x/10wa     0xc000e000   :显示从 0xc000e000开始的10个单位的内存的值

p   $eax    :显示$eax的值




pintos线程转换机制_第5张图片



 pintos栈底是较大的内存地址,栈指针是较小的内存地址

所以push的时候esp是每次减小4的,pop的时候esp是每次加4的

首先,如上图所示,ebp上方ee60和ee5c处的两个值是cur=running_thread()  和 next=next_thread_to_run()函数得到的值,ebp=ee68是函数schedule的栈底的指针,上面已经放好了算好的现在线程的地址和下一将要运行的线程的地址 ,这些是进入switch_thread()函数的准备工作

接着准备进入switch_thread函数内部,接下去都看汇编代码,一开始esp在40处,为什么esp=40会到ebp=68上面这么多,中间空了这么多呢,中间空出来的部分应该是schedule函数自己的一个栈变量的空间吧,现在是switch_thread函数,栈空间应该是从47--2c,其中47--40 8个字节两个int型的空间放了swithc_thread的两个参数,至于怎么找到参数的,还记得上面ee60和ee5c处的两个值吗,就是将这两个值复制过来就好了,看汇编的代码应该能看懂了吧,准备工作,即参数入栈完成,可以进入函数了


四个push应该没有问题,保存当前线程的寄存器的值嘛 这时esp到了2c了,不过有一点要注意,每次调用新函数时,esp会先减少4的,图中是从40跳到3c,然后再进行操作


好了,继续

接下来是四行汇编,切换在这时完成

 SWITCH_CUR 被定义为 20,5个单元

SWITCH_NEXT 被定义为 24 ,6个单元

1. movl SWITCH_CUR(%esp), %eax       将  esp+20的值移到eax寄存器中  esp=2c ,eax保存了当前线程的地址
 2. movl %esp, (%eax,%edx,1)                将esp的值放到    eax+edx的内存中      即当前线程首地址再偏移edx=24的一个位置是恒定的存放esp的位置,这里可以解释线程被切换后再回到该线程它是怎么知道自己下一步该怎么走了,从这个位置找出esp就可以继续啦

 
 3. movl SWITCH_NEXT(%esp), %ecx     将  esp+24的值移到ecx寄存器中  esp=2c,ecx保存了下一个线程的地址
4.  movl (%ecx,%edx,1), %esp               将ecx+edx    的值移到esp中,将下一个线程的esp的值放如寄存器esp中,当当当,线程切换了!!


恩,切换完成了,请看下图,看看是如何完成后续工作的 现在esp=4fec咯,接下去还是只写内存最后两位了,方便一点

4个pop,将栈中寄存器的值pop到对应的四个寄存器中,恩,应该很容易看懂吧,接着遇到一个ret操作 esp会加4   ,还记得上面esp进入函数时 esp减了4吗,这两处是对应的

esp到了e8 了

再把保存原线程地址的eax放到 ec处,esp到ec了,接下去进入thread_schedule_tail函数了,它有个prev参数,不就是刚刚放入的eax嘛,对吧。

进入函数时esp减4 ,esp到e8了

这时会出现一个频繁的操作序列  push ebp ;  ebp=esp;这时做什么呢,我们知道,当在一个函数中进入另一个函数内部时,由于每个函数都有独立的栈空间,有就是说,有独立的ebp和esp了,那么你得先将自己的ebp保存好,才能放心地往下走嘛!

接着 esp会突然加到bc,因为要调用running_thread函数了,接下去一样的步骤, 进入时esp-4,保存ebp,恩,esp到b4

然后esp又加到了9c,因为刚进来这个函数,又要到下一个函数pg_round_page了,将这时的esp赋值给了b0位置的内存,b0这个位置的值又给了eax寄存器,eax寄存器里面的值再赋给9c这个地址,绕了一圈,其实就是9c这个地址的值是9c,是pg_round_page的参数,好了,进入pg_round_page,这里对eax和ffff000与操作,得到esp所在页的初始地址,即当前线程的初始地址,接下去该返回了,逐层返回吧,首先pop ebp ,ebp变成running_thread的ebp,b4了,看到保存ebp的作用了吧,可以快速返回上一层函数

每次ret操作时和上面一样 esp要加4 

有一个leave操作挺特别的,是 esp=ebp;pop ebp的简写吧,上网找了一下好像是这样的,恩,退了两次 ebp到e4了,esp是栈顶端,bc,不要忘记eax里面的值还是当前线程的初始地址哦,把eax放到ebp上面一个位置,名字是cur,它就是cur=running_thread()的结果了,上面这么多其实就是为了实现这一句话。。。。终于搞定了,现在可以看到,成功切换了线程后,cur也变成了next线程的值了,切换到此结束,分析的差不多了!

搞了一个下午和晚上终于通过调试弄清了线程切换的本质了,还是挺值得!

pintos线程转换机制_第6张图片


附调试汇编代码截图:
1.放入cur,next参数

pintos线程转换机制_第7张图片

2
进入switch_thread

pintos线程转换机制_第8张图片

pintos线程转换机制_第9张图片

3.进入thread_schedule_tail


pintos线程转换机制_第10张图片


4 进入running_thread
pintos线程转换机制_第11张图片


pintos线程转换机制_第12张图片

5.进入pg_round_down

pintos线程转换机制_第13张图片

pintos线程转换机制_第14张图片

pintos线程转换机制_第15张图片


6 返回到running_thread

pintos线程转换机制_第16张图片
7 返回到schedule_thread_tail,将cur值保存在%ebp-4的位置,结束


pintos线程转换机制_第17张图片


你可能感兴趣的:(pintos线程转换机制)