当前位置: 代码迷 >> 综合 >> 【操作系统】MIT 6.S081 LAB7
  详细解决方案

【操作系统】MIT 6.S081 LAB7

热度:22   发布时间:2023-12-04 12:27:34.0

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)&current_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系统调用能得到一下结果。

image-20220219110024598

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_safeph_fast了。

Barrier(moderate)

在这个任务中,我们需要修改barrier.c来实现类似与sleepwakeup的功能。

具体要求为:每个进程执行到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的测试了。

  相关解决方案