前言:
相信每个自学操作系统的同学,大致学习路线都离不开 HIT-OS、MIT-6.S081、MIT-6.824、MIT-6.828等经典的公开课。但学习完这些经典公开课并完成相应的Lab,很多同学脑海中对于操作系统的知识其实都是零散的,让你从头开始编写一个操作系统,我相信大部分人还是无从下手。因为Lab只是修改相应的核心模块,对于整体系统的组织、模块间的处理等细节,往往没有人去关注,也就是说我们还需要进一步把这些概念串起来、巩固起来。那么,我相信大部分人都有过一个想法:“我能不能自己写一个操作系统”,这可能是大部分操作系统开发人员的梦想吧。 因此!本项目将展示如何从零开始使用 ANSI C 编写出一个基于 64 位 RISC-V 架构的操作系统——Jokerix,该系统支持在内核上运行用户态(User/Application mode)的终端,并输入命令执行其他程序。
源码公开:Joker001014/Jokerix (github.com)
目录:
0 前置知识 【Create my OS】0 前置知识 | JokerDebug (joker001014.github.io)
0.1 RISC-V硬件机制
0.2 RISC-V 汇编
0.3 SBI 规范
0.4 GDB 调试
0.5 Jokerix 体系结构
0.6 实验环境1 最小内核
1.1 内核入口点
1.2 生成内核镜像
1.3 使用 QEMU 运行
1.4 封装 SBI接口2 开启中断
2.1 RISC-V 中断机制
2.2 触发断点
2.3 中断上下文
2.4 开启时钟中断3 内存管理
3.1 Buddy System
3.2 动态内存分配
3.3 内存按页分配框架
3.4 基于线段树的页帧分配4 虚拟内存
4.1 Sv39内核映射
4.2 实现页表
4.3 内核重映射5 内核线程
5.1 线程切换
5.2 构造线程结构
5.3 从启动线程到新线程6 线程调度
6.1 线程管理
6.2 调度线程
6.3 Round-Robin 调度算法
6.4 调度测试7 用户线程
7.1 创建用户程序
7.2 实现系统调用
7.3 进程内存空间
7.4 创建用户进程8 文件系统
8.1 SimpleFS
8.2 打包镜像
8.3 内核文件驱动
8.4 文件系统测试9 实现终端
9.1 键盘中断
9.2 条件变量与输入缓冲
9.3 echo 程序
9.4 实现终端编写代码文件时间线:
| 步骤 | 功能 | 文件(斜体表示二次修改) |
|---|---|---|
| 1 | CPU自检,跳转到Bootloader | / |
| 2 | 将内核代码从磁盘加载到内存(Bootloader),由OpenSBI提供:把 CPU 从 M-Mode 切换到 S-Mode,并跳转到一个固定的地址 0x80200000 处 |
/ |
| 3 | 编写内核入口点:设置OS启动栈,跳转到main.c执行 |
kernel/entry.Skernel/main.c |
| 4 | 将entry.S和main.c 编译和链接生成ELF文件(需存放在0x80200000),进一步生成二进制镜像文件 |
Makefilekernel/kernel.ld |
| 5 | QEMU加载镜像文件,至此成功运行操作系统。 | / |
| 6 | 封装SBI接口ecall;调用SBI接口实现 printf 功能 |
kernel/sbi.hkernel/printf.c |
| 7 | 封装CSR读写;初始化中断处理程序入口,设置断点中断处理程序 | kernel/riscv.hkernel/interrupt.c |
| 8 | 保存和恢复中断上下文信息 | kernel/context.hkernel/interrupt.S |
| 9 | 初始化开启时钟中断,设置时钟中断处理程序 | kernel/timer.ckernel/interrupt.c |
| 10 | 基于二叉树的动态内存分配,采用Buddy System Allocation算法 | kernel/heap.ckernel/consts.h |
| 11 | 基于线段树的页帧分配 | kernel/memory.c |
| 12 | 设置页表,将内核运行在虚拟地址空间 | kernel/kernel.ld kernel/entry.S |
| 13 | 实现三级页表,将内核各个段映射到页表上 | kernel/mapping.c |
| 14 | 借助中断恢复机制创建内核线程,及线程上下文切换 | kernel/thread.ckernel/switch.Skernel/context.h |
| 15 | 线程管理框架,创建调度线程 | kernel/processor.ckernel/thread.c |
| 16 | 实现Round-Robin线程调度算法 | kernele/rrscheduler.c |
| 17 | 实现系统调用 | user/syscall.h |
| 18 | 实现用户态printf、动态内存分配、用户程序入口点、用户测试函数 | user/io.cuser/malloc.cuser/entry.cuser/ulib.huser/hello.c |
| 19 | 编译用户程序并链接,将用户程序合并到内核 | user/Makefileuser/linkUser.asm |
| 20 | 处理用户态系统调用 | kernel/interrupt.ckernel/syscall.c |
| 21 | 编译的用户程序为ELF文件,实现ELF文件加载和内存映射 | kernel/elf.ckernel/mapping.c |
| 22 | 创建用户线程结构(创建用户栈、创建内核栈、创建上下文) | kernel/thread.c kernel/mapping.c |
| 23 | 打包生成文件系统镜像fs.img,将文件系统内容合并到内核 | mkfs/mksfs.cmkfs/simplefs.hkernel/linkFS.asmMakefile |
| 24 | 从文件系统中找到 Inode,加载 ELF 文件数据到字节数组中 | kernel/fs.ckernel/main.c kernel/thread.c |
| 25 | 处理键盘中断,实现条件变量,维护等待线程队列 | kernel/queue.ckernel/condition.ckernel/interrupt.c kernel/mapping.c kerne/processor.c |
| 26 | 标准输入缓冲区,维护缓存内容和条件变量 | kernel/stdin.c |