Day 27~28 Mit6.828 HW8 uthreads

Day 27~28 Mit6.828 HW8 uthreads

摘要:Mit6.828 HW8 uthreads

0x01 写在前面

感觉做Lab4 PartA有点吃力,所以打算先看一下xv6这边的东西再适应一下。这部分需要先看一下xv6 book、proc.c和switch.S,然后完成HW 7,HW 7是thread,这边是uthread,所以,冲锋!

0x02 Prepared Reading: Scheduling(Up to ‘Sleeping and wakeup’)

这部分是要读xv6的调度部分,到睡眠和唤醒部分,以下部分是我做的笔记:

Multiplexing

处理器切换进程的两种情况:

1)sleep和wakeup系统调用出现时;

2)强制的周期性的切换。

需要解决的问题:

1)如何从一个处理器切换到另一个;

2)如何使得用户进程察觉到切换,xv6使用定时中断;

3)同时切换处理器时如何避免条件竞争;

4)用户进程在退出时无法释放内核栈;

5)机器需要知道它正在运行的处理器。

Code: Context switching

一张图:

swtch指令用于保存和恢复寄存器集的状态(即上下文,contexts)。

xv6中struct context* 用于保存这些上下文,可以在xv6中看到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//PAGEBREAK: 17
// Saved registers for kernel context switches.
// Don't need to save all the segment registers (%cs, etc),
// because they are constant across kernel contexts.
// Don't need to save %eax, %ecx, %edx, because the
// x86 convention is that the caller has saved them.
// Contexts are stored at the bottom of the stack they
// describe; the stack pointer is the address of the context.
// The layout of the context matches the layout of the stack in swtch.S
// at the "Switch stacks" comment. Switch doesn't save eip explicitly,
// but it is on the stack and allocproc() manipulates it.
struct context {
uint edi;
uint esi;
uint ebx;
uint ebp;
uint eip;
};

在proc.c的sched函数用户切换进程(保存原有进程上下文和切换现有进程上下文),见下方代码:

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
// Enter scheduler.  Must hold only ptable.lock
// and have changed proc->state. Saves and restores
// intena because intena is a property of this
// kernel thread, not this CPU. It should
// be proc->intena and proc->ncli, but that would
// break in the few places where a lock is held but
// there's no process.
void
sched(void)
{
int intena;
struct proc *p = myproc();

if(!holding(&ptable.lock))
panic("sched ptable.lock");
if(mycpu()->ncli != 1)
panic("sched locks");
if(p->state == RUNNING)
panic("sched running");
if(readeflags()&FL_IF)
panic("sched interruptible");
intena = mycpu()->intena;
swtch(&p->context, mycpu()->scheduler);
mycpu()->intena = intena;
}

Code: Scheduling

在进程切换时,ptable.lock是必须的,如果不这样会出现两个CPU运行于同一个栈的情况。

Code: Scheduling

mycpu()函数用于获取当前CPU的cpu结构体,里面存储了跟cpu相关的信息,这个调用是禁用中断的。

myproc()函数返回运行于当前CPU的进程结构体proc,其也需要禁用中断,没进程运行时返回的是0。

算是草草读完了该部分,调度部分没有细读,直接做HW8吧。

0x03 HW8: uthreads

这个部分要去我们去实现一个简单的用户级线程包,这边给了两个文件,一个uthread.c和一个uthread_switch.S。加进去,根据教案调好对应的配置之后运行xv6,执行uthread指令,发现报错:

先来熟悉下文件,同样一部分一部分来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Possible states of a thread; */
#define FREE 0x0
#define RUNNING 0x1
#define RUNNABLE 0x2

#define STACK_SIZE 8192
#define MAX_THREAD 4

typedef struct thread thread_t, *thread_p;
typedef struct mutex mutex_t, *mutex_p;

struct thread {
int sp; /* saved stack pointer */
char stack[STACK_SIZE]; /* the thread's stack */
int state; /* FREE, RUNNING, RUNNABLE */
};
static thread_t all_thread[MAX_THREAD];
thread_p current_thread;
thread_p next_thread;
extern void thread_switch(void);

顶一个三个状态的宏定义,而后是栈的大小和最大线程数,再后是将结构体thread另设一个别名thread_t和新增一个thread结构体指针thread_p以及将结构体mutex另设一个别名mutex_t和新增一个mutex结构体指针mutex_p,继续往下,看到我们的thread结构体,里面包含了保存的栈指针,栈以及线程状态,再往下是一个保存了所有线程的数组,然后是当前线程和下一个线程的结构体,最后声明了一个外部函数thread_switch()看样子是用来切换线程的。

继续往下看就是一堆跟线程操作相关的函数的定义了。

thread_init()初始化线程0,也就是main,将其的状态设为RUNNING;

thread_schedule()用于调度线程,也就是切换下一个状态为RUNABLE的线程,如果没有这样的线程,那就运行自身或报错;

thread_create()用于创建一个新的线程,里面有对栈进行了操作;

thread_yield()用于进行的调度,里面调用了上方的thread_schedule();

mythread()该函数就是执行过程中的那个线程包了,不多讲;

main函数调用了thread_create()来执行mythread这个线程。

再来看看uthread_switch.S:

1
2
3
4
5
6
7
8
9
10
	.text

/* Switch from current_thread to next_thread. Make next_thread
* the current_thread, and set next_thread to 0.
* Use eax as a temporary register; it is caller saved.
*/
.globl thread_switch
thread_switch:
/* YOUR CODE HERE */
ret /* pop return address from stack */

这部分用于将当前的线程切换到下一个线程,使下一个线程为当前线程并将其设置为0,可以将eax寄存器作为暂时的寄存器。在这里写代码了…

教案提示了我们整个thread_switch的流程,给了我们C编译器在内存中安置thread结构体的方式:

并且告知了我们如何去控制current_thread sp指针的指向:

1
2
movl current_thread, %eax
movl %esp, (%eax) # 加括号表示取寄存器中的值作为地址

由此,我们可以书写我们的代码了

代码如下:

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

# save current thread's stack pointer
movl current_thread, %eax
movl %esp, (%eax)

# switch stacks to next thread
movl next_thread, %eax
movl (%eax), %esp

# set next_thread to current_thread
movl %eax, current_thread
# set next_thread to 0
movl $0, next_thread

popal

ret /* pop return address from stack */

而后我们可以开始测试我们的代码了。

以上。

0x03 写在后面

这次的HW主要感受了下线程是如何进行调度的,以及熟悉了下线程调度时汇编语言所做的一些操作,并了解了下C编译器下线程结构体的表示。算蛮有收获的。


评论