作业地址

下载两个文件到 xv6 文件夹并在 Makefile 在 _forktest 后面加入下面的代码

1
2
3
_uthread: uthread.o uthread_switch.o
  $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o _uthread uthread.o uthread_switch.o $(ULIB)
  $(OBJDUMP) -S _uthread > uthread.asm

并在 UPROGS 后面加入 _uthread

在重新编译后会遇到 panic

1
2
3
$ uthread
lapicid 1: panic: remap
80106967 80105911 801056fc 0 0 0 0 0 0 0

整个作业是要实现 uthread_switch.S 即线程切换的过程:

The job of uthread_switch is to save the current thread state into the structure pointed to by current_thread, restore next_thread’s state, and make current_thread point to where next_thread was pointing to, so that when uthread_switch returns next_thread is running and is the current_thread.

线程结构如下:

1
2
3
4
5
struct thread {
  int        sp;                /* saved stack pointer */
  char stack[STACK_SIZE];       /* the thread's stack */
  int        state;             /* FREE, RUNNING, RUNNABLE */
};

关于具体的压栈可以参考 thread_create()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void
thread_create(void (*func)())
{
  thread_p t;

  for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
    if (t->state == FREE) break;
  }
  t->sp = (int) (t->stack + STACK_SIZE);   // set sp to the top of the stack
  t->sp -= 4;                              // space for return address
  * (int *) (t->sp) = (int)func;           // push return address on stack
  t->sp -= 32;                             // space for registers that thread_switch expects
  t->state = RUNNABLE;
}

上面的 32 byte 用于 pushalpopal ,再上面就是线程对应的函数

struct thread 的内存分布

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
--------------------
| 4 bytes for state|
--------------------
| stack size bytes |
| for stack        |
--------------------
| 4 bytes for sp   |
--------------------  <--- current_thread
     ......

     ......
--------------------
| 4 bytes for state|
--------------------
| stack size bytes |
| for stack        |
--------------------
| 4 bytes for sp   |
--------------------  <--- next_thread

整个 uthread_switch() 实现如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
thread_switch:
  /* YOUR CODE HERE */

  /* save the current thread state into the structure pointed to by current_thread */
  pushal
  movl current_thread, %eax
  movl %esp, (%eax)

  /* restore next_thread's state */
  movl next_thread, %eax

  /* make current_thread point to where next_thread was pointing to */
  movl %eax, current_thread
  movl (%eax), %esp

  /* clear next_thread */
  movl $0x0, next_thread

  /* pop return address from stack */
  popal
  ret

最后补充一个线程切换的过程 thread_schedule() ,整个过程就是找状态为 RUNNABLE 的线程并对齐进行切换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void
thread_schedule(void)
{
  thread_p t;

  /* Find another runnable thread. */
  next_thread = 0;
  for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
    if (t->state == RUNNABLE && t != current_thread) {
      next_thread = t;
      break;
    }
  }

  if (t >= all_thread + MAX_THREAD && current_thread->state == RUNNABLE) {
    /* The current thread is the only runnable thread; run it. */
    next_thread = current_thread;
  }

  if (next_thread == 0) {
    printf(2, "thread_schedule: no runnable threads\n");
    exit();
  }

  if (current_thread != next_thread) {         /* switch threads?  */
    next_thread->state = RUNNING;
    thread_switch();
  } else
    next_thread = 0;
}