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)¤t_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);
  }
}

                
            
        
浙公网安备 33010602011771号