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

推荐订阅源

让小产品的独立变现更简单 - 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学习笔记(一) 游戏玩后感: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)七:thread 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 动漫观后感:吹响吧上低音号 使用OpenCV对图片进行特征点检测和匹配 git基本操作
rCore-Tutorial-Book-v3学习笔记(三)
VnYzm · 2021-02-26 · via

概述

第三个部分是实现一个分时多任务的系统,也就是能够在多个任务运行期间进行切换,让一个程序在等待IO时其他程序能执行而不是傻等。虽然和并行有点类似,但目前使用到的都只有一个CPU,任何时候都只有一个程序在执行。分时多任务有两种:一种是程序自己主动让出控制权,有点类似于编程语言中协程的概念;一种是由操作系统控制每个程序只能运行一个时间片,到时间了自动切换控制权,有点类似于Python中带了GIL锁的线程。由于第二种是在第一种的基础上建立的,所以这里主要展现的第二种。

内容

和上次相比:

  • hello_world.c修改成了hello_world0.chello_world1.chello_world2.c三个文件,以hello_world0.c为例:

    #include "user.h"
    
    int main() {
        char *s = "Program[0] print0\n";
        isize w;
        for (int i = 0; i < 10; i++) {
            s[16] = (i % 10) + '0';
            write(FD_STDOUT, s);
            w = get_time() + 10;
            while (get_time() < w); // yield();
        }
        return 0;
    }

    每个程序都会输出自己的编号,相隔两次循环会阻塞自己一段时间,hello_world1.c阻塞的时间是20ms,hello_world2.c阻塞的时间是30ms。另外,因为程序比较少,所以没有像教程一样写一个用来编译的Python脚本,而是把链接脚本也分成三个,每个的起始地址不一样,makefile中分别用不同的链接脚本编译这三个文件。

  • 把原来batch.c的一部分代码放在loader.c里,负责加载用户程序和初始化;新添加了管理任务的task.c,管理计时的timer.c和控制任务切换的汇编程序switch.S

注意点不是很多,首先是几个硬编码的部分,如SIE寄存器的初始化,第五位为1开启内核态时钟中断:

    usize sie; asm volatile("csrr %0, sie":"=r"(sie));
    sie |= (1 << 5); asm volatile("csrw sie, %0"::"r"(sie));

读取时间寄存器,有一个专门的指令rdtime:

usize get_time() {
    usize timer; asm volatile("rdtime %0":"=r"(timer)); return timer;
}

scause寄存器的第63位(从0开始)为0表示当前异常是陷入,1表示当前异常是中断(用户态的系统调用属于陷入),后面的0-62位表示陷入或者中断的具体原因。对于中断,低位为5表示内核态时钟中断:

    if (scause == (1L << 63) + 5) {
        set_next_trigger();
        suspend_current_and_run_next();
        return cx;
    }

接着,为了更好的理解切换的过程,还是需要研究一下这个过程中sp寄存器的变化。同样,在程序最开始的时候,sp指向bootstack里,注意在本次任务中,每个用户程序各自拥有一个属于自己的用户栈和内核栈,所以初始化的时候init_app_cx就是给每个程序的内核栈压上TrapContext和TaskContext。

然后进入run_first_task,进入__switch,第二句是sd sp 0(a0),这里有一点挺坑的,就是传给__switch的参数是一个“指针的指针”,就是说,TaskControlBlock里的task_cx_ptr属性存储的是指向TaskContext的指针,或者说是栈顶地址,而传入__switch的参数是指向这个属性的指针。

那么在上面这句汇编代码中,0(a0)解引用,就是把sp(或者说是前一个任务的栈顶地址)保存在这个属性里了,最后把0(a1)传给sp,就是跳转到另一个任务的栈里,对于第一次调用而言,就是在这里从bootstack跳到了第一个任务的内核栈,切换任务之后,sp指向了新任务的TrapContext,由于ra现在指向__restore,所以程序就会再走一遍__restore的流程,进入用户态。

另外,由于第一次调用__restore前sp就已经指向内核栈了,所以原来__restore的第一句mv sp, a0就应该去掉了。在上一部分还提到过一个内核栈重置的问题,当时是在运行完一个程序之后,要压入一个自定义的TrapContext,为了节省空间,就直接放到栈底了,而现在由于每个程序一个栈,在一个程序运行结束之后,这个栈就没有用了,同时task_cx_ptr和scratch又分别保存了当前程序的TaskContext和TrapContext地址,修改都是在对应位置,所以没有“重置栈”这一过程。

最后分析一下输出结果(设定每个程序运行的时间片是10ms,这里IO的时间和时间片相比基本可以忽略不计):

Program[0] print0
Program[1] print0
Program[2] print0
Program[0] print1
Program[0] print2
Program[1] print1
Program[2] print1
Program[0] print3
Program[0] print4
Program[1] print2
Program[2] print2
Program[0] print5
Program[1] print3
Program[2] print3
Program[0] print6
Program[1] print4
Program[2] print4
Program[0] print7
Program[1] print5
Program[2] print5
Program[0] print8
Program[1] print6
Program[2] print6
Program[0] print9
Application exited with code 0
Program[2] print7
Program[1] print7
Program[1] print8
Program[2] print8
Program[1] print9
Program[2] print9
Application exited with code 0
Application exited with code 0

根据输出,画出的前120ms的程序运行情况如图所示:

绿色表示当前占有CPU的程序,箭头表示输出。可以发现不管各个程序休眠多长时间,都是轮流使用CPU,到点即换人。不过值得注意的是在输出结果中出现了程序0连续输出两行的情况,对应上图第40ms和第70ms的地方。而在第100ms则没有出现这一情况。这种情况是否发生取决于程序0休眠10ms后被唤醒准备进入内核态调用输出函数和10ms的时间片到达发生时钟中断这两个事件谁先谁后:

  • 如果前者先,那么程序0进入内核态后时钟中断就不能发生了,必须等输出之后回到用户态才会发生中断,这样结合休眠前的输出,就会连着输出第二次;

  • 如果后者先,那么在输出前控制权就被让出去了,必须等程序2和程序3都输出过后才能轮到程序0输出,这样就不会连着输出两次。

再比如,如果程序0在输出之后,获取当前时间之前让出了控制权,程序1和程序2输出后,程序0才获取时间,休眠10ms然后控制权又被程序1和程序2抢走了,那么就会出现程序0一时半会输出不了的情况。所以说即使是全程只有一个程序在运行,在任务切换的时候仍然可能无法预测的竞争问题,这里也是各种程序Bug的重灾区。

还有一个地方,就是我的输出没有出现教程那样混乱的情况,因为我打印字符串是直接将整个字符串提交到内核态,而一旦进入内核态就不会再收到时钟中断了,所以字符串都是完整输出。