[笔记一] 学习开发一个RISC-V上的操作系统
资料来源
1 环境准备
代码克隆
- git clone --depth=1 git@gitee.com:unicornx/riscv-operating-system-mooc.git
- 参考 README.md 搭建环境
- sudo apt update
- sudo apt install build-essential gcc make perl dkms git gcc-riscv64-unknown-elf gdb-multiarch qemu-system-misc
- 常用构建命令
- make:编译构建
- make clean:清理
- make run:启动 qemu 并运行
- make debug:启动调试
- make code:反汇编查看二进制代码
2 计算机系统复习
讲解 C语言 编写 Hello World 程序,从编译到运行显示过程
- CPU(Central Processing Unit):ALU(Arithmetic Logical Unit) + CU(Control Unit) + Registers
- IO Bridge
- Main Memory
- 总线
- USB Controller + Disk Controller + Graphic Adapter
冯 诺依曼架构
- 指令和数据均存储在存储器中,总线开销小,控制逻辑实现简单
- 但执行效率较低
- 通用计算机体系结构,广泛用于个人电脑、服务器
哈佛架构
现代应用中,也有两种融合应用的场景,也是根据实际应用场景而定
- 从 Disk 中源文件 hello.c > gcc编译链接 a.out > 运行 加载到内存 Main Memory
- Control Unit:取指(Fetch) > 译码(Decode) > 执行(Execute):晶振 驱动 控制单元去执行
存储设备的层次结构
- 寄存器 > 高速缓存 > 主存储器 > 本地存储(磁盘) > 远程存储(网盘)
- 速度 快 > 慢
- 容量 小 > 大
- 成本 高 > 低
操作系统
- 保护硬件被失控的软件应用程序滥用
- 向应用程序提供简单一致的抽象接口来控制复杂的多种外设
- 应用程序与操作系统之间通过 系统调用(System Call) 来交互
- 操作系统 与 硬件 通过 指令集架构ISA 来交互
- 程序计数器(PC,Program counter):用于存放指令的地址
- 指令寄存器(IR,Instruction Register):用来保存当前正在执行的一条指令
- 通用寄存器(GR,General register):通用寄存器可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果
- 程序状态字PSW(PSW,Program Status Word)
- 状态寄存器又名条件码寄存器(SR,Status register)
3 RISC-V ISA介绍
ISA是什么?
- Instruction Set Architecture 指令集架构
- 底层硬件电路面向上层软件程序提供的一种接口规范
为什么要使用ISA?
- 为上层软件提供一层抽象,制定规则和约束,让编程者不用操心具体的电路结构
- IBM 360 是第一个将ISA与其实现分离的计算机
CISC vs RISC
- CISC(Complex Instruction Set Computing)
- 针对特定的功能实现特定的指令
- 导致指令数目比较多,但生成的程序长度相对较短
- RISC(Reduced Instruction Set Computing)
- 只定义常用指令,对于复杂的功能采用常用指令组合实现
- 导致指令数码比较精简,但生成的程序长度相对较长
- 如今,RISC与CISC也逐渐有了相互融合的趋势。
ISA的宽度
- ISA(处理器) 的宽度指的是CPU中通用寄存器的宽度(二进制的位数),这决定了寻址范围的大小、以及数据运算的能力
- 注意:ISA 的宽度和指令编码长度无关
知名 ISA
- X86 (CISC) 闭源,只有英特尔和AMD
- SPARC
- Power
- ARM 闭源,IP授权方式
- MIPI
- RISC-V 开源 简单、清晰的分层设计、模块化、稳定、社区化
- …
模块化的 ISA
RISC ISA = 1个基本整数指令集 + 多个可选的扩展指令集
- 基本整数(Integer)指令集:唯一强制要求实现的基础指令集,其他指令集都是可选的扩展模块
基本指令集- RV32I:32位整数指令集
- RV32E:RV32I的子集,用于小型嵌入式场景
- RV64I:64位整数指令集,兼容RV32I
- RV128:128位整数指令集,兼容RV64I和RV32I
扩展指令集
- M:整数乘法(Multiplication)与出发指令集
- A:存储器原子(Automic)指令集
- F:单精度(32bit)浮点(Float)指令集
- D:双精度(64bit)浮点(Double)指令,兼容F
- C:压缩(Compressed)指令集
- …
32个通用寄存器(General Purpose Registers)和一个程序寄存器(Program Counter Register)
- RV32E将32个通用寄存器缩减为16个
- 寄存器的宽度由ISA指定,RV32为32位,RV64为64位…
HART = HARdowar Thread:独立的指令流,生产线
特权级别(Privileged Level)
- 3中特权级别:机器模式 M、特权模式 S、用户模式 U
- Machine级别是最高的级别,所有实现都需要支持
- 可选的Debug级别
内存管理与保护
- 物理内存保护(Physical Memory Protection,PMP):低级别内存管理方式
- 允许 M 模式指定 U 模式可以访问的内存地址
- 支持R/W/X,以及Lock
- 虚拟内存(Virtual Memory):高级别内存管理方式
- 需要支持 Supervisor Level
- 用于实现高级的操作系统特性(Linux/Unix)
- 多种映射方式 Sv32/Sv39/Sv48
- 虚拟地址与物理地址的转换通过硬件 MMU 完成
异常和中断
- 异常(Exception):异常处理完成后会回到之前处理的指令重新执行
- 中断(Interrupt):中断处理完成后回到下一条指令继续运行
4 GCC 与 ELF
GCC(GNU Compiler Collection)
gcc [Options] [filenames]
-E:只做预处理
-C:只编译不链接,生成目标文件.o
-S:生成汇编代码
-O file:把输出生成到由file指定文件名的文件中
-g:在输出的文件中加入支持调试的信息
-v:显示输出详细的命令执行过程信息
预处理(Preprocessor) + 编译cc1(Compiler) > 汇编as(Assembler) > 链接ld(Linker)
gcc -E hello.c -o hello.i > gcc -S hello.i -o hello.s > gcc -c hello.s -o hello.o > gcc hello.o -o hello
gcc hello.c -v
GCC涉及的文件类型
- .c:C源文件
- .cc/.cxx/.cpp:C++源文件
- .i:经过预处理的C源文件
- .s/.S:汇编语言源文件(S中一般还包含#开头需预处理的指令)
- .h:头(header)文件
- .o:目标(object)文件
- .a/.so:编译后的静态库(archive)文件和共享库(shared object)文件
- a.out:可执行文件
ELF(Executable Linkable Format)
- 一种Unix-like系统上的二进制文件格式标准
- ELF文件格式:ELF Header、链接视图(Section Header Table)、运行视图(Program Header Table)、.text、.init、.data、.bss
- ELF文件处理相关工具:Binutils
- ar:归档文件,将多个文件打包成一个大文件
- as:被gcc调用,输入汇编文件,输出目标文件供链接器ld连接
- ld:GNU链接器。被gcc调用,它把目标文件和各种库文件结合在一起,重定位数据,并链接符号引用
- objcopy:执行文件格式转换
- objdump:显示ELF文件信息
- readelf:显示更多ELF格式文件的信息(包括DWARF调试信息)
- readelf -h hello.o:查看ELF Header
- readelf -SW hello.o:查看Section Header Table
- gcc -g -c hello.c:加入调试信息
- objdump -S hello.o:反汇编
EIL文件类型 | 说明 | 实例 |
---|---|---|
可重定位文件(Relocatable File) | 内容包含了代码和数据,可以被链接成可执行文件或共享目标文件 | Linux上的.o文件 |
可执行文件(Executable File) | 可以直接执行的程序 | Linux上的a.out |
共享目标文件(Shared Object File) | 内容包含了代码和数据,可以作为链接器的输入,在链接阶段和其他Relocatable File或Shared Object File一起链接成新的Object File;或者在运行阶段,作为动态链接器的输入,和Executable File结合,作为进程的一部分来运行 | Linux上的.so |
核心转储文件(Core Dump File) | 进程意外终止时,系统可以将该进程的部分内容和终止时的其他状态信息保存到该文件中以供调试分析 | Linux上的core文件 |
学习编译和链接的好处
- 有利于程序员优化程序的性能
- 理解并解决编译链接时出现的错误
- 写出更健壮的程序
5 嵌入式开发
什么是嵌入式开发?
- 是一种比较综合性的技术,不单指存粹的软件开发技术,也不单是一种硬件配置技术
- 在特定的硬件环境下针对某款硬件进行开发,是一种系统级别的与硬件结合比较紧密的软件开发技术
- 工作四年,深有体会
交叉编译
- 构建(build)系统:生成编译器可执行程序的计算机
- 主机(host)系统:运行编译器可执行程序,编译链接应用程序的计算机系统
- 目标(target)系统:运行应用程序的计算机系统
- 本地(native)编译:build == host == target
- 交叉编译(cross)编译:build == host! == target
- GNU交叉编译链工具(Toolchain)
- 命名格式:arch-vendor-os1-[os2-]xxx
GDB调试
- GDB(The GNU Project Debugger):GNU项目调试器,用于查看另一个程序在执行过程中正在执行的操作,或该程序崩溃时正在执行的操作
- 被调试的程序可能与GDB在同一台计算机上执行,也可能在另一台计算机(远程)上或者在模拟器上执行
- GDB支持调试多种语言:eg:C、Go、Rust
- GDB基本调试流程
- gcc -g test.c:重新编译程序并在编译选项中加入"-g"
- gdb a.out:运行gdb和程序
- (gdb) b 6:设置断点
- (gdb) r:运行程序
- (gdb) p xxx:程序暂停在断点处,执行查看
- (gdb) s/n/c:继续、单步或者恢复程序运行
模拟器QEMU
- 由(Fabrice Bellard)编写的以GPL许可证分发源码的计算机系统模拟软件,在GNU/Linux平台上使用广泛
- 支持多种体系架构,eg:IA-32(x86)、AMD64、MIPS 32/64、RISC-V 32、64
- 两种主要运作模式:
- User mode:直接运行应用程序
- System mode:模拟整个计算机系统,包括中央处理器及其他周边设备
- sudo apt install qemu
- qemu-system-riscv32 … -kernel ./test.elf
- qemu-system-riscv32 … -kernel ./test.elf -s -S
- -s:"-gdb tcp::1234"的缩写,启动gdbserver并在1234端口号上监听客户端
- -S:在启动时停止CPU(只有到客户端键入’c’才会开始执行)
项目构造工具 Make
- make:一种自动化工程管理工具
- Makefile:配合make,用于描述构建工程过程中所管理的对象以及如何构造工程的过程
- Makefile由一条或者多条规则(rule)组成
- target:目标,可以是obj文件也可以是可执行文件
- prerequisites:生成target所需要的依赖
- command:为了生成target需要执行的命令,可以有多条
- 缺省规则
- .DEFAULT_GOAL := all
- all:
- 伪规则
- .PHONY : clean
- clean:
- rm -rf *.o
- 注释:行注释,以 # 开头