MIT6.828学习之homework8:User-level threads

多路复用机制为进程提供了独占处理器的假象,实现多路复用有几个难点。首先,应该如何从运行中的一个进程切换到另一个进程?xv6 采用了普通的上下文切换机制;虽然这里的思想是非常简洁明了的,但是其代码实现是操作系统中最晦涩难懂的一部分。第二,如何让上下文切换透明化?xv6 只是简单地使用时钟中断处理程序来驱动上下文切换。第三,可能出现多个 CPU 同时切换进程的情况,那么我们必须使用一个带锁的方案来避免竞争。第四,进程退出时必须释放其占用内存与资源,但由于它本身在使用自己的资源(譬如其内核栈),所以不能由该进程本身释放其占有的所有资源。

uthread创建两个线程,并在线程之间来回切换。每个线程打印“my thread…”,然后让另一个线程有机会运行。

您需要完成 thread_switch.S,但在跳转到 thread_switch.S之前。首先了解uthread.c如何使用thread_switch。uthread.c有两个全局变量current_threadnext_thread,都是指向thread structure的指针。thread structure有一个线程堆栈和一个保存的堆栈指针(sp,它指向线程的堆栈)。

uthread_switch的任务将当前线程状态保存到current_thread所指向的结构中,恢复next_thread的状态,并使current_thread指向next_thread所指向的位置,这样当uthread_switch返回next_thread正在运行时,它就是当前线程。

您应该学习thread_create,它为新线程设置初始堆栈。它提供了关于thread_switch应该做什么的提示。其目的是thread_switch使用汇编指令popal和pushal来恢复和保存所有8个x86寄存器。注意,thread_create在新线程的堆栈上模拟8个被推入寄存器(32字节)。

要在thread_switch中编写程序集,您需要知道C编译器如何在内存中布局struct线程,如下所示:

				   --------------------
				    | 4 bytes for state|
				    --------------------   ----------
				    | stack size bytes |       eip
				    | for stack        |   registers(8)
				    |				   |       eip
				    |				   |   registers(8)
				    |				   |       ...
				    --------------------   ----------- 
				    | 4 bytes for sp   |
				    --------------------  <--- current_thread
				         ......
				
				         ......
				    --------------------
				    | 4 bytes for state|
				    --------------------
				    | stack size bytes |
				    | for stack        |
				    --------------------
				    | 4 bytes for sp   |
				    --------------------  <--- next_thread

变量next_thread和current_thread都包含一个struct thread的地址。

要编写current_thread指向的结构的sp字段,应该这样编写程序集:

	movl current_thread, %eax
	movl %esp, (%eax)

这在current_thread->sp中保存了%esp。这行得通是因为sp在该结构中的偏移量为0。您可以通过查看uthread.asm来研究编译器为uthread.c生成的程序集。

主要的难点就是得弄清当前%esp指着哪个栈,指着什么?
很明显,在thread_schedule()里调用的uthread_switch(),所以栈还是current_thread->stack,而调用uthread_switch()会把返回地址的下一条指令入栈,所以current_thread->sp=ret eip

uthread_switch.S代码:

	.globl thread_switch
thread_switch:
	/* YOUR CODE HERE */
	/* 下面这四条语句是我没有想明白current_thread的结构
	先把current_thread->sp取出,此时sp=current_thread->stack-4-32
	movl current_thread, %eax
	movl (%eax), %esp

	// 将当前线程状态保存,那八个寄存器
	add $32, %esp
	pushal*/

	//此时%esp=current_thread->sp
	//而C中对thread_switch的调用自动把下一条指令eip入栈了,所以下面只要把寄存器保存
	pushal

	//将当前线程的esp保存
	movl current_thread, %eax
	movl %esp, (%eax)

	// 使current_thread指向next_thread
	movl next_thread, %eax
	movl %eax, current_thread

	// 恢复next_thread的状态
	movl current_thread, %eax
	movl (%eax), %esp
	popal

	// set next_thread to 0
	movl $0, next_thread
	ret				/* pop return address from stack */

要测试代码,可以使用gdb单步执行thread_switch。你可以这样开始:

(gdb) symbol-file _uthread
Load new symbol table from "/Users/kaashoek/classes/6828/xv6/_uthread"? (y or n) y
Reading symbols from /Users/kaashoek/classes/6828/xv6/_uthread...done.
(gdb) b thread_switch
Breakpoint 1 at 0x204: file uthread_switch.S, line 9.
(gdb) 

甚至在运行uthread之前就可能触发(或不触发)断点。怎么会这样呢?

运行xv6 shell后,键入“uthread”,gdb将在thread_switch处断开。现在你可以输入如下命令来检查uthread的状态:

(gdb) p/x next_thread->sp
$4 = 0x4ae8
(gdb) x/9x next_thread->sp
0x4ae8 :      0x00000000      0x00000000      0x00000000      0x00000000
0x4af8 :      0x00000000      0x00000000      0x00000000      0x00000000
0x4b08 :      0x000000d8

位于next_thread堆栈的顶部地址0xd8是什么?
根据对函数的理解,可以直到0xd8是返回地址(即调用uthread_switch返回后的下一条指令)。而在uthread.asm里可以发现

static void 
thread_schedule(void)
{
  d6:	89 e5                	mov    %esp,%ebp
  d8:	83 ec 08             	sub    $0x8,%esp

你可能感兴趣的:(MIT6.828操作系统学习)