惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(六) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) rCore-Tutorial-Book-v3学习笔记(三) rCore-Tutorial-Book-v3学习笔记(二) rCore-Tutorial-Book-v3学习笔记(一) 游戏玩后感:RewritePlus MIT-6.S081-2020实验(xv6-riscv64)十一:net MIT-6.S081-2020实验(xv6-riscv64)十:mmap MIT-6.S081-2020实验(xv6-riscv64)九:fs MIT-6.S081-2020实验(xv6-riscv64)八:lock MIT-6.S081-2020实验(xv6-riscv64)六:cow MIT-6.S081-2020实验(xv6-riscv64)五:lazy MIT-6.S081-2020实验(xv6-riscv64)四:traps MIT-6.S081-2020实验(xv6-riscv64)三:pgtbl MIT-6.S081-2020实验(xv6-riscv64)二:syscall 动漫观后感:吹响吧上低音号 MIT-6.S081-2020实验(xv6-riscv64)一:util 快速生成网络mp4视频缩略图技术 用plantuml画图示例 QQ缩略图和大图不同实现 Python制作字符图片 动漫观后感:命运石之门 Unity3D+Post Processing Stack V2自定义后处理效果研究
MIT-6.S081-2020实验(xv6-riscv64)七:thread
VnYzm · 2021-01-07 · via

实验文档

概述

这次实验主要涉及多线程编程,和之前的实验不太一样,比较偏向于应用层面,除了任务一外都是在宿主机上编写多线程程序,应该是xv6不支持系统级的多线程。

内容

Uthread: switching between threads

这个任务要求对一个程序填空,这个程序在用户层面实现了多线程的调度。但实际上这个调度和xv6内核的进程调度非常相似。

首先是对thread_schedule函数进行填空,只需要调用切换函数就行了,根据文档的说明,这个切换函数是保存当前线程用到的所有callee-saved寄存器,然后将这些寄存器全部赋值为下一个线程里保存的值,联想到xv6内核里的context结构体就是用来保存这些寄存器的,所以直接复制过来并作为thread结构体的一个属性,那么调用切换函数就会像这样:

thread_switch((uint64)(&t->context), (uint64)(&current_thread->context));

thread_switch()在uthread_switch.S里实现,这里我不贴代码了,因为和kernel文件夹里的swtch.S里的内容基本一样。

注意文档里的这句话:

One goal is ensure that when thread_schedule() runs a given thread for the first time, the thread executes the function passed to thread_create(), on its own stack.

这里不是说在thread_schedule()中需要检查线程是否第一次运行,如果是则转入对应函数;而是说thread_schedule()在运行刚才加入的切换代码后,自动开始运行对应函数。这就需要在thread_create初始化线程的时候,把它保存的ra寄存器的值赋成对应函数的入口地址,这样在切换结束后运行汇编代码中的ret就自动跳转到ra指向的位置了;另一个需要初始化的是sp寄存器,因为每个线程都各自有一个栈,所以要让自己的sp寄存器指向自己的栈底,由于栈地址从高向低增长,所以sp寄存器赋为栈数组的最高地址:

  t->context.ra = (uint64)func;
  t->context.sp = (uint64)t->stack + STACK_SIZE;

Using threads

这个任务要求解决一个程序中的竞争问题。通常两个线程的竞争出现在同时写的过程中,所以把put函数用锁包起来,同时为了速度,一个桶弄一个锁:

  int i = key % NBUCKET;

  // is the key already present?
  pthread_mutex_lock(lock + i);
  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]);
  }
  pthread_mutex_unlock(lock + i);

其实我个人觉得只有insert那个地方需要加锁,因为两个线程同时修改一个键的过程不管加不加锁都是一个结果无法预测的行为(先后不明),所以程序肯定不会同时修改一个键。不过既然时限给的是1.25倍,文档的意思应该是整个函数都得加锁。

Barrier

这个任务要求解决一个程序中的同步问题。核心是理解pthread_cond_wait这个函数的功能,我没理解好文档中的注释,结果遇到了各种奇怪的问题。pthread_cond_wait这个函数在调用时会释放锁,隐含的意思就是在执行这个函数前必须先锁上;函数在阻塞结束被唤醒时会获取锁,隐含的意思就是在这个函数调用结束后需要释放锁:

    pthread_mutex_lock(&bstate.barrier_mutex);
    bstate.nthread++;
    if (bstate.nthread == nthread) {
        bstate.nthread = 0; bstate.round++;
        pthread_cond_broadcast(&bstate.barrier_cond);
    } else pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex);
    pthread_mutex_unlock(&bstate.barrier_mutex);

另外一点就是pthread_mutex_lock的调用必须放在前面,如果只在else后面调用的话,可能会出现死锁问题,即如果第一个线程加了nthread以后运行得比较慢,还没有运行cond_wait的时候另一个线程跑得快,把cond_broadcast给运行了,那么第一个线程运行到cond_wait就卡死了,没有线程去唤醒它,所以这整个if语句必须是个原子操作,不能并行。


总结一下,这次实验基本没怎么涉及操作系统的理论,重点还是并行程序的应用。不过不管是国内还是国外,操作系统课在进程(线程)这一章节教的也基本上都是竞争、同步、死锁这些应用上的问题,可能进程(线程)的独特之处就在这里,虽然是操作系统中的重点部分,但最值得关注的还是应用上的问题。