lab7 thread

news2024/11/20 2:44:00

image-20230825171259440

文章目录

  • Uthread: switching between threads
    • task
    • hints
    • 思路
      • 上下文的恢复和保存
      • thread_create
      • thread_schedule
  • Using threads
    • 思路
  • Barrier

Uthread: switching between threads

在这个练习中,你将为一个用户级别线程系统设计上下文切换机制,并实现它。

task

你的任务是提出一个计划,并实现它

  1. 创造线程
  2. 切换线程的时候,保存和恢复寄存器

当你完成的时候,make grade会显示你通过了uthreadtest

你将需要在user/uthread.c中的thread_create()thread_schedule(),在user/uthread_switch.Sthread_switch添加代码

  1. 一个目标是去保证,当thread_schedule()第一次运行一个线程时,这个线程会在它自己的栈上执行传递给thread_create的函数

  2. 另一个目标是去保证thread_switch保存被切换线程的寄存器,恢复被恢复线程的寄存器,并且到被恢复线程上次被中断的地方继续执行。

  3. 你将不得不决定将寄存器存放在哪里,修改struct thread去持有寄存器是不错的想法

  4. 你需要在thread_schedule调用thread_switch

  5. 你可以传递任何你需要的参数给thread_switch,但是目标就是切换线程

hints

  1. thread_switch只需要保存和恢复被调用函数保护寄存器
  2. 你可以在user/uthread.asm中看到uthread的汇编代码

思路

代码非常少,主要是要搞清楚整个流程。线程的切换主要就是通过一个ra寄存器记录切换后函数从哪开始执行,通过一个sp寄存器记录切换之后栈的地址,然后就是一些被调用者保护寄存器。

为什么只需要保存callee保护寄存器?

因为switch函数就是一个普通的c函数,在调用它的时候,调用函数会将调用者保护寄存器压入栈中保存,在它返回之后,会从>栈中恢复被调用者保护寄存器。在switch结束之后,通过栈就可以恢复caller寄存器(这也是为什么要保存和恢复sp指针)。

而对于callee保护寄存器,就是被调用的函数来保护的了。也就是说,通过ra,sp以及callee保护寄存器,我们就可以恢复到某>个线程的某个函数执行之后的镜像,缺一不可。

对于第一次被调度的进程,就更无所谓了,反正也不需要恢复什么caller和callee寄存器,本质上只需要ra和sp即可,但是为了统>一写法,操作一下callee寄存器也没问题

上下文的恢复和保存

而在我们的这个task中,线程切换时也要用到上述功能,因此需要模仿xv6构建一个context的结构体,并将其加入到thread的定义中

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

然后修改uthread_switch的定义为extern void thread_switch(struct context *, struct context *);,并将上下文保存和恢复的汇编加入对应的汇编文件

	.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

在这里,我们需要设置ra和sp寄存器,分别指向函数的入口地址和栈的初始地址。其中栈的地址应该定位在栈的最高地址,因为它向下增长

    // YOUR CODE HERE
    t->ctx.ra = (uint64)func;
    t->ctx.sp = (uint64)t->stack + STACK_SIZE - 1;

thread_schedule

最后在这个函数中加入一行即可

        /* YOUR CODE HERE
     * Invoke thread_switch to switch from t to next_thread:
     * thread_switch(??, ??);
     */
        thread_switch(&t->ctx, &current_thread->ctx);

这个task自己要写的代码非常少,但是uthread.c整个文件可以说包含了上下文切换最关键的部分了,很值得学习。

并且原来在用户态,也可以在c代码里面嵌入汇编代码,神奇。

Using threads

首先,为了避免插入时出错,你需要在putget中使用锁,如果能够在make grade中通过ph_safe,就说明成功

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

然后你应该优化你的代码,使得你能通过ph_fast的测试,你可以在每个桶上添加一个锁。两个线程至少要达到1.25倍的速度

思路

直接一步到位了,给每个bucker设置一个锁,并在main函数中对锁初始化

pthread_mutex_t locks[NBUCKET];

void init_lock() {
    for (int i = 0; i < NBUCKET; i++) {
        pthread_mutex_init(&locks[i], NULL);
    }
}

然后构造两个宏,省的后面输入一大串

#define LOCK(i) (pthread_mutex_lock(&locks[i]));
#define UNLOCK(i) (pthread_mutex_unlock(&locks[i]));

最后在put和get的起始和末尾都加上一个LOCK(i)UNLOC(i)

image-20230825163234832

Barrier

这部分的实验文档看得我迷迷糊糊的,还是看了半天源代码才看懂是啥意思。

关键就是下面这个函数,我们每一次for循环,bstate.round都应该和循环轮数相同。再结合实验文档可以知道,就是要求我们通过barrier实现所有线程都在同一次for循环里,不能有人提前进入下一轮,因为这样的话,这个assert肯定就要错了。

static void *
thread(void *xa) {
    long n = (long)xa;
    long delay;
    int i;

    for (i = 0; i < 20000; i++) {
        int t = bstate.round;
        assert(i == t);
        barrier();
        usleep(random() % 100);
    }

    return 0;
}

然后就是这个结构体,它是关键。其中round代表的就是现在for循环的轮数,而nthread代表的是目前已经有多少个线程到达了屏障正在阻塞等待,然后上面就是两个锁,一个是常规的互斥锁,一个是条件变量

struct barrier {
    pthread_mutex_t barrier_mutex;
    pthread_cond_t barrier_cond;
    int nthread; // Number of threads that have reached this round of the barrier
    int round;   // Barrier round
} bstate;

条件变量的使用也很有意思。第一个wait操作,要求这个线程必须持有锁,然后调用wait之后,这个线程会释放这个锁,然后进入阻塞睡眠。第二个广播操作,会将通过cond阻塞的所有线程都唤醒。

pthread_cond_wait(&cond, &mutex);  // go to sleep on cond, releasing lock mutex, acquiring upon wake up
pthread_cond_broadcast(&cond);     // wake up every thread sleeping on cond

上面两个锁的组合就可以构建barrier函数。有一些宏定义,方便使用。

首先,每个进入barrier的线程都应该将现在进入barrier的线程数量加1。而为了防止并发带来的问题,+1的过程肯定是要用锁的,我们这里正好就是用了barrier_mutex。

然后,我们需要判断目前的数量是否已经达到了线程总数nthread

  1. 如果没达到,那就通过条件变量让它睡觉去吧
  2. 如果达到了,那么我们需要将所有因此阻塞的进程都唤醒
    1. 但是在唤醒之前,我们需要先将bstate的round和nthread变量给更新了
    2. 如果我们是在唤醒之后更新,那么可能cpu瞬间就被别人抢去了,然后那些人就进入了下一轮for循环,直接assert失败。

还有一种很恶心的并发问题,就是如果我们很早就UNLOCK了,那么有可能某个线程还没有wait,就有一个线程调用了广播,那么后果就是这个线程永远不会被唤醒。不过在我们这里是不会出现这种情况的。

#define LOCK() (pthread_mutex_lock(&bstate.barrier_mutex))
#define UNLOCK() (pthread_mutex_unlock(&bstate.barrier_mutex))
#define WAIT() (pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex))
#define BROADCAST() (pthread_cond_broadcast(&bstate.barrier_cond))
static void
barrier() {
    // YOUR CODE HERE
    //
    // Block until all threads have called barrier() and
    // then increment bstate.round.
    //
    LOCK();
    bstate.nthread += 1;
    if (bstate.nthread < nthread) {
        WAIT();
    } else {
        bstate.round += 1;
        bstate.nthread = 0;
        BROADCAST();
    }
    UNLOCK();
}

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

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

相关文章

算法leetcode|73. 矩阵置零(rust重拳出击)

文章目录 73. 矩阵置零&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a;进阶&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 73. 矩阵置零&#xff1a; 给定一个 m x n 的矩…

邮件营销:高效的节日宣传方式

每个国家都有当地的传统节日&#xff0c;像是我国刚过去的端午节&#xff0c;即将迎来的中秋节、国庆节。我们除了会进行一些传统习俗外&#xff0c;各路商家还会趁这个机会开启促销活动。 对于公司来讲&#xff0c;抓住每一次营销活动都可能会带来更高的营销额&#xff0c;或…

ATA-7000系列高压放大器——应用场景介绍

ATA-7000系列是一款理想的可放大交、直流信号的高压放大器。单端输出20kVp-p&#xff08;10kVp&#xff09;高压&#xff0c;可以驱动高压型负载。电压增益数控可调&#xff0c;一键保存常用设置&#xff0c;为您提供了方便简洁的操作选择。 图&#xff1a;ATA-7000系列高压放大…

2021年长安杯电子数据取证比赛

VC挂载 一&二检材 长安杯-1 检材一 请计算检材一Apk的SHA256值 3fece1e93be4f422c8446b77b6863eb6a39f19d8fa71ff0250aac10f8bdde73a 长安杯-2 该APK的应用包名为 plus.H5B8E45D3 长安杯-3 APPID 该APK程序在封装服务商的应用唯一标识&#xff08;APPID&#xff09;为…

设计模式之抽象工厂

文章目录 一、介绍二、基本组件三、演示案例1. 定义抽象工厂2. 定义抽象产品3. 定义具体工厂4. 定义具体产品5. 代码演示6. 代码改造 四、总结 一、介绍 抽象工厂模式(Abstract Factory Pattern)属于创建型设计模式。用于解决比工厂方法设计模式更加复杂的问题。 复杂到哪里了…

U盘怎么加密?U盘加密方法有哪些?

U盘是我们生活和工作中最常用的移动储存设备&#xff0c;经常被用来存放各种重要数据&#xff0c;为了保证数据的安全&#xff0c;我们需要加密U盘。那么&#xff0c;U盘加密方法有哪些呢&#xff1f; U盘加密普通方法 如果你的U盘储存数据不多&#xff0c;并且对于加密的要求…

Orchestrator介绍三 命令行工具

Orchestrator-client orchestrator 支持两种方式通过命令行操作&#xff1a; 一种是 通过命令 orchestrator&#xff1a; 需要在服务器上安装 orchestrator&#xff0c;但是可以不作为服务启动。 需要配置orchestrator的文件&#xff0c;以便能够连接后端数据库 一种是通过…

Docker部署gogs仓库

Docker部署gogs Git仓库 拉取镜像 docker pull gogs/gogs查看本地镜像 docker images启动gogs仓库服务 创建数据挂在目录 我在/root目录下创建gogs挂在目录 mkdir gogs启动gogs docker run --namegogs -d -p 10022:22 -p 10880:3000 -v /root/gogs:/data gogs/gogs10022…

知识图谱Neo4j安装到实践全过程

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 在本次实战中&#xff0c;我们将一起完成知识图谱Neo4j安装到实践全过程&#xff0c;探索其中的关系和属性。知识图谱是一种以三元组形式存储的数据结构&#xff0c;由实体、关系和属性组成&#xff0c;能够帮助我们更好地…

西部AI小镇-构建自主虚拟世界

背景 未来曜文有接入市场上所有面向chatGPT开发的应用&#xff0c;例如开源聊天组件&#xff0c;西部小镇等 内容介绍 生成代理起床&#xff0c;做早餐&#xff0c;然后去上班&#xff1b;艺术家作画&#xff0c;作家写作&#xff1b;他们形成意见、互相关注并发起对话&…

window如何实时刷新日志文件

1 安装windows git 下载地址&#xff1a;Git - Downloading Package (git-scm.com) 2 打开git bash 输入tail.exe -f 日志文件路径

yo!这里是Linux权限入门理解

目录 前言 权限概念 权限管理 分类 1.用户 2.文件&&目录 表示 设置 1.chmod指令 2.chown指令 3.chgrp指令 4.umask指令 粘滞位 后记 前言 对于Linux基本指令&#xff0c;基本上就是操作文件或者目录&#xff0c;但是&#xff0c;是谁可以操作文件或目录&…

8个月打磨,打造出的全能工具箱,让你事半功倍!

这款工具叫即时工具&#xff0c;目前有网页端和客户端可以下载至本地离线使用&#xff0c;区别在于客户端采用原生适配性能更好&#xff0c;网页端需要上传至服务器或浏览器内部处理。 体验地址&#xff1a;点击直达 一、为什么会开发这个工具 综合性和多功能性&#xff1a;…

MySQL数据库 索引、事务、储存引擎

索引 索引的概念 索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过指针指向数据记录的内存地址&#xff09;。 使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索引表找…

harbor升级后镜像项目访问无权限问题

一、问题背景 将环境中现运行的harbor版本升级到2.6.2版本&#xff0c;相关同事升级完&#xff0c;发现有部分镜像项目点进去报无权限问题&#xff0c;镜像项目无法使用&#xff0c;但是也有部分项目是可以正常提供使用的。 二、问题处理过程 1、根据报错反馈没权限&#xff…

【Flutter】Flutter 使用 device_info_plus 获取设备的制造商、型号等信息

【Flutter】Flutter 使用 device_info_plus 获取设备的制造商、型号等信息 文章目录 一、前言二、安装和基本使用三、实际业务中的用法四、完整示例五、总结 一、前言 在这篇博客中&#xff0c;我将为你介绍一个非常实用的 Flutter 插件&#xff1a;device_info_plus。这个插件…

基于SSM+vue框架的校园代购服务订单管理系统源码和论文

基于SSMvue框架的校园代购服务订单管理系统源码和论文070 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 在新发展的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&am…

调查问卷平台哪家好?

在如今的数字化时代&#xff0c;问卷调查已成为企业和组织了解顾客需求、员工满意度以及市场趋势的重要工具。然而&#xff0c;在众多的在线调查工具中&#xff0c;为什么我们要选择Zoho Survey&#xff1f; 一、强大的功能和灵活的问卷设计 1、多种问卷题型&#xff1a; Zo…

浪潮信息 KeyarchOS 助力 IT 企业安全管理业务完成 CentOS 迁移替换 | 龙蜥案例

前言 安全稳定是操作系统永恒的追求&#xff0c;某知名 IT 企业的安全管理业务服务于公司多个业务&#xff0c;涵盖代码安全管理平台、数字签名平台以及内部使用的多个研发平台&#xff0c;其底层运行着 CentOS 7 操作系统。随着 CentOS 各操作系统版本陆续停服&#xff0c;将…

掌握指针和数组:经典笔试题攻略(万字详解)

&#x1f341;博客主页&#xff1a;江池俊的博客 &#x1f4ab;收录专栏&#xff1a;C语言刷题专栏 &#x1f4a1;代码仓库&#xff1a;江池俊的代码仓库 &#x1f3aa;我的社区&#xff1a;GeekHub &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文章目录 前…