【操作系统】MIT 6.S081 LAB7

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

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.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的测试了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值