天天看點

從零開發一個RTOS系列--準備篇

  • 工具準備:

(1)交叉編譯器:

不同的開發闆可能需要不同的交叉編譯器,筆者之前使用的開發闆型号好像是STM32F407VG-Discovery,記不太清了,目前開發闆不在身邊,不能真機調試,先說一些理論方面的内容。筆者選用的交叉編譯器是arm-none-eabi-gcc、arm-none-eabi-gdb, 大家可以到這個位址下載下傳最新版:https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads。

關于編譯器的選擇這裡要簡單講一下,arm-none-eabi-gcc這個名字是依據一種特定格式的。

命名規則:

交叉編譯工具鍊的命名規則為:

  • arch [-vendor] [-os] [-(gnu)eabi]
  • arch - 體系架構,如ARM,MIPS
  • verdor - 工具鍊提供商
  • os - 目标作業系統
  • eabi - 嵌入式應用二進制接口

由于我們要嘗試開發一款RTOS核心,是開發針對裸機的程式,是以選擇了arm-none-eabi-gcc。

有時候為了定制交叉編譯器,可能還需要我們自己編譯一個交叉編譯器以及相對應的c庫(由于庫體積原因,一般是用newlib這個嵌入式c庫,而不用gnu的完整版c庫)

(2)OpenOCD:這個是支援線上單步調試的,具體用法後面再詳解,需要STLinkV2硬體

(3)Makefile:

Makefile是用來管理如何編譯我們的代碼的,具體Makefile知識讀者可以自行百度學習。

下面這個Makefile是筆者從網上整理并修改的,大家可以簡單看一下。

TOOLCHAIN_DIR=/home/jeremy/bin/gcc-arm-none-eabi-8-2018-q4-major

AS=$(TOOLCHAIN_DIR)/bin/arm-none-eabi-as

CC=$(TOOLCHAIN_DIR)/bin/arm-none-eabi-gcc

OBJCOPY=$(TOOLCHAIN_DIR)/bin/arm-none-eabi-objcopy

LD=$(TOOLCHAIN_DIR)/bin/arm-none-eabi-ld





CFLAGS=-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mthumb -mfloat-abi=soft --specs=nosys.specs -nostartfiles -Og -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra -g3 -DDEBUG -DUSE_FULL_ASSERT -DTRACE -DOS_USE_TRACE_SEMIHOSTING_DEBUG -DSTM32F407xx -DUSE_HAL_DRIVER -std=gnu11 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@)" #-c -o "$@" "$<"

# CFLAGS += -fpic -pie

CFLAGS+=-I$(INCLUDE_DIR) -I$(INCLUDE_DIR)/cmsis -I$(INCLUDE_DIR)/stm32f4-hal



SUBDIRS=$(shell ls -l | grep ^d | awk '{if($$9 != "bin" && $$9 != "include") print $$9}')



ROOT_DIR=$(shell pwd)

INCLUDE_DIR=$(ROOT_DIR)/include



ELF=bootloader.elf

BIN=bootloader.bin

BIN_DIR=$(ROOT_DIR)/bin



CUR_DIR=$(shell pwd)



CUR_SOURCE=$(wildcard $(CUR_DIR)/src/*.c)

CUR_ASM_SRC=$(wildcard $(CUR_DIR)/src/*.s)



CUR_OBJS=$(patsubst %.c, %.o, $(CUR_SOURCE))

CUR_ASM_OBJS=$(patsubst %.s, %.o, $(CUR_ASM_SRC))

# OBJS=$(shell ls -l $(OBJS_DIR) | grep ^- | awk '{print $9}' | sed "s:^:$(OBJS_DIR)/:")

OBJS+=$(CUR_OBJS) $(CUR_ASM_OBJS)

# OBJS=$(wildcard $(OBJS_DIR)/*.o)

# OBJ=kernel_obj.elf



export CC ROOT_DIR CFLAGS OBJS



all: subdirs bootloader



target: subdirs

$(CC) $(CFLAGS) -T $(ROOT_DIR)/bootloader.ld $(OBJS) -o $(BIN_DIR)/$(ELF)

# $(LD) -T $(ROOT_DIR)/kernel.ld -L /Users/apple/bin/gcc-arm-none-eabi-5_4-2016q3/lib/gcc/arm-none-eabi/5.4.1/ -lgcc $^ -o $(BIN_DIR)/$(ELF)



bootloader: target

$(OBJCOPY) -O binary $(BIN_DIR)/$(ELF) $(BIN_DIR)/$(BIN)



define build-dir

echo building $1 with action $2

make -C $1 $2

endef



subdirs: $(SUBDIRS)

# $(foreach dir, $(SUBDIRS), $(call build-dir $(dir)))

make -C src



%.o:%.c

$(CC) $(CFLAGS) -c $^ -o $@



%.o:%.s

$(CC) $(CFLAGS) -c $^ -o $@





clean: $(SUBDIRS)

# $(foreach dir, $(SUBDIRS), $(call build-dir $(dir) clean))

make -C src clean

rm -f $(CUR_DIR)/*.o

rm -f $(BIN_DIR)/*

           

裡面注意一個CFLAGS選項 -nostartfiles,這個flag的作用是不連結c庫的crt0.c(c運作時庫代碼),這基本意味着malloc和printf等函數已經不可用了,筆者打算是用開發闆自帶的uart接口,自己實作printf的簡單實作,以及自己的記憶體管理子產品。當然讀者如果感興趣也可以自己實作crt0.c裡面的stub函數,來支援标準的malloc和printf等函數,但是這需要自己使用crosstool-NG編譯交叉編譯器和c庫。也可以通過semihosting的方式進行調試,讀者可以了解一下semihosting的機制。

(4)連結腳本:

這個的功能可以總結為是控制把某些資料放到指定地方的連結器配置腳本,大家也可以百度學習。下面是筆者使用的連結腳本的例子。

/*

* Memory Spaces Definitions.

*

* Need modifying for a specific board.

* FLASH.ORIGIN: starting address of flash

* FLASH.LENGTH: length of flash

* RAM.ORIGIN: starting address of RAM bank 0

* RAM.LENGTH: length of RAM bank 0

*

* The values below can be addressed in further linker scripts

* using functions like 'ORIGIN(RAM)' or 'LENGTH(RAM)'.

*/



MEMORY

{

RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K

CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K

FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K

FLASHB1 (rx) : ORIGIN = 0x00000000, LENGTH = 0

EXTMEMB0 (rx) : ORIGIN = 0x00000000, LENGTH = 0

EXTMEMB1 (rx) : ORIGIN = 0x00000000, LENGTH = 0

EXTMEMB2 (rx) : ORIGIN = 0x00000000, LENGTH = 0

EXTMEMB3 (rx) : ORIGIN = 0x00000000, LENGTH = 0

MEMORY_ARRAY (xrw) : ORIGIN = 0x20002000, LENGTH = 32

}



/*

* For external ram use something like:



RAM (xrw) : ORIGIN = 0x64000000, LENGTH = 2048K



*/



/*

* Default linker script for Cortex-M (it includes specifics for STM32F[34]xx).

*

* To make use of the multi-region initialisations, define

* OS_INCLUDE_STARTUP_INIT_MULTIPLE_RAM_SECTIONS for the _startup.c file.

*/

/* OUTPUT_FORMAT("elf32littlearm","elf32littlearm","elf32littlearm") */

OUTPUT_ARCH(arm)

ENTRY(Reset_Handler)



/*

* The '__stack' definition is required by crt0, do not remove it.

*/

__stack = ORIGIN(RAM) + LENGTH(RAM);



_estack = __stack - 0x010000; /* STM specific definition */



/*

* Default stack sizes.

* These are used by the startup in order to allocate stacks

* for the different modes.

*/



/* __Main_Stack_Size = 1024 ;



//PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;



//__Main_Stack_Limit = __stack - __Main_Stack_Size ;



*/





/* "PROVIDE" allows to easily override these values from an

* object file or the command line. */

/*

//PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit ) ;

*/

/*

* There will be a link error if there is not this amount of

* RAM free at the end.

*/



/* */

_Minimum_Stack_Size = 256 ;



/*

* Default heap definitions.

* The heap start immediately after the last statically allocated

* .sbss/.noinit section, and extends up to the main stack limit.

*/



/*

//PROVIDE ( _Heap_Begin = _end_noinit ) ;

//PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;

*/

/*

* The entry point is informative, for debuggers and simulators,

* since the Cortex-M vector points to it anyway.

*/



/*

//ENTRY(_start)

*/



/* Sections Definitions */



SECTIONS

{

. = 0x08000000;

. = ALIGN(4);

/*

* For Cortex-M devices, the beginning of the startup code is stored in

* the .isr_vector section, which goes to FLASH.

*/

.isr_vector :

{

FILL(0xFF)

__vectors_start = ABSOLUTE(.) ;

__vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */

KEEP(*(.isr_vector)) /* Interrupt vectors */



KEEP(*(.cfmconfig)) /* Freescale configuration words */



/*

* This section is here for convenience, to store the

* startup code at the beginning of the flash area, hoping that

* this will increase the readability of the listing.

*/

*(.after_vectors .after_vectors.*) /* Startup code and ISR */

__vectors_end = ABSOLUTE(.) ;

__vectors_end__ = ABSOLUTE(.) ; /* STM specific definition */

} > FLASH



/*

* The program code is stored in the .text section,

* which goes to FLASH.

*/

. = ALIGN(4);

.text :

{

__text_start__ = .;

*(.text .text.*) /* all remaining code */



/* read-only data (constants) */

*(.rodata .rodata.* .constdata .constdata.*)



/* *(vtable) /* C++ virtual tables */



KEEP(*(.eh_frame*))



/*

* Stub sections generated by the linker, to glue together

* ARM and Thumb code. .glue_7 is used for ARM code calling

* Thumb code, and .glue_7t is used for Thumb code calling

* ARM code. Apparently always generated by the linker, for some

* architectures, so better leave them here.

*/

*(.glue_7)

*(.glue_7t)



}> FLASH







/* ARM magic sections */

.ARM.extab :

{

*(.ARM.extab* .gnu.linkonce.armextab.*)

}> FLASH



. = ALIGN(4);

__exidx_start = .;

.ARM.exidx :

{

*(.ARM.exidx* .gnu.linkonce.armexidx.*)

}> FLASH

__exidx_end = .;





_etext = .;

__etext = .;



. = ALIGN(4);

/*

* For some STRx devices, the beginning of the startup code

* is stored in the .flashtext section, which goes to FLASH.

*/

.flashtext :

{

*(.flashtext .flashtext.*) /* Startup code */

}> FLASH



. = ALIGN(4);

.index :

{

__data_array_start = .;



LONG(ADDR(.data));

LONG(LOADADDR(.data));

LONG(SIZEOF(.data));



__data_array_end = .;



__bss_array_start = .;



LONG(ADDR(.bss));

LONG(SIZEOF(.bss));



__bss_array_end = .;

__bootloader_end = .;

LONG(LOADADDR(.bss)+SIZEOF(.bss));

} >FLASH



. = ALIGN(4);



__data_lma = .;

/*

* The initialised data section.

*

* The program executes knowing that the data is in the RAM

* but the loader puts the initial values in the FLASH (inidata).

* It is one task of the startup to copy the initial values from

* FLASH to RAM.

*/

. = 0x20010200;

_sidata = .;

.data _sidata :

{

. = ALIGN(4);

FILL(0xFF)

/* This is used by the startup code to initialise the .data section */

_sdata = . ; /* STM specific definition */

__data_start__ = . ;

*(.data_begin .data_begin.*)



*(.data .data.*)



*(.data_end .data_end.*)

. = ALIGN(4);



/* This is used by the startup code to initialise the .data section */

_edata = . ; /* STM specific definition */

__data_end__ = . ;

} > RAM AT>FLASH

. = ALIGN(4);

__bss_start__ = .;

__bss_start = . ; /* standard newlib definition */

/* The primary uninitialised data section. */

.bss (NOLOAD):

{

_sbss = .; /* STM specific definition */

*(.bss_begin .bss_begin.*)



*(.bss .bss.*)

*(COMMON)



*(.bss_end .bss_end.*)

_ebss = . ; /* STM specific definition */

} > RAM

. = ALIGN(4);

__bss_end = .; /* standard newlib definition */

__bss_end__ = .;

__end__ = .;

end = .;

/* Remove information from the standard libraries */

/DISCARD/ :

{

libc.a ( * )

libm.a ( * )

libgcc.a ( * )

}

}
           
  • 實作一個RTOS核心所需的理論支援:

CPU位址空間:目前市面上大多數的32位MCU最大支援4G的位址空間(如果沒有PAE功能),目前的MCU大部分都是IO記憶體尋址的,也就是說外設的部分寄存器和裝置記憶體空間占用了4G中的一部分位址空間,能支援直接映射的SRAM或DRAM是達不到4G的。一個硬體平台的外設和RAM的映射位址一般是不可變化的,我們目前也隻關注這種情況。大家心裡可以形成一個概念,往某個固定位址寫入資料可能就是往RAM中寫,從某個位址讀,就是從外設的寄存器讀取資料。

  • RTOS包含的功能子產品:

RTOS的功能基本就是管理硬體資源,基本包括記憶體管理,Task切換管理,中斷異常管理,消息管理。目前實作的RTOS核心是不考慮MMU的,最多有對MPU的支援。統一位址空間,不區分核心态和使用者态。貼一張RTOS常見的架構圖:

從零開發一個RTOS系列--準備篇
  • RTOS的引導:

類似Linux常用的UBoot,RTOS也可以有自己的bootloader,這些loader都是很專用的,功能很簡單就是基本把copy核心到特定位址(如果需要的話),以及copy資料段,設定BSS段等。上面給出的Makefile和*.ld連結腳本就是針對筆者實作的bootloader的。下期會簡單聊一下BootLoader的簡單實作,感興趣的讀者可以繼續關注。

繼續閱讀