はじめに
wrynoseでベアメタルシリーズ
前回はwrynose環境でベアメタルのレシピであるbaremetal-helloworldをビルドした。
今回はそのレシピが取り扱うベアメタルのソースコード周りを確認する。
ベアメタル
ソースコードの取得
せっかくのPokyの環境なのでソースコードの取得はdevtoolを使用するのが手っ取り早い。
下記のコマンドを実行しworkspaceにソースコードを展開する。
$ devtool modify baremetal-helloworld
ソースコード
下記にソースコードが展開されている。
$ pushd ./workspace/sources/baremetal-helloworld
ベアメタルの根幹
C言語を勉強すると「プログラムはmain関数から開始されます」と解説されることがほとんどだが、 ベアメタルではmain関数が呼び出されるまでのコードも自作する必要がある。
PC上のリッチなOSの上で実行されるプログラムとは異なり、 ベアメタル環境では、MCU(CPU)に対して、どのアドレスにどんなペリフェラルが接続されており、 プログラムがどこに配置されるべきかを、プログラマがすべて指示する必要がある。
ベアメタルに必要なもの
ベアメタルに最低限必要なものは以下の3つ。
- リンカスクリプト(プログラムの配置場所を指示する設定ファイル)
- スタートアップコード(電源ONからmain関数に繋ぐための最初のコード。アセンブリ言語で書くことが多い)
- アプリケーションのソースコード(main関数以降の、いわゆる普通のプログラム)
リンカスクリプト
マシンをqemuarmにしたのでlinkerscript_arm.ldが適用される。
/* Use the _start symbol as entry point */ ENTRY(_start) SECTIONS{ /* Ram starts at 0x40000000 */ /* QEMU when given -kernel adds an offset of 0x00010000 for 32-bit ARM (KERNEL_LOAD_ADDR) */ . = 0x40010000 ; /* Put startup code at the beginning */ /* Only put the text section of build/startup_arm.o */ /* Put all other sections from others */ .stub : { build/startup_arm.o(.text) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } /* Put common symbols from bss on the bss section */ .bss : { *(.bss COMMON) } /* Use 4KB for stack */ . = ALIGN(8); stack_bottom = .; . += 0x1000; stack_top = .; }
このリンカスクリプトでは大きく2つのものを定義している。
- エントリポイント
- セクション
エントリポイント
エントリポイントとは、プログラムが実行された時に一番最初に処理を開始する位置のこと。 これが"main"とならないのは、main関数を実行する前にメモリの初期化など、いくつかの処理を実行する必要があるため。
セクション
セクションとはプログラムが使用する変数領域やコードをメモリ上のどこに配置するかを決定するためのもの。今回は、QEMU(ARM32)の仕様に合わせて、RAMの開始アドレス(0x40000000)にオフセットを加えた 0x40010000 を起点として、以下の順番でメモリを割り当てている。
| セクション | 概要 | 備考 |
|---|---|---|
| .stub | スタートアップコード | startup_arm.o のプログラムコード(最優先で戦闘に配置) |
| .text | プログラムコード | それ以外のすべてのプログラムコード |
| .rodata | 定数データ | 読み出し専用の定数や文字列など |
| .data | 初期値ありの変数 | グローバル変数など |
| .bss | 初期値なしの変数 | 起動時に0で初期化する変数領域 |
| (スタック領域) | ローカル変数 | 4KB(0x1000)分のサイズを確保 |
スタートアップコード
この環境ではstartup_arm.sが採用される。
/* Declare the _start symbol as global */ .globl _start _start: /* No main function, jump to c_entry as the beginning of our program */ ldr r14, =stack_top mov sp, r14 bl c_entry b .
このコードでは以下のことを行っている。
- エントリポイント(_start)の定義
- スタックポインタの初期化
- c_entry(mainの代わり)の呼び出し
このコードは実装を簡単にするために意図的に不完全なコードになっている。 不完全な部分としては以下のセクションの初期化処理が実装されていない。
- .data(初期値あり変数の配置)
- .bss(初期値なし変数のゼロクリア)
スタックポインタ
スタックはローカル変数の領域であり、関数の呼び出しごとに使用する領域が切り替わる。 そのため領域自体の初期化は必要なく、現在のスタックの基準アドレスさえ決まればいい。電源投入直後のMCUはスタックポインタの位置が決まっていないため、このコードではリンカスクリプトで定義した stack_top を使って位置を設定している。
c_entryについて
mainではなく "c_entry" としているのは、スタートアップが不完全であるため。通常のC言語プログラムのつもりでmain関数を呼び出してしまうと、グローバル変数やスタティック変数がまともに動かないという不具合が発生する。
また、関数名をmainにすると、コンパイラがOS上での動作を前提とした標準ライブラリの初期化などの処理を意図せず追加してしまうことがあり、エラーや不具合の原因になる。そのため、コンパイラの自動処理を避ける目的であえて異なる関数名にしている。
アプリケーションのソースコード
この環境ではhello_baremetal_arm.cが採用される。
/* QEMU virt machine memory map sets VIRT_UART as 0x90000000 */ #define UART0_BASE 0x09000000 volatile unsigned int * const UART0DR = (unsigned int *)UART0_BASE; /* Until we reach to the end of the string, put each char on UART0 */ void print_uart0(const char *str) { while(*str != '\0') { *UART0DR = (unsigned int)(*str); str++; } } /* Entry function from startup.s */ void c_entry() { print_uart0("Hello OpenEmbedded on ARM!\n"); }
ペリフェラルの制御
ペリフェラルは周辺機器のことでMCUのコアの周辺に配置された補助回路で、接続される外部機器の制御に特化している。 ペリフェラルを制御するにはMCUに割り当てられたレジスタのアドレスのデータを読み書きする。
レジスタへのアクセスはMCUからはただのアドレスへの読み書きにしか見えないので、メモリアクセスと区別がつきにくいが、 レジスタはハードウェアが制御しているため、MCUから書き換えなくても値が変化することがあるなど明確な違いがある。
このようなレジスタの性質をコンパイラに正しく伝えるため、変数定義には volatile キーワードを付与し、意図しない最適化による処理の省略などを防ぐ必要がある。
QEMU環境では実際のハードウェア回路が存在しないため、ソフトウェアでレジスタアクセスを擬似的にエミュレートする。
UART
UARTは非同期シリアル通信方式及びその回路のこと。 PCとMCUの間でシリアルコンソール接続を行う際によく使用される。
データ送信用のレジスタに文字データを書き込むと、UARTのペリフェラルにより、信号ピンの電圧を変化させて物理的な通信を行う。受信した開発用PC側のコンソールアプリケーションがその信号を文字として画面に描画する。
print_uart0の実装
qemuarmの環境でUARTで送信するためのレジスタは0x09000000となっている。
このアドレスをポインタ変数UART0DRに割り当てて、
UART0DRを介してデータをレジスタに書き込むことで文字列を送信している。
QEMUではコンソール機能もエミュレートしており、書き込まれたデータの画面表示まで行っている。
c_entryの実装
c_entry関数では、print_uart0関数を呼び出し"Hello OpenEmbedded on ARM!\n"と出力している。
まとめ
qemuarm(32ビット)向けのベアメタルのコードを調査した。 中身を読んでみるときちんとベアメタルだった。


