基于 riscv32 的 OS 设计:任务同步和锁 前言在前面,我们已经实现了抢占式任务,但是依然有很多的问题。 如果,两个任务不相关,也就是说没有数据上面的交互,那么是没有问题的。但是一旦涉及到访问共享资源,比如终端,就会出现问题: 1234567891011121314151617Task 0: Running...Task 0timer interruption!tick: 288Task 1: Running...Task 1: Runni 2025-03-26 OS #OS #riscv
基于 riscv32 的 OS 设计:抢占式多任务 前言关于任务如何切换,可以分为三种: 抢占式:任务自己不能决定是否让出,由外部原因引起; 协作式:任务自己放弃 CPU; 兼容协作式:抢占式任务的同时,自己还可以自愿放弃。 关于协作式,前面已经提到了。task0 通过 yield 来放弃当前 CPU。 与定时器配合抢占式可以基于时钟中断来实现,通过不断地产生时钟中断来做到任务切换。 思路大致为,当时钟中断到达的时候,进入到 trap_vect 2025-03-25 OS #OS #riscv
基于 riscv32 的 OS 设计:硬件定时器 CLINTRISC-V 规范规定,CLINT 的寄存器编址采用内存映射(memory map)方式。具体寄存器编址采用 base + offset 的格式,且 base 由各个特定 platform 自己定义。针对QEMU-virt,其 CLINT 的设计参考了 SFIVE,base 为 0x2000000。 相关寄存器mtime系统全局唯一,在 RV32 和 RV64 上都是 64-bit。系统 2025-03-24 OS #OS #riscv
基于 riscv32 的 OS 设计:外部设备中断(uart) RISC-V 中断(Interrupt)的分类一共有三种中断: 本地软件中断; 本地时钟中断; 外部中断; 中断的控制很复杂,具有多个级别的开关,以确保系统能够灵活、高效地处理各种中断: 1. 设备级别: 这是中断控制的最底层。每个外围设备(例如,UART、SPI、GPIO)都有自己的中断使能位。 通过设置这些位,可以控制设备是否能够产生中断请求。 如果设备级别的中断被禁用,即使设备产生了中 2025-03-23 OS #OS #riscv
基于 riscv32 的 OS 设计:Trap 和 Exception trap 和 exception 相关寄存器不管是 trap 还是 exception,大致处理流程都是一致的。 mcause当 trap 发生时,hart 会设置该寄存器通知我们 trap 发生的原因。最高位 Interrupt 为 1 时标识了当前 trap 为 interrupt,否则是 exception。剩余的 Exception Code 用于标识具体的 interrupt 或者 ex 2025-03-21 OS #OS #riscv
基于 riscv32 的 OS 设计:多任务 多任务首先创造两个任务: 12345678910111213141516171819void user_task0(void){ uart_puts("Task 0: Created!\n"); while (1) { uart_puts("Task 0: Running...\n"); task_delay(DELAY); tas 2025-03-20 OS #OS #riscv
基于 riscv32 的 OS 设计:切换上下文 ContextOS 中实际上并没有什么线程、进程,不过是一个个不同的上下文而已。 上下文非常重要,任务 A 和任务 B 的切换,核心就是保存 A 的上下文、恢复 B 的上下文。 在之前的 MIPS yieldOS 中,已经实现过上下文以及上下文的切换了,本质都是差不多的。 1234567891011121314151617181920212223242526272829303132333435/* 2025-03-19 OS #OS #riscv
基于 riscv32 的 OS 设计:内存分页 内存的组织kernel 跑起来之后,要在 kernel 中运行一些程序,这些程序有的会使用堆区、有的仅仅使用栈区。因此,组织这些内存很重要。 初始的组织,要使用链接脚本。链接脚本告诉编译器 OS 的代码段、数据段、bss 等位置在哪里。另外,编译器还提供了可以在 ld 脚本中使用的一些命令,比如一个简单的脚本如下: 12345678910111213141516171819202122232425 2025-03-18 OS #OS #riscv
基于 riscv32 的 OS 设计:启动最简单的 OS 启动最简单的 OS这个 OS 超级简单,就是一个裸机程序: 1234void start_kernel(void){ while (1) {}; // stop here!} 我们只需要将这个程序编译成二进制文件,然后丢给 qemu,就可以跑了。 但是,本节会提到一个大大的问题,这个问题在上一个博客中并没有提到。 CPU 是多核心的如果在多核心上启动一个 k 2025-03-16 OS #OS #riscv
基于 riscv32 的 OS 设计:qemu 启动! 写的 OS 如何运行?如果写过 CPU,那就会明白,OS 和其它裸机程序几乎没有什么区别。当然,复杂的 OS 可能引入各种模式,比如 S-mode,但这不影响 OS 是一个比较复杂的裸机程序的事实。 因此,写好的 OS 加载到内存中,让 CPU 的 PC 指向 OS 的第一条指令,然后开始运行,OS 就成功运行了。 根据以上判断,OS 想要跑起来,需要: 一个 CPU; 一个 RAM。 有一个 2025-03-15 OS #OS #riscv