本实验将使您熟悉多线程。您将在用户级线程包中实现线程之间的切换,使用多个线程来加速程序,并实现一个屏障。
Attention
在编写代码之前,您应该确保已经阅读了xv6手册中的“第7章: 调度”,并研究了相应的代码。
在本练习中,您将为用户级线程系统设计上下文切换机制,然后实现它。为了让您开始,您的xv6有两个文件:user/uthread.c和user/uthread_switch.S,以及一个规则:运行在Makefile中以构建uthread程序。uthread.c包含大多数用户级线程包,以及三个简单测试线程的代码。线程包缺少一些用于创建线程和在线程之间切换的代码。
YOU JOB
您的工作是提出一个创建线程和保存/恢复寄存器以在线程之间切换的计划,并实现该计划。完成后,make grade应该表明您的解决方案通过了uthread测试。
这一部分的实现可以参考 kernel/swtch.S、kernel/proc.h
因为在切换线程的过程中需要保存寄存器,所已需要在thread里增添一个用来保存寄存器的变量,
struct thread_frame
{
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 thread_frame thread_context; //用来保存寄存器
};
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->thread_context.ra=(uint64)func;
t->thread_context.sp=(uint64)t->stack+STACK_SIZE;
}
修改 ra、sp
void thread_schedule(void)
{
struct thread *t, *next_thread;
/* Find another runnable thread. */
//省略 ``````````````````
for (int i = 0; i < MAX_THREAD; i++)
{
//省略 ``````````````````
thread_switch((uint64)&t->thread_context,(uint64)¤t_thread->thread_context);
}
else
next_thread = 0;
}
$ make qemu
...
$ uthread
thread_a started
thread_b started
thread_c started
thread_c 0
thread_a 0
thread_b 0
thread_c 1
thread_a 1
thread_b 1
...
thread_c 99
thread_a 99
thread_b 99
thread_c: exit after 100
thread_a: exit after 100
thread_b: exit after 100
thread_schedule: no runnable threads
YOUR JOB
为什么两个线程都丢失了键,而不是一个线程?确定可能导致键丢失的具有2个线程的事件序列。在answers-thread.txt中提交您的序列和简短解释。
[!TIP] 为了避免这种事件序列,请在notxv6/ph.c中的put和get中插入lock和unlock语句,以> 便在两个线程中丢失的键数始终为0。相关的pthread调用包括:
- pthread_mutex_t lock; // declare a lock
- pthread_mutex_init(&lock, NULL); // initialize the lock
- pthread_mutex_lock(&lock); // acquire lock
- pthread_mutex_unlock(&lock); // release lock
- 当make grade说您的代码通过ph_safe测试时,您就完成了,该测试需要两个线程的键缺
失数为0。在此时,ph_fast测试失败是正常的。
因为多个线程同时访问同一块内存是不安全的,所以需要进行加锁
我们可以为每一个散列桶加一个锁,防止多线程同时写同一个散列桶的内容
int main(int argc, char *argv[])
{
//`````````````省略
//初始化互斥锁
for(int i=0;i<NBUCKET;i++)
{
pthread_mutex_init(&locks[i],NULL);
}
//`````````````省略
//销毁锁
for(int i=0;i<NBUCKET;i++)
{
pthread_mutex_destroy(&locks[i]);
}
//`````````````省略
}
在写前加锁,写后解锁
static void put(int key, int value)
{
//````````````省略
pthread_mutex_lock(&locks[i]); //加锁
if (e)
{
// update the existing key.
e->value = value;
}
else
{
// the new is new.
insert(key, value, &table[i], table[i]);
}
pthread_mutex_unlock(&locks[i]); //释放锁
}
$ ./ph 2
100000 puts, 2.751 seconds, 36349 puts/second
1: 0 keys missing
0: 0 keys missing
200000 gets, 5.685 seconds, 35181 gets/second
要求
您的目标是实现期望的屏障行为。除了在ph作业中看到的lock原语外,还需要以下新的pthread原语;详情请看这里和这里。
- pthread_cond_wait(&cond, &mutex);
// 在cond上进入睡眠,释放锁mutex,在醒来时重新获取- pthread_cond_broadcast(&cond);
// 唤醒睡在cond的所有线程
static void
barrier()
{
pthread_mutex_lock(&bstate.barrier_mutex);
bstate.nthread++;
if(bstate.nthread!=nthread) //如果所有线程还没到达,就阻塞
{
pthread_cond_wait(&bstate.barrier_cond,&bstate.barrier_mutex);
}
else //所有线程都到达了
{
bstate.round++;
bstate.nthread=0;
pthread_cond_broadcast(&bstate.barrier_cond);
}
pthread_mutex_unlock(&bstate.barrier_mutex);
}
./barrier 2
OK; passed
$ make qemu-gdb
uthread: OK (2.6s)
== Test answers-thread.txt == answers-thread.txt: OK
== Test ph_safe == make[1]: 进入目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
gcc -o ph -g -O2 -DSOL_THREAD -DLAB_THREAD notxv6/ph.c -pthread
make[1]: 离开目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
ph_safe: OK (8.7s)
== Test ph_fast == make[1]: 进入目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
make[1]: “ph”已是最新。
make[1]: 离开目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
ph_fast: OK (20.5s)
== Test barrier == make[1]: 进入目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
gcc -o barrier -g -O2 -DSOL_THREAD -DLAB_THREAD notxv6/barrier.c -pthread
make[1]: 离开目录“/home/gty/vscode/MIT6.S081/xv6-labs-2021”
barrier: OK (2.9s)
== Test time ==
time: OK
Score: 60/60