前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于xv6 riscv实现学习os 其零:helloworld

基于xv6 riscv实现学习os 其零:helloworld

作者头像
AkemiHomura
发布2023-04-07 16:17:37
8380
发布2023-04-07 16:17:37
举报
文章被收录于专栏:homura的博客homura的博客
30933181
30933181

学习os的时间开始了! pixiv:30933181

前言

这个系列的目的还是以讲解xv6-riscv的代码以及记录我在做的事情为主,也会掺杂许多mini-riscv-os的代码介绍(关于xv6-riscv和mini-riscv-os的链接请看参考),并非教程倾向(但也会尽可能讲解一些基础知识),很多细节不会讲到。如果想要更详细的教程我建议你查看参考资料中引用的内容,在这一期我会列出一部参考的项目。

compiler的坑还没走多远,我又要开新的坑了,这是我很久之前想做但不敢做的事情。以前也做过一些尝试,比如说《30天自制os》以及6.828,前者讲的相对比较容易理解一些,但是当时的我缺少实践,后者难度较高,看不懂课后习题只能去查看别人的实现,东抄西抄总算抄完了前四章的内容,最后只留下了一些概念的印象。对我来说学会什么东西只有通过具体去实现,自己很难从什么概念去理解某个东西,也因此之前学的很多知识其实都是非常肤浅无用的。

实现os这件事情看起来是挺吓人的,本身复杂的概念和各种实现,同时需要许多前置知识。同时大多数os的开始都是离谱的x86bootloader,我想这个应该劝退了非常多的人。但最近发现做一个最简易的os或许并没有那么可怕,搜了一些项目,最简单的功能很少的系统只有一两千行代码,相对比较容易学习,同时riscv的bootloader部分没有乱七八糟的历史遗留,十分简洁,不会再因为这个劝退别人。也在这里感谢他们的付出。

注意事项

  1. mini-riscv-os是针对riscv32,而xv6针对的是riscv64,导致一些汇编上、编译选项以及一些其他的内容会有所不同
  2. 代码引用会直接使用项目名/路径的格式

此后不再赘述

环境配置

交叉编译工具链

参考链接

https://pdos.csail.mit.edu/6.828/2019/tools.html

我是在mac(M1)下开发的,homebrew在安装riscv-tools的时候会提示需要安装一些依赖。在我配置的时候遇到了flock这个依赖搞不定的问题,发现直接brew install flock安装的flock是其他东西,因此需要卸载flock并且使用brew tap的命令,安装好依赖再去按riscv-tools

代码语言:javascript
复制
brew uninstall flock
brew tap discoteq/discoteq
brew install flock
brew install riscv-tools

qemu

这个没什么好说的,直接用包管理安装就是

启动所需代码

bootloader

做了什么

  1. 设置栈的起始地址
  2. 跳转到c代码中

代码

mini-riscv-os/01-HelloOs/start.s

代码语言:javascript
复制
.equ STACK_SIZE, 8192

.global _start

_start:
    # setup stacks per hart
    csrr t0, mhartid                # read current hart id
    slli t0, t0, 10                 # shift left the hart id by 1024
    la   sp, stacks + STACK_SIZE    # set the initial stack pointer 
                                    # to the end of the stack space
    add  sp, sp, t0                 # move the current hart stack pointer
                                    # to its place in the stack space

    # park harts with id != 0
    csrr a0, mhartid                # read current hart id
    bnez a0, park                   # if we're not on the hart 0
                                    # we park the hart

    j    os_main                    # hart 0 jump to c

park:
    wfi
    j park

stacks:
    .skip STACK_SIZE * 4            # allocate space for the harts stacks

csrr是从csr(Control and Status Register)寄存器中read值,而其中的csrr reg, mhartid则是将hart id读到对应的reg中。hart是riscv中硬件线程的最小单位,在riscv的spec中是这样描述的

A RISC-V compatible core might support multiple RISC-V-compatible hardware threads, or harts, through multithreading.

这里的代码判断如果hart id不是0就跳到park这个循环中。实质上是只开启了一个hart

xv6-riscv/kernel/entry.S

代码语言:javascript
复制
# qemu -kernel loads the kernel at 0x80000000
        # and causes each hart (i.e. CPU) to jump there.
        # kernel.ld causes the following code to
        # be placed at 0x80000000.
.section .text
.global _entry
_entry:
        # set up a stack for C.
        # stack0 is declared in start.c,
        # with a 4096-byte stack per CPU.
        # sp = stack0 + (hartid * 4096)
        la sp, stack0
        li a0, 1024*4
        csrr a1, mhartid
        addi a1, a1, 1
        mul a0, a0, a1
        add sp, sp, a0
        # jump to start() in start.c
        call start
spin:
        j spin

xv6的启动代码中考虑了多个hart启动的情况,给每一个hard都设置stack的起始地址。而stack的起始地址是写在其他的c代码中

代码语言:javascript
复制
// entry.S needs one stack per CPU.
__attribute__ ((aligned (16))) char stack0[4096 * NCPU];

c代码

在c代码中打印出一个血统纯正的helloworld。这里其实隐含了很多的内容,但是暂且知道这样做就可以打印出helloworld即可。

对于xv6来说在进入os的main之前有许多设置状态的内容,这里暂且不讨论。

mini-riscv-os/01-HelloOs/os.c

代码语言:javascript
复制
#include <stdint.h>

#define UART        0x10000000
#define UART_THR    (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR    (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40          // LSR Bit 6: Transmitter empty; both the THR and LSR are empty

int lib_putc(char ch) {
	while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
	return *UART_THR = ch;
}

void lib_puts(char *s) {
	while (*s) lib_putc(*s++);
}

int os_main(void)
{
	lib_puts("hello, world\n");
	while (1) {}
	return 0;
}

ldscript

这里主要是需要指定这么几项内容

  1. 对于qemu来说,启动之后会读位于0x80000000这个地址的内容,因此我们需要将我们的内容放到这个地址开始。
  2. 指定OUTPUT_ARCH( “riscv” )
  3. 指定汇编入口地址,比如ENTRY( _entry )

xv6-riscv/kernel/entry.S

代码语言:javascript
复制
OUTPUT_ARCH( "riscv" )
ENTRY( _entry )

SECTIONS
{
  /*
   * ensure that entry.S / _entry is at 0x80000000,
   * where qemu's -kernel jumps.
   */
  . = 0x80000000;

  .text : {
    *(.text .text.*)
    . = ALIGN(0x1000);
    _trampoline = .;
    *(trampsec)
    . = ALIGN(0x1000);
    ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page");
    PROVIDE(etext = .);
  }

  .rodata : {
    . = ALIGN(16);
    *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */
    . = ALIGN(16);
    *(.rodata .rodata.*)
  }

  .data : {
    . = ALIGN(16);
    *(.sdata .sdata.*) /* do not need to distinguish this from .data */
    . = ALIGN(16);
    *(.data .data.*)
  }

  .bss : {
    . = ALIGN(16);
    *(.sbss .sbss.*) /* do not need to distinguish this from .bss */
    . = ALIGN(16);
    *(.bss .bss.*)
  }

  PROVIDE(end = .);
}

makefile

mini-riscv-os/01-HelloOs/Makefile

代码语言:javascript
复制
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32

QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none

OBJDUMP = riscv64-unknown-elf-objdump

all: os.elf

os.elf: start.s os.c
	$(CC) $(CFLAGS) -T os.ld -o os.elf $^

qemu: $(TARGET)
	@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
	@echo "Press Ctrl-A and then X to exit QEMU"
	$(QEMU) $(QFLAGS) -kernel os.elf

clean:
	rm -f *.elf

这里没什么好讲的,绝大多数选项都用不到,唯一要注意的是-march的值

riscv是一种模块化的指令集,不同的名字代表支持的扩展指令集不同,关于详情参考

RISC-V#ISA_base_and_extensions

之后直接通过make命令编译出elf之后通过qemu启动就好

参考

https://github.com/cccriscv/mini-riscv-os

https://github.com/mit-pdos/xv6-riscv

Specifications - RISC-V International

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-11-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 注意事项
  • 环境配置
    • 交叉编译工具链
      • qemu
      • 启动所需代码
        • bootloader
          • 做了什么
          • 代码
        • c代码
          • ldscript
            • makefile
            • 参考
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档