reL4 工作
1. 简介 近期深度参与了清华大学 reL4 项目的开发,记录下本人在其中的工作内容. 2. SMP 功能开发 独立完成了 SMP 功能的开发 SMP 移植开发说明 3. 配置编译系统 设计完成了 Kernel 编译配置系统的开发 编译配置系统移植说明 4. 架构相关代码移植 完成了 riscv aarch64 一部分架构相关代码的移植 架构相关代码的移植
1. 简介 近期深度参与了清华大学 reL4 项目的开发,记录下本人在其中的工作内容. 2. SMP 功能开发 独立完成了 SMP 功能的开发 SMP 移植开发说明 3. 配置编译系统 设计完成了 Kernel 编译配置系统的开发 编译配置系统移植说明 4. 架构相关代码移植 完成了 riscv aarch64 一部分架构相关代码的移植 架构相关代码的移植
1 前言 Forfun-OS 简介 前几章我们介绍了内核实现,但由于是微内核,很多功能需要在用户层实现。本章将介绍如何开发 Forfun OS 用户程序,以及文件系统和 Shell 的实现。 2 用户程序 简单起见,用户程序也使用 rust 开发。用户程序运行在 Forfun OS 上,而不是 linux 上,因此也需要使用 no_std 模式编程。 在 user/src 路径下,我们可以看到如下文件,相当于一个基础的 Forfun OS 标准库和编译环境。 lib.rs rust lib 库的主文件,定义了 entry 函数,初始化了堆 console.rs: 实现了 rust println! 宏,方便开发 lang_items.rs: 由于使用 no_std 模式开发,需要实现一些必要的接口,如 panic linker.ld: 链接脚本,定义 entry 地址 syscall.rs: 定义了所有系统调用函数 signal.rs: 定义了 signal 类别,用处不大,后面可删掉 在这个环境中进行应用程序开发,rust 语言的特性基本都可以使用,只是在使用 syscall 的时候需要了解下 syscall 用法。 应用程序暂且都放在 user/src/bin 文件夹下,后面也可以成独立项目,将编译环境作为 lib 引入 3 用户程序示例 下面是一个最简单的示例,可以看出,和 std 模式下开发差不多,只是需要加一些定义 ...
1 前言 Forfun-OS 简介 本章主要介绍 Forfun OS 的进程管理和 IPC 功能。目前简单考虑,没有设计线程功能,所以每一个进程就是一个任务,后期再考虑增加线程功能。 对于微内核来说,IPC 是非常重要的功能,因为微内核不提供文件系统,也不提供驱动,这些基础组件都是一个独立的用户进程。因此内核需要提供方便好用的 IPC 功能,帮助进程间通信。 2 进程管理 Forfun OS 的进程管理采用经典的 UNIX 式方式,通过 Fork,Exec,Wait 三个函数实现父进程对子进程的创建到回收。内核在启动时,会启动初始进程,再由初始进程启动子进程。子进程完全由父进程进行管理。 一个进程只存在一个任务,因此进程管理和任务控制块放在一个对象里即可,在 Forfun OS 中叫做 Process,内容如下 pub struct Process { pub tick: usize, // 运行时间片 pub status: ProcessStatus, // 任务状态, ready,running,sleep,exited pub pid: PidHandler, pub parent: Option<usize>, // 父进程 pub children: BTreeMap<usize, Arc<Mutex<Self>>>, // 子进程 ctx: SwitchContext, // 任务上下文 mm: MemoryManager, // 内存集 asid: AisdHandler, // 内存空间 id fds: Vec<Option<Arc<dyn File>>>, // 文件描述符 signals: SignalFlags, // 信号,用于 ipc signals_mask: SignalFlags, // 信号掩码 signal_actions: Vec<Option<SignalAction>>, // 信号量 handler trap_ctx_backup: Option<TrapContext>, // trap 上下文备份 } 主要通过以下三个函数管理进程从创建到回收 ...
1 前言 Forfun-OS 简介 本文以 riscv64 架构为例介绍,aarch64 架构会在后续 CPU 适配中介绍 本章主要介绍 Forfun OS 的内存管理功能。该功能主要设计以下方面 页表管理 内存区域管理 内存集管理 物理页帧管理 虚拟页面管理 elf 文件解析和加载 请注意,目前不包括对内核堆的管理,内核在启动使用时,预先分配一块固定大小内存,用于堆管理,而不是动态分配内存。 2 概念介绍 2.1 虚拟内存 在本章之前,我们一直使用的都是实地址模式。实地址模式简单,但存在两个问题, 操作系统需要预先分配一块内存区域,这个区域大小很难决定 每个应用程序需要预先知道自己被放置在哪块区域,并在链接脚本中说明起始地址 因此,前人发明了虚拟内存机制。简单来说,就是每个应用程序独享一块虚拟内存空间,让其产生独占内存空间的错觉(和让其觉得独占 CPU 一样)。同时通过页表机制,建立虚拟内存和物理内存之间的联系。关于虚拟内存的详细介绍可参考 虚拟内存 2.2 页表 页表用于 虚拟内存 -> 物理内存 的转换过程,CPU 在执行指令时,会根据页表查询到对应的物理内存地址。关于页表的详细说明可参考 rCore 文档介绍 页表介绍 每个进程都有独立的地址空间,即一套页表,所以在切换进程时,也需要切换页表。下面就是页表切换函数,它会在 __switch 函数之前执行。 // 页表切换函数 // os/src/process/app.rs pub fn activate(&mut self) { let satp: usize = self.satp(); unsafe { satp::write(satp); asm!("sfence.vma"); // 刷新块表,确保切换成功 } } 这就是为什么进程切换比线程切换的代价更大,因为进程切换需要切换页表,而线程切换则不用 2.3 内存区域和内存集 我们将一段在虚拟内存上连续的区域叫做内存区域,而这些内存区域的集合就叫做内存集。一个进程拥有一个内存集,代码中如下 ...
1 前言 Forfun-OS 简介 本章主要介绍 Forfun OS 的系统调用和任务调度功能的设计和实现细节 本文以 riscv64 架构为例介绍,aarch64 架构会在后续 CPU 适配中介绍 系统调用:指运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务 任务调度:操作系统为每个任务分配时间片,轮流执行任务,让每个用户进程认为自己独占了 CPU 这两章实现参考 rCore-OS,可以参考 rCore 中的以下两章 批处理系统 多道程序与分时多任务 2 系统调用 系统调用流程如下图 kernelkernelappappTrap 软中断Trap...保存用户上下文保存用户上下文syscall 处理函数syscall 处理函数寄存器填结果 恢复用户上下文寄存器填结果 恢复用户上下文返回用户空间返回用户空间Text is not SVG - cannot displaysyscall 流程 ...
1 前言 在学习 rcore 之后,为了更加了解其中的细节,萌生了自己写一个操作系统练练手的想法。这个操作系统只是用于个人学习所用,所有设计都是简化处理。 Forfun OS 项目源码-Github kernelkernelArch-related features (linker scripts, entry assembly...)Arch-related features (linker scripts, entry assembly...)Memory Manager (PageTable, Phys Frame Allocator, Virtual Address Allocator, MemoryArea, Memory Manager for process)Memory Manager (PageTable, Phys Frame Allocator, Virtual Address Allocator, MemoryArea, Memory Manager for proce...Board-related (Not used now)Board-related (Not used now)RUSTSBI/BootloaderRUSTSBI/BootloaderProcess Manager (TCB)Process Manager (TCB)Scheduler (Task Switch, Round-robin)Scheduler (Task Switch, Round-robin)Interrupt& Trap HandlerInterrupt...Timer Timer IPC (sem, pipe, signal, shared memory...)IPC (sem, pipe, signal, shared memory...)syscall handlersyscall handlerTrapTrapFilesystem in user envFilesystem in user envShell in user envShell in user envAPP1APP1APPnAPPnText is not SVG - cannot display项目架构 ...
1. 前言 和其他现代语言不同,C/C++ 的编译系统是散装的,由众多工具组成。比如包管理器有 Conan,Vcpkg 等等,构建系统有 Cmake, Ninja, autotools, scons 以及原始的 Makefile. 也有想要一统天下的 Bazel 说句题外话,我深入用过 Conan + Cmake 和 Bazel 两种方案,并在公司负责这两种编译系统的开发和维护。相比来说,我认为前者方案适合纯 C/C++/cuda 项目,非常方便好用,官方提供的库菜单很多,对交叉编译支持完备。后者则更适合大型、复杂项目,以及多语言,多个模块项目,Bazel 目标依赖系统做的很好,上游目标改动后会触发下游目标编译,这点是 Conan 和 Cmake 系统无法做到的。 但是 Bazel 对 C/C++ 的交叉编译没有支持,Cuda 规则没有官方提供,编译器规则开发经过多次迭代,可能前几年使用的在新版中已经无法使用,相对维护成本高很多。 回到正题,无论采用那种构建系统,最后总都归到编译器,主流的有 GCC 和 Clang 两种,我只用过 GCC,因此本文主要记录一些使用中觉得比较重要的点,供自己记录参考 2. 依赖问题 依赖问题应该是编译中大家遇到最多的问题。 简单来说,编译过程分为 编译 和 链接 两步,如果你写的代码没问题,那我们在进行编译时主要会遇到以下三种错误 编译的时候报找不到头文件 编译的时候报找不到依赖库 编译过了运行时候报找不到对应符号 下面我们分别讨论如何解决上述问题 2.1 找不到头文件 这个问题最容易解决,编译器默认只会查询 /usr/include, /usr/local/include 等几个标准路径下的头文件,将这些路径和代码里引用的相对路径拼起来就是头文件位置。 而对于放在非标准路径下的头文件,我们可以通过 -I 参数告诉编译器,如下 gcc -I{include_dir} -o my_program my_program.c 还有一种方法是使用 isystem gcc -isystem {include_dir} -o my_program my_program.c 这两种方式都可以引入头文件搜索路径,两者区别如下 ...
说明 本文主要记录一些 C++ 知识点,记录的知识点没有任何规律和归纳,好记性不如烂笔头,供自己查看。 知识点笔记 1. C++ list 和 vector 区别 Vector 是数组,List 是链表 具体来说,区别如下 Chatgpt 答案 数据结构: vector:基于数组的容器,提供快速的随机访问能力,即可以在常数时间内访问任何元素。然而,向vector中间插入或删除元素可能较慢,因为这可能导致内存中大量元素的移动。 list:基于双向链表的容器,不支持直接的随机访问。这种结构使得在任何位置(包括中间)插入和删除元素都非常快速,因为这仅仅涉及到修改指针。 内存分配: vector:通常以块的形式分配内存,每当其空间不足以容纳更多元素时,就会进行重新分配和复制整个数组到新的内存块。这可以导致较大的内存移动和复制开销。(所以最好使用 reserve 方法提前预留空间,以免每次添加元素都需要重新分配内存) list:通常为每个新元素单独分配内存。因此,它的内存分配更为频繁,但删除或添加元素时无需移动其他元素。 性能考量: vector:由于数据连续存储,对缓存友好,这可以提升遍历性能。适合用于经常需要访问元素的场景。 list:由于元素不连续存储,每次访问元素都可能导致缓存未命中,遍历性能较低。但在需要频繁插入和删除的情况下表现更好。 功能特性: vector 支持直接通过索引访问元素(例如 v[3]),而 list 不支持这种操作,必须通过迭代器遍历访问。 list 允许进行快速的双向遍历,而 vector 则优化了向后遍历。 适用场景: vector:适合元素数量变化不大,或主要在末尾添加或删除元素的情况。 list:适合元素数量频繁变动,或在列表中间频繁插入和删除元素的场景。 2. C++ 类的拷贝创建,如果成员中有指针,怎么做 该问题本质是说如何在拷贝创建是否需要进行深拷贝 所以需要将这个问题分开讨论 不需要对指针进行深拷贝,即创建出的实例和之前的实例共用这个地址里的数据,这样减少拷贝。但是如果该地址成员是一个 raw ptr, 会很难处理。如果在析构中 free,会出现 double free,如果不 free,会内存泄漏。所以最好是使用 shared_ptr 解决该类问题 需要进行深拷贝,即你需要创建出的实例拷贝地址数据,创建完全新的。例如 stl 中 vector 就是如此。这时需要定义拷贝构造函数和拷贝赋值运算符,以实现深拷贝。 class MyClass { public: int* data; // 构造函数 MyClass(int val) : data(new int(val)) {} // 拷贝构造函数 auto b = MyClass(a) MyClass(const MyClass& other) : data(new int(*other.data)) {} // 拷贝赋值运算符 auto b = a MyClass& operator=(const MyClass& other) { if (this != &other) { int* newData = new int(*other.data); delete data; data = newData; } return *this; } // 析构函数 ~MyClass() { delete data; } }; 3. 如何提高 C++ 代码效率,有哪些编程基础准则 这个问题非常宽泛,我们可以提炼一些准则,但是并不完全 ...
1 介绍 本文介绍一些我写 Bash 脚本常用的技巧,帮助我们写一些比较复杂的 Bash 脚本。说句题外话,我个人觉得用 Bash 写脚本还是比 Python 方便,当然用 Python 写更简单,门槛更低。但是 Python 太大了,很多嵌入式系统中根本不适合安装,而 Bash 的话就没有这个问题。从我的经验来说,我觉得嵌入式系统更适合用 Bash,功能也足够强大。 2 通用模块 1 命令行解析 Bash 脚本通常需要通过命令行输入参数,这时候一个好的命令行解析功能就相当重要。这里提供一个可以解析 Option 和 Command 的解析函数。 #! /usr/bin/env bash COMMAND="" # hint for users function show_usage() { cat <<EOF Usage: $0 [options] ... OPTIONS: -h, --help Display this help and exit. -a, --aoption Change it to your option. -b, --boption Change it to your option. acommand Change it to your command. bcommand Change it to your command. EOF } function acommand() { # do acommand } function bcommand() { # do bcommand } function parse_cmdline() { # show hint when there is no args if [ "$#" -eq 0 ]; then show_usage exit 0 fi while [ $# -gt 0 ]; do local cmd="$1" shift case "${cmd}" in acommand) COMMAND="start" ;; bcommand) COMMAND="save" ;; -a| --aoption) some_avalue=$1 shift ;; -b| --boption) some_bvalue=$1 shift ;; -h | --help) show_usage exit 0 ;; -* | --*) show_usage exit 1 ;; esac done } parse_cmdline "$@" ${COMMAND} 3 一些技巧 经常使用 bash -c 当我们需要执行多条命令,并且可能涉及到一些改变当前环境的操作时,我们可以使用 bash -c “commnad1; command2; command3”,这样会创造出一个独立的 bash 环境,方便的将这些组合命令在一个环境中执行完成。 前面也可以加上 sudo 创造一个 root 环境。 ...
1. 介绍 今天再次读到 Rust 所有权章节时有感,想在此简单讨论下 C++ 和 Rust 的深浅拷贝机制。Rust 和 C++ 在很多特性上十分相似,这里说的相似并不是说设计一样,而是说其想要解决的问题是相同的,但是采用的方法不同。深浅拷贝就是一个例子。 2. 深浅拷贝 在编程中,深拷贝和浅拷贝是处理对象和数据结构时常见的两种拷贝方式,它们在拷贝复杂对象(如,包含其他对象或动态分配的内存的对象)时的行为上有本质的区别。 浅拷贝 浅拷贝(Shallow Copy)仅复制对象的顶层结构,不复制对象内部的深层数据。如果对象中含有指针指向动态分配的内存,浅拷贝会复制指针的值,但不会复制指针所指向的内存。因此,原始对象和拷贝对象会共享部分数据。在某些情况下,这可能导致问题,如: 如果一个对象被释放(例如,析构函数被调用),它可能会释放共享的内存,使得另一个对象中的相应指针变成悬挂指针(指向已经被释放的内存)。 对共享数据的修改会影响到所有共享此数据的对象。 浅拷贝在处理简单对象或仅需要复制值而不需要独立修改数据的场景中比较合适。 深拷贝 深拷贝(Deep Copy)不仅复制对象的顶层结构,还递归地复制对象内部的所有内容,包括指向的对象和动态分配的内存。这意味着原始对象和拷贝对象在内存中是完全独立的;对一个对象的修改不会影响到另一个对象。 深拷贝通常用于: 需要复制的对象包含了指向动态分配内存的指针。 想要完全独立地修改原始对象和拷贝对象,而不希望它们之间有任何数据上的依赖。 深拷贝相比浅拷贝在实现上更复杂,可能也更耗费资源,因为需要逐个复制对象内部的所有元素,并且可能涉及到递归拷贝。 Move Move 直接将对象的顶层结构移动到新的变量中,之前的变量会失效 从内存的角度 浅拷贝: 只复制栈上的值,也就是该对象的地址,不复制堆上的数据 深拷贝: 复制栈上的值,不复制堆上的数据 Move: 直接将栈上的值赋给新的变量名,之前的失效 3. C++ 中的深浅拷贝和 Move 深拷贝 在现代 C++ 中 (C++11 及之后),有一些典型类型都是深拷贝 std::string std::vector std::map … 使用该类型赋值是需要注意 浅拷贝 C/C++ 中直接使用地址就是一种浅拷贝,这种浅拷贝存在风险,及开发者需要手动管理该地址对应的内存,如果忘记 free 或者提前 free 都会内存错误,造成 coredump. 当然,在现代 C++ 中,智能指针可以解决该问题,我们可以使用 shared_ptr 进行浅拷贝 Move C++ 中 std::move 实现了 Move 功能,该功能将所有权转换到新的变量 ...