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