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-saveduint64 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);} elsenext_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 HEREt->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.nthreadbstate.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
的测试了。