MIT6.s081 2021 Lab Multithreading

news2024/9/25 21:29:37

Uthread: switching between threads

思路

xv6 已经实现了进程的切换机制,本实验要求参考进程的切换,实现一个用户态线程的切换。

要实现线程切换,必然涉及上下文,即寄存器的保存和恢复,那么需要保存哪些寄存器?实际上,只需要保存被调用者保存寄存器(callee-saved registers),而实现调用者保存寄存器(caller-saved registers)的保存与恢复的代码由编译器自动生成。关于调用者保存与被调用者保存寄存器有哪些可以参照下述 RISC-V 的 calling convention:

请添加图片描述

另外,根据 user/uthread_switch.S 的注释,thread_switch 最后通过 ret 指令将当前程序计数器的值切换为 ra 寄存器中存储的地址,实现进程的“切换”,因此 struct thread 中还需要保存每个线程对应程序的起始地址(即函数指针)。

在了解需要保存哪些寄存器之后以及如何进行线程切换之后,还有一个细节需要考虑,即栈指针寄存器(sp)的初始化。线程栈的存储位置为 struct thread 中的 stack 数组,那么 sp 应该指向 stack 的位置,但由于栈的地址从大到小增长,因此 sp 应该初始化为 (uint64)t->stack + STACK_SIZE.

代码

diff --git a/user/uthread.c b/user/uthread.c
index 06349f5..74b7f20 100644
--- a/user/uthread.c
+++ b/user/uthread.c
@@ -12,6 +12,20 @@
 
 
 struct thread {
+  /* 0 */  uint64 ra;
+  /* 8 */  uint64 sp;
+  /* 16 */  uint64 s0;
+  /* 24 */ uint64 s1;
+  /* 32 */ uint64 s2;
+  /* 40 */ uint64 s3;
+  /* 48 */ uint64 s4;
+  /* 56 */ uint64 s5;
+  /* 64 */ uint64 s6;
+  /* 72 */ uint64 s7;
+  /* 80 */ uint64 s8;
+  /* 88 */ uint64 s9;
+  /* 96 */ uint64 s10;
+  /* 104 */ uint64 s11;
   char       stack[STACK_SIZE]; /* the thread's stack */
   int        state;             /* FREE, RUNNING, RUNNABLE */
 };
@@ -62,6 +76,7 @@ thread_schedule(void)
      * Invoke thread_switch to switch from t to next_thread:
      * thread_switch(??, ??);
      */
+	thread_switch((uint64)t, (uint64)current_thread);
   } else
     next_thread = 0;
 }
@@ -76,6 +91,8 @@ thread_create(void (*func)())
   }
   t->state = RUNNABLE;
   // YOUR CODE HERE
+  t->ra = (uint64)func;
+  t->sp = (uint64)t->stack + STACK_SIZE;
 }
 
 void 
diff --git a/user/uthread_switch.S b/user/uthread_switch.S
index 5defb12..0eb0a2c 100644
--- a/user/uthread_switch.S
+++ b/user/uthread_switch.S
@@ -7,5 +7,34 @@
 
 	.globl thread_switch
 thread_switch:
	/* YOUR CODE HERE */
+	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    /* return to ra */

Using threads

思路

后两个实验与 xv6 无关,而是练习使用 POSIX 线程库在实际的 Linux 平台进行并发编程。

本实验要求使用锁机制,实现一个支持并发的哈希表。首先需要确定的是:哪部分的操作会出现竞态(race condition)?根据观察不难得知 put() 操作可能存在下面这种情况:

线程 1 和线程 2 本次 put() 映射到一个桶中(i 相同),都执行完 line 46 ~ 49 的循环之后,e 都为 0,随后先后执行 insert(),都创建一个新的 entry,并先后更新 table[i] 的值,导致先插入的键被覆盖。
像这样,在一次插入操作未完成的情况下,另一次插入也开始进行且映射到一个桶中,就会导致丢键(keys missing)的情况发生。

首先最简单无脑的办法就是给整个 put() 函数加一把大锁:

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

请添加图片描述

可以看到,keys missing 的问题已经被解决,但是大锁带来的就是更低的性能,实际上根据上图可知,该实现在双核情况下的运行速度甚至慢于单核。

实际上,对 table 数组的遍历并不会导致竞态,因此将加锁的操作延迟到遍历结束后:

for (e = table[i]; e != 0; e = e->next) {
    if (e->key == key)
        break;
}
pthread_mutex_lock(&lock);  // lock
if(e){
    // update the existing key.
    e->value = value;
} else {
    // the new is new.
    insert(key, value, &table[i], table[i]);
}
pthread_mutex_unlock(&lock);  // unlock

请添加图片描述

做了上述修改后,仍然没有出现 key missing 的情况,同时效率提升了一倍以上。

最后,更细化一些,只有当两个 put() 映射到同一个桶时才会发生竞态,因此可以为每个桶分别设置一把锁,以进一步提高并发性:

for (e = table[i]; e != 0; e = e->next) {
    if (e->key == key)
        break;
}
pthread_mutex_lock(&locks[i]);  // lock
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]);  // unlock

请添加图片描述

可见,效率又有进一步提升。

代码

diff --git a/notxv6/ph.c b/notxv6/ph.c
index 82afe76..321e269 100644
--- a/notxv6/ph.c
+++ b/notxv6/ph.c
@@ -17,6 +17,7 @@ struct entry *table[NBUCKET];
 int keys[NKEYS];
 int nthread = 1;
 
+pthread_mutex_t locks[NBUCKET];
 
 double
 now()
@@ -47,6 +48,7 @@ void put(int key, int value)
     if (e->key == key)
       break;
   }
+  pthread_mutex_lock(&locks[i]);
   if(e){
     // update the existing key.
     e->value = value;
@@ -54,7 +56,7 @@ void put(int key, int value)
     // the new is new.
     insert(key, value, &table[i], table[i]);
   }
-
+  pthread_mutex_unlock(&locks[i]);
 }
 
 static struct entry*
@@ -118,6 +120,10 @@ main(int argc, char *argv[])
     keys[i] = random();
   }
 
+
+  for (int i = 0; i < NBUCKET; ++i) {
+    pthread_mutex_init(&locks[i], NULL);
+  }
   //
   // first the puts
   //

Barrier

思路

最后一个实验主要是熟悉 POSIX 线程库中条件变量(conditional variable)的使用,实现的思路比较简单:前 nthread - 1 个线程在条件变量上休眠,最后一个线程将休眠的所有进程进行唤醒。有关条件变量的用法可以参考 OSTEP:OSTEP: Condition Variables.

代码

diff --git a/notxv6/barrier.c b/notxv6/barrier.c
index 12793e8..e4fd03e 100644
--- a/notxv6/barrier.c
+++ b/notxv6/barrier.c
@@ -30,7 +30,18 @@ barrier()
   // Block until all threads have called barrier() and
   // then increment bstate.round.
   //  
+
+  pthread_mutex_lock(&bstate.barrier_mutex);
+  ++bstate.nthread;
+  if (bstate.nthread == nthread) {
+    pthread_cond_broadcast(&bstate.barrier_cond);
+	++bstate.round;
+	bstate.nthread = 0;
+  }
+  else {
+    pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex);
+  }
+  pthread_mutex_unlock(&bstate.barrier_mutex);
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2053101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

建筑工程项目管理系统-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

建筑工地安全检查

在现代化的建筑工地中&#xff0c;安全始终是至关重要的核心问题。随着科技的不断进步&#xff0c;凡尔码建筑工地安全系统应运而生&#xff0c;灵活根据施工现场管理要求搭建建筑工地安全系统各个模块&#xff0c;为施工安全带来了全新的保障。 如何注册建筑工地安全系统后台…

自动打电话软件给企业带来了什么?

使用机器人外呼系统肯定都是想要给自己企业带来好处和解决问题的&#xff0c;想让自己的企业有所改变&#xff0c;有更好的发展&#xff0c;所以才会选择使用机器人外呼系统。而它也确实没让大家失望&#xff0c;使用了机器人外呼系统之后确实有许多企业发生了很大改变和进步&a…

鲁棒性目标检测 TOP2 方案分享

关联比赛: ACM MM2021 安全AI挑战者计划第七期&#xff1a;鲁棒性标识检测 ACM MM2021 鲁棒性目标检测比赛 TOP 2 方案 ​ 赛题背景 在商品知识产权领域&#xff0c;知识产权体现为在线商品的设计和品牌。不幸的是&#xff0c;在每一天&#xff0c;存在着非法商户通过一些…

一文学会本地部署可视化应用JSONCrack并配置公网地址实现远程协作

文章目录 前言1. Docker安装JSONCrack2. 安装Cpolar内网穿透工具3. 配置JSON Crack界面公网地址4. 远程访问 JSONCrack 界面5. 固定 JSONCrack公网地址 前言 本文主要介绍如何在Linux环境使用Docker安装数据可视化工具JSONCrack&#xff0c;并结合cpolar内网穿透工具实现团队在…

[Python学习日记-9] Python中的运算符

简介 计算机可以进行的运算有很多种&#xff0c;但可不只加减乘除这么简单&#xff0c;运算按种类可分为算数运算、比较运算、逻辑运算、赋值运算、成员运算、身份运算、位运算&#xff0c;而本篇我们暂只介绍算数运算、比较运算、逻辑运算、赋值运算 算数运算 一、运算符描述…

猫头虎分享:Python库 Pillow 的简介、安装、用法详解入门教程

猫头虎分享&#xff1a;Python库 Pillow 的简介、安装、用法详解入门教程 &#x1f4da; 大家好&#xff0c;今天猫头虎要和大家分享一款非常实用的 Python 图像处理库——Pillow。 &#x1f4a1; Pillow 是 Python 中非常流行的图像处理库&#xff0c;基于已经停止维护的 PI…

CE修改器步骤9学习教程

一、打开教程&#xff0c;因为我的电脑是64位的&#xff0c;所以打开这个&#xff08;x86_64&#xff09; 二、 跳转到步骤9&#xff0c;并让ce读取其内存 三、使用之前教程学到的知识&#xff0c;找到四个角色的健康值地址&#xff08;找到即可&#xff0c;不必找基址&#xf…

【STM32 FreeRTOS】Tickless低功耗模式

STM32低功耗模式 STM32 提供了 3 种低功耗模式&#xff0c;以达到不同层次的降低功耗的目的 睡眠模式&#xff08;内核停止工作&#xff0c;外设仍在运行&#xff09;停止模式&#xff08;所有时钟都停止&#xff09;待机模式&#xff08; 1.8 V 内核电源关闭&#xff09; Fr…

Qt-认识Qt(1)

目录 QT是做什么的&#xff1f; 什么是QT GUI开发的各种技术方案 QT支持的平台 Qt的版本和优点 开发工具概述 Qt是做什么的&#xff1f; Qt是用来干嘛的&#xff1f; 什么是Qt Qt是⼀个跨平台的C图形用户界⾯应用程序框架。它为应用程序开发者提供了建立艺术级图形界⾯所…

SSH远程管理/TCP Wrappers访问控制

文章目录 SSH远程管理/TCP Wrappers访问控制SSH(Secure Shell)协议OpenSSH配置信息服务监听选项用户登录控制登录验证方式 常用目录---ssh 远程安全登录---scp 远程安全复制---sftp FTP上下载 配置密钥对验证环境配置ECDSA算法RSA算法RSA算法实操在centos7 IP:20.0.0.51操作一、…

【嵌入式linux开发】智能家居入门5:老版ONENET,多协议接入(QT、微信小程序、HTTP协议、ONENET云平台、旭日x3派)

智能家居入门5&#xff08;QT、微信小程序、HTTP协议、ONENET云平台、旭日x3派&#xff09; 前言一、QT界面设计二、云平台产品创建与连接三、下位机端QT代码总览&#xff1a;四、微信小程序端代码总览五、板端测试 前言 前四篇智能家居相关文章都是使用STM32作为主控&#xf…

用时间序列数据画蜡烛图

数据集&#xff1a;时间序列数据集&#xff08;2024.8.16收集&#xff09;-修改date资源-CSDN文库 示例一 import pandas as pd import mplfinance as mpf# 读取CSV文件 df pd.read_csv(999999_dcolhchg.csv)# 将日期列加上19000000&#xff0c;然后转换为日期格式 df[date]…

Jmeter系列之作用域、执行顺序

这一节主要解释元件作用域和执行顺序&#xff0c;以及整理之前说过的参数化的方式。 作用域 之前也留下了一个问题。怎么给不同的请求设置不同的Header&#xff1f;后续也透露了可以使用Sample Controller&#xff0c;结合元件的作用域来实现 在Jmeter中&#xff0c;元件的作…

轻松搞定由于找不到msvcr120.dll,无法继续执行代码的问题,总结五种msvcr120.dll丢失修复方法

当您在使用基于Windows的系统运行软件或游戏时&#xff0c;可能会遇到如下错误提示&#xff1a;“由于找不到 msvcr120.dll&#xff0c;无法继续执行代码”。这个问题表明您的系统缺少 Microsoft Visual C Redistributable Packages for Visual Studio 2013 中的一个关键组件&a…

【LeetCode Cookbook(C++ 描述)】一刷二叉树综合(下)

目录 LeetCode #257&#xff1a;Binary Tree Paths 二叉树的所有路径深度优先搜索广度优先搜索 LeetCode #404&#xff1a;Sum of Left Leaves 左叶子之和深度优先搜索广度优先搜索 LeetCode #199&#xff1a;Binary Tree Right Side View 二叉树的右视图广度优先搜索深度优先搜…

单体应用spring Task和分布式调度

Spring Task 1.通过 Spring Task&#xff0c;您可以方便地在 Java 应用程序中实现定时任务&#xff0c;比如每天凌晨进行数据同步、每小时执行一次清理操作等。 2.1 启动类添加EnableScheduling注解(默认情况下&#xff0c;系统会自动启动一个线程) 2.2 在需要定时执行的方…

解决 Swift 6 全局变量不能满足并发安全(concurrency-safe)读写的问题

概述 WWDC 24 终于在 Swift 十岁生日发布了全新的 Swift 6。这不仅意味着 Swift 进入了全新的“大”版本时代&#xff0c;而且 Swift 编译器终于做到了并发代码执行的“绝对安全”。 不过&#xff0c;从 Swift 5 一步迈入“新时代”的小伙伴们可能对新的并发检查有些许“水土不…

连锁美业门店收银系统拓客系统预约系统Java源码-博弈美业APP如何进行课程核销?

* 课程开课后&#xff0c;到课程结束前&#xff0c;这段时间均可以进行课程核销 * 课程核销的权限&#xff0c;仅限内部员工 * 核销课程时&#xff0c;需要切换到总部&#xff0c;才有核销课程的权限 方法一&#xff1a;通过“课程核销”直接核销 点击“课程核销”&#xff…

强大的接口测试可视化工具:Postman Flows

Postman Flows是一种接口测试可视化工具&#xff0c;可以使用流的形式在Postman工作台将请求接口、数据处理和创建实际流程整合到一起。如下图所示 Postman Flows是以API为中心的可视化应用程序开发界面。它提供了一个无限的画布用于编排和串连API&#xff0c;数据可视化来显示…