【MTI 6.S081 Lab】thread
【MTI 6.S081 Lab】thread
- 前言
- 调度
- Uthread: switching between threads (moderate)
- 实验任务
- Hints
- 解决方案
- thread_switch
- thread_create()
- thread_schedule()
- Using threads (moderate)
- 实验任务
- 解决方案
- Barrier (moderate)
- 实验任务
- 解决方案
本实验前去看《操作系统导论》第29章基于锁的并发数据结构,将会是很有帮助的。
前言
在做MIT 6.S081的实验过程中,每看一些讲座(lecture)和xv6 book就来做相应的实验。这个实验是让我最爽的两个实验之一(还有一个是trap),因为其他大多c语言,在我已经有了很多关于操作系统的知识的情况下,实现并没有那么的让人惊艳,但是这个实验确实让人非常惊艳。
这个实验里面的进程切换、线程切换,以前我确实知道怎么做的,但是像这样通过改变寄存器的值,以前我是没有想到的,所以以前只知道要保存状态,但是对于如何保存不知道原理,这次实验的过程中,通过理解源码,懂了这个,很神奇的东西。
调度
实现多路复用(multiplexing)的挑战:
- 如何从一个进程切换到另一个进程?尽管上下文切换的思想很简单,但它的实现是xv6中最不透明的代码之一
- 如何以对用户进程透明的方式强制切换?xv6使用标准技术,通过定时器终端驱动上下文切换
- 许多CPU可能同时在进程之间切换,使用一个锁方案来避免争用是很有必要的
- 进程退出时必须释放进程的内存以及其他资源,但它不能自己完成所有这一切,因为它不能在仍然使用自己内核栈的情况下释放它
- 多核机器的每个核心都必须记住它正在执行哪个进程,以便系统调用正确影响对应进程的内核状态
- sleep运行一个进程放弃CPU,wakeup允许另一个进程唤醒第一个进程。需要小心避免唤醒通知丢失的竞争。
Uthread: switching between threads (moderate)
在本练习中,您将为用户级线程系统设计上下文切换机制,然后实现它。首先,您的xv6有两个文件user/uthread.c和user/uthread_switch.S,并且Makefile中有一个规则来构建uthread程序。uthread.c包含大部分用户级线程包,以及三个简单测试线程的代码。线程程序包缺少一些用于创建线程和在线程之间切换的代码。
实验任务
您的工作是制定一个创建线程和保存/恢复寄存器以在线程之间切换的计划,并实现该计划。完成后,make grade应该表明您的解决方案通过了uthread测试。
完成后,当您在xv6上运行uthread时,您应该会看到以下输出(这三个线程可能以不同的顺序启动):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KHSla5rT-1690632901891)(Images/Screenshot from 2023-07-27 18-43-03.png)]
这个输出来自三个测试线程,每个线程都有一个循环,打印一行,然后将CPU分配给其他线程。
然而,现在你将看不到输出,因为咩有上下文切换代码。
你将需要在user/uthread.c
中添加代码thread_create()
和thread_schedule()
,在user/uthread_switch.S
中添加thread_switch
。一个目标是确保当thread_schedule()第一次运行给定线程时,该线程在自己的堆栈上执行传递给thread_create()的函数。另一个目标是确保thread_switch保存要切换的线程的寄存器,恢复要切换到的线程的注册表,并返回到后一个线程指令中上次停止的点。您必须决定保存/恢复寄存器的位置;修改struct thread
以容纳寄存器是一个不错的计划。您需要在thread_schedule中添加对thread_switch的调用;您可以向threadswitch传递所需的任何参数,但目的是从threadt切换到nextthread。
Hints
-
thread_switch
仅仅需要保存被调用者保存寄存器吗,为什么?答:是,其他调用者保存寄存器已经在调用thread_swtich时保存在当前线程的栈上。
-
你能在
user/uthread.asm
中看到uthread的汇编代码,可能更方便调试 -
为了测试你的代码,使用gdb单步调试thread_switch将是有用的,你可以通过这种方式开始:
(gdb) file user/_uthread Reading symbols from user/_uthread... (gdb) b uthread.c:60
这将在uthread.c的第60行设置断点。断点可能(也可能不)在您运行uthread之前被触发。这怎么可能发生?
一旦xv6shell运行,键入“uthread”,gdb将在第60行中断。如果您从另一个进程中遇到了断点,请继续进行,直到您在uthread进程中遇到断点为止。现在,您可以键入以下命令来检查uthread的状态:
(gdb) p/x *next_thread
使用“x”,您可以检查内存位置的内容:
(gdb) x/x next_thread->stack
您可以跳到thread_switch的开头,这样:
(gdb) b thread_switch (gdb) c
单步执行汇编代码
(gdb) si
gdb的在线文档 is here.
解决方案
thread_switch
.text/** save the old thread's registers,* restore the new thread's registers.*/.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 */
thread_create()
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->context.sp = (uint64)(t->stack + STACK_SIZE);t->context.ra = (uint64)func; // 使得在func处返回
}
thread_schedule()
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(??, ??);*/// 恢复当前进程的上下文,保存上一个进程t的上下文,此时会自动从上一个切换的上下文位置切换// 这里会破坏a0和a1thread_switch((uint64)&t->context, (uint64)¤t_thread->context);} elsenext_thread = 0;
}
Using threads (moderate)
在本次作业中,您将探索使用哈希表使用线程和锁进行并行编程。您应该在具有多个核心的真实Linux或MacOS计算机(不是xv6,不是qemu)上执行此任务。最新的笔记本电脑都有多核处理器。
此作业使用UNIX pthread线程库。您可以通过man phreads从手册页中找到有关它的信息,也可以在网上查看,例如here, here, and here.
文件notxv6/ph.c包含一个简单的哈希表,如果从单个线程使用,该哈希表是正确的,但如果从多个线程使用,则该哈希表不正确。在您的xv6主目录(可能~/xv6-labs-2021)中,键入以下内容:
$ make ph
$ ./ph 1
请注意,要构建ph,Makefile使用操作系统的gcc,而不是6.S081工具。ph的参数指定对哈希表执行put和get操作的线程数。运行一段时间后,ph 1将产生类似的输出:
100000 puts, 3.991 seconds, 25056 puts/second
0: 0 keys missing
100000 gets, 3.981 seconds, 25118 gets/second
你看到的数字可能与这个样本输出相差两倍或更多,这取决于你的电脑速度有多快,它是否有多个内核,以及它是否忙于做其他事情。
ph运行两个基准。首先,它通过调用put()将大量键添加到哈希表中,并以每秒puts为单位打印实现的速率。它使用get()从哈希表中获取密钥。它打印由于put而应该在哈希表中但丢失的数字键(在这种情况下为零),并打印每秒获得的次数。
您可以告诉ph同时从多个线程使用其哈希表,方法是给它一个大于1的参数。尝试ph 2:
$ ./ph 2
100000 puts, 1.885 seconds, 53044 puts/second
1: 16579 keys missing
0: 16579 keys missing
200000 gets, 4.322 seconds, 46274 gets/second
该ph2输出的第一行表明,当两个线程同时向哈希表添加条目时,它们的总插入率为每秒53044次。这大约是运行ph1的单个线程的速率的两倍。这是一个大约2倍的出色“并行加速”,这是人们所希望的(即每单位时间产生两倍功的两倍内核)。
然而,缺少16579个键的两行表示哈希表中不存在大量本应存在的键。也就是说,puts本应将这些键添加到哈希表中,但出现了问题。看看notxv6/ph.c,特别是put()和insert()。
实验任务
-
为什么2个线程的时会缺少一些key,而1个线程时没有缺少key?识别能在两个线程时导致键缺失的代码片。在answers-thread.txt中提交您的序列并附上简短解释
答:在put和insert函数中都可能发生错误
static void insert(int key, int value, struct entry **p, struct entry *n) {struct entry *e = malloc(sizeof(struct entry));e->key = key;e->value = value;e->next = n;*p = e; }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.insert(key, value, &table[i], table[i]);} }
假设现在有一个线程运行到第7行,此时添加的key为1,在执行第八行前,另一个线程刚好在put中搜索key=1是否存在,当然此时的结果是不存在,所以会被插入,此时key=1被重复插入了导致错误。
另一个问题是,同样假设当前两个线程均运行到第7行,此时会导致×p的更新只有一个更新有效,导致key缺失。
-
为了避免这种事件序列,请在notxv6/ph.c中的put和get中插入lock和unlock语句,以便在两个线程中丢失的key数始终为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测试时,您就完成了,该测试需要两个线程的零丢失键。在这一点上,不通过ph_fast测试是可以的(也就是说只要我们自己测试,没有missing就行)。
解决方案
在原始代码单线程情况下,我的机器的输出如下
$ ./ph 1
100000 puts, 10.889 seconds, 9183 puts/second
0: 0 keys missing
100000 gets, 11.494 seconds, 8700 gets/second
在修改后,给每个table一个锁,加在上面展现的代码的地16行和第28行的位置,编译程序,得到的双线程结果是
$ ./ph 2
100000 puts, 5.834 seconds, 17140 puts/second
0: 0 keys missing
1: 0 keys missing
200000 gets, 10.783 seconds, 18547 gets/second
修改代码,使一些put操作在保持正确性的同时并行运行。当make grade表示您的代码通过了ph_safe和ph_fast测试时,您就完成了。ph_fast测试要求两个线程每秒的输出量至少是一个线程的1.25倍。(这里我用的小粒度的锁,基本达到了两倍速度,所以make grade相应测试肯定通过)
Barrier (moderate)
在这项任务中,您将实现一个屏障:应用程序中的一个点,所有参与线程都必须等待,直到所有其他参与线程也到达该点。您将使用pthread条件变量,这是一种类似于xv6的睡眠和唤醒的序列协调技术。
您应该在真实的计算机上(不是xv6,也不是qemu)执行此任务。
文件notxv6/barrier.c包含一个损坏的屏障。
$ make barrier
$ ./barrier 2
barrier: notxv6/barrier.c:42: thread: Assertion `i == t' failed.
2指定在barrier.c中同步的线程数。每个线程执行一个循环。在每次循环迭代中,线程都会调用barrier(),然后休眠随机微秒。assert触发,因为一个线程在另一个线程到达屏障之前就离开了屏障。所需的行为是,每个线程在barrier()中阻塞,直到所有线程都调用了barrier()
实验任务
您的目标是实现所需的障碍行为。除了您在ph赋值中看到的锁基元之外,您还需要以下新的pthread基元;请看这里和这里了解详细信息。
pthread_cond_wait(&cond,&mutex);//在cond上进入睡眠,释放锁互斥,在唤醒时获取
pthread_cond_broadcast(&cond);//唤醒每一个睡在cond上的线程
确保你的解决方案通过make grade的barrier测试。
pthread_cond_wait 被调用的时候释放mutex,在返回时重新获得mutex
我们已经给你barrier_init()。你的任务是执行barrier(),以便于panic不发生。我们已经为你定义struct barrier,他的一些字段对你有用
有两个问题使您的任务复杂化:
- 你必须处理一系列的障碍调用,每一次我们都会调用一轮。bstate.round记录本轮。每次所有线程到达屏障时,都应该增加bstate.round。
- 您必须处理这样一种情况,即一个线程在其他线程退出屏障之前围绕循环进行竞争。特别是,从一轮到下一轮都在重新使用bstate.nthread变量。确保在上一轮仍在使用bstate.nthread时,离开障碍并在循环中奔跑的线程不会增加bstate.nhread。
使用一个、两个或两个以上的线程测试代码。
解决方案
对进入barrier的线程数计数,一旦最后一个线程到达了,那么不要再
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) {pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex); // 在这里等待其他的进入} else { // 所有的线程都到这里了bstate.nthread = 0;bstate.round++;// 一定要在round、nthread更新后才能唤醒,因为如果先唤醒,此时别的线程执行,到t=bstate.round,但是没有更新,导致t!=i,会assertpthread_cond_broadcast(&bstate.barrier_cond);}pthread_mutex_unlock(&bstate.barrier_mutex);
}
相关文章:
【MTI 6.S081 Lab】thread
【MTI 6.S081 Lab】thread 前言调度Uthread: switching between threads (moderate)实验任务Hints解决方案thread_switchthread_create()thread_schedule() Using threads (moderate)实验任务解决方案 Barrier (moderate)实验任务解决方案 本实验前去看《操作系统导论》第29章基…...

AWS / VPC 云流量监控
由于安全性、数据现代化、增长、灵活性和成本等原因促使更多企业迁移到云,将数据存储在本地的组织正在使用云来存储其重要数据。亚马逊网络服务(AWS)仍然是最受追捧和需求的服务之一,而亚马逊虚拟私有云(VPC࿰…...
【C++学习笔记】extern “c“以及如何查看符号表
如何查看符号表 要查看.a文件的内容,可以使用ar命令。下面是一些常见的用法: 列出.a文件中包含的所有文件: ar t <filename.a>提取.a文件中的单个文件: ar x <filename.a> <filename.o>将.a文件中的所有文件提…...

24考研数据结构-数组和特殊矩阵
目录 数据结构:数组与特殊矩阵数组数组的特点数组的用途 特殊矩阵对角矩阵上三角矩阵和下三角矩阵稀疏矩阵特殊矩阵的用途 结论 3.4 数组和特殊矩阵3.4.1数组的存储结构3.4.2普通矩阵的存储3.4.3特殊矩阵的存储1. 对称矩阵(方阵)2. 三角矩阵(方阵)3. 三对角矩阵(方阵…...
服务器后台运行程序
代码运行 要让代码在服务器后台运行,有多种方法。在 Linux 系统中,最常见的有以下几种方式: **1. 使用 & 符号:** 在命令后面添加 & 符号可以让程序在后台运行。例如: bash python myscript.py &但是…...

大数据课程D7——hadoop的YARN
文章作者邮箱:yugongshiyesina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解YARN的概念和结构; ⚪ 掌握YARN的资源调度流程; ⚪ 了解Hadoop支持的资源调度器:FIFO、Capacity、Fair; ⚪ 掌握YA…...

Rust vs Go:常用语法对比(十三)
题图来自 Go vs. Rust: The Ultimate Performance Battle 241. Yield priority to other threads Explicitly decrease the priority of the current process, so that other execution threads have a better chance to execute now. Then resume normal execution and call f…...
【【51单片机DA转换模块】】
爆改直流电机,DA转换器 main.c #include <REGX52.H> #include "Delay.h" #include "Timer0.h"sbit DAP2^1;unsigned char Counter,Compare; //计数值和比较值,用于输出PWM unsigned char i;void main() {Timer0_Init();whil…...
[SQL挖掘机] - 字符串函数 - substring
介绍: substring函数是在mysql中用于提取字符串的一种函数。它接受一个字符串作为输入,并返回从该字符串中指定位置开始的一部分子串。substring函数可以用于获取字符串中的特定字符或子串,以便进行进一步的处理或分析。 用法: 下面是substring函数的…...

第一百一十六天学习记录:C++提高:STL-string(黑马教学视频)
string基本概念 string是C风格的字符串,而string本质上是一个类 string和char区别 1、char是一个指针 2、string是一个类,类内部封装了char*,管理这个字符串,是一个char型的容器。 特点: string类内部封装了很多成员方…...

Meta-Transformer 多模态学习的统一框架
Meta-Transformer是一个用于多模态学习的新框架,用来处理和关联来自多种模态的信息,如自然语言、图像、点云、音频、视频、时间序列和表格数据,虽然各种数据之间存在固有的差距,但是Meta-Transformer利用冻结编码器从共享标记空间…...

tinkerCAD案例:24.Tinkercad 中的自定义字体
tinkerCAD案例:24.Tinkercad 中的自定义字体 原文 Tinkercad Projects Tinkercad has a fun shape in the Shape Generators section that allows you to upload your own font in SVG format and use it in your designs. I’ve used it for a variety of desi…...

list与流迭代器stream_iterator
运行代码: //list与流迭代器 #include"std_lib_facilities.h" //声明Item类 struct Item {string name;int iid;double value;Item():name(" "),iid(0),value(0.0){}Item(string ss,int ii,double vv):name(ss),iid(ii),value(vv){}friend ist…...

九耶:冯·诺伊曼体系
冯诺伊曼体系(Von Neumann architecture)是一种计算机体系结构,它由匈牙利数学家冯诺伊曼于1945年提出。冯诺伊曼体系是现代计算机体系结构的基础,几乎所有的通用计算机都采用了这种体系结构。 冯诺伊曼体系的核心思想是将计算机硬…...
探索UCI心脏病数据:利用R语言和h2o深度学习构建预测模型
一、引言 随着机器学习模型在实际应用中的广泛应用,人们对于模型的解释性和可理解性日益关注。可解释性机器学习是指能够清晰、透明地解释机器学习模型决策过程的一种方法和技术。在许多领域中,如医疗诊断、金融风险评估和自动驾驶等,解释模型…...

基于 moleculer 微服务架构的智能低代码PaaS 平台源码 可视化开发
低代码开发平台源码 低代码管理系统PaaS 平台 无需代码或通过少量代码就可以快速生成应用程序的开发平台。 本套低代码管理后台可以支持多种企业应用场景,包括但不限于CRM、ERP、OA、BI、IoT、大数据等。无论是传统企业还是新兴企业,都可以使用管理后台…...

xrdp登录显示白屏且红色叉
如上图所示,xrdp登录出现了红色叉加白屏,这是因为不正常关闭导致,解决方法其实挺简单的 #进入/usr/tmp cd /usr/tmp #删除对应用户的kdecache-** 文件(我这里使用的是kde桌面),例如删除ywj用户对应的文件 …...

Docker安装 Mysql 8.x 版本
文章目录 Docker安装 Mysql 8.0.22Mysql 创建账号并授权Mysql 数据迁移同版本数据迁移跨版本数据迁移 Mysql 5.x 版本与 Mysql 8.x版本是两个大版本,这里演示安装Mysql 8.x版本 Docker安装 Mysql 8.0.22 # 下载mysql $ docker pull mysql 默认安装最新…...

【数理知识】刚体 rigid body 及刚体的运动
文章目录 1 刚体2 刚体一般运动1 平移运动2 旋转运动 Ref 1 刚体 刚体是指在运动中和受力作用后,形状和大小不变,而且内部各点的相对位置不变的物体。绝对刚体实际上是不存在的,只是一种理想模型,因为任何物体在受力作用后&#…...

【UE5 多人联机教程】03-创建游戏
效果 步骤 打开“UMG_MainMenu”,增加创建房间按钮的点击事件 添加如下节点 其中,“FUNL Fast Create Widget”是插件自带的函数节点,内容如下: “创建会话”节点指游戏成功创建一个会话后,游戏的其他实例即可发现&am…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...