Lab Thread
前置知识
一、进程切换中的状态保存
在常规的强制进程切换时,需要kernel的调配。所以需要先切换到内核态,然后通过scheduler函数来切换到目标进程对应的内核态,然后再切换到user。在这个过程中,进程的切换需要保存的是context,而user和kernel的切换需要保存的是trapframe。
Task: Uthread: switching between threads
这个task要求我们实现一个用户级的进程切换。注意在这种情况下,进程自愿放弃cpu并转让给另一个需要cpu的进程。这个过程中kernel是完全不知道的,因此不需要保存trapframe,stack之类的麻烦东西,只需要一个类似context的数据结构来保存部分寄存器就好。
- 首先,模仿context创建一个结构体来保存寄存器状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24struct ucontext {
uint64 ra;
uint64 sp;
// callee-saved
uint64 s0;
uint64 s1;
uint64 s2;
uint64 s3;
uint64 s4;
uint64 s5;
uint64 s6;
uint64 s7;
uint64 s8;
uint64 s9;
uint64 s10;
uint64 s11;
};
struct thread {
char stack[STACK_SIZE]; /* the thread's stack */
int state; /* FREE, RUNNING, RUNNABLE */
struct ucontext ucontext;
}; - 模仿swtch中的汇编代码进行寄存器保存(其实是直接拷贝过来)
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
31
32
33
34
35
36
37
38
39
40.text
/*
* save the old thread's registers,
* restore the new thread's registers.
*/
.globl thread_switch
thread_switch:
sd ra, 0(a0)
sd sp, 8(a0)
sd s0, 16(a0)
sd s1, 24(a0)
sd s2, 32(a0)
sd s3, 40(a0)
sd s4, 48(a0)
sd s5, 56(a0)
sd s6, 64(a0)
sd s7, 72(a0)
sd s8, 80(a0)
sd s9, 88(a0)
sd s10, 96(a0)
sd s11, 104(a0)
ld ra, 0(a1)
ld sp, 8(a1)
ld s0, 16(a1)
ld s1, 24(a1)
ld s2, 32(a1)
ld s3, 40(a1)
ld s4, 48(a1)
ld s5, 56(a1)
ld s6, 64(a1)
ld s7, 72(a1)
ld s8, 80(a1)
ld s9, 88(a1)
ld s10, 96(a1)
ld s11, 104(a1)
/* YOUR CODE HERE */
ret /* return to ra */ - 在thread_schedule里填空,即调用thread_switch。
1
thread_switch((uint64)&t->ucontext, (uint64)¤t_thread->ucontext);
- 在thread_create函数中填空,记录当前进程再次进行加载时的地址,记录当前栈的顶端(注意,栈向下增长),最后设置状态为RUNNABLE
1
2
3
4t->ucontext.ra = (uint64)func;
t->ucontext.sp = (uint64)t->stack + STACK_SIZE;
t->state = RUNNABLE;
// YOUR CODE HERE
Task: Using threads
这个task旨在加深我们对freelist中因为并行导致的list丢失问题的理解。为了避免这种情况,我们需要适当加锁。
- 首先来简答一下问题。key丢失是因为当两个cpu同时将不同的key指向链表的next时,链表的head只会指向最后的做修改head.next操作的key,另一个key就丢失了。
- 首先,定义lock数组并初始化(注意,你不能把锁定义在entry结构体里,但是你或许可以把它和key定义在一起)
1
2
3
4
5
6
7pthread_mutex_t lock[NKEYS];
void lockinit()
{
for (int i = 0; i < NKEYS; i++)
pthread_mutex_init(&lock[i], NULL);
} - 通过浏览源码可知,对key的插入在put函数中,对key的读取在get函数中,因此我们应该在这两个函数里加锁。但我并没有修改get,因为key使用头插法,在整个插入过程中最后修改的是head,在并行中唯一可能导致的问题就是读不到正在写的那个key。
1
2
3
4// put()
pthread_mutex_lock(&lock[i]);
insert(key, value, &table[i], table[i]);
pthread_mutex_unlock(&lock[i]);
Task: barrier
这个task要求我们写一个barrier函数,当有进程呼叫这个函数的时候就让这个进程sleep,直到呼叫这个函数的进程数量累计达到一定量后再统一唤醒。
- 首先在main中初始化锁。
1
pthread_mutex_init(&bstate.barrier_mutex, NULL); // init the lock
- 完善barrier函数,如果有进程呼叫就添加计数并沉睡进程,当计数累计到指定数量就全部清零,增加”barrier的轮数”,并唤醒所有进程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21static void
barrier()
{
// YOUR CODE HERE
//
// Block until all threads have called barrier() and
// then increment bstate.round.
//
pthread_mutex_lock(&bstate.barrier_mutex);
bstate.nthread ++;
if (bstate.nthread == nthread) {
bstate.nthread = 0;
bstate.round++;
pthread_cond_broadcast(&bstate.barrier_cond);
}
else {
pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex);
}
pthread_mutex_unlock(&bstate.barrier_mutex);
}