MIT6.1810 Lab6

Lab: Multithreading

实验链接

实验准备

切换到thread 分支

ruby 复制代码
  $ git fetch
  $ git checkout thread
  $ make clean

Uthread: switching between threads

需求

在本练习中,您将为用户级线程系统 设计上下文切换机制,然后实现它。首先,您的xv6有两个文件user/uthread.c和user/uthread_switch.s。uthread.c包含了大部分用户级线程package,以及三个简单测试线程的代码。线程package缺少一些创建线程和在线程之间切换的代码。

您的工作是提出一个计划来创建线程和保存/恢复寄存器以在线程之间切换,并实现该计划。当你完成后,make grade的结果应该表明你的解决方案通过了uthread测试。

The solution

注:用户及线程系统是在单核上运行

首先在user/uthread.c的thread结构体中添加切换线程时需要保存的寄存器:

c 复制代码
struct thread {
  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;
  
  char       stack[STACK_SIZE]; /* the thread's stack */
  int        state;             /* FREE, RUNNING, RUNNABLE */
};

然后借鉴kernel/swtch.S中在xv6进程调度时用来保存寄存器状态的汇编代码,将其copy到user/uthread_switch.s中:

risc—v 复制代码
	.globl thread_switch
thread_switch:
        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

然后在user/uthread.c的thread_create函数中添加线程初始化所需的代码: 注:xv6中栈是从高地址向低地址增长,故要将stack的高地址赋给线程的sp寄存器

c 复制代码
void 
thread_create(void (*func)())
{
  ...
  // YOUR CODE HERE
  t->ra = (uint64)func;//令返回地址寄存器保存测试函数入口地址,从thread_switch返回时可切换到对应线程
  t->sp = (uint64) (t->stack + STACK_SIZE);//为线程准备的栈
}

最后在user/uthread.c的thread_schedule函数中添加线程切换调用即可:

c 复制代码
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, (uint64)next_thread);
  } else
    next_thread = 0;
}

Using threads

需求

在本作业中,您将探索使用哈希表进行线程和锁并行编程。您应该在具有多个内核的真正的Linux或MacOS计算机(不是xv6,也不是qemu)上执行此任务。大多数最新的笔记本电脑都有多核处理器。

这个任务使用UNIX pthread线程库。您可以通过man pthreads从手册页找到有关它的信息,也可以在web上查找,例如pthread_mutex_t互斥锁

文件notxv6/ph.c包含一个简单的哈希表,如果从单个线程使用它是正确的,但如果从多个线程使用它就不正确了。需要通过加锁来保证其在多线程下的正确性。

The solution

Hint:可以为每个哈希桶加一个锁,减少线程间冲突的情况以提高运行速率。

首先为每个哈希桶配上互斥锁

c 复制代码
struct{
  pthread_mutex_t lock;//互斥锁
  struct entry *tab;
}table[NBUCKET];

然后在main函数中添加初始化互斥锁和释放互斥锁的代码:

c 复制代码
int
main(int argc, char *argv[])
{
  for(int i = 0; i < NBUCKET; i++)//初始化互斥锁
    pthread_mutex_init(&table[i].lock,NULL);
  ...
  for(int i = 0; i < NBUCKET; i++)//销毁互斥锁
    pthread_mutex_destroy(&table[i].lock);
}

最后就是去找需要加锁的地方,发现当多个线程同时执行到insert()函数时会出现丢失更新问题(具体就是在向单链表插入时会多次修改表头指针导致某些key插入失败)。故我们需要在调用insert()函数前上锁。

c 复制代码
static 
void put(int key, int value)
{
  int i = key % NBUCKET;

  // is the key already present?
  struct entry *e = 0;
  for (e = table[i].tab; 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(&table[i].lock);//加🔓
    insert(key, value, &table[i].tab, table[i].tab);
    pthread_mutex_unlock(&table[i].lock);//解🔓
  }
}

Barrier

需求

在这个任务中,你将实现一个屏障:在应用程序中的一个点,所有参与的线程必须等待,直到所有其他参与的线程也到达该点。你将使用pthread条件变量,它们是一种类似于xv6的sleep和wakeup的序列协调技术。您应该在一台真正的计算机(不是xv6,也不是qemu)上完成这个任务。

文件notxv6/barrier.c包含一个损坏的barrier。您需要借助pthread库函数来修复它以保证其通过barrier测试。

The solution

pthread_cond_wait(cond, mutex)的功能有3个:

  • 调用者线程首先释放mutex
  • 然后阻塞,等待被别的线程唤醒
  • 当调用者线程被唤醒后,调用者线程会再次获取mutex

通常的应用场景下,当前线程执行pthread_cond_wait时,处于临界区访问共享资源,存在一个mutex与该临界区相关联

  • 当前线程执行pthread_cond_wait前,已经获得了和临界区相关联的mutex;执行pthread_cond_wait会阻塞,但是在进入阻塞状态前,必须释放已经获得的mutex,让其它线程能够进入临界区
  • 当前线程执行pthread_cond_wait后,阻塞等待的条件满足,条件满足时会被唤醒;被唤醒后,仍然处于临界区,因此被唤醒后必须再次获得和临界区相关联的mutex

直接上代码,思路在注释中:

c 复制代码
static void 
barrier()
{
  pthread_mutex_lock(&bstate.barrier_mutex);//加🔓
  //-------------------临界区
  if(++bstate.nthread == nthread){//判断被阻塞线程数是否等于总线程数
    bstate.nthread = 0;//重置计数
    bstate.round++;//记录轮数,由于bstate.round被共享故只能在最后一个未被阻塞的线程中更新
    pthread_cond_broadcast(&bstate.barrier_cond);//唤醒所有在cond上休眠的线程
  }
  else pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex);
  //-------------------临界区
  pthread_mutex_unlock(&bstate.barrier_mutex);//解🔓
}
相关推荐
彩妙不是菜喵12 小时前
操作系统中的Linux:进程详解--->(深入浅出)从入门到精通
linux·操作系统
农民真快落13 小时前
【操作系统】手撸xv6操作系统——types.h/param.h/memlayout.h/riscv.h/defs.h头文件解析
操作系统·risc-v·嵌入式软件·xv6
小当家.1051 天前
操作系统期末考试基础知识点速成:高频考点与题集精要
考研·操作系统·计算机基础·速成·大学·期末考试
seasonsyy1 天前
为虚拟机分配内存和磁盘容量
windows·操作系统·内存·vmware·磁盘空间
想用offer打牌1 天前
一站式讲清IO多路复用(轻松愉悦版)
后端·面试·操作系统
seasonsyy1 天前
在虚拟机中安装操作系统需要U盘吗?
windows·操作系统·vmware·虚拟机
fakerth1 天前
【OpenHarmony】升级服务组件(UpdateService)
操作系统·openharmony
fakerth2 天前
【OpenHarmony】Updater 升级包安装组件
操作系统·openharmony
AlexMercer10122 天前
【操作系统】操作系统期末考试 简答题 焚决
c语言·经验分享·笔记·操作系统
brave and determined3 天前
ESP32 FreeRTOS (day1)入门教程 (ESP-IDF版):从超级循环到多任务的系统化思维
操作系统·esp32·freertos·任务·任务调度器·任务控制块·嵌入式设计