HNU-2024操作系统实验-Lab1-环境配置

写在前面:

从2024年开始,HNU信息院计科和智能专业统一使用李老师基于UniProton编写的新实验,一共有八个实验,如果仅是按照指导书的内容将实验跑通还是比较容易的(第一个实验除外),而对于想要在将来走系统方向的同学,或者遇上比较苛刻的助教,需要将整个实验的代码完全看懂,那就是一件任务量很大的事情了。我在完成这一系列实验的过程也十分坎坷,获得了很多同学的帮助,向他们表示感谢,本文旨在为下一届的同学提供一些参考和帮助,需要注意的是,每个实验的作业题没有标准的答案,仅由我与其他同学讨论得到,欢迎指正。

一、实验目的

1.安装交叉工具链(aarch64)以及安装QEMU模拟器
2.创建裸机(Bare Metal)程序
3.构件工程
4.完成调试支持
5.建立自动化脚本
 

二、实验过程

1.配置交叉工具链及安装QEMU模拟器

1.安装交叉工具链
下载工具链,以下载工具链版本为11.2,宿主机为x86 64位 Linux机器为例  
wget https://developer.arm.com/-/media/Files/downloads/gnu/11.2-2022.02/binrel/gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf.tar.xz  
解压工具链  
tar -xf gcc-arm-(按Tab键补全)  
重命名工具链目录  
mv gcc-arm-(按Tab键补全) aarch64-none-elf  
2.将工具链目录加入到环境变量

这里的话需要在终端的命令行中输入vim ~/.bashrc指令,然后在最底下新增加一行将自己的工具链目录添加到环境变量,例如此处为/home/moonwine/arrch64-none-elf/bin,需要根据自己实际的目录进行替换,最后完成编辑后,在命令行输入source ~/.bashrc进行环境变量设置。

3.测试工具链是否安装完成

使用最常用的查看版本指令,如果正常显示则表明成功安装,如果报错多半是环境变量没有添加成功,检查工具链目录是否正确设置。

4.安装QEMU模拟器
sudo apt-get update  
sudo apt-get install qemu  
sudo apt-get install qemu-system  
5.安装Cmake
sudo apt-get install cmake  

2.创建裸机程序

1.按照UniProton设计项目的目录层次:

src目录下放置了所有的的源代码,src目录下包含bsp目录以及include目录,bsp目录存放与硬件关系密切的代码,本实验中包含CMakeList.txt以及两个汇编文件start.S和prt_reset_vector.S,include目录中存放项目的绝大部分头文件,如prt_typedef.h文件,该文件中实现了UniProton使用的基本数据类型和结构的定义。

2.在src目录下创建main.c:

就直接复制指导书就好

3.在src/include目录下创建prt_typedef.h:
/* 
 * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved. 
 * 
 * UniProton is licensed under Mulan PSL v2. 
 * You can use this software according to the terms and conditions of the Mulan PSL v2. 
 * You may obtain a copy of Mulan PSL v2 at: 
 *          http://license.coscl.org.cn/MulanPSL2 
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 
 * See the Mulan PSL v2 for more details. 
 * Create: 2009-12-22 
 * Description: 定义基本数据类型和数据结构。 
 */  
#ifndef PRT_TYPEDEF_H  
#define PRT_TYPEDEF_H  
  
#include <stddef.h>  
#include <stdint.h>  
#include <stdbool.h>  
  
#ifdef __cplusplus  
#if __cplusplus  
extern "C" {  
#endif /* __cpluscplus */  
#endif /* __cpluscplus */  
  
typedef unsigned char U8;  
typedef unsigned short U16;  
typedef unsigned int U32;  
typedef unsigned long long U64;  
typedef signed char S8;  
typedef signed short S16;  
typedef signed int S32;  
typedef signed long long S64;  
  
typedef void *VirtAddr;  
typedef void *PhyAddr;  
  
#ifndef OS_SEC_ALW_INLINE  
#define OS_SEC_ALW_INLINE  
#endif  
  
#ifndef INLINE  
#define INLINE static __inline __attribute__((always_inline))  
#endif  
  
#ifndef OS_EMBED_ASM  
#define OS_EMBED_ASM __asm__ __volatile__  
#endif  
  
/* 参数不加void表示可以传任意个参数 */  
typedef void (*OsVoidFunc)(void);  
  
#define ALIGN(addr, boundary) (((uintptr_t)(addr) + (boundary) - 1) & ~((uintptr_t)(boundary) - 1))  
#define TRUNCATE(addr, size)  ((addr) & ~((uintptr_t)(size) - 1))  
  
#ifdef YES  
#undef YES  
#endif  
#define YES 1  
  
#ifdef NO  
#undef NO  
#endif  
#define NO 0  
  
#ifndef FALSE  
#define FALSE ((bool)0)  
#endif  
  
#ifndef TRUE  
#define TRUE ((bool)1)  
#endif  
  
#ifndef NULL  
#define NULL ((void *)0)  
#endif  
  
#define OS_ERROR   (U32)(-1)  
#define OS_INVALID (-1)  
  
#ifndef OS_OK  
#define OS_OK 0  
#endif  
  
#ifndef OS_FAIL  
#define OS_FAIL 1  
#endif  
  
#ifndef U8_INVALID  
#define U8_INVALID 0xffU  
#endif  
  
#ifndef U12_INVALID  
#define U12_INVALID 0xfffU  
#endif  
  
#ifndef U16_INVALID  
#define U16_INVALID 0xffffU  
#endif  
  
#ifndef U32_INVALID  
#define U32_INVALID 0xffffffffU  
#endif  
  
#ifndef U64_INVALID  
#define U64_INVALID 0xffffffffffffffffUL  
#endif  
  
#ifndef U32_MAX  
#define U32_MAX 0xFFFFFFFFU  
#endif  
  
#ifndef S32_MAX  
#define S32_MAX 0x7FFFFFFF  
#endif  
  
#ifndef S32_MIN  
#define S32_MIN (-S32_MAX-1)  
#endif  
  
#ifndef LIKELY  
#define LIKELY(x) __builtin_expect(!!(x), 1)  
#endif  
#ifndef UNLIKELY  
#define UNLIKELY(x) __builtin_expect(!!(x), 0)  
#endif  
  
#ifdef __cplusplus  
#if __cplusplus  
}  
#endif /* __cpluscplus */  
#endif /* __cpluscplus */  
  
#endif /* PRT_TYPEDEF_H */  
4.在src/bsp目录下创建start.S:
    .global   OsEnterMain  
    .extern __os_sys_sp_end  
  
    .type     start, function  
    .section  .text.bspinit, "ax"  
    .balign   4  
  
    .global OsElxState  
    .type   OsElxState, @function  
OsElxState:  
    MRS    x6, CurrentEL // 把系统寄存器 CurrentEL 的值读入到通用寄存器 x6 中  
    MOV    x2, #0x4 // CurrentEL EL1: bits [3:2] = 0b01  
    CMP    w6, w2  
  
    BEQ Start // 若 CurrentEl 为 EL1 级别,跳转到 Start 处执行,否则死循环。  
  
OsEl2Entry:  
    B OsEl2Entry  
  
Start:  
    LDR    x1, =__os_sys_sp_end // 符号在ld文件中定义  
    BIC    sp, x1, #0xf // 设置栈指针  
  
    B      OsEnterMain  
  
OsEnterReset:  
    B      OsEnterReset  
5.在src/bsp目录下创建prt_reset_vector.S
DAIF_MASK = 0x1C0       // disable SError Abort, IRQ, FIQ  
  
    .global  OsVectorTable  
    .global  OsEnterMain  
  
    .section .text.startup, "ax"  
OsEnterMain:  
    BL      main  
  
    MOV     x2, DAIF_MASK // bits [9:6] disable SError Abort, IRQ, FIQ  
    MSR     DAIF, x2 // 把通用寄存器 x2 的值写入系统寄存器 DAIF 中  
  
EXITLOOP:  
    B EXITLOOP 
6.在src目录下创建链接文件arrch64-qemu.ld:

脚本较长,此处仅展示需要理解的部分:

ENTRY(__text_start)  
  
_stack_size = 0x10000;  
_heap_size = 0x10000;  
  
MEMORY  
{  
    IMU_SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x800000 /* 内存区域 */  
    MMU_MEM (rwx) : ORIGIN = 0x40800000, LENGTH = 0x800000 /* 内存区域 */  
}  
  
SECTIONS  
{  
    text_start = .;  
    .start_bspinit :  
    {  
        __text_start = .; /* __text_start 指向当前位置, "." 表示当前位置 */  
        KEEP(*(.text.bspinit))  
    } > IMU_SRAM  
  
    ... ... ...  
  
    .heap (NOLOAD) :  
    {  
        . = ALIGN(8);  
        PROVIDE (__HEAP_INIT = .);  
        . = . + _heap_size; /* 堆空间 */  
        . = ALIGN(8);  
        PROVIDE (__HEAP_END = .);  
    } > IMU_SRAM  
  
    .stack (NOLOAD) :  
    {  
        . = ALIGN(8);  
        PROVIDE (__os_sys_sp_start = .);  
        . = . + _stack_size; /* 栈空间 */  
        . = ALIGN(8);  
        PROVIDE (__os_sys_sp_end = .);  
    } > IMU_SRAM  
    end = .;  
  
    ... ... ...  
}

3.工程构建

1.创建src目录下的CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.12)

set(CMAKE_SYSTEM_NAME "Generic") # 目标系统(baremental):  cmake/tool_chain/uniproton_tool_chain_gcc_arm64.cmake 写的是Linux
set(CMAKE_SYSTEM_PROCESSOR "aarch64") # 目标系统CPU

set(TOOLCHAIN_PATH "/home/moonshine/aarch64-none-elf") # 修改为交叉工具链实际所在目录 build.py config.xml中定义
set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-g++)
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc)
set(CMAKE_LINKER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-ld)

# 定义编译和链接等选项
set(CC_OPTION "-Os   -Wformat-signedness    -Wl,--build-id=none   -fno-PIE -fno-PIE --specs=nosys.specs -fno-builtin -fno-dwarf2-cfi-asm -fomit-frame-pointer -fzero-initialized-in-bss -fdollars-in-identifiers -ffunction-sections -fdata-sections -fno-aggressive-loop-optimizations -fno-optimize-strlen -fno-schedule-insns -fno-inline-small-functions -fno-inline-functions-called-once -fno-strict-aliasing -finline-limit=20  -mlittle-endian -nostartfiles -funwind-tables")
set(AS_OPTION "-Os   -Wformat-signedness    -Wl,--build-id=none   -fno-PIE -fno-PIE --specs=nosys.specs -fno-builtin -fno-dwarf2-cfi-asm -fomit-frame-pointer -fzero-initialized-in-bss -fdollars-in-identifiers -ffunction-sections -fdata-sections -fno-aggressive-loop-optimizations -fno-optimize-strlen -fno-schedule-insns -fno-inline-small-functions -fno-inline-functions-called-once -fno-strict-aliasing -finline-limit=20  -mlittle-endian -nostartfiles -funwind-tables")
set(LD_OPTION " ")
set(CMAKE_C_FLAGS "${CC_OPTION} ")
set(CMAKE_ASM_FLAGS "${AS_OPTION} ")
set(CMAKE_LINK_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本
set(CMAKE_EXE_LINKER_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本
set (CMAKE_C_LINK_FLAGS " ")
set (CMAKE_CXX_LINK_FLAGS " ")

set(HOME_PATH ${CMAKE_CURRENT_SOURCE_DIR})

set(APP "miniEuler") # APP变量,后面会用到 ${APP}
project(${APP} LANGUAGES C ASM) # 工程名及所用语言
set(CMAKE_BUILD_TYPE Debug) # 生成 Debug 版本

include_directories( # include 目录
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/bsp
)

add_subdirectory(bsp) # 包含子文件夹的内容

list(APPEND OBJS $<TARGET_OBJECTS:bsp>)
add_executable(${APP} main.c ${OBJS}) # 可执行文件

此处需要将set(TOOLCHAIN_PATH)中的路径更改为实际交叉工具链所在位置,此处为:“/home/moonwine/arrch64-none-elf”

2.创建src/bsp目录下的CMakeLists.txt文件:
set(SRCS start.S prt_reset_vector.S )
add_library(bsp OBJECT ${SRCS})  # OBJECT类型只编译生成.o目标文件,但不实际链接成库

此处需要将我们所创建的文件纳入构建系统,即在set中包含start.S和prt_reset_vector.S文件,同时在add_library中加入当前目录bsp

4.编译运行

1.编译

在lab1目录下创建目录build用于存放编译过程产生的一系列中间文件,并进入build目录执行指令(注意一定要在build目录下,如果错在其他目录下执行了指令,需要将生成的中间文件全部删除,否则会报错):

cmake ../src  
cmake --build .  

对于大多数同学,这个地方第一次执行指令的时候都会产生报错“error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory”,原因是缺少相应的库,需要使用指令进行安装(这里是本实验最难的部分,需要安装一系列的依赖库,目的是为了后面调试使用):

sudo apt-get update sudo apt-get install -y build-essential libssl-dev libffi-dev libbz2-dev libreadline-dev libsqlite3-dev libncurses5-dev libncursesw5-dev xz-utils tk-dev   
下载并解压python3.6的源码   
wget -c https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tar.xz tar -xf Python-3.6.15.tar.xz   
编译安装   
cd Python-3.6.15 LDFLAGS="-L/usr/lib/x86_64-linux-gnu" ./configure ./configure --enable-shared   
make sudo   
make install   
sudo ldconfig  
2.运行

如果使用cmake指令后没有任何报错,显示built target miniEuler,表明已经完成裸机的创建,接着进入lab1目录下(不在这个目录下运行会报错)运行指令:

qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler  -s  

正常运行后屏幕上会显示AArch Bare Metal,然后卡住不动,这就是正常的(当时我还以为是什么bug),因为我们的裸机现在只具有打印的功能,打印完之后就进入无限循环。如果想退出裸机内核需要同时按下ctrl和字母键a,再按下x。

5.创建自动化脚本

sh makeMiniEuler.sh   
sh runMiniEuler.sh   

至此,整个内核的构建已经完成。

三、测试及分析

1.如上图自动化脚本类似,正常运行程序:

我这里使用的是运行指令,使用自动化脚本也是一样的效果。

qemu-system-aarch64代表启动armv8架构的虚拟机

-machine virt 来指定虚拟机类型为 virt

-m 1024M 来指定虚拟机内存大小为 1024M

-cpu cortex-a53 来指定虚拟机的 CPU 类型为 cortex-a53

-nographic 来禁用图形界面

-kernel build/miniEuler 来指定内核映像文件为我们自己的操作系统内核miniEuler

-S  选项表示在启动时暂停虚拟机并等待 gdb 连接。默认服务器端口为1234

2.代码原理分析

main.c:

定义了一个宏UART_REG_WRITE,用于实现将字符写入地址为0x9000000的位置,main.c文件的主题功能就是将字符串AArch64 Bare Metal逐个输入0x9000000处,实现字符串输出。(关于为什么向内存地址0x9000000输入字符串就可以实现字符串输入,这是一种操作系统与硬件交互的规定,这个地址被规定为操作系统与I/O设备之间的借口而非我们平常理解的地址,想要具体深入理解的同学可以自行上网查阅)

start.S:

首先声明两个外部定义的变量OsEnterMain 和 __os_sys_sp_end,其中OsEnterMain来自于prt_reset_vector.S,__os_sys_sp_end来自于脚本aarch64-qemu.ld,然后定义.text.bspinit包含本行之后的代码,表示一个可分配可执行的段,接着定义整个程序的入口OsElxState,程序运行时会跳转到此处开始执行(入口的定义在链接脚本aarch64-qemu.ld中实现)程序开始运行时,首先会执行指令MRS x6,CurrentEL,将系统寄存器的值读入到通用寄存器x6(MRS表示Move to register from system),然后比较该寄存器的低32位w6与0x4是否相等,由于CurrentEL系统寄存器中的索引第2位和第三位表示EL级别,所以等价于判断当前的EL级别是否为1,即内核态,若当前级别处于内核态,则进入start区,首先利用链接文件中定义的全局栈低指针__os_sys_sp_end对栈区进行初始化,然后跳转到prt_reset_vector.S中的OsEntermain进行执行;若当前级别处于用户态,则无法陷入操作系统内核执行内核指令,进入死循环。

prt_reset_vector.S:

跳转进入操作系统内核的主程序main.c,执行完毕后返回(BL指令返回),由于此时还未设置中断处理,故将DAIF寄存器中的Debug、SError、IRQ和FIQ位禁用,最后进入死循环。

aarch64-qemu.ld:

本文件为链接脚本文件,首先通过ENTRY声明整个程序的入口为__text_start,接着定义堆区、栈区大小均为0x10000,然后使用MEMORY定义两个内存空间IMU_SRAM与MMU_MEM,并定义了它们的起始位置分别为0x40000000,0x40800000、大小均为0x800000以及权限均为“rwx”,最后在SECTIONS中定义了__text_start的具体位置,然后将start.S文件中的代码段.text.bspinit插入__text_start后,如此操作,实质上则定义了整个程序的入口为start.S文件中的OsElxState,以及定义了堆区空间和栈区空间的起始位置和结束位置,并将起始代码段、堆栈空间保存于内存空间IMU_SRAM。

. = ALIGN(8);表示将链接器的当前位置对齐为8的倍数

PROVIDE表示定义起始位置和结束位置

. = . + _heap_size(_stack_size);将链接器的当前位置向前偏移堆栈空间的大小,以便为程序的堆栈分配空间。

四、Lab1作业

作业一:商业操作系统都有复杂的构建系统,试简要分析 UniProton 的构建系统。

UniProton通过在根目录下执行 python build.py m4 (m4是指目标平台,还有如hi3093等)进行构建,在这个python文件的主函数中,会根据命令行参数创建一个Compile类的实例,并调用这个实例的UniProtonCompile()方法来开始编译过程,完成项目的构建。

分析构建的核心文件build.py:

#!/usr/bin/env python3
# coding=utf-8
# The build entrance of UniProton.
# Copyright (c) 2009-2023 Huawei Technologies Co., Ltd. All rights reserved.

import os
import sys
import time
import shutil
import subprocess
import platform
from sys import argv
UniProton_home = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, "%s/cmake/common/build_auxiliary_script"%UniProton_home)
from make_buildef import make_buildef
sys.path.insert(0, "%s/build/uniproton_ci_lib"%UniProton_home)
import globle
from logs import BuilderNolog, log_msg
from get_config_info import *


class Compile:

    # 根据makechoice获取config的配置的环境,compile_mode, lib_type,
    def get_config(self, cpu_type, cpu_plat):
        self.compile_mode = get_compile_mode()
        self.lib_type, self.plam_type, self.hcc_path, self.kconf_dir, self.system, self.core = get_cpu_info(cpu_type, cpu_plat, self.build_machine_platform)
        if not self.compile_mode and self.lib_type and self.plam_type and self.hcc_path and self.kconf_dir:
            log_msg('error', 'load config.xml env error')
            sys.exit(0)
        self.config_file_path = '%s/build/uniproton_config/config_%s'%(self.home_path, self.kconf_dir)

        self.objcopy_path = self.hcc_path

    def setCmdEnv(self):
        self.build_time_tag = time.strftime('%Y-%m-%d_%H:%M:00')
        self.log_dir = '%s/logs/%s' % (self.build_dir, self.cpu_type)
        self.log_file = '%s.log' % self.kconf_dir

    def SetCMakeEnviron(self):
        os.environ["CPU_TYPE"] = self.cpu_type
        os.environ["PLAM_TYPE"] = self.plam_type
        os.environ["LIB_TYPE"] = self.lib_type
        os.environ["COMPILE_OPTION"] = self.compile_option
        os.environ["HCC_PATH"] = self.hcc_path
        os.environ["UNIPROTON_PACKING_PATH"] = self.UniProton_packing_path
        os.environ["CONFIG_FILE_PATH"] = self.config_file_path
        os.environ["LIB_RUN_TYPE"] = self.lib_run_type
        os.environ["HOME_PATH"] = self.home_path
        os.environ["COMPILE_MODE"] = self.compile_mode
        os.environ["BUILD_MACHINE_PLATFORM"] = self.build_machine_platform
        os.environ["SYSTEM"] = self.system
        os.environ["CORE"] = self.core
        os.environ["OBJCOPY_PATH"] = self.objcopy_path
        os.environ['PATH'] = '%s:%s' % (self.hcc_path, os.getenv('PATH'))

    # 环境准备,准备执行cmake,make,makebuildfile,CmakeList需要的环境
    # 每次compile之前请调用该函数
    def prepare_env(self, cpu_type, choice):
        # makebuildfile需要的环境kconf_dir
        # cmake需要的环境cmake_env_path,home_path(cmakelist所在的路径),home_path,
        # make cmd拼接需要的环境:home_path,UniProton_make_jx,log_dir,log_file,build_time_tag, UniProton_make_jx

        #根据cpu_type, choice从config文件中获取并初始化初始化hcc_path, plam_type, kconf_dir
        #根据输入分支获取
        #从编译镜像环境获取
        self.get_config(cpu_type, choice)
        self.setCmdEnv()
        self.SetCMakeEnviron()

    #获取编译环境是arm64还是x86,用户不感知,并将其写入环境变量。
    def getOsPlatform(self):
        self.cmake_env_path = get_tool_info('cmake', 'tool_path')

        if platform.uname()[-1] == 'aarch64':
            self.build_machine_platform = 'arm64'
        else:
            self.build_machine_platform = 'x86'

    # 获取当前编译的路径信息,配置文件信息,编译选项信息
    def __init__(self, cpu_type: str, make_option="normal", lib_run_type="FPGA", choice="ALL", make_phase="ALL",
                 UniProton_packing_path=""):
        # 当前路径信息
        self.system = ""
        self.objcopy_path = ""
        self.builder = None
        self.compile_mode = ""
        self.core = ""
        self.plam_type = ""
        self.kconf_dir = ""
        self.build_tmp_dir = ""
        self.log_dir = ""
        self.lib_type = ""
        self.hcc_path = ""
        self.log_file = ""
        self.config_file_path = ""
        self.build_time_tag = ""
        self.build_dir = globle.build_dir
        self.home_path = globle.home_path
        self.kbuild_path = globle.kbuild_path
        # 当前选项信息
        self.cpu_type = cpu_type
        self.compile_option = make_option
        self.lib_run_type = lib_run_type
        self.make_choice = choice.lower()
        self.make_phase = make_phase
        self.UniProton_packing_path = UniProton_packing_path if make_phase == "CREATE_CMAKE_FILE" else '%s/output'%self.home_path
        self.UniProton_binary_dir = os.getenv('RPROTON_BINARY_DIR')
        self.UniProton_install_file_option = os.getenv('RPROTON_INSTALL_FILE_OPTION')
        self.UniProton_make_jx = 'VERBOSE=1' if self.UniProton_install_file_option == 'SUPER_BUILD' else 'VERBOSE=1 -j' + str(os.cpu_count())
        # 当前编译平台信息
        self.getOsPlatform()

    #调用cmake生成Makefile文件,需要
    def CMake(self):
        if self.UniProton_binary_dir:
            self.build_tmp_dir = '%s/output/tmp/%s' % (self.UniProton_binary_dir, self.kconf_dir)
        else:
            self.build_tmp_dir = '%s/output/tmp/%s' % (self.build_dir, self.kconf_dir)
        os.environ['BUILD_TMP_DIR'] = self.build_tmp_dir

        if not os.path.exists(self.build_tmp_dir):
            os.makedirs(self.build_tmp_dir)
        if not os.path.exists(self.log_dir):
            os.makedirs(self.log_dir)

        log_msg('info', 'BUILD_TIME_TAG %s' % self.build_time_tag)
        self.builder = BuilderNolog(os.path.join(self.log_dir, self.log_file))
        if self.make_phase in ['CREATE_CMAKE_FILE', 'ALL']:
            real_path = os.path.realpath(self.build_tmp_dir)
            if os.path.exists(real_path):
                shutil.rmtree(real_path)
            os.makedirs(self.build_tmp_dir)
            #拼接cmake命令
            if self.compile_option == 'fortify':
                cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
                      '-DCMAKE_C_COMPILER_LAUNCHER="sourceanalyzer;-b;%sproject" ' \
                      '-DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
                self.cmake_env_path, self.home_path, self.home_path, self.cpu_type,
                self.UniProton_packing_path, self.log_dir, self.log_file)
            elif self.compile_option == 'hllt':
                cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
                      '-DCMAKE_C_COMPILER_LAUNCHER="lltwrapper" -DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
                self.cmake_env_path, self.home_path, self.home_path, self.UniProton_packing_path, self.log_dir, self.log_file)
            else:
                cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
                      '-DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
                self.cmake_env_path, self.home_path, self.home_path, self.UniProton_packing_path, self.log_dir, self.log_file)
            #执行cmake命令
            if self.builder.run(cmd, cwd=self.build_tmp_dir, env=None):
                log_msg('error', 'generate makefile failed!')
                return False

        log_msg('info', 'generate makefile succeed.')
        return True

    def UniProton_clean(self):
        for foldername,subfoldernames,filenames in os.walk(self.build_dir):
            for subfoldername in subfoldernames:
                if subfoldername in ['logs','output','__pycache__']:
                    folder_path = os.path.join(foldername,subfoldername)
                    shutil.rmtree(os.path.relpath(folder_path))
            for filename in filenames:
                if filename == 'prt_buildef.h':
                    file_dir = os.path.join(foldername,filename)
                    os.remove(os.path.relpath(file_dir))
        if os.path.exists('%s/cmake/common/build_auxiliary_script/__pycache__'%self.home_path):
            shutil.rmtree('%s/cmake/common/build_auxiliary_script/__pycache__'%self.home_path)
        if os.path.exists('%s/output'%self.home_path):
            shutil.rmtree('%s/output'%self.home_path)
        if os.path.exists('%s/tools/SRE/x86-win32/sp_makepatch/makepatch'%self.home_path):
            os.remove('%s/tools/SRE/x86-win32/sp_makepatch/makepatch'%self.home_path)
        if os.path.exists('%s/build/prepare/__pycache__'%self.home_path):
            shutil.rmtree('%s/build/prepare/__pycache__'%self.home_path)
        return True


    def make(self):
        if self.make_phase in ['EXECUTING_MAKE', 'ALL']:
            self.builder.run('make clean', cwd=self.build_tmp_dir, env=None)
            tmp = sys.argv
            if self.builder.run(
                    'make all %s &>> %s/%s' % (
                    self.UniProton_make_jx, self.log_dir, self.log_file), cwd=self.build_tmp_dir, env=None):
                log_msg('error', 'make %s %s  failed!' % (self.cpu_type, self.plam_type))
                return False
            sys.argv = tmp
            if self.compile_option in ['normal', 'coverity', 'single']:
                if self.builder.run('make install %s &>> %s/%s' % (self.UniProton_make_jx, self.log_dir, self.log_file), cwd=self.build_tmp_dir, env=None):
                    log_msg('error', 'make install failed!')
                    return False
            if os.path.exists('%s/%s' % (self.log_dir, self.log_file)):
                self.builder.log_format()
        
        log_msg('info', 'make %s %s succeed.' % (self.cpu_type, self.plam_type))
        return True

    def SdkCompaile(self)->bool:
        # 判断该环境中是否需要编译
        if self.hcc_path == 'None':
            return True

        self.MakeBuildef()
        if self.CMake() and self.make():
            log_msg('info', 'make %s %s lib succeed!' % (self.cpu_type, self.make_choice))
            return True
        else:
            log_msg('info', 'make %s %s lib failed!' % (self.cpu_type, self.make_choice))
            return False

    # 对外函数,调用后根据类初始化时的值进行编译
    def UniProtonCompile(self):
        #清除UniProton缓存
        if self.cpu_type == 'clean':
            log_msg('info', 'UniProton clean')
            return self.UniProton_clean()
        # 根据cpu的编译平台配置相应的编译环境。
        if self.make_choice == "all":
            for make_choice in globle.cpu_plat[self.cpu_type]:
                self.prepare_env(self.cpu_type, make_choice)
                if not self.SdkCompaile():
                    return False
        else:
            self.prepare_env(self.cpu_type, self.make_choice)
            if not self.SdkCompaile():
                return False
        return True

    def MakeBuildef(self):

        if not make_buildef(globle.home_path,self.kconf_dir,"CREATE"):
            sys.exit(1)
        log_msg('info', 'make_buildef_file.sh %s successfully.' % self.kconf_dir)

# argv[1]: cpu_plat 表示要编译的平台:
# argv[2]: compile_option 控制编译选项,调用不同的cmake参数,目前只有normal coverity hllt fortify single(是否编译安全c,组件化独立构建需求)
# argv[3]: lib_run_type lib库要跑的平台 faga sim等
# argv[4]: make_choice 
# argv[5]: make_phase 全量构建选项
# argv[6]: UniProton_packing_path lib库的安装位置
if __name__ == "__main__":
    default_para = ("all", "normal", "FPGA", "ALL", "ALL", "")
    if len(argv) == 1:
        para = [default_para[i] for i in range(0, len(default_para))]
    else:
        para = [argv[i+1] if i < len(argv) - 1 else default_para[i] for i in range(0,len(default_para))]

    cur_cpu_type = para[0].lower()
    cur_compile_option = para[1].lower()
    cur_lib_run_type = para[2]
    cur_make_choice = para[3]
    cur_make_phase = para[4]
    cur_UniProton_packing_path = para[5]
    for plat in globle.cpus_[cur_cpu_type]:
        UniProton_build = Compile(plat, cur_compile_option, cur_lib_run_type, cur_make_choice, cur_make_phase, cur_UniProton_packing_path)
        if not UniProton_build.UniProtonCompile():
            sys.exit(1)
    sys.exit(0)

在上面的代码中我们可以看到,代码的主体是一个类Compile,主函数通过根据参数对Compile类进行实例化,然后调用类中的方法UniProtonCompile(,从而实现使用cmake进行构建,对类中的各方法进行分析:

  1. __init__方法:根据python的类相关语法可知,本方法相当于C++中的构造函数,在类Compile被实例化之后就会自动调用,获取当前编译的路径信息,配置文件信息,编译选项信息并保存在实例化对象UniProton_build中。
  2. get_config方法:本方法是进行编译前的准备工作,获取config配置的环境,通过调用get_compile_mode函数来获取编译模式、: 调用 get_cpu_info() 函数获取 CPU 相关信息,并将结果分别赋值给 self.lib_type、self.plam_type、self.hcc_path、self.kconf_dir、self.system 和 self.core,然后检查以上获取的参数是否存在,如果任何一个参数不存在,则打印错误信息并退出程序,最后设置config的文件路径。
  3. setCmdEnv方法:本方法同样是进行编译前的准备工作,首先获取的当前的时间,按照年-月-日_小时:分钟的格式进行存储,同时创建一个日志文件,用于保存构建过程的日志信息。
  4. SetCMakeEnviron方法,本方法是设置Cmake编译过程的环境变量,包括 CPU类型、平台类型、库类型、编译选项、编译器路径、打包路径、配置文件路径。
  5. prepare_env方法,这个方法相当于是一个封装,调用了234三个方法,准备执行cmake,make,makebuildfile,CmakeList需要的环境,在每一次编译前都调用此函数。
  6. getOsPlatform方法:获取编译环境是arm64还是x86,用户不感知,并将编译环境写入环境变量。
  7. Cmake方法:本方法是类中的核心方法,执行 CMake 构建过程。首先,它设置了临时构建目录self.build_tmp_dir 的路径,如果 UniProton_binary_dir 存在,就使用它,否则使用 build_dir。然后将这个临时构建目录的路径设置为环境变量 BUILD_TMP_DIR。接着将检查临时构建目录和日志目录是否存在,如果不存在,则创建这些目录。同时,它记录了构建时间标签,并创建了一个 BuilderNolog 对象,用于执行构建操作。如果 make_phase 是 'CREATE_CMAKE_FILE' 或 'ALL',就删除临时构建目录并重建,然后构造 CMake 命令。根据 compile_option 的值,创建不同的 CMake 命令,这里考虑了 'fortify'、'hllt' 和其他情况。然后,函数会执行拼接好的 CMake 命令。如果命令执行失败,它会记录错误信息并返回 False。如果 CMake 命令执行成功,它会记录成功信息并返回 True。
  8. UniProton_clean方法:本方法在构建过程结束后清理不再需要的文件和文件夹,以释放磁盘空间。
  9. Make方法:本方法负责执行项目的构建和安装,首先检查make_phase属性是否为'EXECUTING_MAKE'或'ALL',如果是,那么将清理构建目录,并执行make all命令构建项目。如果构建失败,它会记录一条错误日志,并返回False。然后进行判断,如果compile_option属性是'normal', 'coverity', 或'single',它会执行make install命令将项目安装到预定位置。如果安装失败,它也会记录一条错误日志,并返回False。如果日志文件存在,它会调用log_format方法来格式化日志。最后,如果构建和安装都成功,它会记录一条信息日志,并返回True。
  10. Sdkcompaile方法:本方法是一个验证方法,用于判断当前环境是否需要编译。
  11. UniProtonCompile方法:该方法是一个函数的封装,调用后首先执行clean操作清除UniProton的缓存,接着调用prepare_env方法根据cpu的编译平台配置相应的编译环境,最后调用Sdkcompaile方法来判断是否编译成功。

作业二:学习如何调试项目。

1.在wsl远程调试项目:

首先在打开一个wsl终端(对应虚拟机终端),并启动程序,加上-S指令冻结CPU,使程序在入口停止等待调试:

然后再新打开一个wsl终端(这里对应虚拟机的终端),在新的终端中启动调试客户端:

在gdb下输入指令:target remote localhost:1234远程连接到第一个终端中运行的程序,并使用disassemble进行反汇编:

使用指令i r查看所有寄存器的值:

使用si指令进行单步运行:

使用x/20xw 0x40000000可以查看从地址0x40000000起始的20个四字节的地址空间中的值,以16进制形式表示,0x40000000是第一条指令对应的机器码的起始地址:

 可以使用指令set $x24 = 0x7来设置寄存器x24的值为7,使用p指令可以查看寄存器的内容:

使用指令layout asm可以在运行时实时查看当前运行到具体的哪一条指令:

2.将调试集成到vscode中:

首先在终端中的lab1目录下输入指令code .,在vscode中远程连接虚拟机:

然后与直接在终端中调试相同,在终端处运行程序(或者使用自动化脚本),加上-S指令冻结CPU,使程序在入口停止等待调试:

点击左侧的调试选项,最初会弹出一个launch.json文件,点击创建后会自动填充相关信息,注意将miDebuggerPath修改为自己实际的交叉调试器路径,格式可以参考如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Windows 上的 Bash 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "输入程序名称,例如 ${workspaceFolder}/a.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "pipeTransport": {
                "debuggerPath": "/usr/bin/gdb",
                "pipeProgram": "${env:windir}\\system32\\bash.exe",
                "pipeArgs": ["-c"],
                "pipeCwd": ""
            },
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        },


        {
            "name": "aarch64-gdb",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/miniEuler",
            "stopAtEntry": true,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "launchCompleteCommand": "exec-run",
            "MIMode": "gdb",
            "miDebuggerPath":"/home/moonwine/arrch64-none-elf/bin/aarch64-none-elf-gdb", // 修改成交叉调试器gdb对应位置
            "miDebuggerServerAddress": "localhost:1234",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ],

}

准备工作就绪后,开始调试,可以看到程序顺利在入口处停止:

在调试控制台处输入指令-exec x/20xw 0x40000000可以查看从地址0x40000000起始的20个四字节的地址空间中的值,以16进制形式表示:

此外,交叉编译工具链中可以执行gdb的所有调试指令,不过要在gdb指令前加上-exec,如:

显示所有寄存器。-exec info all-registers

查看寄存器内容。-exec p/x $pc

修改寄存器内容。-exec set $x24 = 0x5 (将寄存器x24的值设置为0x5)

修改指定内存位置的内容。-exec set {int}0x4000000 = 0x1 或者 -exec set *((int *) 0x4000000) = 0x1

修改指定MMIO 寄存器的内容。 -exec set *((volatile int *) 0x08010004) = 0x1

退出调试 -exec q

单步执行代码,直到完成输出后进入无限循环:

五、心得体会

1.对操作系统的理解更加深入。

2.深入理解操作系统内核的底层原理。

3.学会对qemu模拟器运行的程序进行调试,对vscode的json配置更加清楚。

4.对Ubuntu的使用也更熟悉。

  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 申请内存、释放内存和查看内存使用情况。这些操作将通过系统调用来实现。内存管理器的主要功能是管理系统中的内存分配和释放,确保内存使用的高效性和安全性。虽然内存管理器还没有完成,但开发人员正在努力工作,以便在不久的将来发布完整的版本。 ### 回答2: 内存分配、内存释放和内存查找。内存分配可以让程序在运行时获取需要的内存空间,内存释放则是将不再需要的内存空间还给操作系统,以便重新分配给其他程序使用。内存查找则是检查某个内存地址是否已经被分配。这些功能对于操作系统运行和程序开发都具有很大的重要性。 内存管理器的实现主要考虑了几个因素。首先是内存的物理组织形式。操作系统需要能够识别内存的物理组织形式,才能够有效地进行内存管理。其次是内存的分配和释放。操作系统需要能够根据不同的需求分配不同大小的内存空间,并且能够及时地释放内存空间以提高系统的运行效率。还有一个重要的因素是内存的安全性。操作系统需要确保不同程序之间的内存空间互相隔离,避免出现内存泄露或者内存破坏等问题。 对于内存管理器的实现方式,目前有很多种不同的方法。其中,最常见的是分页内存管理方式。这种方式将内存空间分成若干个大小相等的页面,每个页面都有自己的地址和内存状态,操作系统只需要记录每个页面的状态和分配情况即可。当程序需要内存空间时,内存管理器会寻找空闲的页面,并将其分配给程序使用。当程序不再需要这些内存空间时,内存管理器会将其释放,并将页面标记为未分配状态。 总之,内存管理器是一个操作系统非常重要的组件,对于系统的性能和稳定性都有很大的影响。hnu-os的内存管理器的实现将进一步完善,以满足更多的需求。 ### 回答3: 内存分配、内存释放和内存查看。但是,由于内存管理器的实现需要许多复杂的算法和微妙的技巧,因此开发人员需要花费更多的时间来完成它。 内存管理器是操作系统中最重要的组件之一,因为它确保操作系统能够正确地使用计算机的内存。内存管理器的主要任务是将物理内存地址映射到虚拟内存地址,并跟踪哪些内存块已经被分配、哪些是空闲的,以及哪些正在使用。 在操作系统中,内存被分割成多个块。当一个程序需要内存时,内存管理器会分配一个或多个块来满足程序的要求。当程序完成它的工作并将内存释放时,内存管理器会将这些块标记为空闲状态,以备后续程序再次使用。 内存管理器还需要处理许多其他事项,例如内存碎片化和内存保护。内存碎片化是指当程序释放一些内存块时,内存管理器会留下一些无法使用的小空间,这些空间可能过小,无法再分配给其他程序。内存保护是指内存管理器可以防止程序访问不属于它的内存区域,从而保护操作系统和其他程序的稳定性和安全性。 总之,内存管理器对于计算机系统的稳定性和性能至关重要。虽然它可能需要一些时间来完成,但一旦实现,它将使操作系统功能更加完整且能够更好地支持更复杂的任务和应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值