- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
本随笔是非常菜的菜鸡写的。如有问题请及时提出.
可以联系:1160712160@qq.com 。
GitHhub:https://github.com/WindDevil (目前啥也没有 。
本章最开始的时候讲解了有类似于多道程序与协作式调度的区别. 。
回想上一节,我们提到的,如果我们仍然是不使用上一节实现的yeild,仍然和上上节(多道程序加载)的实现效果是一样的. 。
因为如果我们不主动释放CPU,任务仍然是顺序执行的. 。
那么并不是所有的程序员都会在写程序的时候考虑到别人.比如写单片机的代码,我使用IIC通信,我就嗯等ACK信号,我宁愿while()也不愿意放弃CPU给你. 。
这时候使用抢占式调度就非常有必要了. 。
官方文档有云:我们需要对 任务 的概念进行进一步扩展和延伸.
文档中还有一些概念
现代的任务调度算法基本都是抢占式的,它要求每个应用只能连续执行一段时间,然后内核就会将它强制性切换出去。一般将 时间片 (Time Slice) 作为应用连续执行时长的度量单位,每个时间片可能在毫秒量级.
调度算法需要考虑:每次在换出之前给一个应用多少时间片去执行,以及要换入哪个应用。可以从性能(主要是吞吐量和延迟两个指标)和 公平性 (Fairness) 两个维度来评价调度算法,后者要求多个应用分到的时间片占比不应差距过大.
这里插一句,这时候往往就想到写RT-Thread的时候给每个应用添加一个500ms时的纠结.(当然现在想到RT-Thread似乎应该有更先进的调度算法,我心里悬着的石头就放下了). 。
这时候就提到 调度算法 的重要性了(怪不得这个专门有一个岗位). 。
在这个项目中,使用的调度算法就是时间片轮转算法 (RR, Round-Robin) . 本章中我们仅需要最原始的 RR 算法,用文字描述的话就是维护一个任务队列,每次从队头取出一个应用执行一个时间片,然后把它丢到队尾,再继续从队头取出一个应用,以此类推直到所有的应用执行完毕.
时间片我们在本章开始的时候猜想它是用定时器加中断实现的,目前看来也确实如此. 。
官方文档对中断的解析是这样的,很深刻,尤其是提到了同步和异步的概念: 在 RISC-V 架构语境下, 中断 (Interrupt) 和我们第二章中介绍的异常(包括程序错误导致或执行 Trap 类指令如用于系统调用的 ecall )一样都是一种 Trap ,但是它们被触发的原因却是不同的。对于某个处理器核而言, 异常与当前 CPU 的指令执行是 同步 (Synchronous) 的,异常被触发的原因一定能够追溯到某条指令的执行;而中断则 异步 (Asynchronous) 于当前正在进行的指令,也就是说中断来自于哪个外设以及中断如何触发完全与处理器正在执行的当前指令无关.
关于中断,这里提到一个关键点, 检查中断是在每次处理器执行完指令之后 的: 对于中断,可以理解为发起中断的是一套与处理器执行指令无关的电路(从时钟中断来看就是简单的计数和比较器),这套电路仅通过一根导线接入处理器。当外设想要触发中断的时候则输入一个高电平或正边沿,处理器会在每执行完一条指令之后检查一下这根线,看情况决定是继续执行接下来的指令还是进入中断处理流程。也就是说,大多数情况下,指令执行的相关硬件单元和可能发起中断的电路是完全独立 并行 (Parallel) 运行的,它们中间只有一根导线相连.
有关于RISC-V的中断类型和优先级规则,还是要仔细阅读官方文档. 。
这里只提出其中比较重点的一个规则,就是进入中断之后,会 屏蔽低于和等于该优先级的其他中断 . 。
这里提到了 RISC-V架构 的计时机制,要求它一定要有一个内置时钟. 。
这里就让我们想起了之前学到过的Cortex-M3内置的systick.还有很少人注意到的内置在Cortex-M3中的DWT模块. 。
(这里也可以去做 第一章作业 里 第三题 那个延时程序了,有没有记得我们之前去访问sepc寄存器的时候使用的riscv::register,现在我们可以直接调用它来实现了) 。
在 RISC-V 64 架构中,这个计时器的具体实现是mtime寄存器. 。
这个寄存器的不能在S特权级直接访问,回想我们之前学过的OS架构层次
创建os/src/timer.rs文件. 。
只需要在M特权级的上层( SBI )构建一个接口就行了. RustSBI 恰恰帮我们实现了这样的一个接口,因此我们可以这样实现
// os/src/timer.rs
use riscv::register::time;
pub fn get_time() -> usize {
time::read()
}
为了实现 定时一段时间 强行切换应用的效果,我们需要实现一个 定时中断 的特性. 。
RustSBI 可以实现这个功能,在os/src/sbi.rs里使用sbi_call实现这个功能,即设置一个计时器,实际原理是设置mtimecmp模块的值,等待mtime计时达到之后 触发中断
// os/src/sbi.rs
pub fn set_timer(time: usize)
{
sbi_rt::set_timer(time as _);
}
这里 注意 ,手册中写的代码是 旧版 ,在按照第0章配置的基础上sbi-rt的版本是0.02.应该使用 如上 代码. 。
这时候在os/src/timer.rs里进行封装.计算计时器在10ms内的增量,然后把当前mtime的值和增量加起来设置到mtimecmp中
// os/src/timer.rs
use crate::config::CLOCK_FREQ;
const TICKS_PER_SEC:usize = 100;
pub fn set_next_trigger()
{
set_timer(get_time()+CLOCK_FREQ/TICKS_PER_SEC);
}
这里注意CLOCK_FREQ是写在os/src/config.rs里的常量,这里直接参考源码即可,这里可以看到K210的时钟频率和QEMU默认的时钟频率
/*
#[cfg(feature = "board_k210")]
pub const CLOCK_FREQ: usize = 403000000 / 62;
#[cfg(feature = "board_qemu")]
pub const CLOCK_FREQ: usize = 12500000;
*/
pub use crate::board::CLOCK_FREQ;
这个board模块需要在src/board/qemu.rs里实现. 。
//! Constants used in rCore for qemu
pub const CLOCK_FREQ: usize = 12500000;
最后board模块和timer模块也需在os/src/main.rs里声明. 。
...
mod timer;
#[path = "boards/qemu.rs"]
mod board;
...
#[path = "boards/qemu.rs"] 是一个属性宏 (attribute macro),用来告诉 Rust 编译器该模块的实际源文件位置是在 boards/qemu.rs 这个文件中. 。
还需要实现一个 获取当前定时器时间 的函数.可以 用来 统计应用运行时长. 。
// os/src/timer.rs
const MICRO_PER_SEC: usize = 1_000_000;
pub fn get_time_us() -> usize {
time::read() / (CLOCK_FREQ / MICRO_PER_SEC)
}
这里官方文档说要实现一个 系统调用 ,用于应用获取当前时间.那么我们已经有了get_time_us函数了. 。
根据上一节的经验,我们只需要分别在 用户层 和 内核层 写实现即可. 。
首先是在用户层实现
// user/src/syscall.rs
const SYSCALL_GET_TIME: usize = 169;
pub fn sys_get_time() -> isize {
syscall(SYSCALL_GET_TIME, [0, 0, 0])
}
进一步封装
// user/src/lib.rs
pub fn get_time() -> isize {
sys_get_time()
}
然后需要在内核层实现,这里是回调
// os/src/syscall/mod.rs
...
const SYSCALL_GET_TIME: usize = 169;
/// handle syscall exception with `syscall_id` and other arguments
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
match syscall_id {
SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]),
SYSCALL_EXIT => sys_exit(args[0] as i32),
SYSCALL_YIELD => sys_yield(),
SYSCALL_GET_TIME => sys_get_time(),
_ => panic!("Unsupported syscall_id: {}", syscall_id),
}
}
...
这里还需要具体实现sys_get_time
// os/src/syscall/process.rs
use crate::timer::get_time_us;
pub fn sys_get_time() -> isize {
get_time_us() as isize
}
那么我们也不能直接返回当前mtime的值,而是需要更换单位,因此get_time_us的实现也显而易见,根据 系统时钟频率 计算出当前的 微秒级 时间
// os/src/timer.rs
const MICRO_PER_SEC: usize = 1_000_000;
pub fn get_time_us() -> usize
{
time::read()/(CLOCK_FREQ/MICRO_PER_SEC)
}
这里要回忆起 中断也是一种trap . 。
那么要实现抢占式调度就很简单了.只需要在发生 定时器中断 之后 继续设置一个定时器,然后执行suspend_current_and_run_next, 挂起当前应用并且执行下一个应用. 。
那么只需要修改trap_handler函数,加入相应的处理逻辑. 。
// os/src/trap/mod.rs
match scause.cause() {
Trap::Interrupt(Interrupt::SupervisorTimer) => {
set_next_trigger();
suspend_current_and_run_next();
}
}
那么第一个时间片是哪里来的呢,答案是我们 自己设置一个定时器 timer::set_next_trigger();.为了避免S特权级时钟中断被屏蔽,我们需要 启动时钟中断的陷入 trap::enable_timer_interrupt();. 。
// os/src/main.rs
#[no_mangle]
pub fn rust_main() -> ! {
clear_bss();
println!("[kernel] Hello, world!");
trap::init();
loader::load_apps();
trap::enable_timer_interrupt();
timer::set_next_trigger()
task::run_first_task();
panic!("Unreachable in rust_main!");
}
trap::enable_timer_interrupt();实际是设置了sie寄存器的stie位,使得S特权级时钟中断不会被屏蔽. 。
// os/src/trap/mod.rs
/// timer interrupt enabled
pub fn enable_timer_interrupt() {
unsafe {
sie::set_stimer();
}
}
这里这个特殊的处理机制也需要关注,虽然以我的水平暂时发现不了这么高深的玩意: 有同学可能会注意到,我们并没有将应用初始 Trap 上下文中的 sstatus 中的 SPIE 位置为 1 。这将意味着 CPU 在用户态执行应用的时候 sstatus 的 SIE 为 0 ,根据定义来说,此时的 CPU 会屏蔽 S 态所有中断,自然也包括 S 特权级时钟中断。但是可以观察到我们的应用在用尽一个时间片之后能够正常被打断。这是因为当 CPU 在 U 态接收到一个 S 态时钟中断时会被抢占,这时无论 SIE 位是否被设置都会进入 Trap 处理流程.
这时候要注意 主动交出CPU 的机制仍然需要保留.比如这个应用
// user/src/bin/03sleep.rs
#[no_mangle]
fn main() -> i32 {
let current_timer = get_time();
let wait_for = current_timer + 3000;
while get_time() < wait_for {
yield_();
}
println!("Test sleep OK!");
0
}
这里如果不执行yield_()就需要 等定时器中断 需要10ms的时间片,这样就 浪费了时间 . 。
这里主要是对 分时多任务系统与抢占式调度 的测试应用没有实现. 。
我们只需在源码中找到具体实现就行了. 。
这里是一些解析和提点
// user\src\bin\00power_3.rs
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const LEN: usize = 100;
#[no_mangle]
fn main() -> i32 {
let p = 3u64;
let m = 998244353u64;
let iter: usize = 200000;
let mut s = [0u64; LEN];
let mut cur = 0usize;
s[cur] = 1;
for i in 1..=iter {
let next = if cur + 1 == LEN { 0 } else { cur + 1 };
s[next] = s[cur] * p % m;
cur = next;
if i % 10000 == 0 {
println!("power_3 [{}/{}]", i, iter);
}
}
println!("{}^{} = {}(MOD {})", p, iter, s[cur], m);
println!("Test power_3 OK!");
0
}
这个应用计算$p^{iter} \mod m$的值
s[cur]
乘以 p
并对 m
取模的结果,然后将这个结果存放在下一个位置 next
中。cur
为 next
,如果 cur
已经到达数组的末尾 (LEN
),则将 cur
设置为 0,实现数组的滚动使用。其余的两个app,user\src\bin\01power_5.rs和user\src\bin\02power_7.rs,只是更换了p的值.这里掠过. 。
对于最后一个应用就是我们上一节最后提到的应用user\src\bin\03sleep.rs.也不进行赘述. 。
这里具体把app都实现之后
cd user
make build
cd ../os
make run
最后得出运行结果
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
Test sleep OK!
[kernel] Application exited with code 0
All applications completed!
可以看到在sleep期间成功运行了三种求幂任务. 。
最后此篇关于[rCore学习笔记025]分时多任务系统与抢占式调度的文章就讲到这里了,如果你想了解更多关于[rCore学习笔记025]分时多任务系统与抢占式调度的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我刚刚更新了 Ruby,现在我在尝试启动 compass 时遇到以下错误: Encoding::CompatibilityError on line ["28"] of /usr/local/Cell
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 6 年前。
关闭。这个问题需要debugging details .它目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and th
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在尝试在我的 iOS 应用程序中开发可折叠/ Accordion 式的功能。这将是您可以在网站上找到的典型 FAQ 类型功能。我想点击标题,然后显示详细信息。 因为这是帮助部分,只有几个项目,我认
我正在尝试设计一个基于 REST 的 Web 服务来与我正在开发的农场动物管理系统进行交互。 为了详细说明问题,我收藏了动物 属于一个农场。每只动物都有自己的信息——例如姓名、身份证号、品种年龄等。因
我有 3 种不同的表单,其中复选框数量不同,每个部分基本上代表一个表单,因此当用户选择该部分中的复选框时,它会显示他们在该部分的总金额中 checkout 了多少 HTML
我有一份 32 页的 PDF 版家谱。与其将家谱全部放在一个非常大的 PDF 页面上(这是我想要的),不如将其格式化为一组 8 个单独的美国信纸大小的页面应该在整个宽度上缝合; 4 行这样就完成了树。
指SASS implementation for Java? : 在 Maven 目标编译包中自动编译 compass-style.org 样式表的最佳方法是什么? 我不想发送太多的自编译库,也不想通
鉴于以下 XAML... 我正在寻找一种绑定(bind) ComboBox、Button 和 Command 的方法,以便当 ComboBox 的值更改时,在 Command 上调用 CanExe
在玩具应用程序中,我有一个显示所有帖子标题的“帖子”模板。当您单击每个标题时,我不想直接进入“显示” View ,而是直接内联展开该帖子的其余内容。 我考虑过让 postRoute 重用 postsR
我需要一些使用 Twitter Bootstrap 或其他响应式框架的自定义 Swagger-UI 实现。需要在我的移动设备上使用这样的 UI 测试我的 API,但 swagger-ui 不能很好地扩
我正在做一个项目,我真的在尝试编写面向对象的 JavaScript 代码。我刚刚开始阅读Douglas Crockford's JavaScript: The Good Parts我很快开始意识到用
在 C# 中,我通过执行以下操作来加密文本数据(请注意我正在以 block ( block )的形式加密数据): public string EncryptData(string pu
我正在构建一个社交网站,该网站将向全世界公开 REST API (WCF WebAPI),以便任何开发人员都能够为该网站创建客户端应用程序、将其与其他服务集成等。 我想为 API 实现 Faceboo
我是一名优秀的程序员,十分优秀!