购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 BenOS基础实验代码解析

本书中大部分的实验代码是基于BenOS来实现的。BenOS是一个基于ARM64体系结构的小型操作系统。本书的实验会从最简单的裸机程序开始,逐步扩展和丰富,让其具有进程调度、系统调用等现代操作系统的基本功能。

本节介绍最简单的BenOS的代码体系结构,目前它仅仅只有串口显示功能,类似于逻辑程序。

由于我们写的是裸机程序,因此需要手动编写Makefile和链接脚本。对于任何一种可执行程序,不论是.elf还是.exe文件,都是由代码(.text)段、数据(.data)段、未初始化数据(.bss)段等段(section)组成的。链接脚本最终会把一大堆编译好的二进制文件(.o文件)整合为二进制可执行文件,也就是把所有二进制文件整合到一个大文件中。这个大文件由总体的.text/.data/.bss段描述。下面是本实验中的一个链接文件,名为link.ld。

1    SECTIONS
2    {
3        . = 0x80000;
4        .text.boot : { *(.text.boot) }
5        .text : { *(.text) }
6        .rodata : { *(.rodata) }
7        .data : { *(.data) }
8        . = ALIGN(0x8);
9        bss_begin = .;
10       .bss : { *(.bss*) } 
11       bss_end = .;
12   }

在第1行中,SECTIONS是LS(Linker Script)语法中的关键命令,用来描述输出文件的内存布局。SECTIONS命令告诉链接文件如何把输入文件的段映射到输出文件的各个段,如何将输入段整合为输出段,以及如何把输出段放入程序地址空间和进程地址空间。

在第3行中,“.”非常关键,它代表位置计数(Location Counter,LC),这里把.text段的链接地址设置为0x80000,这里的链接地址指的是加载地址(load address)。

在第4行中,输出文件的.text.boot段内容由所有输入文件(其中的“*”可理解为所有的.o文件,也就是二进制文件)的.text.boot段组成。

在第5行中,输出文件的.text段内容由所有输入文件(其中的“*”可理解为所有的.o文件,也就是二进制文件)的.text段组成。

在第6行中,输出文件的.rodata段由所有输入文件的.rodata段组成。

在第7行中,输出文件的.data段由所有输入文件的.data段组成。

在第8行中,设置为按8字节对齐。

在第9~11行中,定义了一个.bss段。

因此,上述链接文件定义了如下几个段。

●.text.boot段:启动首先要执行的代码。

●.text段:代码段。

●.rodata段:只读数据段。

●.data段:数据段。

●.bss段:包含未初始化的全局变量和静态变量。

下面开始编写启动用的汇编代码,将代码保存为boot.S文件。

1    #include "mm.h"
2 
3    .section ".text.boot"
4 
5    .globl _start
6    _start:
7        mrs x0, mpidr_el1
8        and x0, x0,#0xFF   
9        cbz x0, master
10       b   proc_hang
11
12   proc_hang: 
13       b   proc_hang
14
15   master:
16       adr x0, bss_begin
17       adr x1, bss_end
18       sub x1, x1, x0
19       bl  memzero
20
21       mov sp, #LOW_MEMORY 
22       bl  start_kernel
23       b   proc_hang

启动用的汇编代码不长,下面做简要分析。

在第3行中,把boot.S文件编译链接到.text.boot段中。我们可以在链接文件link.ld中把.text.boot段链接到这个可执行文件的开头,这样当程序执行时将从这个段开始执行。

在第6行中,_start为程序的入口点。

在第7行中,由于树莓派4B有4个CPU内核,但是本实验的裸机程序不希望4个CPU内核都运行,我们只想让第一个CPU内核运行起来。mpidr_el1寄存器是表示处理器内核的编号。

在第8行中,and指令用于完成与操作。

第9行,cbz为比较并跳转指令。如果X0寄存器的值为0,则跳转到master标签处。若X0寄存器的值为0,则表示第1个CPU内核。其他CPU内核则跳转到proc_hang标签处。

在第12和13行,proc_hang标签这里是死循环。

在第15行,对于master标签,只有第一个CPU内核才能运行到这里。

在第16~19行,初始化.bss段。

在第21行中,使SP指向内存的4 MB地址处。树莓派至少有1 GB内存,我们这个裸机程序用不到那么大的内存。

在第22行中,跳转到C语言的start_kernel函数,这里最重要的一步是设置C语言运行环境,即栈。

总之,上述汇编代码还是比较简单的,我们只做了3件事情。

●只让第一个CPU内核运行,让其他CPU内核进入死循环。

●初始化.bss段。

●设置栈,跳转到C语言入口。

接下来,编写C语言的start_kernel函数。本实验的目的是输出一条欢迎语句,因而这个函数的实现比较简单。将代码保存为kernel.c文件。

#include "mini_uart.h"

void start_kernel(void)
{
     uart_init();
     uart_send_string("Welcome BenOS!\r\n");

     while (1) {
         uart_send(uart_recv());
     }
}

上述代码很简单,主要操作是初始化串口和向串口中输出欢迎语句。

接下来,实现一些简单的串口驱动代码。树莓派有两个串口设备。

●PL011串口,在BCM2711芯片手册中简称UART0,是一种全功能的串口设备。

●Mini串口,在BCM2711芯片手册中简称UART1。

本实验使用PL011串口设备。Mini串口设备比较简单,不支持流量控制(flow control),在高速传输过程中还有可能丢包。

BCM2711芯片里有不少片内外设复用相同的GPIO接口,这称为GPIO可选功能配置(GPIO Alternative Function)。GPIO14和GPIO15可以复用UART0与UART1串口的TXD引脚和RXD引脚,如表2.4所示。关于GPIO可选功能配置的详细介绍,读者可以查阅BCM2711芯片手册。在使用PL011串口之前,我们需要通过编程来使能TXD0和RXD0引脚。

表2.4 GPIO可选功能配置

BCM2711芯片提供了 GFPSEL n 寄存器来设置GPIO可选功能配置,其中 GPFSEL0用来配置GPIO0~GPIO9,而GPFSEL1用来配置GPIO10~GPIO19,以此类推。其中,每个GPIO使用3位来表示不同的含义。

●000:表示GPIO配置为输入

●001:表示GPIO配置为输出。

●100:表示GPIO配置为可选项0。

●101:表示GPIO配置为可选项1。

●110:表示GPIO配置为可选项2。

●111:表示GPIO配置为可选项3。

●011:表示GPIO配置为可选项4。

●010:表示GPIO配置为可选项5。

首先,在include/asm/base.h头文件中加入树莓派寄存器的基地址。

#ifndef  _P_BASE_H
#define  _P_BASE_H

#ifdef CONFIG_BOARD_PI3B
#define PBASE 0x3F000000
#else
#define PBASE 0xFE000000
#endif

#endif  /*_P_BASE_H */

下面是PL011串口的初始化代码。

void uart_init ( void )
{
     unsigned int selector;

     selector = readl(GPFSEL1); selector &= ~(7<<12);
     /* 为GPIO14设置可选项0*/
     selector |= 4<<12;
     selector &= ~(7<<15);
     /* 为GPIO15设置可选项0 */
     selector |= 4<<15;
     writel(selector, GPFSEL1);

上述代码把 GPIO14 和 GPIO15 设置为可选项0,也就是用作PL011 串口的RXD0 和TXD0引脚。

/*设置gpio14/15为下拉状态*/
selector = readl(GPIO_PUP_PDN_CNTRL_REG0);
selector |= (0x2 << 30) | (0x2 << 28);
writel(selector, GPIO_PUP_PDN_CNTRL_REG0);

通常GPIO引脚有3个状态——上拉(pull-up)、下拉(pull-down)以及连接(connect)。连接状态指的是既不上拉也不下拉,仅仅连接。上述代码已把GPIO14和GPIO15设置为连接状态。

下列代码用来初始化PL011串口。

/* 暂时关闭串口 */
writel(0, U_CR_REG);

/* 设置波特率 */
writel(26, U_IBRD_REG);
writel(3, U_FBRD_REG);

/* 使能FIFO设备 */
writel((1<<4) | (3<<5), U_LCRH_REG);

/* 屏蔽中断 */
writel(0, U_IMSC_REG);
/* 使能串口,打开收发功能 */
writel(1 | (1<<8) | (1<<9), U_CR_REG);

接下来,实现如下几个函数以收发字符串。

void uart_send(char c)
{
     while (readl(U_FR_REG) & (1<<5))
         ;

     writel(c, U_DATA_REG);
}

char uart_recv(void)
{
     while (readl(U_FR_REG) & (1<<4))
         ;

     return(readl(U_DATA_REG) & 0xFF);
}

uart_send()和uart_recv()函数分别用于在while循环中判断是否有数据需要发送和接收,这里只需要判断U_FR_REG寄存器的相应位即可。

接下来,编写Makefile文件。

board ?= rpi3

ARMGNU ?= aarch64-linux-gnu

COPS += -DCONFIG_BOARD_PI4B
QEMU_FLAGS  += -machine raspi4

COPS += -g -Wall -nostdlib -nostdinc -Iinclude
ASMOPS = -g -Iinclude 

BUILD_DIR = build
SRC_DIR = src

all : benos.bin

clean :
     rm -rf $(BUILD_DIR) *.bin 

$(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c
     mkdir -p $(@D)
     $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@

$(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S
     $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@

C_FILES = $(wildcard $(SRC_DIR)/*.c)
ASM_FILES = $(wildcard $(SRC_DIR)/*.S)
OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o)
OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o)

DEP_FILES = $(OBJ_FILES:%.o=%.d)
-include $(DEP_FILES)

benos.bin: $(SRC_DIR)/linker.ld $(OBJ_FILES)
     $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/benos.elf  $(OBJ_FILES)
     $(ARMGNU)-objcopy $(BUILD_DIR)/benos.elf -O binary benos.bin

QEMU_FLAGS  += -nographic

run:
     qemu-system-aarch64 $(QEMU_FLAGS) -kernel benos.bin
debug:
     qemu-system-aarch64 $(QEMU_FLAGS) -kernel benos.bin -S -s

ARMGNU用来指定编译器,这里使用aarch64-linux-gnu-gcc。

COPS和ASMOPS用来在编译C语言与汇编语言时指定编译选项。

●-g:表示编译时加入调试符号表等信息。

●-Wall:表示打开所有警告信息。

●-nostdlib:表示不连接系统的标准启动文件和标准库文件,只把指定的文件传递给连接器。这个选项常用于编译内核、bootloader等程序,它们不需要标准启动文件和标准库文件。

●-nostdinc:表示不包含C语言的标准库的头文件。

上述文件最终会被编译、链接成名为benos.elf的.elf文件,这个.elf文件包含了调试信息,最后使用objcopy命令把elf文件转换为可执行的二进制文件。 0GXfKGolPMHmHxYTKDHJunJghxduvwqB8iLYokaPqYkQDCt9URMpFyJYjtOpgw7Y

点击中间区域
呼出菜单
上一章
目录
下一章
×