ysyx-运行时环境和基础设施(PA2.3)

运行时环境和基础设施 | 官方文档

PA2.3.程序, 运行时环境与AM

运行时环境,将运行时环境封装成库函数和AM - 裸机(bare-metal)运行时环境,这三个部分文档写的十分完善,这里就跳过了。

RTFSC(3)

代码中abstract-machine/目录下的源文件组织如下(部分目录下的文件并未列出):

abstract-machine
├── am                                  # AM相关
│   ├── include
│   │   ├── amdev.h
│   │   ├── am.h
│   │   └── arch                        # 架构相关的头文件定义
│   ├── Makefile
│   └── src
│       ├── mips
│       │   ├── mips32.h
│       │   └── nemu                    # mips32-nemu相关的实现
│       ├── native
│       ├── platform
│       │   └── nemu                    # 以NEMU为平台的AM实现
│       │       ├── include
│       │       │   └── nemu.h
│       │       ├── ioe                 # IOE
│       │       │   ├── audio.c
│       │       │   ├── disk.c
│       │       │   ├── gpu.c
│       │       │   ├── input.c
│       │       │   ├── ioe.c
│       │       │   └── timer.c
│       │       ├── mpe.c               # MPE, 当前为空
│       │       └── trm.c               # TRM
│       ├── riscv
│       │   ├── nemu                    # riscv32(64)相关的实现
│       │   │   ├── cte.c               # CTE
│       │   │   ├── start.S             # 程序入口
│       │   │   ├── trap.S
│       │   │   └── vme.c               # VME
│       │   └── riscv.h
│       └── x86
│           ├── nemu                    # x86-nemu相关的实现
│           └── x86.h
├── klib                                # 常用函数库
├── Makefile                            # 公用的Makefile规则
└── scripts                             # 构建/运行二进制文件/镜像的Makefile
    ├── isa
    │   ├── mips32.mk
    │   ├── riscv32.mk
    │   ├── riscv64.mk
    │   └── x86.mk
    ├── linker.ld                       # 链接脚本
    ├── mips32-nemu.mk
    ├── native.mk
    ├── platform
    │   └── nemu.mk
    ├── riscv32-nemu.mk
    ├── riscv64-nemu.mk
    └── x86-nemu.mk
  • abstract-machine/am/ - 不同架构的AM API实现, 目前我们只需要关注NEMU相关的内容即可. 此外, abstract-machine/am/include/am.h列出了AM中的所有API, 我们会在后续逐一介绍它们.
  • abstract-machine/klib/ - 一些架构无关的库函数, 方便应用程序的开发
//abstract-machine/am/src/platform/nemu/trm.c
#include <am.h>
#include <nemu.h>

extern char _heap_start;
int main(const char *args);

Area heap = RANGE(&_heap_start, PMEM_END);//结构用于指示堆区的起始和末尾
#ifndef MAINARGS
#define MAINARGS ""
#endif
static const char mainargs[] = MAINARGS;

void putch(char ch) {
  outb(SERIAL_PORT, ch);
}

void halt(int code) {
  nemu_trap(code);

  // should not reach here
  while (1);
}

void _trm_init() {
  int ret = main(mainargs);
  halt(ret);
}
  • Area heap结构用于指示堆区的起始和末尾
  • void putch(char ch)用于输出一个字符
  • void halt(int code)用于结束程序的运行
  • void _trm_init()用于进行TRM相关的初始化工作

这里解释一下什么是堆区:堆区是给程序自由使用的一段内存区间, 为程序提供动态分配内存的功能. TRM的API只提供堆区的起始和末尾, 而堆区的分配和管理需要程序自行维护。

这里halt()调用了nemu_trap()(函数在abstract-machine/am/src/platform/nemu/include/nemu.h)

# define nemu_trap(code) asm volatile("mv a0, %0; ebreak" : :"r"(code))
#elif defined(__ISA_LOONGARCH32R__)

这里使用了内联汇编语句,内联汇编语句允许我们在C代码中嵌入汇编语句。这里简单解释一下这个语句宏定义了nemu_trap()函数,这里asm是内联汇编的关键字,volatile是防止编译器编译时候对代码进行优化,如果不理解为什么这么做推荐读一下这边文章C语言中关键字volatile的含义及用法_volatile在c语言中的用法-CSDN博客

这条指令执行的就是ebreak指令,nemu_trap()宏还会把一个标识结束的结束码移动到通用寄存器中, 这样, 这段汇编代码的功能就和nemu/src/isa/$ISA/inst.cnemu_trap的行为对应起来了: 通用寄存器中的值将会作为参数传给set_nemu_state(), 将halt()中的结束码设置到NEMU的monitor中, monitor将会根据结束码来报告程序结束的原因.

am-kernels子项目用于收录一些可以在AM上运行的测试集和简单程序:

am-kernels
├── benchmarks                  # 可用于衡量性能的基准测试程序
│   ├── coremark
│   ├── dhrystone
│   └── microbench
├── kernels                     # 可展示的应用程序
│   ├── hello
│   ├── litenes                 # 简单的NES模拟器
│   ├── nemu                    # NEMU
│   ├── slider                  # 简易图片浏览器
│   ├── thread-os               # 内核线程操作系统
│   └── typing-game             # 打字小游戏
└── tests                       # 一些具有针对性的测试集
    ├── am-tests                # 针对AM API实现的测试集
    └── cpu-tests               # 针对CPU指令实现的测试集

注意NEMU运行程客户程序之前,需要把程序变成可执行文件,但是我们不能运行gcc直接编译,因为这里编译成的是linux下的可执行文件,但是我们当前的NEMU是无法执行这个文件的。 所以我们使用了交叉编译(交叉编译(cross compilation)是指在一种体系结构的计算机上生成另一种体系结构的可执行程序),为了不让链接器ld使用默认的方式链接, 我们还需要提供描述$ISA-nemu的运行时环境的链接脚本。

这里说一下他的编译过程:

  • gcc将$ISA-nemu的AM实现源文件编译成目标文件, 然后通过ar将这些目标文件作为一个库, 打包成一个归档文件abstract-machine/am/build/am-$ISA-nemu.a
  • gcc把应用程序源文件(如am-kernels/tests/cpu-tests/tests/dummy.c)编译成目标文件
  • 通过gcc和ar把程序依赖的运行库(如abstract-machine/klib/)也编译并打包成归档文件
  • 根据Makefile文件abstract-machine/scripts/$ISA-nemu.mk中的指示, 让ld根据链接脚本abstract-machine/scripts/linker.ld, 将上述目标文件和归档文件链接成可执行文件

我们对编译得到的可执行文件的行为进行简单的梳理:

  1. 第一条指令从abstract-machine/am/src/$ISA/nemu/start.S开始, 设置好栈顶之后就跳转到abstract-machine/am/src/platform/nemu/trm.c_trm_init()函数处执行.
  2. _trm_init()中调用main()函数执行程序的主体功能, main()函数还带一个参数, 目前我们暂时不会用到, 后面我们再介绍它.
  3. main()函数返回后, 调用halt()结束运行.

阅读Makefile

makefile文件

# Makefile for AbstractMachine Kernels and Libraries

### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)
html:
	cat Makefile | sed 's/^\([^#]\)/    \1/g' | markdown_py > Makefile.html
.PHONY: html

## 1. Basic Setup and Checks

### Default to create a bare-metal kernel image
ifeq ($(MAKECMDGOALS),)
  MAKECMDGOALS  = image
  .DEFAULT_GOAL = image
endif

### Override checks when `make clean/clean-all/html`
ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

### Print build info message
$(info # Building $(NAME)-$(MAKECMDGOALS) [$(ARCH)])

### Check: environment variable `$AM_HOME` looks sane
ifeq ($(wildcard $(AM_HOME)/am/include/am.h),)
  $(error $$AM_HOME must be an AbstractMachine repo)
endif

### Check: environment variable `$ARCH` must be in the supported list
ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk)))
ifeq ($(filter $(ARCHS), $(ARCH)), )
  $(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)")
endif

### Extract instruction set architecture (`ISA`) and platform from `$ARCH`. Example: `ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu`
ARCH_SPLIT = $(subst -, ,$(ARCH))
ISA        = $(word 1,$(ARCH_SPLIT))
PLATFORM   = $(word 2,$(ARCH_SPLIT))

### Check if there is something to build
ifeq ($(flavor SRCS), undefined)
  $(error Nothing to build)
endif

### Checks end here
endif

## 2. General Compilation Targets

### Create the destination directory (`build/$ARCH`)
WORK_DIR  = $(shell pwd)
DST_DIR   = $(WORK_DIR)/build/$(ARCH)
$(shell mkdir -p $(DST_DIR))

### Compilation targets (a binary image or archive)
IMAGE_REL = build/$(NAME)-$(ARCH)
IMAGE     = $(abspath $(IMAGE_REL))
ARCHIVE   = $(WORK_DIR)/build/$(NAME)-$(ARCH).a

### Collect the files to be linked: object files (`.o`) and libraries (`.a`)
OBJS      = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))
LIBS     := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions
LINKAGE   = $(OBJS) \
  $(addsuffix -$(ARCH).a, $(join \
    $(addsuffix /build/, $(addprefix $(AM_HOME)/, $(LIBS))), \
    $(LIBS) ))

## 3. General Compilation Flags

### (Cross) compilers, e.g., mips-linux-gnu-g++
AS        = $(CROSS_COMPILE)gcc
CC        = $(CROSS_COMPILE)gcc
CXX       = $(CROSS_COMPILE)g++
LD        = $(CROSS_COMPILE)ld
AR        = $(CROSS_COMPILE)ar
OBJDUMP   = $(CROSS_COMPILE)objdump
OBJCOPY   = $(CROSS_COMPILE)objcopy
READELF   = $(CROSS_COMPILE)readelf

### Compilation flags
INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS)))
INCFLAGS += $(addprefix -I, $(INC_PATH))

ARCH_H := arch/$(ARCH).h
CFLAGS   += -O2 -MMD -Wall -Werror $(INCFLAGS) \
            -D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \
            -D__ARCH__=$(ARCH) -D__ARCH_$(shell echo $(ARCH) | tr a-z A-Z | tr - _) \
            -D__PLATFORM__=$(PLATFORM) -D__PLATFORM_$(shell echo $(PLATFORM) | tr a-z A-Z | tr - _) \
            -DARCH_H=\"$(ARCH_H)\" \
            -fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \
            -Wno-main -U_FORTIFY_SOURCE -fvisibility=hidden
CXXFLAGS +=  $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions
ASFLAGS  += -MMD $(INCFLAGS)
LDFLAGS  += -z noexecstack

## 4. Arch-Specific Configurations

### Paste in arch-specific configurations (e.g., from `scripts/x86_64-qemu.mk`)
-include $(AM_HOME)/scripts/$(ARCH).mk

### Fall back to native gcc/binutils if there is no cross compiler
ifeq ($(wildcard $(shell which $(CC))),)
  $(info #  $(CC) not found; fall back to default gcc and binutils)
  CROSS_COMPILE :=
endif

## 5. Compilation Rules

### Rule (compile): a single `.c` -> `.o` (gcc)
$(DST_DIR)/%.o: %.c
	@mkdir -p $(dir $@) && echo + CC $<
	@$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.cc` -> `.o` (g++)
$(DST_DIR)/%.o: %.cc
	@mkdir -p $(dir $@) && echo + CXX $<
	@$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.cpp` -> `.o` (g++)
$(DST_DIR)/%.o: %.cpp
	@mkdir -p $(dir $@) && echo + CXX $<
	@$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.S` -> `.o` (gcc, which preprocesses and calls as)
$(DST_DIR)/%.o: %.S
	@mkdir -p $(dir $@) && echo + AS $<
	@$(AS) $(ASFLAGS) -c -o $@ $(realpath $<)

### Rule (recursive make): build a dependent library (am, klib, ...)
$(LIBS): %:
	@$(MAKE) -s -C $(AM_HOME)/$* archive

### Rule (link): objects (`*.o`) and libraries (`*.a`) -> `IMAGE.elf`, the final ELF binary to be packed into image (ld)
$(IMAGE).elf: $(OBJS) $(LIBS)
	@echo + LD "->" $(IMAGE_REL).elf
	@$(LD) $(LDFLAGS) -o $(IMAGE).elf --start-group $(LINKAGE) --end-group

### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar)
$(ARCHIVE): $(OBJS)
	@echo + AR "->" $(shell realpath $@ --relative-to .)
	@$(AR) rcs $(ARCHIVE) $(OBJS)

### Rule (`#include` dependencies): paste in `.d` files generated by gcc on `-MMD`
-include $(addprefix $(DST_DIR)/, $(addsuffix .d, $(basename $(SRCS))))

## 6. Miscellaneous

### Build order control
image: image-dep
archive: $(ARCHIVE)
image-dep: $(OBJS) $(LIBS)
	@echo \# Creating image [$(ARCH)]
.PHONY: image image-dep archive run $(LIBS)

### Clean a single project (remove `build/`)
clean:
	rm -rf Makefile.html $(WORK_DIR)/build/
.PHONY: clean

### Clean all sub-projects within depth 2 (and ignore errors)
CLEAN_ALL = $(dir $(shell find . -mindepth 2 -name Makefile))
clean-all: $(CLEAN_ALL) clean
$(CLEAN_ALL):
	-@$(MAKE) -s -C $@ clean
.PHONY: clean-all $(CLEAN_ALL)

makefile的逐行解释

html:
    cat Makefile | sed 's/^\([^#]\)/    \1/g' | markdown_py > Makefile.html
.PHONY: html

这里是当目标为html就把makefile转换成html文本,中间的操作就是用cat把makefile输出到管道中,当不是注释的行行首插入四个空格,然后使用markdown_py把文件保存为html文本。

ifeq ($(MAKECMDGOALS),)
  MAKECMDGOALS  = image
  .DEFAULT_GOAL = image
endif

如果没有指定目标,则执行MAKECMDGOALS  = image ,.DEFAULT_GOAL = image,这里解释一下MAKECMDGOALS这是一个变量表示用户输入的目标是什么。.DEFAULT_GOAL可用于告知如果在命令行中未指定目标,应该构建哪个目标(或目标)。否则,Make会简单地使它遇到的第一个目标。

ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

这句话就是对比目标是不是clean,clean-all,html如果是就进行如下操作(这里makefile没写,可能是后面需要自己补充)

$(info # Building $(NAME)-$(MAKECMDGOALS) [$(ARCH)])

打印一条信息,显示正在构建的目标。

ifeq ($(wildcard $(AM_HOME)/am/include/am.h),)
  $(error $$AM_HOME must be an AbstractMachine repo)
endif

这里wildcard的作用就是函数会展开为匹配指定模式的文件名列表。如果找到文件,则展开结果不为空;否则,展开结果为空。所以这句话的意思就是如果能找到$(AM_HOME)目录下的am.h不存在则执行下面的语句。下面一句话就是打印错误信息。

ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk)))
ifeq ($(filter $(ARCHS), $(ARCH)), )
  $(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)")
endif

第一句话获取 $AM_HOME/scripts 目录下所有 .mk 文件的基本名称,并将它们存储在变量 ARCHS 中。这里解释一下爱notdir它的作用是去除所有的目录信息,SRC里的文件名列表将只有文件名。basename就是返回字符串.前面的字符。ifeq中间的语句就是看看$(ARCHS)的文本呢是否在$(ARCH)中,这里解释一下就是filter是什么意思,$(filter 匹配模式,文本),下面一条还是打印错误信息。

ARCH_SPLIT = $(subst -, ,$(ARCH))
ISA        = $(word 1,$(ARCH_SPLIT))
PLATFORM   = $(word 2,$(ARCH_SPLIT))

第一句话就是替换ARCH中的 - 换成空格存储在ARCH_SPLIT变量中,第二句话就是提取刚刚赋值的变量中的第一个单词赋值给ISA,最后一句就是把第二个单词赋值给PLATFORM

ifeq ($(flavor SRCS), undefined)
  $(error Nothing to build)
endif

这里先介绍一下flavor函数 $(flavor var)

1.当输入变量在makefile及其include中都没有时,函数输出为’undefined’字符串
2.当输入变量在makefile及其include中有,且是用于了循环的变量时,输出’recursive’
3.当输入变量在makefile及其include中有且不是循环变量时,函数输出为’simple’
所以这句话就是判断SRCS变量是否定义,若没有定义则打印底下的错误信息。

WORK_DIR  = $(shell pwd)
DST_DIR   = $(WORK_DIR)/build/$(ARCH)
$(shell mkdir -p $(DST_DIR))

第一句话就是获取当前工作目录复制到WORK_DIR第二句和第一句语法类似,这里就不说了,最后一句就是在DST_DIR保存的目录下mkdir -p的作用就是递归创建目录,即使上级目录不存在,会按目录层级自动创建目录

IMAGE_REL = build/$(NAME)-$(ARCH)
IMAGE     = $(abspath $(IMAGE_REL))
ARCHIVE   = $(WORK_DIR)/build/$(NAME)-$(ARCH).a

第一句就是计算二进制映像文件的相对路径,并将其存储在变量 IMAGE_REL 中。第二句是保存的绝对路径,最后一句计算存档文件的路径,并将其存储在变量 ARCHIVE 中。

OBJS      = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))
LIBS     := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions
LINKAGE   = $(OBJS) \
  $(addsuffix -$(ARCH).a, $(join \
    $(addsuffix /build/, $(addprefix $(AM_HOME)/, $(LIBS))), \
    $(LIBS) ))

这里解释一下第一句,addprefix是把$(DST_DIR)/放到后面的变量前面,后面用了两个函数,addsuffix和basename,这里解释一下basename,把SRCS变量的后缀名去掉,然后用addsufix把.o变成SRCS的后缀名。第二句话就是对库列表进行排序,并将结果存储在变量 LIBS 中。最后一句就不写了,和第一句大同小异。

AS        = $(CROSS_COMPILE)gcc
CC        = $(CROSS_COMPILE)gcc
CXX       = $(CROSS_COMPILE)g++
LD        = $(CROSS_COMPILE)ld
AR        = $(CROSS_COMPILE)ar
OBJDUMP   = $(CROSS_COMPILE)objdump
OBJCOPY   = $(CROSS_COMPILE)objcopy
READELF   = $(CROSS_COMPILE)readelf

这里就是一些基本的定义

INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS)))
INCFLAGS += $(addprefix -I, $(INC_PATH))

ARCH_H := arch/$(ARCH).h
CFLAGS   += -O2 -MMD -Wall -Werror $(INCFLAGS) \
            -D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \
            -D__ARCH__=$(ARCH) -D__ARCH_$(shell echo $(ARCH) | tr a-z A-Z | tr - _) \
            -D__PLATFORM__=$(PLATFORM) -D__PLATFORM_$(shell echo $(PLATFORM) | tr a-z A-Z | tr - _) \
            -DARCH_H=\"$(ARCH_H)\" \
            -fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \
            -Wno-main -U_FORTIFY_SOURCE -fvisibility=hidden
CXXFLAGS +=  $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions
ASFLAGS  += -MMD $(INCFLAGS)
LDFLAGS  += -z noexecstack

这里首先介绍一下+=这个是追加赋值,就是在之前赋值的后面继续赋值。LIBS这里就是告诉链接器链接哪些库文件,这里前三句都是基本的赋值这里就不多说了。下面CFLAGS部分就是定义C编译器,CXXFLAGS是定义C++编译器,ASFLAGS汇编编译器,LDFLAGS定义链接器,

-include $(AM_HOME)/scripts/$(ARCH).mk

这个和-include和include一样唯一区别就是不会警告,include会先停止makefile的读取,先去读取include后面的文件,

### Fall back to native gcc/binutils if there is no cross compiler
ifeq ($(wildcard $(shell which $(CC))),)
  $(info #  $(CC) not found; fall back to default gcc and binutils)
  CROSS_COMPILE :=
endif

这句话的语法前面全部讲过这里就跳过了。
后面就是一些编译规则了这里也就跳过了。

这些大概就是abstract-machine的makefile,下面是必做题的一个做题思路。

必做题通过批处理模式运行NEMU

这里注意一下这句话
 

### Paste in arch-specific configurations (e.g., from `scripts/x86_64-qemu.mk`)
-include $(AM_HOME)/scripts/$(ARCH).mk

这句话打开了riscv32-nemu.mk,然后这个文件有打开了platform下的nemu.mk这里注意一下这句话,这句话表示make前我们先要切换到指定目录下。

NEMUFLAGS += -l -b $(shell dirname $(IMAGE).elf)/nemu-log.txt
run: image
	$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMUFLAGS)" IMG=$(IMAGE).bin

这里再看一下nemu的makefile,注意这里的make run,run是直接用$(NEMU_EXEC)执行的。而我们注意到ARGS这个变量在nemu.mk中定义过,所以我们只需要修改NEMUFLAGS的变量即可,我们当时读nemu源代码的时候函数sdb_set_batch_mode()的is_batch_mode()定义了这个模式,所以我用-b来表示这个模式。

//nemu/scripts/native.mk
NEMU_EXEC := $(BINARY) $(ARGS) $(IMG)
 
run: run-env
	$(call git_commit, "run NEMU")
	$(NEMU_EXEC)

实现常用的库函数

这一部分根据手册了解函数的含义写即可,没有什么难度就跳过了。
注意RTFM

介绍stdarg.h头文件

这个头文件用来用于函数接受可变数量的参数。这里推荐下面这篇文章

C库 —— <stdarg.h>-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值