綜合能力訓練:在樹莓派上動手寫一個小OS(2):實驗16-1:輸出welcome benos

rlk8888發表於2022-03-18

本文節選自《奔跑吧linux核心 入門篇》第二版第16.2.1章


和笨叔一起玩樹莓派,動手寫BenOS!

實驗16-1:輸出“Welcome BenOS!”

1.實驗目的

1)瞭解和熟悉ARM64彙編。
2)瞭解和熟悉如何使用QEMU和GDB除錯裸機程式。

2.實驗要求

1)編寫一個裸機程式並在QEMU模擬器中執行,輸出“Welcome BenOS!”字串。
2)在樹莓派上執行編譯好的裸機程式。

3.實驗詳解

由於我們寫的是裸機程式,因此需要手動編寫Makefile和連結指令碼。
對於任何一種可執行程式,不論是.elf還是.exe檔案,都是由程式碼段.text、資料段.data、未初始化資料段.bss等段(section)組織的。連結指令碼最終會把一大堆編譯好的二進位制檔案(.o檔案)綜合生成為二進位制可執行檔案,也就是把所有二進位制檔案整合到一個大檔案中。這個大檔案由總體的.text/.data/.bss段描述。下面是本實驗中的一個連結檔案,名為link.ld。


1     SECTIONS

2     {
3          . =  0x0;
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命令告訴連結檔案如何把輸入檔案的段對映到輸出檔案的各個段,如何將輸入段合為輸出段,以及如何把輸出段放入程式地址空間(VMA)和程式地址空間(LMA)。SECTIONS命令格式如下。

SECTIONS

{
  sections-command
  sections-command
  …
}

sections-command有4種。

  • ENTRY命令。

  • 符號賦值語句。

  • 輸出段的描述(output section description)。

  • 段的疊加描述(overlay description)。

在第3行中,“.”非常關鍵,它代表位置計數(Location Counter,LC),這裡把.text段的連結地址被設定為0x0,這裡連結地址指的是裝載地址(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段:包含初始化的或初始化為0的全域性變數和靜態變數。

下面開始編寫啟動用的彙編程式碼,將程式碼儲存為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段連結到這個可執行檔案的開頭,這樣當程式執行時將從這個段開始執行。
在第5行中,_start為程式的入口點。
在第7行中,由於樹莓派有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語言的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串列埠,在BCM2837晶片手冊中簡稱UART0,是一種全功能的串列埠裝置。

  • Mini串列埠,在BCM2837晶片手冊中簡稱UART1。

本實驗使用PL011串列埠裝置。Mini串列埠裝置比較簡單,不支援流量控制(flow control),在高速傳輸過程中還有可能丟包。

BCM2837晶片裡有不少片內外設複用相同的GPIO介面,這稱為GPIO可選功能配置(GPIO Alternative Function)。GPIO14和GPIO15可以複用UART0和UART1串列埠的TXD引腳和RXD引腳,如表16.1所示。關於GPIO可選功能配置的詳細介紹,讀者可以查閱BCM2837晶片手冊的6.2節。在使用PL011串列埠之前,我們需要通過程式設計來使能TXD0和RXD0引腳。

BCM2837晶片提供了 GFPSELn暫存器用來設定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引腳。

    writel(
0, GPPUD);

    delay( 150);
    writel(( 1<< 14)|( 1<< 15), GPPUDCLK0);
    delay( 150);
    writel( 0, GPPUDCLK0);

通常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

ifeq ($(board), rpi3)
COPS += -DCONFIG_BOARD_PI3B
QEMU_FLAGS  += -machine raspi3
else ifeq ($(board), rpi4)
COPS += -DCONFIG_BOARD_PI4B
QEMU_FLAGS  += -machine raspi4
endif

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

board用來選擇板子,目前支援樹莓派3和樹莓派4。
ARMGNU用來指定編譯器,這裡使用aarch64-linux-gnu-gcc。
COPS和ASMOPS用來在編譯C語言和組合語言時指定編譯選項。

  • -g:表示編譯時加入除錯符號表等資訊。

  • -Wall:開啟所有警告資訊。

  • -nostdlib:表示不連線系統的標準啟動檔案和標準庫檔案,只把指定的檔案傳遞給聯結器。這個選項常用於編譯核心、bootloader等程式,它們不需要標準啟動檔案和標準庫檔案。

  • -nostdinc:表示不包含C語言的標準庫的標頭檔案。
    上述檔案最終會被編譯連結成名為kernel8.elf的.elf檔案,這個.elf檔案包含了除錯資訊,最後使用objcopy命令把.elf檔案轉換為可執行的二進位制檔案。
    在Linux主機上使用make命令編譯檔案。在編譯之前可以選擇需要編譯的板子型別。例如,要編譯在樹莓派3上執行的程式,可使用如下命令。

export board=rpi3

$ make

要編譯在樹莓派4上執行的程式,可使用如下命令。

export board=rpi4

$ make

在放到樹莓派之前,可以使用QEMU虛擬機器來模擬樹莓派以執行我們的裸機程式,可直接輸入“make run”命令。

$ make run

qemu-system-aarch64 -machine raspi3 -nographic -kernel benos.bin
Welcome BenOS!

也可輸入如下命令。

$ qemu-system-aarch64 -machine raspi3 -nographic -kernel benos.bin

如果讀者想用QEMU虛擬機器模擬樹莓派4B,那麼需要打上樹莓派4B的補丁並且重新編譯QEMU虛擬機器。
要在樹莓派上執行剛才編譯的裸機程式,需要準備一張格式化好的MicroSD卡。

  • 使用MBR分割槽表。

  • 格式化boot分割槽為FAT32檔案系統。

參照實驗3-1中介紹的方法燒錄MicroSD卡,這樣就可以得到格式化好的boot分割槽和燒錄的樹莓派韌體。讀者也可以使用Linux主機上的分割槽工具(比如GParted)來格式化MicroSD卡,把樹莓派韌體複製到這個FAT32分割槽裡,其中包括如下幾個檔案。

  • bootcode.bin:載入程式。樹莓派復位上電時,CPU處於復位狀態,由GPU負責啟動系統。GPU首先會啟動固化在晶片內部的韌體(BootROM程式碼),讀取MicroSD卡中的bootcode.bin檔案,並裝載和執行bootcode.bin中的載入程式。樹莓派4B已經把bootcode.bin載入程式固化到BootROM裡。

  • start4.elf:樹莓派4上的GPU韌體。bootcode.bin載入程式檢索MicroSD卡中的GPU韌體,載入韌體並啟動GPU。

  • start.elf:樹莓派3上的GPU韌體。

  • config.txt:配置檔案。GPU啟動後讀取config.txt配置檔案,讀取Linux核心映像(比如kernel8.img等)以及核心執行引數等,然後把核心映像載入到共享記憶體中並啟動CPU,CPU結束復位狀態後開始執行Linux核心。
    把benos.bin檔案複製到MicroSD卡的boot分割槽,修改裡面的config.txt檔案。

<config.txt檔案>


[pi4]
kernel=benos.bin
max_framebuffers= 2

[pi3]
kernel=benos.bin

[all]
arm_64bit= 1

enable_uart= 1

kernel_old= 1
disable_commandline_tags= 1

插入MicroSD卡到樹莓派,連線USB電源線,使用Windows端的串列埠軟體可以看到輸出,如圖16.6所示。

圖16.6  輸出歡迎語句

下載入門篇第二版全套海量資料

登入“奔跑吧linux社群”微信公眾號,在微信公眾號裡輸入“奔跑吧2”,下載奔跑吧第二版入門篇全套資料:

  • 全套實驗環境,基於Ubuntu 20.04製作的VMware/VirtualBox虛擬機器映象

  • 實驗指導手冊

  • 電子課件

  • 實驗參考程式碼

  • 插圖下載

  • 勘誤

  • 免費視訊

  • Docker映象





好多同學問金色那本實驗指導手冊哪裡可以買?

其實,實驗指導手冊pdf版本已經開源,大家可以免費下載,自由列印。


列印小貼士:到淘寶上隨便找一家列印店,列印B5大小即可。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70005277/viewspace-2872517/,如需轉載,請註明出處,否則將追究法律責任。

相關文章