Dvorak
Dvorak

Dvorak Chen

All Posts


编译 RustSBI 并在 QEMU 中启动 RISC-V 内核

本文围绕RISC-V架构中SBI接口的实现与应用展开,通过RustSBI项目展示了如何构建RISC-V系统的引导流程与硬件交互机制。文章重点解析了RustSBI代码结构中library与prototyper两部分的分工——前者处理底层硬件交互逻辑,后者负责系统启动过程,并通过QEMU模拟器演示了完整的内核启动链路。编译环节揭示了Dynamic Firmware与Jump Firmware两种模式的本质差异,尤其是jump_address参数与内核加载地址0x80200000的关联性设计,这种内存地址规划直接影响系统启动执行路径。值得注意的是配置文件中堆栈大小、页面尺寸等参数的选择逻辑,这些数值是否经过性能优化?当SBI跳转地址与内核入口地址严格对齐时,是否意味着RISC-V系统存在特定的内存布局规范?在QEMU启动命令中,-bios参数与-device loader的协作机制究竟如何实现多阶段引导?这些细节背后是否隐藏着更深层的架构设计哲学?当读者尝试修改jump_address值时,系统行为会发生哪些有趣的变化?或许正是这些看似简单的配置参数,构成了RISC-V生态中灵活可扩展的核心奥秘。--Qwen3

Rust RISC-V qemu SBI RustSBI Firmware

Cross build kernel and build BusyBox of arm64 at amd64 mechine

在x86平台上构建ARM64生态的完整实践过程中本文通过交叉编译工具链搭建与内核编译流程解析揭示了跨架构开发的核心逻辑从基础工具链的安装到Linux内核的定制化编译从BusyBox的静态构建到initramfs文件系统的封装最终通过QEMU虚拟化平台实现了ARM64环境的启动验证。当开发者面对不同架构间的兼容性挑战时如何通过环境变量的精准控制实现编译器的智能切换?当处理BusyBox的ncurses依赖冲突时临时修改Makefile的解决方案是否触及了开源软件生态的深层协作机制?在构建initramfs时为何需要手动创建proc sys dev等特殊文件系统目录?而QEMU启动参数中-M virt与-cpu cortex-a72的组合又暗示了怎样的硬件抽象设计哲学?这些看似技术细节的探索实际上都在叩问操作系统底层架构的本质当我们通过一个简单的init脚本就能让ARM64内核在x86硬件上运行时是否正在见证计算架构的无限可能性?这种跨架构的开发实践究竟是在打破硬件壁垒还是在重新定义软件的边界?当你启动QEMU看到"ARM64 System Ready!"的提示时是否也在思考未来计算平台的形态正在如何被这种跨架构能力所重塑?--Qwen3

qemu Linux Kernel ARM64 Cross Compile BusyBox From Source

如何让前端写的更舒服一点

文章探讨了前端开发中JavaScript、HTML和CSS三大套件的痛点与优化思路。JavaScript因历史遗留的语法缺陷和浏览器兼容性问题长期被诟病,如var变量提升、转译器对现代语法的降级处理等,这些特性被美化为“语言特性”却频繁导致BUG。文章指出通过TypeScript的类型系统和严格Lint配置可规避缺陷,但这也引发新的思考:当转译后的代码仍需兼容旧浏览器时,我们是否在用现代语法的壳子包装过时的内核?HTML作为声明式语言虽然直观,但重复性高导致开发效率受限,对比Flutter用Dart封装Widget的复用性,暴露出声明式语言在工程化上的局限——当界面复杂度攀升时,HTML的冗余写法是否正在阻碍前端工程的进化?CSS虽未展开讨论,但文章暗示三大套件的割裂状态始终是前端开发的痛点。最终抛出核心问题:在声明式语言与编程范式之间,是否存在更优雅的平衡点?当框架开始用编程逻辑重构界面描述时,HTML是否正在被重新定义?--Qwen3

Javascript css typescript HTML Front end Development Dart

从一段代码来学习 Rust - 2

Rust通过trait重构了面向对象的实现逻辑当传统继承体系因过度抽象而变得臃肿时Rust选择用trait作为接口契约化设计的基石这迫使开发者思考如何在不依赖继承的情况下实现行为复用与多态特性文章通过Box<dyn Run>的实例揭示了动态分配的必要性当结构体字段需要持有不确定大小的trait对象时堆内存的指针包装成为必然选择但这种动态分配并非万能编译器对异步trait的特殊处理暴露了Rust设计的深层逻辑当async方法被写入trait时返回值自动脱糖为Future trait导致Box无法持有此时async_trait crate通过Pin<Box<Future>>的封装既解决了堆分配问题又规避了异步对象在多线程迁移时的内存漂移风险而Send标记的引入则暗含了所有权模型与线程安全的哲学碰撞当Future被标注为Send时开发者必须重新审视代码中潜在的跨线程数据共享风险文章最后抛出值得深思的命题:为何在Rust已支持trait异步方法的今天仍需async_trait?生命周期标注'async_trait如何影响堆对象的存活边界?当传统面向对象的继承树被trait解构后我们是否正在创造新的复杂度?这些问题的答案或许就藏在Rust对安全与性能的极致追求中--Qwen3

Rust Concurrency OOP Polymorphism Async Pin

从一段代码来学习 Rust - 1

这篇博客通过一个复杂的Rust函数签名,揭示了Rust语言中面向对象特性和生命周期系统的深层逻辑。文章从`&self`的语法糖开始,展示了Rust如何将面向对象的`self`调用转化为显式的函数参数传递,这种设计既保持了所有权模型的纯粹性,又打破了传统OOP的思维惯性。通过展开`self`的不同形式(`&self`、`&mut self`、`self`),作者引导读者思考Rust如何用类型系统实现资源管理的确定性。 当讨论转向生命周期注解时,文章用直观的生命周期标注示例,解构了Rust编译器如何通过`'a`这样的泛型参数确保引用的有效性。通过对比`'life0`和`'async_trait`的生命周期约束,揭示了异步编程中生命周期管理的特殊性。特别是`Self: 'async_trait`和`T: 'static`的约束关系,展现了Rust在并发场景下对内存安全的严格把控。 在`'static`约束的讨论中,文章通过对比值传递和引用传递的编译错误,揭示了`'static`在泛型约束中的双重含义:当标注在值类型上时它意味着所有权的转移,而标注在引用时则要求全局生命周期。这种设计如何影响异步代码中资源的生命周期管理,成为理解Rust异步编程的关键线索。 最后文章暗示了`Box`和`Future`在Rust异步生态中的核心地位,通过动态分配的`Future`对象与生命周期约束的结合,为后续讨论`Pin`和异步编程模型埋下伏笔。当读者看到`async_trait`生成的代码中复杂的生命周期约束时,不禁会思考:这种看似繁琐的类型系统设计,是否正是Rust实现"零成本抽象"的底层保障?而那些看似晦涩的编译器错误信息,是否在默默守护着并发场景下的内存安全?--Qwen3

Rust rust-programming Pin Lifetimes Future Static Lifetimes

如何编写易维护的代码 - Rust 实现-1

本文围绕Rust中如何通过函数式思想构建易维护代码展开探讨,核心在于纯函数设计与依赖管理的实践。纯函数因其输出仅由输入决定而具备可预测性与可测试性,例如简单算术函数与依赖数据库查询的函数形成鲜明对比——前者始终返回确定结果而后者可能因外部数据变化产生不可预期的输出,这种差异直接导致维护成本的悬殊。当需要处理数据库等外部依赖时,依赖注入通过将依赖作为参数传递而非在函数内部创建,有效解耦了函数行为与外部环境,使测试环境切换仅需修改注入参数即可实现。随着依赖增多,将多个依赖抽象为结构体并通过new方法初始化,既能保持函数式特征又提升代码可读性,结构体方法本质上仍是接收self指针作为隐式参数的函数调用。这种设计模式将依赖管理转化为可组合的结构化组件,使错误定位更精确测试更便捷。当结构体封装了十几个依赖后,如何平衡结构体设计与函数式纯度?如何在复杂系统中确定依赖边界的划分?当业务逻辑需要跨多个结构体协作时,又该如何保持函数式设计的简洁性?这些问题或许能引导我们进一步思考代码设计的深层原则。--Qwen3

Rust rust-programming Functional Programming Pure Functions Dependency Injection Structs in Rust

上一篇的页表错误修正

在页表实现的修正过程中作者发现了一个关键性的认知误区——物理地址与物理页号的混淆导致页表构建失败。物理页帧作为4K大小的内存单元其地址本质是物理地址而页表项中44位的PPN字段需要的是物理地址右移12位后的页号而非直接填入地址本身这种差异在RISC-V手册第10.3.2节的页表转换流程图中隐藏着重要线索。当试图将物理地址直接写入PPN字段时实际上破坏了地址分层结构这种错误源于对PPN[0]与PPN[1]分段标识的过度解读。这种认知偏差暴露出操作系统底层开发中概念边界模糊的普遍困境——当文档以分段方式展示物理地址时是否必然暗示着非连续的页号处理?保留错误代码的原始版本既是对开发成本的妥协也暗示着错误本身的价值:它是否揭示了操作系统教学中常见的认知陷阱?页表实现的复杂性是否恰恰来源于这种看似简单实则精密的地址转换逻辑?当开发者面对手册中抽象的地址分解图时又该如何避免陷入概念混淆的泥潭?--Qwen3

RISC-V risc-v-architecture page-table-implementation physical-address ppn-misunderstanding physical-page-frame error-correction

(deprecated)使用 Rust 为 RISC-V 设置页表

文章详细描述了RISC-V架构下页表的构建与satp寄存器配置过程。通过RootPageTable结构管理整张页表及其所有页帧和分配器,以LevelPageTable结构处理具体页表项的访问与创建,将4k页表内存视为512个PTE切片。PTE结构体以C语言布局存储64位页表项,通过检查V位判断有效性并动态分配下级页表。Map方法通过MapBlock参数(含虚拟页号、映射类型和权限)实现三级页表的层级映射,支持恒等映射(虚拟地址直接对应物理地址)和页帧映射两种模式。最终通过satp_token方法将根页表物理地址与RISC-V模式(8<<60)组合生成satp寄存器值,并通过汇编sfence.vma指令刷新转译旁路缓存完成页表启用。--Qwen3

Rust RISC-V riscv memory-management page-tables pte-construction satp-register rust-memory

Rust 编写裸机代码使用链接器脚本

这篇文章探讨了在Rust中编写Risc-V裸机代码时如何通过自定义链接器脚本控制内存布局与符号定义。通过链接器脚本可以精确指定代码段数据段的位置和对齐方式例如用`ALIGN(4K)`确保内存4KB对齐或通过`.text.entry`定义入口段位置这种细粒度控制让开发者能优化程序的内存占用和执行效率。文章通过示例展示了如何定义`sbss`和`ebss`符号来标记`.bss`段的起始与结束地址并揭示了这些符号在Rust代码中如何被使用以获取内存布局信息。当开发者观察到`ARR`数组的地址恰好落在`.bss`段范围内时不禁会思考如何通过调整链接器脚本进一步优化全局变量的存储策略。文章还抛出了一个值得深究的问题:如果将`.rodata`段与`.data`段的对齐方式改为非4KB会否影响程序性能?更进一步当链接器脚本中定义的`.skernel`和`.ekernel`符号用于计算整个程序的大小时是否意味着我们可以用这些符号构建更灵活的内存管理机制?通过自定义符号和段布局开发者不仅能够掌控程序的物理内存映射还能为后续的内存保护或动态加载功能打下基础。这种从链接层面介入程序构建的方式是否能让Rust在嵌入式领域实现更高效的资源利用?--Qwen3

Rust RISC-V memory-management linker-script rust-programming section-alignment symbol-table print-implementation

使用 Rust 编写 Risc-V 裸机代码

本文探讨了如何利用Rust语言构建Risc-V架构的裸机程序揭示了脱离操作系统支持的底层开发方法。通过解析Rust工具链的交叉编译机制重点阐述了riscv64gc-unknown-none-elf目标平台的特殊性及其四部分命名规则背后的逻辑。文章展示了从环境配置到代码构建的完整流程包括通过rustup添加目标平台组件使用llvm工具链配置链接器脚本以及在CARGO配置中指定ELF格式的链接参数。核心开发实践中no_std和no_main宏的应用突破了常规Rust开发范式迫使开发者直接面对硬件层面的编程挑战而自定义panic处理函数则体现了对程序异常控制流的完全掌控。这种开发模式不仅需要重新理解Rust的编译机制更要求开发者思考如何在没有标准库支持的环境下构建基础功能。当代码执行流从_start函数开始时每一个内存操作都直面硬件真实映射这是否意味着Rust的类型安全特性在此场景下会遇到新的边界?当开发者亲手编写链接器脚本时如何平衡内存布局的灵活性与程序稳定性?这些思考或许能帮助我们更深刻地理解系统底层运行机制。--Qwen3

Rust RISC-V embedded-systems bare-metal cross-compilation no-std