Lab7: Multithreading
原文地址:YSBLOG
实验目的:学习进程间切换流程,实现用户线程切换,学习使用pthread。
Uthread: switching between threads (moderate)
这个任务的目的是补全uthread.c
以及uthread_switch.S
来实现用户级的进程切换。
这里的用户级进程切换不需要经过内核,只是在当前用户线程中切换不同的进程。
1、参考proc.h
,修改struct thread
向其中添加线程的上下文。
// Saved registers for user context switches.
struct context {
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 context context; // 用户线程的上下文
};
为什么这里上下文只保存了callee-saved
寄存器,而不保存caller-saved
寄存器?
caller-saved
在函数调用的过程中已经被编译器自动保存了,所以无需再次保存。
2、在thread_schedule
函数中添加进程上下文切换的函数调用
void
thread_schedule(void)
{
...
if (current_thread != next_thread) { /* switch threads? */
next_thread->state = RUNNING;
t = current_thread;
current_thread = next_thread;
/* YOUR CODE HERE
* Invoke thread_switch to switch from t to next_thread:
* thread_switch(??, ??);
*/
thread_switch((uint64)&t->context, (uint64)¤t_thread->context);
} else
next_thread = 0;
}
3、在thread_create
函数中添加调度器返回地址以及对应的栈指向
void
thread_create(void (*func)())
{
struct thread *t;
for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
if (t->state == FREE) break; // 找到一个空的栈页面
}
t->state = RUNNABLE;
// YOUR CODE HERE
t->context.ra = (uint64)func; // 线程执行时,从调度器返回的目标位置
t->context.sp = (uint64)t->stack + STACK_SIZE; // 将栈的起始位置指向栈底
}
4、参考switch.S
的实现,完成uthread_switch.S
.text
/*
* save the old thread's registers,
* restore the new thread's registers.
*/
.globl thread_switch
thread_switch:
/* YOUR CODE HERE */
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)
ret /* return to ra */
最后,使用uthread
系统调用能得到一下结果。
Using threads (moderate)
该任务是使用UNIX的pthread线程库,保证多线程的安全性以及加速。
在给出的ph.c
文件中,实现的是多个线程向一个哈希表进行插入,然后对插入结果进行检测。
首先要保证多线程执行的安全性。
1、在ph.c
中定义一个锁,这个锁用于保护哈希表
pthread_mutex_t lock; // declare a lock
2、在插入时先加锁,在再遍历哈希表
static
void put(int key, int value)
{
int i = key % NBUCKET;
pthread_mutex_lock(&lock); // acquire lock
// is the key already present?
struct entry *e = 0;
for (e = table[i]; e != 0; e = e->next) {
if (e->key == key)
break;
}
if(e){
// update the existing key.
e->value = value;
} else {
// the new is new.
insert(key, value, &table[i], table[i]);
}
pthread_mutex_unlock(&lock); // release lock
}
3、最后在主函数中初始化锁即可。
int
main(int argc, char *argv[])
{
...
assert(NKEYS % nthread == 0);
for (int i = 0; i < NKEYS; i++) {
keys[i] = random();
}
pthread_mutex_init(&lock, NULL); // initialize the lock
...
}
到此为止,我们使用make grade
能通过ph_safe
的测试,还不能通过ph_fast
,主要是由于我们锁的粒度太大,导致多个线程长期相互阻塞,没办法提高性能。所以我们可以降低锁的粒度,让哈希表的每一个桶都具有一个锁,这样只要多个线程不使用同一个桶,就不会被阻塞。
具体修改如下:
修改锁的定义,定义一个与桶数量相同的锁数组
pthread_mutex_t lock[NKEYS]; // declare a lock
在加锁时,只添加对应桶的锁
static
void put(int key, int value)
{
int i = key % NBUCKET;
pthread_mutex_lock(&lock[i]); // acquire lock
// is the key already present?
struct entry *e = 0;
for (e = table[i]; e != 0; e = e->next) {
if (e->key == key)
break;
}
if(e){
// update the existing key.
e->value = value;
} else {
// the new is new.
insert(key, value, &table[i], table[i]);
}
pthread_mutex_unlock(&lock[i]); // release lock
}
对数组中每一个锁都进行初始化。
int
main(int argc, char *argv[])
{
...
for (int i = 0; i < NKEYS; i++) {
keys[i] = random();
pthread_mutex_init(&lock[i], NULL); // initialize the lock
}
...
}
至此,在执行make grade
我们就能够通过ph_safe
和ph_fast
了。
Barrier(moderate)
在这个任务中,我们需要修改barrier.c
来实现类似与sleep
和wakeup
的功能。
具体要求为:每个进程执行到barrier()
时都会阻塞,当全部进程都被阻塞时,唤醒全部进程,并将阻塞的round加一。
我们只需要完成barrier.c
中的barrier()
函数,具体如下:
static 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
bstate.nthread++;
if (bstate.nthread < nthread) {
// 若不是所有进程都被阻塞,则等待条件变量
pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex);
} else {
// 若所有进程都被阻塞了,则重新开始计数,并唤醒所有进程
bstate.nthread = 0;
bstate.round++;
pthread_cond_broadcast(&bstate.barrier_cond);
}
pthread_mutex_unlock(&bstate.barrier_mutex);
}
执行make grade
可以看到我们已经能通过barrier
的测试了。