首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

[rCore学习笔记 023]任务切换

编程知识
2024年08月08日 11:04

导读

还是要先看官方手册.

学过DMA的同志可能比较好理解,一句话, 释放CPU总线 :
如果把应用程序执行的整个过程进行进一步分析,可以看到,当程序访问 I/O 外设或睡眠时,其实是不需要占用处理器的,于是我们可以把应用程序在不同时间段的执行过程分为两类,占用处理器执行有效任务的计算阶段和不必占用处理器的等待阶段。这些阶段就形成了一个我们熟悉的“暂停-继续…”组合的控制流或执行历史。从应用程序开始执行到结束的整个控制流就是应用程序的整个执行过程。
本节的重点是操作系统的核心机制—— 任务切换 ,在内核中这种机制是在 __switch 函数中实现的。 任务切换支持的场景是:一个应用在运行途中便会主动或被动交出 CPU 的使用权,此时它只能暂停执行,等到内核重新给它分配处理器资源之后才能恢复并继续执行。

任务概念的形成

这里直接看官方手册.

这里主要是提到了一些概念,我把它们摘抄出来:

  1. 执行片段称为 “ 计算任务片 ”
  2. 空闲片段称为“ 空闲任务片 ”
  3. 需要保存与恢复的资源称为“ 任务上下文 ”

不同类型的上下文与切换

这部分之前第二章复习第一章的知识的时候我们就重复过关于第一章的 函数调用栈 和第二章的 内核栈/用户栈 的类比和区别.

这里直接看官方手册回顾一下就行,应该是为 任务切换 打基础.

在我的脑海里,任务切换是一个直接用sp指针进行操作的过程,按照我们上一章学到的知识,只需要在切换之前把 上下文 保存到用户栈就行.可能会增加的功能:

  1. 给用户栈增加入栈功能
  2. 增加切换APP的函数,可能需要调用汇编代码,有点类似于__restore,但是不需要触发Trap.

任务切换的设计与实现

官方手册中提到的异同和我自己脑子里总结的异同还是非常不同的:

  • 与 Trap 切换不同,它不涉及特权级切换;
  • 与 Trap 切换不同,它的一部分是由 编译器帮忙 完成的;
  • 与 Trap 切换相同,它对应用是 透明 的。

这个 编译器帮忙对应用透明 是需要在后续学习过程中注意的.

任务切换的流程:

  1. 某个应用Trap到 S模式 的操作系统内核中.
  2. Trap控制流调用__switch.
  3. Trap 控制流 A 会先被暂停并被切换出去.
  4. CPU 转而运行另一个应用在内核中的 Trap 控制流 B
  5. 然后在某个合适的时机,原 Trap 控制流 A 才会从某一条 Trap 控制流 C (很有可能不是它之前切换到的 B )切换回来继续执行并最终返回

问题:既然不需要特权级切换,那它为什么还要进入Trap呢?是怎么进行的Trap吗?还是通过ecall吗?

从实现的角度讲, __switch 函数和一个普通的函数之间的核心差别仅仅是它会 换栈 。

说起栈的上下文切换,我们不得不想到上一章我们保存的包含CSRX0~X31的上下文,那么同样地,在 任务切换 的过程中也有任务的上下文:

认真看这个图,左侧写得是 运行 状态的一个任务,它的内核栈里保存了两部分的东西:

  1. 上一章我们学到的TrapContext
  2. 那么当Trap之后把sp指针指向内核栈,函数调用的一些上下文也会保存在内核里,除了TrapContext内核栈里还保存着TrapHandler函数的 调用栈信息 .

右侧写得是 准备 状态的一个任务(可以看到一个细节 sp 寄存器 没有指向 这个栈).

为了保证sp重新指向右侧的内核栈的时候能够 恢复现场 , 因此一定有一些东西是需要保存的,那么它就是任务上下文.

这里定义 任务上下文 : CPU 当前的某些寄存器.

可以看到左侧和右侧的图的下面都有一个TASK_MANAGER,它是一个类似于我们上一章实现的APP_MANAGER的东西,是一个结构体TaskManager的一个 全局实例 .

可以看到它保存了sp,ra,s0~s11等寄存器. 为什么 这些寄存器要保存才能 保证 任务能够继续运行,是我们接下去学习的重点.

对于TaskManager的具体实现官方手册提供了思路和细节,

  1. 实现一个TaskControlBlock结构体,用于储存任务上下文TaskContext.
  2. TaskManager实现一个TaskControlBlock数组,用于储存多个上下文.

对于当前正在执行的任务的 Trap 控制流,我们用一个名为 current_task_cx_ptr 的变量来保存放置当前任务上下文的地址;而用 next_task_cx_ptr 的变量来保存放置下一个要执行任务的上下文的地址.

这里直接看示意图,可以看到实现了一个以 current_task_cx_ptr 和 next_task_cx_ptr 为参数的swtich函数用以切换上下文.

这里也说明了一件事,就是控制流本身在进行切换之前就可以感知到:

  1. 当前执行的是哪个任务
  2. 接下去要执行的是哪个任务

官方手册为我们描述了任务切换的四个阶段:

  • 阶段 [1]:在 Trap 控制流 A 调用 __switch 之前,A 的内核栈上只有 Trap 上下文和 Trap 处理函数的调用栈信息,而 B 是之前被切换出去的;
  • 阶段 [2]:A 在 A 任务上下文空间在里面保存 CPU 当前的寄存器快照;
  • 阶段 [3]:这一步极为关键,读取 next_task_cx_ptr 指向的 B 任务上下文,根据 B 任务上下文保存的内容来恢复 ra 寄存器、s0~s11 寄存器以及 sp 寄存器。只有这一步做完后, __switch 才能做到一个函数跨两条控制流执行,即 通过换栈也就实现了控制流的切换 。
  • 阶段 [4]:上一步寄存器恢复完成后,可以看到通过恢复 sp 寄存器换到了任务 B 的内核栈上,进而实现了控制流的切换。这就是为什么 __switch 能做到一个函数跨两条控制流执行。此后,当 CPU 执行 ret 汇编伪指令完成 __switch 函数返回后,任务 B 可以从调用 __switch 的位置继续向下执行。

这里我们可以直接看__switch的具体实现:

# os/src/task/switch.S

.altmacro
.macro SAVE_SN n
    sd s\n, (\n+2)*8(a0)
.endm
.macro LOAD_SN n
    ld s\n, (\n+2)*8(a1)
.endm
    .section .text
    .globl __switch
__switch:
    # 阶段 [1]
    # __switch(
    #     current_task_cx_ptr: *mut TaskContext,
    #     next_task_cx_ptr: *const TaskContext
    # )
    # 阶段 [2]
    # save kernel stack of current task
    sd sp, 8(a0)
    # save ra & s0~s11 of current execution
    sd ra, 0(a0)
    .set n, 0
    .rept 12
        SAVE_SN %n
        .set n, n + 1
    .endr
    # 阶段 [3]
    # restore ra & s0~s11 of next execution
    ld ra, 0(a1)
    .set n, 0
    .rept 12
        LOAD_SN %n
        .set n, n + 1
    .endr
    # restore kernel stack of next task
    ld sp, 8(a1)
    # 阶段 [4]
    ret

这里应该没什么看不懂的部分,我画了一张图来表述TaskContext的内存情况:

对应rust的代码:

// os/src/task/context.rs

pub struct TaskContext {
    ra: usize,
    sp: usize,
    s: [usize; 12],
}

这里提一下:

  1. 在RISC-V架构中,ra寄存器(Return Address Register)是一个特殊的通用寄存器,编号为x1。这个寄存器主要用于存储返回地址,即函数调用之后应该返回的指令地址。当一个函数被调用时,调用者(caller)通常会将返回地址存入 ra寄存器 ,以便在函数执行完毕后能够正确返回到调用点。__swtich执行结束后使用ret返回到ra的位置,我们修改了ra为下一个要执行的任务上下文的ra自然会继续执行到执行的任务上次保存上下文时调用__swtich的位置 .
  2. Rust/C 编译器会在函数的起始位置自动生成代码来保存 s0~s11 这些被调用者保存的寄存器。但 __switch 是一个用汇编代码写的特殊函数,它不会被 Rust/C 编译器处理,所以我们需要在 __switch 中手动编写保存 s0~s11 的汇编代码.
  3. 不用保存其它寄存器是因为:其它寄存器中,属于调用者保存的寄存器是由编译器在 高级语言 编写的调用函数中 自动生成 的代码来完成保存的;还有一些寄存器属于临时寄存器,不需要保存和恢复。

对应 第三点 ,我们应该理解,要使用Rust调用才能使得编译器自动帮我们 保存/恢复调用者保存寄存器 :

// os/src/task/switch.rs

global_asm!(include_str!("switch.S"));

use super::TaskContext;

extern "C" {
    pub fn __switch(
        current_task_cx_ptr: *mut TaskContext,
        next_task_cx_ptr: *const TaskContext
    );
}
From:https://www.cnblogs.com/chenhan-winddevil/p/18348681
本文地址: http://www.shuzixingkong.net/article/908
0评论
提交 加载更多评论
其他文章 双指针优化
双指针优化 为什么我为OI泪目?因为我菜得离谱...... 引入 双指针是一种简单而又灵活的技巧和思想,单独使用可以轻松解决一些特定问题,和其他算法结合也能发挥多样的用处。 双指针顾名思义,就是同时使用两个指针,在序列、链表结构上指向的是位置,在树、图结构中指向的是节点,通过或同向移动,或相向移动来
双指针优化 双指针优化
总有坏人想爬我网站的数据,看我用这 10 招干他!
大家好,我是程序员鱼皮。前两天模拟面试一位社招两年的老哥,由于他的表现不错,我就临时起意,跟他交流一下我们最近遇到的业务场景问题。问题如下: 最近我们不是做了个 程序员刷题网站 - 面试鸭 嘛,有很多坏人盯上了我们网站,想把我们 4,000 多道面试题、100 多个面试题库的数据都用爬虫抓下来。那我
总有坏人想爬我网站的数据,看我用这 10 招干他! 总有坏人想爬我网站的数据,看我用这 10 招干他! 总有坏人想爬我网站的数据,看我用这 10 招干他!
零基础学习人工智能—Python—Pytorch学习(二)
前言 数学的学习跟数学的计算是要分开的,现在回头再去看大学的高数和线性代数,如果只是学习的话,其实一门课程3天,也就学完了。 学校的课程之所以上那么久,其实是为了考试,也就是为计算准备的。计算有意义的,在有计算机的情况下,计算的意义并不是很大。 所以,如果大学数学没学好,只要花一星期,就能补回来。甚
零基础学习人工智能—Python—Pytorch学习(二) 零基础学习人工智能—Python—Pytorch学习(二) 零基础学习人工智能—Python—Pytorch学习(二)
记一次 .NET某智慧出行系统 CPU爆高分析
一:背景 1. 讲故事 前些天有位朋友找到我,说他们的系统出现了CPU 100%的情况,让我帮忙看一下怎么回事?dump也拿到了,本想着这种情况让他多抓几个,既然有了就拿现有的分析吧。 二:WinDbg 分析 1. 为什么会爆高 既然说是 100%,作为调试者得拿数据说话,可以使用 !tp 来观测一
记一次 .NET某智慧出行系统 CPU爆高分析 记一次 .NET某智慧出行系统 CPU爆高分析 记一次 .NET某智慧出行系统 CPU爆高分析
前端使用 Konva 实现可视化设计器(20)- 性能优化、UI 美化
这一章主要分享一下使用 Konva 遇到的性能优化问题,并且介绍一下 UI 美化的思路,主要使用 Naive UI。
前端使用 Konva 实现可视化设计器(20)- 性能优化、UI 美化 前端使用 Konva 实现可视化设计器(20)- 性能优化、UI 美化
前后端分离项目,后期前端身份验证的麻烦
软件构成 后端 后端是一个asp.netcore webapi项目,使用jwt进行身份验证和鉴权。 前端 前端是一个基于http协议的asp.netcore RezorPage项目,但实际上完全使用的wwwwroot目录下的静态文件。没有使用RazorPage。 目前只有后端接口鉴权,前端页面任意访
前后端分离项目,后期前端身份验证的麻烦 前后端分离项目,后期前端身份验证的麻烦
vue前端自适应布局,一步到位所有自适应
1,左右布局 - 左侧固定宽带,右侧自适应剩余的宽度。 - 中间一条分割线,可以拖拉,自适应调整左右侧的宽度。 - 左侧的高度超长自动出现横向滚动条,左侧宽度超长,自动出现竖向滚动条。 2,上中下布局 - 最上面的 搜索条件 div 固定占用 100 px 高度,下面的 查询条件 div 固定
vue前端自适应布局,一步到位所有自适应 vue前端自适应布局,一步到位所有自适应
Codeforces Round 964 (Div. 4)
Codeforces Round 964 (Div. 4) A送分 B 大意:两个人两张牌 随机翻 求a翻出来的牌比b大的可能 #include <cstdio> #include <cmath> #include <algorithm> #include &lt