Loading

6.S081-2021-Lab6 Multithreading

Uthread: switching between threads

前置知识:知道任何一种语言创建并运行线程的方式

以 Posix 为例

int pthread_create(

pthread_t *tid, //指向线程ID的指针

const pthread_attr_t *atrr, // 指向线程属性的指针,没特殊需求可以为NULL

void * (*start_routine) (void *), // 指向线程需要执行的函数指针

void *arg // 指向参数的指针,任务函数不需要参数时设为NULL,需要参数的话设置为指向struct的指针

);

这部分需要我们完善 thread_create() and thread_schedule()以及 uthread_switch.S中的汇编代码

线程切换需要保存当前线程上下文,也就是当前的用户寄存器中的值,并恢复下一个将要执行的线程的上下文。

所以我们需要在当前的struct thread中添加 context 的定义,这一部分主要参考allocproc/proc.c中对进程 context 的初始化代码

// uthread.c
// 先添加context声明
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; 
};

void 
thread_create(void (*func)())
{
  ...
  // YOUR CODE HERE
  memset(&t->context, 0, sizeof(t->context));
  // ra是返回地址,线程当然是要执行创建的时候给他的函数啦
  t->context.ra = (uint64)func;
  t->context.sp = (uint64)t->stack + STACK_SIZE;
}

void 
thread_schedule(void)
{
    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);
}

最后是填写 uthread_switch.S,虽然是汇编代码,但实际上是一个体力活。

代码中的a0 ,a1详细大家在 trap 实验中就已经知道他们两分别表示函数的第一个参数和第二个参数。

剩下的都是根据偏移量来进行现场的保存与恢复了。

/* uthread_switch.S */
	.text

	/*
         * save the old thread's registers,
         * restore the new thread's registers.
         */

	.globl thread_switch
thread_switch:
	/* YOUR CODE HERE */
	/* save old */
	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)

	/* restore new */
	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 */

Using threads

相信 Java 选手对这个应该非常熟悉

// ph.c

// 一个桶一个锁
pthread_mutex_t bucket_lock[NBUCKET];

static 
void put(int key, int value)
{
  
  int i = key % NBUCKET;

  // 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.
    pthread_mutex_lock(&bucket_lock[i]);
    insert(key, value, &table[i], table[i]);
    pthread_mutex_unlock(&bucket_lock[i]);
  }
}

void main(int argc, char *argv[])
{
  // init locks
  for(int i=0; i < NBUCKET; i++){
    pthread_mutex_init(&bucket_lock[i],NULL);
  }
}

Barrier

每次线程执行的时候都将计数加1,当总数达到当前的线程数时,就可以将当前所有等待的线程唤醒,否则就让线程等待。同时需要注意的是 bstate.nthread 是个线程共享变量,所以我们需要上锁再对它进行加一操作。

一个小细节是 pthread_cond_wait 的描述告诉我们解锁的操作应该放在 pthread_cond_wait之后。

Wait for condition variable COND to be signaled or broadcast.
MUTEX is assumed to be locked before.

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++;
  if(bstate.nthread==nthread){
    bstate.round++;
    bstate.nthread = 0;
    pthread_cond_broadcast(&bstate.barrier_cond);
    pthread_mutex_unlock(&bstate.barrier_mutex);
  }
  else {
    pthread_cond_wait(&bstate.barrier_cond,&bstate.barrier_mutex);
    pthread_mutex_unlock(&bstate.barrier_mutex);
  }
}
posted @ 2022-04-23 20:50  AD_milk  阅读(108)  评论(0编辑  收藏  举报