rCore-Tutorial-Book-v3 icon indicating copy to clipboard operation
rCore-Tutorial-Book-v3 copied to clipboard

rCore-Tutorial-Book-v3/chapter3/2task-switching

Open utterances-bot opened this issue 4 years ago • 37 comments

任务切换 — rCore-Tutorial-Book-v3 0.1 文档

https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter3/2task-switching.html

utterances-bot avatar Feb 20 '21 04:02 utterances-bot

有个不太理解的地方,TaskContext保存了s寄存器,而这些寄存器都保存在TrapContext里,__switch的时候恢复了TaskContext,之后又跳到__restore恢复了TrapContext,所以TaskContext保存的s寄存器实际没有起到作用吧

Spxg avatar Feb 20 '21 04:02 Spxg

有个不太理解的地方,TaskContext保存了s寄存器,而这些寄存器都保存在TrapContext里,__switch的时候恢复了TaskContext,之后又跳到__restore恢复了TrapContext,所以TaskContext保存的s寄存器实际没有起到作用吧

TrapContext中保存的寄存器记录了应用陷入S特权级之前的CPU状态,而TaskContext则可以看成一个应用在S特权级进行Trap处理的过程中调用__switch之前的CPU状态。当恢复TaskContext之后会继续进行Trap处理,而__restore恢复TrapContext之后则是会回到用户态执行应用。

另外,保存TrapContext之后进行Trap处理的时候,s0-s11寄存器可能会被覆盖,后面进行任务切换时这些寄存器会被保存到TaskContext中,也就是说这两个Context中的s0-s11也很可能是不同的。

wyfcyx avatar Feb 20 '21 05:02 wyfcyx

有个不太理解的地方,TaskContext保存了s寄存器,而这些寄存器都保存在TrapContext里,__switch的时候恢复了TaskContext,之后又跳到__restore恢复了TrapContext,所以TaskContext保存的s寄存器实际没有起到作用吧

TrapContext中保存的寄存器记录了应用陷入S特权级之前的CPU状态,而TaskContext则可以看成一个应用在S特权级进行Trap处理的过程中调用__switch之前的CPU状态。当恢复TaskContext之后会继续进行Trap处理,而__restore恢复TrapContext之后则是会回到用户态执行应用。

另外,保存TrapContext之后进行Trap处理的时候,s0-s11寄存器可能会被覆盖,后面进行任务切换时这些寄存器会被保存到TaskContext中,也就是说这两个Context中的s0-s11也很可能是不同的。

谢谢,了解了。看来我之前对ra和ret有所误解,还以为ret指令执行后,程序直接跳转到ra寄存器所存的地址

Spxg avatar Feb 21 '21 11:02 Spxg

为什么每个任务都有自己的内核栈,都有TrapContext ucos rt-thread这些都是每个任务有自己的栈,任务切换过程类似,但是它们是不是没有内核的概念,有点懵

zhjc2014 avatar Mar 04 '21 01:03 zhjc2014

而保存调用者保存的寄存器是因为,调用者保存的寄存器可以由编译器帮我们自动保存。

应该是“不保存调用者保存的寄存器”吧?

Gallium70 avatar Mar 09 '21 14:03 Gallium70

有个不太理解的地方,TaskContext保存了s寄存器,而这些寄存器都保存在TrapContext里,__switch的时候恢复了TaskContext,之后又跳到__restore恢复了TrapContext,所以TaskContext保存的s寄存器实际没有起到作用吧

TrapContext中保存的寄存器记录了应用陷入S特权级之前的CPU状态,而TaskContext则可以看成一个应用在S特权级进行Trap处理的过程中调用__switch之前的CPU状态。当恢复TaskContext之后会继续进行Trap处理,而__restore恢复TrapContext之后则是会回到用户态执行应用。 另外,保存TrapContext之后进行Trap处理的时候,s0-s11寄存器可能会被覆盖,后面进行任务切换时这些寄存器会被保存到TaskContext中,也就是说这两个Context中的s0-s11也很可能是不同的。

谢谢,了解了。看来我之前对ra和ret有所误解,还以为ret指令执行后,程序直接跳转到ra寄存器所存的地址

ret指令执行后,程序就是直接跳转到ra寄存器所存的地址的吧。rcore第一个task就是通过构造trap context模拟__alltraps完成的状态,然后将ra指向__restore,然后在switch.S中ret直接执行__restore,再通过__restore回到user space执行代码。

WellOptimized avatar Dec 27 '21 14:12 WellOptimized

有一点疑惑的地方:每次处理trap的时候从call trap_handler一直到__switch函数执行,这中间的调用链是有一些压栈操作的,但是当一个task exit之后,就再也不会__switch回来了,那么这个task对应的调用链里的栈空间就是无法再使用了是么? 不清楚是我的理解不对,还是目前的设计没有考虑到这一点,谢谢!

itewqq avatar Jan 16 '22 09:01 itewqq

@itewqq 你的理解是正确的。第二章为了简单起见,每个task的栈空间仅会被使用一次,对应的task退出之后就会永久闲置。等到第五章引入了进程之后,可以看到在进程退出之后,它的栈空间所在的物理内存会被回收并可以供其他进程使用。

wyfcyx avatar Jan 16 '22 09:01 wyfcyx

@czhWellOptimized ra在switch中被修改了,我觉得是trap-> switch taskA-> switch taskB -> restore过程

dzwduan avatar Mar 04 '22 07:03 dzwduan

在阶段 [1] 可以看到它的函数原型中的两个参数分别是当前 A 任务上下文指针 next_task_cx_ptr 和即将被切换到的 B 任务上下文指针 next_task_cx_ptr

第一处 next_task_cx_ptr,应该是current_task_cx_ptr

workerwork avatar Mar 08 '22 08:03 workerwork

为什么对比第二章的trap.S文件少了 mv sp, a0

Yui5427 avatar Mar 16 '22 11:03 Yui5427

请问一下这个的任务切换能保证一致性嘛?打个比方任务A对变量进行修改,然后发生切换,任务B也对变量进行修改,那最后恢复到任务A的时候,变量是不是也变了?

Shuimo03 avatar Apr 18 '22 12:04 Shuimo03

请问大家有遇到过这种问题么: error: linking with rust-lld failed: exit status: 1 rust-lld: error: section .text file range overlaps with .riscv.attributes >>> .text range is [0x1000, 0x325F] >>> .riscv.attributes range is [0x1000, 0x1034]

      rust-lld: error: section .symtab file range overlaps with .rodata
      >>> .symtab range is [0x1048, 0xF2AF]
      >>> .rodata range is [0x3260, 0x395A]

status-code-404 avatar May 29 '22 13:05 status-code-404

为什么对比第二章的trap.S文件少了 mv sp, a0

__restore在这里被两种情况复用了:

  1. 正常从__alltraps走下来的trap_handler流程。如果是这种情况,trap_handler会在a0里返回之前通过mv a0, sp传进去的&mut TrapContext,所以这里spa0相同没有必要再mv sp, a0重新设置一遍。
  2. app第一次被__switch的时候通过__restore开始运行。这时候a0是个无关的数据(指向上一个TaskContext的指针),这里再mv sp a0就不对了,而__restore要的TrapContext已经在__switch的恢复过程中被放在sp上了。(这个sp就是初始化时写完TrapContext后的内核栈顶)
        for (i, task) in tasks.iter_mut().enumerate() {
            task.task_cx = TaskContext::goto_restore(init_app_cx(i));
            task.task_status = TaskStatus::Ready;
        }
/// get app info with entry and sp and save `TrapContext` in kernel stack
pub fn init_app_cx(app_id: usize) -> usize {
    KERNEL_STACK[app_id].push_context(TrapContext::app_init_context(
        get_base_i(app_id),
        USER_STACK[app_id].get_sp(),
    ))
}
impl TaskContext {
    /// set task context {__restore ASM funciton, kernel stack, s_0..12 }
    pub fn goto_restore(kstack_ptr: usize) -> Self {
        extern "C" {
            fn __restore();
        }
        Self {
            ra: __restore as usize,
            sp: kstack_ptr,
            s: [0; 12],
        }
    }
}

whfuyn avatar Jul 19 '22 13:07 whfuyn

有没有人能解释一下为什么会有多个内核栈呀,不应该就一个吗

kidcats avatar Oct 08 '22 12:10 kidcats

有没有人能解释一下为什么会有多个内核栈呀,不应该就一个吗

这章改成了每个任务都有一个内核栈了。

一开始我也是想着怎么能优雅地在不同的任务间切换并安全地共享同一个内核栈,发现很困难。

每个任务一个内核栈是最简单直接的做法。

whfuyn avatar Oct 09 '22 07:10 whfuyn

@kidcats @whfuyn 哈哈,我来带货了,所有应用程序共享同一个内核栈的设计参考

YdrMaster avatar Oct 10 '22 06:10 YdrMaster

__switch 讲解非常精彩,通过寥寥几行汇编代码,把任务的上下文切换解释的透彻。

shzy2012 avatar Dec 15 '22 09:12 shzy2012

原文__switch实现描述中:“阶段 [2] 体现在第 19~27 行,即根据 B 任务上下文保存的内容来恢复 ra 寄存器、s0~s11 寄存器以及 sp 寄存器。” 这段文字描述好像是说的阶段 [3] 的内容,阶段 [2] 是根据 A 的任务上下文来“保存”这些寄存器。 ^_^

VisualMike-I avatar Mar 09 '23 11:03 VisualMike-I

@kidcats 没什么不合理的吧,正常的话也是每个程序有自己独立的地址空间,高地址是内核空间,低地址是用户空间,只不过这一章还没有虚拟内存而已

ghost avatar Apr 03 '23 15:04 ghost

要是作者在这一节的开头就讲清楚:“这一节的任务是实现《任务切换》的核心功能——__switch()噢!!”,就好了,就不会有向我一样的遇到疑问就死磕源码结果花死力气把下一节马上讲到的内容都提前磕明白了的笨蛋了

linyn-zero avatar Apr 24 '23 12:04 linyn-zero

@linyn-zero 第一遍阅读不必要求每一个细节都读懂,可以先把问题记录下来,第二遍第三遍重读同一节甚至读完整章再回过头阅读就会发现问题的解决思路了。作为一个完整的系统,不可能真正完全做到事无巨细循序渐进地讲解。读完整章有了整体概念,时常复习尽量记住足够多的上下文,有问题时才能了解从哪里寻找线索,得出答案。🌹

VisualMike-I avatar Apr 24 '23 14:04 VisualMike-I

这部分讲得很好,受用了

qiaokang92 avatar May 17 '23 21:05 qiaokang92

有一点没有搞清楚,想请教一下如果只有两个任务,只考虑时钟中断的话,任务执行流程是否是这样的:trap A -> switch (A, B) -> trap B -> switch (B, A) -> trap A ......

即这里面trap保存的上下文实际上没有被用到?

Neverfomo avatar Aug 03 '23 11:08 Neverfomo

@Neverfomo 被用到了啊,第二个trap A能回到进入第一个trap A之前的用户态的上下文继续执行就要靠我们在trap里保存的上下文啊。

wyfcyx avatar Aug 06 '23 06:08 wyfcyx

我有个问题,任务从taskA 通过 switch函数切换栈后,此时ra寄存器的值应该是switch函数的下个地址,然后函数会返回到taskB的trap_handler继续执行。trap_handler执行完毕后,此时应该会返回到__restore函数执行,这里是如何返回到__restore的我没想明白,ra寄存器被编译器自动设置成了__restore的地址?

yanglianoo avatar Aug 09 '23 06:08 yanglianoo

我有个问题,任务从taskA 通过 switch函数切换栈后,此时ra寄存器的值应该是switch函数的下个地址,然后函数会返回到taskB的trap_handler继续执行。trap_handler执行完毕后,此时应该会返回到__restore函数执行,这里是如何返回到__restore的我没想明白,ra寄存器被编译器自动设置成了__restore的地址?

@yanglianoo 我也有过这个问题,当时没学过汇编,所以对汇编文件trap.S理解不到位,后来根据文档有关汇编脚本文件的上下文猜测到: __alltraps和__restore都是定义在汇编文件trap.S中的,__alltraps的最后一条指令是call trap_handler,其下一条指令是符号__restore:指示的ld t0, 32*8(sp),即__restore第一条指令。而call trap_handler应该是“为内核支持函数调用”那节所说的用jalr实现的伪指令call实现函数调用,会保存当前指令的下一条指令地址ld的地址到ra寄存器并跳转到函数trap_handlertrap_handler执行完毕ret就返回到__restore首指令ld了。

为内核支持函数调用

总结一下,在进行函数调用的时候,我们通过 jalr 指令保存返回地址并实现跳转;而在函数即将返回的时候,则通过 ret 伪指令回到跳转之前的下一条指令继续执行。

VisualMike-I avatar Aug 09 '23 14:08 VisualMike-I

有个问题,请问ra和sepc的作用是不是重复了?在A-switch-B返回后,restore加载B的trap context后,请问pc究竟是跳转到sepc指定的地址还是跳转到ra指向的地址?

ctangam avatar Aug 21 '23 08:08 ctangam

有个问题,请问ra和sepc的作用是不是重复了?在A-switch-B返回后,restore加载B的trap context后,请问pc究竟是跳转到sepc指定的地址还是跳转到ra指向的地址?

@ctangam 执行执行环境返回指令sret会将sepc写到pc,执行函数调用返回指令ret会将ra写到pc

前面定义了pub fn __switch(~),因此在源代码中直接当做函数调用__switch(~)__switch最后会执行指令ret。而__restore的调用是通过call trap_handler执行完毕后接着执行下条指令实现的,__alltraps最后会执行sret。下一节会看到__switch会在系统调用中执行,因此先是__alltraps保存trap上下文,再执行__switch切换任务,以此保存被调用保存寄存器(其中一个目的G是:任务切换回来时从调用__switch()的地方回来接着往下执行,这是通过执行retra写到pc实现的,ra存储着同特权级函数调用完成返回时要跳转到的位置),恢复下个任务的被调用者保存寄存器(其中恢复ra就是为实现目的G做的准备工作)和切换至下个任务(这样就暂停了当前任务,暂停在调用函数__switch的代码处)的暂停点(系统调用内核代码调用__switch的地方)继续执行(执行ret实现),系统调用代码__switch执行完了在执行剩余的系统调用代码完了会在__restore处接着执行恢复trap上下文代码,最后执行sret恢复trap上下文。

简言之ret是为了从系统调用时内核函数调用暂停点继续执行,sret是为了切换特权级,从用户态调用系统调用的函数暂停点接着执行,并使用系统调用返回的结果。

特权级切换的硬件控制

而当 CPU 完成 Trap 处理准备返回的时候,需要通过一条 S 特权级的特权指令 sret 来完成,这一条指令具体完成以下功能:

  • CPU 会将当前的特权级按照 sstatus 的 SPP 字段设置为 U 或者 S ;
  • CPU 会跳转到 sepc 寄存器指向的那条指令,然后继续执行。

VisualMike-I avatar Aug 21 '23 12:08 VisualMike-I

trapContext保存的用户态应用程序的上下文,taskContext保存的是系统os执行过程中的上下文,taskContext保存的寄存器没有trapContext多,相当于一个调用函数栈帧保存的寄存器加sp寄存器。跟函数调用不同的是,和函数调用的正常控制流不同,__switch调用前后执行控制流发生改变,相当于从一个trap处理转换到另一条trap处理。

201430098137 avatar Oct 29 '23 01:10 201430098137