一生一芯学习:多道程序 yield-os.c

news/2025/10/12 18:47:36/文章来源:https://www.cnblogs.com/yuweijie/p/19135451

一生一芯学习:多道程序 yield-os.c

随着处理器主频的越来越高,每次读写一次磁盘要耗费很多个时钟周期来等待磁盘操作的完成,与其傻傻等待,在这等待的过程中我们可以做更多有意义的事情,如当第一个程序需要等待输入输出的时候,切换到第二个程序来运行,第二个程序也等待输入输出的时候就可以切换到第三个程序,以此类推。

这就是多道程序的思想,要实现一个多道程序操作系统, 我们只需要实现以下两点就可以了:

  1. 在内存中可以同时存在多个进程
  2. 在满足某些条件的情况下, 可以让执行流在这些进程之间切换

什么是进程? 进程 = 程序 + 执行

进程是执行中的程序,除了可执行代码外还包含进程的活动信息和数据,比如用来存放函数变量、局部变量、返回值的用户栈,存放进程相关数据的数据段,内核中进程间切换的内核栈,动态分配的堆。

上下文切换

yield-os.c中构建了两个执行流,不断交替输出A和B,基本原理就是进程A运行的时候触发了系统调用,通过自陷指令陷入到内核中,根据__am_asm_trap(),A的上下文结构(Context)将会被保存在A的栈上。系统调用完后通过__am_asm_trap()恢复A的上下文,如果此时不恢复A的上下文,而是恢复B的上下文,那么执行完__am_asm_trap()

来看下yield-os.c执行流是如何进行进程切换的。首先贴出它的代码。

这个PCBunion类型的,而不是struct类型的,原因如下:定义数据的时候把PCB的stack栈空间和cp 记录上下文指针的元数据存放在同一块内存上。即pcb.stack占满整个PCB内存,然后PCB.CP放在内存的栈底。这样在上下文恢复时用 cp 指向的地址就能直接恢复栈上保存的 Context。

#define STACK_SIZE (4096 * 8)
typedef union {uint8_t stack[STACK_SIZE];struct { Context *cp; };  //(context pointer)来记录上下文结构的位置
} PCB;int main() {cte_init(schedule);pcb[0].cp = kcontext((Area) { pcb[0].stack, &pcb[0] + 1 }, f, (void *)1L);pcb[1].cp = kcontext((Area) { pcb[1].stack, &pcb[1] + 1 }, f, (void *)2L);yield();panic("Should not reach here!");
}

第一件事先初始化一下CTE
cte_init的作用是定义了待会跳转去异常处理的地址传给mtvec,然后注册回调函数shedule`

bool cte_init(Context*(*handler)(Event, Context*)) {// initialize exception entryasm volatile("csrw mtvec, %0" : : "r"(__am_asm_trap));  //把amasmtrap的地址传给mtvecuser_handler = handler;return true;
}

这个

static Context *schedule(Event ev, Context *prev) {current->cp = prev; current = (current == &pcb[0] ? &pcb[1] : &pcb[0]);return current->cp;
}

然后把执行完cte_init(schedule)之后到了

  pcb[0].cp = kcontext((Area) { pcb[0].stack, &pcb[0] + 1 }, f, (void *)1L);pcb[1].cp = kcontext((Area) { pcb[1].stack, &pcb[1] + 1 }, f, (void *)2L);

先来看下kcontext()的代码。第一个参数{ pcb[0].stack, &pcb[0] + 1 }就是栈空间,随后将函数名当成指针,函数f 会自动“退化”为指向该函数的指针。于是此时entry就是f了。如果指针后面赋值为mepc=(uintptr_t)entry,那么就会自动执行函数f,带上参数1。
下一行同理

Context *kcontext(Area kstack, void (*entry)(void *), void *arg) {Context *cp = (Context *)(kstack.end - sizeof(Context));cp->mepc = (uintptr_t)entry;cp->mstatus = 0x1800;cp->gpr[10] = (uintptr_t)arg;   //a0传参return cp;
}

随后陷入yield()

void yield() {
#ifdef __riscv_easm volatile("li a5, -1; ecall");
#elseasm volatile("li a7, -1; ecall");#endif
}

于是进行ecall指令

  INSTPAT("0000000 00000 00000 000 00000 11100 11", ecall  , I, s->dnpc = isa_raise_intr(11,s->pc);etrace());

然后调用isa_raise_intr(11,s->pc)函数。

word_t isa_raise_intr(word_t NO, vaddr_t epc) {/* TODO: Trigger an interrupt/exception with ``NO''. 待办事项:使用“NO”触发中断/异常。* Then return the address of the interrupt/exception vector. 然后返回中断/异常向量的地址*/cpu.mstatus = 0x00001800; cpu.mepc = epc; cpu.mcause = NO;return cpu.mtvec;
}

此时PC会跳转到之前定义的mtvec中,也就是cte_init中的__am_asm_trap函数。

__am_asm_trap:addi sp, sp, -CONTEXT_SIZEMAP(REGS, PUSH)csrr t0, mcausecsrr t1, mstatuscsrr t2, mepcSTORE t0, OFFSET_CAUSE(sp)STORE t1, OFFSET_STATUS(sp)STORE t2, OFFSET_EPC(sp)# set mstatus.MPRV to pass difftestli a0, (1 << 17)or t1, t1, a0csrw mstatus, t1mv a0, spcall __am_irq_handlemv sp, a0LOAD t1, OFFSET_STATUS(sp)LOAD t2, OFFSET_EPC(sp)csrw mstatus, t1csrw mepc, t2MAP(REGS, POP)addi sp, sp, CONTEXT_SIZEmret

这个函数作用之前讲过了,将上下文保存在栈上,然后调用handler之后还原现场,但此时我们把a0作为参数给sp,那就能做到线程切换,具体来看代码。会跳转到__am_irq_handle这个函数,看看他的源码。

Context* __am_irq_handle(Context *c) {if (user_handler) {Event ev = {0};switch (c->mcause) {case 11:ev.event=EVENT_YIELD;if(c->GPR1!=-1)ev.event = EVENT_SYSCALL;c->mepc += 4;break;default: ev.event = EVENT_ERROR; break;}//printf("mcause = %s\n",c->mcause);c = user_handler(ev, c);    //调用之前注册的handlerassert(c != NULL);}return c;
}

目前识别出是yield之后然后调用之前注册的回调函数。也就是shedule

static Context *schedule(Event ev, Context *prev) {current->cp = prev; current = (current == &pcb[0] ? &pcb[1] : &pcb[0]);return current->cp;
}

可以看到cte_init()在trace中是这么传递参数的。

image

意思就是根据riscv地abi切换a0的值,也就是切换线程,随后

  mv sp, a0LOAD t1, OFFSET_STATUS(sp)LOAD t2, OFFSET_EPC(sp)csrw mstatus, t1csrw mepc, t2MAP(REGS, POP)addi sp, sp, CONTEXT_SIZEmret

恢复现场,切换为B线程,也就是所有寄存器,什么通用寄存器堆,mepc,mcause, mstatus, mepc都一模一样。

然后调用mret,pc变成cpu.mepc,于是跳到刚刚kcontext定义的entry中,也就是f函数里面,然后判断参数是多少进行对应的输出之后又陷入到yield,一直循环。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/935590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

速通ACM省铜第十六天 赋源码(Sigma Cubes和Find Permutation 2和Rotate and Sum Query) - 教程

速通ACM省铜第十六天 赋源码(Sigma Cubes和Find Permutation 2和Rotate and Sum Query) - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !i…

Linux操作系统扫盲汇总

linux 基本概念概括VFS 树链接:虚拟文件系统就是一个树,树的根部就是 / , 树上不同的节点,都会指向不同的物理地址(文件系统的目录树的不同节点其实是来自不同的分区),可以是具体的文件系统,或者网络节点,或者…

ABC round 427

ABC round 427T3注意到 \(n\) 非常小,那么枚举染色方式然后判断二分图即可。 #include <bits/stdc++.h> #define int long long #define rep(i, a, b) for(int i = a; i <= b; ++i) #define rep_(i, a, b) f…

卸载驱动模块,内核崩溃排查调试记录

问题 在学习串口子系统,在卸载串口模块的时候,引起内核崩溃。具体的崩溃日志如下: /mnt/uartdeep # rmmod virtual_uart.ko [ 23.208560] /home/book/bsp/mcu/uartdeep/virtual_uart_ok/virtual_uart.c virtual_u…

详细介绍:游戏引擎以及游戏开发

详细介绍:游戏引擎以及游戏开发pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&qu…

springboot大学校园旧物捐赠网站(代码+数据库+LW) - 详解

springboot大学校园旧物捐赠网站(代码+数据库+LW) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consol…

DropLoRA 论文浅读:通过动态子空间学习突破 LoRA 的性能瓶颈

DropLoRA是一项简单而深刻的工作。它通过一个极其轻量的动态剪枝模块,巧妙地解决了传统LoRA的静态子空间瓶颈问题。DropLoRA 论文浅读:通过动态子空间学习突破 LoRA 的性能瓶颈 一、研究背景 随着大规模语言模型(LL…

实用指南:PCB 半固化片:多层板制造的技术基石,猎板的场景化适配与质控逻辑

实用指南:PCB 半固化片:多层板制造的技术基石,猎板的场景化适配与质控逻辑pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-fam…

操作系统CPU和内核思维导图总结

操作系统CPU和内核思维导图总结 https://www.processon.com/view/link/60eef701e0b34d06fba955b4

defold游戏引擎与lua(teal)编程语言

最近有时在想:UE5很强大,但放不进浏览器;C++对个人游戏开发不友好(掉头发嘞~) Godot类似Blender,300MB编辑器大小,itch.io大量使用godot制作网页游戏。但是对WebGPU的支持遥遥无期,见 https://github.com/godo…

个人用云计算学习笔记 --16(DHCP 服务器) - 实践

个人用云计算学习笔记 --16(DHCP 服务器) - 实践2025-10-12 18:20 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displ…

03 数值类型拓展

数据类型拓展二进制0b八进制0十六进制0x 打印的结果默认为十进制浮点数拓展 #float 有限且离散,舍入误差,接近但不等于 #double #最好完全使用浮点数进行比较!!! #非要比较的话,有专用的类float f=0.1f; double d = 1…

python如何引用变量的名称

在Python中,引用变量的名称通常指的是动态地获取变量的标识符(即变量名)作为字符串。然而,需要注意的是,Python的变量更像是名字贴纸,贴在对象上,而不是容器。这种操作在一些特殊的场景下可能会用到,比如调试、…

Python GIL与No-GIL技术详解

Python的全局解释器锁(Global Interpreter Lock,简称GIL)是CPython解释器中最具争议和核心的设计之一。它深刻影响着Python的并发编程模型,也是许多开发者在使用Python进行多线程编程时遇到性能瓶颈的主要原因。Py…

题解:AT_abc288_h [ABC288Ex] A Nameless Counting Problem

用这个题总结一些互异容斥的 trick,参考了周子衡的论文。 题意:求出满足以下两个条件的长度为 \(N\) 的整数序列 \(A = (A_1, A_2, \ldots, A_N)\) 的个数,并对 \(998244353\) 取模。\(0 \leq A_1 \leq A_2 \leq \c…

2025 年 CBN 砂轮源头厂家最新推荐榜单:专业实力与客户满意度全景解析及选购指南

在航空航天零部件精密切削、电子芯片超精密研磨、汽车发动机关键部件加工等高端制造领域,CBN 砂轮作为核心耗材,其品质直接决定加工精度与生产效率。然而当前市场乱象频发:部分厂家技术薄弱,产品耐用性与精度稳定性…

JDK安装和卸载

新的快捷键 常用 shift+delete:永久删除文件 ctrl+shift+esc:打开任务管理器 alt+f4:关闭进程 ps:前阵子ow流行发一些图片表情和不同颜色字体的时候,很多人用来伪装系统提示引诱玩家按下alt+f4,结果真有人不知道…

Python定义一个User类的基本写法

定义一个类的基础语法如下: class User:# 初始化方法或者叫构造器def __init__(self, name, email):self.name = name # 实例变量name定义和赋值self.email = email # 实例变量email定义和赋值# 一个实例方法def gr…

Python飞快入门专业版(四十八):Python面向对象之多态:不同对象调用同一办法的不同达成(实战案例)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …