【uCore 操作系统】1. 应用程序与基本执行环境

文章目录

  • 【 1. 代码框架简述 】
    • 1.1 OS 是怎么跑起来的?
      • 1.1.1 qemu 的作用
      • 1.1.2 rustsbi.bin 的作用
    • 1.2 qemu 是怎么跑起来的?
    • 1.3 OS 文件夹
      • 1.3.1 kernel.ld
      • 1.3.2 entry.S
      • 1.3.3 main.c
      • 1.3.4 sbi.c
    • 1.4 bootloader 文件夹
  • 【 2. makefile 和 qemu 】
    • 2.1 makefile 内部
      • 2.1.1 指定编译使用的工具
      • 2.1.2 添加编译flag
      • 2.1.3 设置编译目标
    • 2.2 运行 qemu
    • 2.3 gdb 调试
    • 2.4 LOG 支持
  • 参考

  • 本章展现了操作系统的一个基本目标:让应用与硬件隔离,这种形式 简化了应用访问硬件的难度和复杂性。这也是远古操作系统雏形和现代的一些简单嵌入式操作系统的主要功能。具有这样功能的操作系统形态 就是一个函数库,可以被应用访问,并 通过函数库的函数来访问硬件
  • 大多数程序员的第一行代码都从 Hello, world! 开始,当我们满怀着好奇心在编辑器内键入仅仅数个字节,再经过几行命令编译(靠的是编译器)、运行(靠的是操作系统),终于在黑洞洞的终端窗口中看到期望中的结果的时候,一扇通往编程世界的大门已经打开。生成应用程序二进制执行代码所依赖的是以 编译器 为主的开发环境 ;运行应用程序执行码所依赖的是以 操作系统 为主的执行环境
  • 系统调用(syscall) 是操作系统提供给软件的一系列接口,使得软件能够使用系统的功能。
    syscall 本质上属于一种 异常/中断,它在 riscv 的汇编指令中以 ecall 的形式出现。
    syscall 的种类有很多,操作系统通过区分 syscall 的 id 来判断是哪一个syscall。
  • 本章我们将从操作系统最简单但也是最重要的println入手,要求大家实现一个裸机上的println以及带色彩的LOG,如info和warn,error等功能。本章的 println 所需要的在 console 中打印字符,也需要调用到 syscall。

【 1. 代码框架简述 】

  • Ucore 项目的代码树如下:
.
├── bootloader
│   └── rustsbi-qemu.bin
├── LICENSE
├── Makefile
├── os
│   ├── console.c
│   ├── console.h
│   ├── defs.h
│   ├── entry.S
│   ├── kernel.ld
│   ├── log.h
│   ├── main.c
│   ├── printf.c
│   ├── printf.h
│   ├── riscv.h
│   ├── sbi.c
│   ├── sbi.h
│   └── types.h
├── README.md

1.1 OS 是怎么跑起来的?

1.1.1 qemu 的作用

  • 我们的 OS 的运行,是要依赖著名的模拟器软件 qemu的。
    比较形象的比喻是,OS 就是一个内核软件,qemu就类似一个主板,它模拟了许多硬件,比如CPU,I/O串口等等,OS 会和 qemu 模拟出来的这些硬件打交道,而 qemu 则把得到的指令分配给实际存在的硬件完成

1.1.2 rustsbi.bin 的作用

  • OS 启动的时候,就像一个真正的操作系统启动一样。qemu 使用我们提供的 rustsbi 的bin文件做为引导程序 来启动OS。
  • SBI 是 RISC-V 的一种底层规范,RustSBI 是SBI的一种实现。 操作系统内核与 RustSBI 的关系有点像应用与操作系统内核的关系,后者(即RustSBI)向前者(即操作系统内核)提供一定的服务,只是SBI提供的服务很少, 比如关机,显示字符串,读入字符串等。
  • SBI 的一个直接作用:提供输入输出。
    我们的内核作为运行在qemu中的虚拟机,是无法直接和我们的外部 host系统通信的。因此我们OS自己实现的printf函数,想要真正地输出到我们外部运行的shell上被我们看到,是要经过qemu的。实际上,在 启动时sbi已经初始化好了,经过 qemu模拟出来的串口,最终打印到我们外部的shell上 的,从我们的shell之中读取输入,也是同样的道理。
    SBI 为我们内核提供的功能不止于输入输出,在sbi.c文件的可以看到其他支持的功能,比如关机。

1.2 qemu 是怎么跑起来的?

  • qemu 拓展阅读: qemu参数
  • makefile 之中提供了具体运行qemu所需要的参数:
    在这里插入图片描述
  • make run这条指令首先执行上面 kernel 所需要的链接以及编译操作,得到一个二进制的 kernel,之后执行按照 QEMUOPTS 变量指定的参数启动qemu。
    QEMUOPTS意义如下:
    • nographic: 无图形界面。
    • smp 1: 单核 (默认值,可以省略)。
    • machine virt: 模拟硬件 RISC-V VirtIO Board。
    • bios $(bios): 使用指定 bios,这里指向的是我们提供的 rustsbi 的bin文件。
    • kernel: 使用 elf 格式的 kernel。这里就是我们需要写的OS内核了。
  • makerun 指令完成了内核代码的编译生成kernel,并按照QEMUOPTS变量指定的参数加载我们的kernel,“加电”启动qemu。 此时,CPU 的其它通用寄存器清零,而 PC 会指向 0x1000 的位置,这里有固化在硬件中的一小段引导代码,它会很快跳转到 0x80000000 的 RustSBI 处。 RustSBI完成硬件初始化后,会跳转到 $(KERNEL_BIN) 所在内存位置 0x80200000 处, 执行我们操作系统的第一条指令。
    在这里插入图片描述
  • 那么,知道了这些步骤之后,关键就是怎么去写我们的OS了,这也是我们接下来各个实验的内容。

1.3 OS 文件夹

  • os文件夹下存放了所有我们构建操作系统的源代码,是本次实验中最最重要的一部分,也是整个实验过程中唯一需要修改的部分。在开始实验之前,大家一定要清楚我们这是自己设计的 OS,是无法使用C提供的官方标准库的,也就是说,就算是最简单的 printf 之类的函数都无法使用。还好,作为一个轻量级的 OS,我们也用不到那么多函数。
  • 我们的os是一个由 makefile 来构建的C项目。下面介绍框架之中一些重要文件的作用,以及整个项目是如何链接及编译的。

1.3.1 kernel.ld

  • kernel.ld是我们用于链接项目的脚本。链接脚本决定了 elf 程序的内存空间布局(严格的讲是虚存映射,注意程序中的各种绝对地址就在链接的时候确定),由于刚进入 S 态的时候我们尚未激活虚存机制,我们必须把 os 置于物理内存的 0x80200000 处(这个地址的来由请参考 rustsbi)
BASE_ADDRESS = 0x80200000;
SECTIONS
{
   . = BASE_ADDRESS;
   skernel = .;

   stext = .;
   .text : {
      *(.text.entry)   # 第一行代码
      *(.text .text.*)
   }

   ...
}
  • SECTIONS 之中是从 BASE_ADDRESS 开始的各段。对程序内存布局还不太熟悉的同学可以翻看后面内存布局的章节。以 text 段为例,它是由不同文件的 text 组成。我们没有规定这些 text 段的具体顺序,但是我们规定了一个特殊的 text 段:.text.entry 段,该 text 段是 BASE_ADDRESS 后的第一个段,该段的第一行代码就在 0x80200000 处。这个特殊的段不是编译生成的,它在 entry.S 中人为设定。

1.3.2 entry.S

# entry.S
   .section .text.entry
   .globl _entry
_entry:
   la sp, boot_stack
   call main

   .section .bss.stack
   .globl boot_stack
boot_stack:
   .space 4096 * 16
   .globl boot_stack_top
boot_stack_top:
  • .text.entry 段中只有一个函数 _entry,它干的事情也十分简单,设置好 os 运行的堆栈(la sp, boot_stack语句。bootloader 并没有好心的设置好这些),然后调用 main 函数。main 函数位于 main.c 中,从此开始我们就基本进入了 C 的世界。

1.3.3 main.c

  • 它是os的入口函数。在其中我们会完成一系列的初始化并开始运行os。 作为第一章,它在初始化完毕之后实际上起到了一个测试的作用。如果你的main.c能够完成一系列打印并且最后成功退出(Shutdown),那么祝贺你,你完成了os的第一步。
extern char s_text[];
extern char e_text[];
// ...

void main()
{
   clean_bss();
   console_init();
   printf("\n");
   printf("hello wrold!\n");
   errorf("stext: %p", s_text);
   // ...
   errorf("ebss : %p", e_bss);
   panic("ALL DONE");
}
  • 其中,main.c 之中众多的 extern 声明的内存段是在 ld 文件之中定义的,通过这些 symbol 我们可以大致了解 OS 的内存布局。
  • 此外 clean_bss() 清空了 bss 段,注意,清空 elf 程序 .bss 段这一工作通常是由 OS 做的,而我们就只好自立更生了。
  • 你可能注意到除了 printf 之外,还有一些用于 log 的彩色输出宏。感兴趣的同学可以看看 log.h 。

1.3.4 sbi.c

  • printf 的实现在 printf.c,在函数之中我们完成了对 format 字符串的解析工作。那么我们是如何把字符串真正地打印到 shell 上的呢? 我们 调用consputc 函数输出一个 char 到 shell,而 consputc 函数其实就是调用了 sbi.c 之中的 console_putchar 函数。这个 console_putchar 函数的本质是调用了 sbi_call。剥开层层套娃,大家可以发现 打印的最终实现是使用 sbi 包装好的 ecall 汇编代码,通过指定 ecall 的 idx 为 SBI_CONSOLE_PUTCHAR,并将我们的字符做为参数传入到 ecall 指定的寄存器之中完成一次系统调用来实现的。 本来,作为一个 OS,串口输出(也就是输出到 shell)的事情也应该我们自己来做,但这里为了简化这些硬件强相关的实现,我们利用 rust-sbi 的 M 态支持。这也是 riscv 灵活性的一个体现。
    rustsbi 拓展阅读: rsutsbi。

1.4 bootloader 文件夹

  • 这个文件夹是用来存放 bootloader(也就是 rustsbi) 的 bin 文件的,这一章以及之后都无需我们做任何修改。
  • 硬件加电之后是处于M态,而 rustsbi 帮助我们完成了 M 态的初始化,最终将 PC 移动至我们 os 开始执行的位置。同时,它也会帮助S态的 os 完成一些基本管理,详情可以看 os/sbi.c 文件。

【 2. makefile 和 qemu 】

  • makefile 在整个实验过程中不可修改,否则可能导致 CI 无法通过!

2.1 makefile 内部

2.1.1 指定编译使用的工具

  • 这里 makefile 调用了大家 设定好的PATH之中的riscv64工具链。如果没有设置好,那么之后的编译就会因为找不到这些文件而出错。
TOOLPREFIX = riscv64-unknown-elf-
CC = $(TOOLPREFIX)gcc
AS = $(TOOLPREFIX)gas
LD = $(TOOLPREFIX)ld
OBJCOPY = $(TOOLPREFIX)objcopy
OBJDUMP = $(TOOLPREFIX)objdump
GDB = $(TOOLPREFIX)gdb

2.1.2 添加编译flag

  • 比较需要注意的是 这里设置了警告也会报错,因此大家写代码的时候最好避免 warning 的出现,这是良好的编程习惯。
CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb
CFLAGS += -MD
CFLAGS += -mcmodel=medany
CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax
CFLAGS += -I.
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)

2.1.3 设置编译目标

# 目录定义
K = os
BUILDDIR = build
# .o 目标的确定,也就是 os 目录下所有的 .c 和 .s 都编译成 .o
C_SRCS = $(wildcard $K/*.c)
AS_SRCS = $(wildcard $K/*.S)
C_OBJS = $(addprefix $(BUILDDIR)/, $(addsuffix .o, $(basename $(C_SRCS))))
AS_OBJS = $(addprefix $(BUILDDIR)/, $(addsuffix .o, $(basename $(AS_SRCS))))
OBJS = $(C_OBJS) $(AS_OBJS)
# kernel 镜像由所有的 .o 按照 kernel.ld 链接而成
$(BUILDDIR)/kernel: $(OBJS) $(K)/kernel.ld
   $(LD) $(LDFLAGS) -T kernel.ld -o kernel $(OBJS)

2.2 运行 qemu

  • 这里和前面一致。大家不需要太关心qemu的更多细节,我们涉及它的操作已经在makefile和sbi之中处理了。
QEMU = qemu-system-riscv64
QEMUOPTS = \
   -nographic \
   -smp $(CPUS) \
   -machine virt \
   -bios $(BOOTLOADER) \
   -kernel kernel

run: $(BUILDDIR)/kernel
$(QEMU) $(QEMUOPTS)

2.3 gdb 调试

  • 使用 make debug 来使用 gdb 调试 qemu,程序自身执行的机制和直接 make run 一样。
  • 在解析 bootloader 的行为时可以使用 gdb 在其中添加断点来查看对应寄存器和内存的内容。
# QEMU's gdb stub command line changed in 0.11
QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
   then echo "-gdb tcp::1234"; \
   else echo "-s -p 1234"; fi)

debug: kernel .gdbinit
   $(QEMU) $(QEMUOPTS) -S $(QEMUGDB) &
   sleep 1
   $(GDB)

2.4 LOG 支持

  • 我们的 log 等级选择是通过 -D 参数来实现的,这也是大家 make run LOG=xxx 的原理。从这里我们也可以看到 LOG 的可选值。
ifeq ($(LOG), error)
CFLAGS += -D LOG_LEVEL_ERROR
else ifeq ($(LOG), warn)
CFLAGS += -D LOG_LEVEL_WARN
else ifeq ($(LOG), info)
CFLAGS += -D LOG_LEVEL_INFO
else ifeq ($(LOG), debug)
CFLAGS += -D LOG_LEVEL_DEBUG
else ifeq ($(LOG), trace)
CFLAGS += -D LOG_LEVEL_TRACE
endif

参考

代码框架简述

makefile 和 qemu

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/393735.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试架构师必备技能 —— Nginx安装部署实战

Nginx("engine x")是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的免费开源Web和 反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。在高并发访问的情况下,Nginx是Apache服务器不错的替代品。官网数据显示每秒TPS高达50W左右。本文…

基于51单片机的智能火灾报警系统的设计与实现

摘要:电子科技和城市建设的快速发展,电子设备产品使用频率和城市高层、地下以及大型综合性建筑修建的日益增多,在享受便捷生活的同时火灾隐患大大增加,一旦发生火灾,将带来严重危害。为预防火灾的发生,本文设计开发一种新型便捷智能火灾报警系统,由MCS-51 单片机、烟雾传…

C++学习—单例模式

目录 ​编辑 一,单例模式介绍 二,单例模式下的两种模式 1,饿汉模式 2,懒汉模式 一,单例模式介绍 单例:在全局只有一份实例。 单例模式是编程的经典模式之一。 二,单例模式下的两种模式 1&am…

面试:百度一面,吓尿了

公众号:程序员白特,可加前端技术交流群 前言 这是某211高校软件工程专业的师弟百度一面的题目和回答,全程高能,来看看你会多少~ 宇宙铁律,介绍下自己 还好,之前看到过敖丙大佬面试自我介绍5句话公式 - 掘…

C++6.0

思维导图 .编程题: 以下是一个简单的比喻,将多态概念与生活中的实际情况相联系:比喻:动物园的讲解员和动物表演 想象一下你去了一家动物园,看到了许多不同种类的动物,如狮子、大象、猴子等。现在&#xff0…

包教包会的Kotlin Flow教程

原文链接 包教包会的Kotlin Flow教程 公众号「稀有猿诉」 Kotlin中的Flow是专门用于处理异步数据流的API,是函数响应式编程范式(Functional Reactive Programming FRP)在Kotlin上的一个实现,并且深度融合了Kotlin的协程。是Kotlin中处理异步数据…

Springboot+vue的物流管理系统(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频: Springbootvue的物流管理系统(有报告)。Javaee项目,springboot vue前后端分离项目 项目介绍: 本文设计了一个基于Springbootvue的前后端分离的物流管理系统,采用M(model)…

Unity设备分级策略

Unity设备分级策略 前言 之前自己做的设备分级策略,在此做一个简单的记录和思路分享。希望能给大家带来帮助。 分级策略 根据拟定的评分标准,预生成部分已知机型的分级信息,且保存在包内;如果设备没有被评级过,则优…

四.Linux实用操作 12-14.环境变量文件的上传和下载压缩和解压

目录 四.Linux实用操作 12.环境变量 环境变量 环境变量--PATH $ 符号 自行设置环境变量 自定义环境变量PATH 总结 四.Linux实用操作 13.文件的上传和下载 上传,下载 rz,sz命令 四.Linux实用操作 14.压缩和解压 压缩格式 tar命令 tar命令压缩…

【C++初阶】deque容器的介绍以及为什么stack和queue选择deque的作为底层容器适配器

👦个人主页:Weraphael ✍🏻作者简介:目前学习C和算法 ✈️专栏:C航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞&#x1…

STM32中断定时器的使用

使用systimer来产生较为精确的定时,之前使用for循环来产生。 用示例工程时产生错误,原因是调用F103的3种容量器件,需要更换S汇编头函数。 另外在工程设置中,需要把HD设置为MD,重新编译即可成功。

运行错误(竞赛遇到的问题)

在代码提交时会遇见这样的错误: 此处运行错误不同于编译错误和答案错误,运行错误是指是由于在代码运行时发生错误,运行错误可能是由于逻辑错误、数据问题、资源问题等原因引起的。这些错误可能导致程序在运行时出现异常、崩溃。 导致不会显示…

Python——列表

一、列表的特性介绍 列表和字符串⼀样也是序列类型的数据 列表内的元素直接⽤英⽂的逗号隔开,元素是可变的,所以列表是可变的数据类型,⽽字符串不是。 列表的元素可以是 Python 中的任何类型的数据对象。如:字符串、…

2011-2021年商业银行财务指标面板数据

2011-2021年商业银行财务指标面板数据 1、时间:2011-2021年 2、来源:银行年报 3、指标:银行代码、银行中文简称、银行中文全称、银行英文全称、国家代码、银行性质、会计期间、会计年度、资产总计、净资产、负债总计、营业收入、营业利润、…

【C++】C++入门—初识构造函数 , 析构函数,拷贝构造函数,赋值运算符重载

C入门 六个默认成员函数1 构造函数语法特性 2 析构函数语法特性 3 拷贝构造函数特性 4 赋值运算符重载运算符重载赋值运算符重载特例:前置 与 后置前置:返回1之后的结果后置: Thanks♪(・ω・)ノ谢谢阅读&…

NNLM - 神经网络语言模型 | 高效的单词预测工具

本系列将持续更新NLP相关模型与方法,欢迎关注! 简介 神经网络语言模型(NNLM)是一种人工智能模型,用于学习预测词序列中下一个词的概率分布。它是自然语言处理(NLP)中的一个强大工具,…

DNS及相关实验

一、DNS DNS的定义:(domain name server)名字解析,又叫名称解析协议,传输协议TCP(端口号:53)和UDP(端口号:53) 解释: tcp:…

程序员也需要休息:为什么有时候他们不喜欢关电脑

程序员为什么不喜欢关电脑? 背景:作为程序员,长时间与电脑为伴是家常便饭。然而,有时候他们也会觉得厌倦和疲惫,不喜欢过多地与电脑打交道。本文将探讨程序员为何需要适当的休息和放松,以及如何更好地管理…

Excel TEXT函数格式化日期

一. 基本语法 ⏹Excel 的 TEXT 函数用于将数值或日期格式化为指定的文本格式 TEXT(value, format_text)二. 拼接路径案例 # 将当前单元格日期格式化 "ls -ld /data/jmw/01/"&TEXT(A2,"YYYYMMDD")&""# 此处的日期, 是名称管理器里面定…

踩坑实录(Fourth Day)

今天开工了,其实还沉浸在过年放假的喜悦中……今天在自己写 Vue3 的项目,虽说是跟着 B 站在敲,但是依旧是踩了一些个坑,就离谱……照着敲都能踩到坑,我也是醉了…… 此为第四篇(2024 年 02 月 18 日&#x…