フルスクラッチの“Hello World”の仕組みを見てみよう:H8マイコンボードで動作する組み込みOSを自作してみよう!(2)(2/3 ページ)
今回は、前回動作させた「Hello World」のソースコードについて解説し、組み込みソフトウェアでのHello Worldの動作の仕組みを見ていきます。その中身は、意外と奥深いものです。
3.3.リンカスクリプト
リンカスクリプトは、読者の方々にはあまりなじみのないファイルだと思います。また、名前くらいは聞いたことがあっても、実際に触れたことはないという方も多くいることと思われます。
リンカスクリプトは、リンカがオブジェクトファイルをリンクして実行形式を作成する際に、オブジェクトのメモリ配置を指定するためのファイルです。
これは、実は普通のアプリケーションプログラムのリンク時にも必要なのですが、通常はリンカにビルトインされているデフォルトのリンカスクリプト(注1)が勝手に利用されるため、プログラマ側で指定する必要はありません。このため、OS上で動作する通常のアプリケーションプログラムを書いている場合には、とくに意識する必要はありません。
しかし、組み込みソフトウェア開発では、メモリ調整もプログラマ側の責任で行う必要があるため、リンカスクリプトの読み書きが必要になる場合も多々あります。
リンカスクリプトは、表1の「ld.scr」というファイルです。内部ではリスト1のような記述があります。
…(中略)… SECTIONS { . = 0x0; .vectors : { vector.o(.data) ★vector.oを0x0に配置する } …(後略)…
リスト1は、“「vector.o」というオブジェクトファイルをアドレス「0x0」に配置する”という意味になります。
「vector.o」は、「vector.c」をコンパイルすることで作成されます。「vector.c」はリスト2のようになっています。
#include "defines.h" extern void start(void); /* スタートアップ */ /* * 割り込みベクターの設定 * リンカスクリプトの定義により、先頭番地に配置される */ void (*vectors[])(void) = { start, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ★リセットベクターにはstart()のアドレスを設定する …(後略)…
よって、「vector.c」で定義されている配列「vectors[]」が、割り込みベクターに設定されます。リセットベクターは、先頭の「vectors[0]」に相当しますが、リスト2では、「vectors[0]」には関数「start()」のアドレスが初期値として設定されています。これにより、電源ON時には「start()」という関数から実行が開始されることになります。
3.4.スタートアップ
ここまでで、リセットベクターの設定により、電源ON直後に関数「start()」から実行が開始されることが分かりました。
それでは、C言語で「start()」という関数を作成しておけば、電源ONでCPUはその関数から実行を開始してくれるのでしょうか?
実は、そうはうまくいきません。正確にいうと、実行を開始すること“だけ”はできるのですが、正常に動作を継続することができないのです。
というのは、C言語の関数というのは内部で「スタック」を利用しているため(注2)、スタックポインタが初期化されていなければ、関数呼び出しが正常に動作しないからです。もしも、スタックポインタの初期化を行わずに関数呼び出しを行った場合、おかしなアドレスを勝手にスタックとして利用してしまい、暴走して固まったり勝手に再起動するなどしてしまうでしょう。
他にも静的変数領域の初期化など、C言語で書いたプログラムを動かすにはさまざまな前処理が必要です。リセットベクターから呼ばれる割り込みハンドラでは、これらの初期化を行い、その後にC言語の関数を呼ぶようにすれば、後は、C言語でプログラムを記述することができます。例えば、初期化後に「main()」関数を呼ぶようにしておけば、あたかも通常のCプログラムのように「main()」から処理が始まるものとしてプログラムを記述できるわけです。
このような初期化を行う部分を「スタートアップ」と呼びます。スタートアップでは、レジスタの初期化などC言語では記述できないような処理を行う必要があるため、通常はアセンブラで記述します。
今回作成した「Hello World」のスタートアップは、表1の「startup.s」というファイルです。その内容は、リスト3のようになっています。ラベル名が「_start」になっているため、関数「start()」として(注3)リセットベクター(vector.cのvectors[0])に登録され、電源ON時にはここから動作を開始することになります。
.h8300h .section .text .global _start # .type _start,@function _start: mov.l #0xffff00,sp ★スタックポインタの定義 jsr @_main ★main()を呼び出す 1: bra 1b
また、リスト3ではスタックポインタの初期化を行い、「main()」関数(アセンブラではアンダースコアが付加され、「_main」になります)を呼び出しています。このため、必要な処理を「main()」関数に記述すれば、「main()」から始まるような通常のC言語のイメージでプログラムが書けるわけです。
「main()」関数は、表1の「main.c」で定義されています。「main.c」の内容はリスト4のようになっています。「puts()」により「Hello World」を出力して、後は無限ループにより、動作を停止するという簡単なものです。
#include "defines.h" #include "serial.h" #include "lib.h" int main(void) ★メイン関数 { serial_init(SERIAL_DEFAULT_DEVICE); puts("Hello World!\n"); ★「Hello World!」を出力する while (1) ★無限ループで動作停止する ; return 0; }
Copyright © ITmedia, Inc. All Rights Reserved.