1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

putsの実装

Last updated at Posted at 2019-07-19

概要

タイマーを使ったスレッドの実装の前に,動作を確認するための便利な関数を作っておきたい.具体的には,'print'に相当する関数を作りたい.ここまでの実装では,シリアルを使ったputcを実装したものの,この関数は一文字出力することしかできなかった.そこで,文字列を出力できるputsの実装を試みる.
ところが,putsを正しく機能させるには,メモリマップと,それを指定するためのリンカスクリプトを書く必要がある.かつ,静的変数を読み書きするため,ROMからRAMへのデータコピーも必要になる.

失敗例

シリアル通信で作成したputcを利用し,main.cにputsを実装し,mainから呼び出してみる.

main.c
int puts(char *str) {
    while (*str) {
        putc(*(str++));
    }
    return 0;
}

int main (void)
{
  serial_init();
  char str1[] = {'a','b','c', '\0'};

  char *s = "hello world\n";
  puts(str1);
  puts(s);  
  return 0;
}

この結果をシリアルモニタで確認すると,以下のようになる.

Screen Shot 2019-06-19 at 10.50.53.png

よく見ると,スタックに確保したstr1はちゃんと出力できているのに,静的変数である"hello world\n"がおかしいことに気づく.つまり,putsとかputcの実装が悪いのではなく,データを格納しているアドレスを正しく参照できないことによる不具合,という結論にたどり着いた.

P.S. とはいえ,静的変数だとしても,書き換えをしなければ正しくアクセスできるはずで,こんな不具合起こらないと思うんだけど...謎

メモリマップ

上記の不具合に対応するため,メモリマップを設定し,変数それに対応する初期値を正しくメモリに配置する.
具体的には,

  1. リンカスクリプトの修正
  2. 起動時に初期値のある変数のデータのコピー

を行う.

リンカスクリプトの修正

今回対象とするatmega328pは,32KBのROMと2KBのRAMを持っているので,それに合わせてリンカスクリプト(ld.scr)を修正する.

OUTPUT_FORMAT("elf32-avr")
OUTPUT_ARCH(avr)
ENTRY("start")

MEMORY
{
	romall(rx)	: o = 0x000000, l = 0x008000     // ROM全体で32K(0x8000)
	vectors(r)	: o = 0x000000, l = 0x000100     // 先頭には割り込みベクタを配置.256Bは少し多め
	rom(rx)		: o = 0x000100, l = 0x007800 - 0x000100 // 残りのROM.offsetは0x100
	bootloader(r)	: o = 0x007800, l = 0x008000 - 0x007800 // (1)

	ramall(rwx)	: o = 0x800000, l = 0x000800     // RAM全体で2K(0x800)
	registers(rw)	: o = 0x800000, l = 0x000100     // 先頭はレジスタやIOレジスタが配置されている
	ram(rwx)	: o = 0x800100, l = 0x800600 - 0x800100 // 残りのRAM全体
	bootstack(rw)	: o = 0x8007fc, l = 0x000000     // (2)
}

SECTIONS
{
	.vectors : {
		 vector.o(.text)
	} > vectors                  // (3)

	.text : {
	      	 _text_start = . ;
		 *(.text)
		 _etext = . ;
	} > rom                      // (3)

	.rodata : {
		 _rodata_start = . ;
		 *(.strings)
		 *(.rodata)
		 *(.rodata.*)
		 _erodata = . ;
	} >ram AT> rom              // (4)

	.data : {
		 _data_start = . ;
		 *(.data)
		 _edata = . ;
	} > ram AT> rom            // (5)

	.bss : {
		 _bss_start = . ;
		 *(.bss)
		 *(COMMON)
		 _ebss = . ;
	} > ram AT> rom           // (5)


	. = ALIGN(4);
	_end = . ;


	.bootstack : {
		 _bootstack = .;
	} > bootstack           // (6)

}
  1. Arduino Unoのatmega328pは,ヒューズビットの設定によってブートローダが0x7800から書き込まれているので,そこを潰さないように保護する.
  2. これまでのスタック領域を踏襲して,bootstack領域を作っておく.ここには単にシンボルを配置するのみ.
  3. ここは割り込みベクタを配置する.割り込みベクタはアセンブリ言語で定義され(vector.s),アセンブル後それらはプログラムとして保存されている(データじゃない!).よって,vector.oのテキストセグメント(.text)を,vectorsという領域に配置する,という意味になる.その後,textセクションを生成し,残りすべてのファイル(vectors.o)のテキストセグメントをROMに配置する.
  4. READONLYデータをRAMに配置する(>ram).その上で,.rodataセクションの物理アドレスはROMに配置される(AT>rom).これは,VA != PA処理に当たる(※1).
  5. 初期値のあるデータ(RW可能)をRAMに配置する(ram).それ以外はREADONLYと同じく,.dataセクションの物理アドレスをROMに配置する..bss(初期値のないデータの変数の値のための領域)も同様
  6. スタック領域にシンボルを配置(_bootstack)

※1: そもそもRODATAは変更しないので,ROMに配置して置けばいい気がするが,ROMに配置するとシリアルで出力するときに失敗例のように文字化けする.謎
謎が解けた.AVRはROMとRAMが別のメモリになっており,ROMを読む命令とRAMを読む命令が異なっている.そして,gccはRAMのデータを読むようなコードを出力するので,RAMにデータをコピーしておかないと文字化けする.

データのコピー

上記の4で,VA!=PAにしているため,で.rodate, .data. .bssはプログラムがアクセスするときはRAMのアドレスにアクセスするようにリンクされるが,物理的にはROMにデータが配置される.よって,このまま実行すると,例えばdataを読みにいってもRAMにはそのデータがない,ということになる.
そのため,OSが起動したらすぐにROMからRAMの適切な位置にデータをコピーしておかないといけない.

どこからどこに,コピーするかといえば,シンボルで書くと,_etextから_rodata_startにコピーする.そして,そのサイズは_edata - _rodata_startとなる.何故かというと,rodata, dataはtextが配置された直後から配置されるため,開始アドレスは_etext,コピー先はRAMの先頭に配置した_rodata_startとなるため.

また,BSSに関しても0で初期化する.

startup.s
start:
	ldi	r28, lo8(_bootstack) // スタックポインタの設定. ldiは即値を読み込む命令
	ldi	r29, hi8(_bootstack) // 0x3d, 0x3eSPのアドレス
	out	0x3d, r28
	out	0x3e, r29

data_copy_set:
	eor	r1, r1              // r10にクリア

	/* length */
	ldi	r18, lo8(_rodata_start) // _rodata_startのアドレスを読み込む
	ldi	r19, hi8(_rodata_start)
	ldi	r20, lo8(_edata)        // _edataを読み込む
	ldi	r21, hi8(_edata)
	sub	r20, r18                // _edata - _rodata_startの実行
	sbc	r21, r19                // コピーするデータサイズがr20, r21に入る

	/* src */
	ldi	r30, lo8(_etext)    // コピー元のアドレスをr30, r31に設定
	ldi	r31, hi8(_etext)

	/* dst */
	ldi	r26, lo8(_rodata_start) // コピー先のアドレスをr26, r27に設定
	ldi	r27, hi8(_rodata_start)

data_copy:
	cp	r1, r20
	cpc	r1, r21
	brge	bss_clear_set          // コピーサイズが0になったらbss_clear_setにジャンプ
	lpm	r24, Z+                 // Zレジスタからr24にデータを読みインクリメント.Zr30, r31を連結したレジスタ
	st	X+, r24                 // r24の値をXレジスタにコピーし,Xをインクリメント.Xr26, r27を連結したレジスタ
	subi	r20, 0x01               // コピーサイズを1減らす
	sbci	r21, 0x00
	rjmp	data_copy
()

call_main:
	call	main                   // mainにジャンプ

アセンブリ言語でコピーしているが,C言語で記述した関数でコピーすると何故かうまくいかない(文字化け).

void cpdata() {
    extern long _rodata_start, _edata, _etext;
    extern long _bss_start, _ebss;
    memcpy(&_rodata_start, &_etext, (unsigned long)&_edata - (unsigned long)&_rodata_start);
    memset(&_bss_start, 0, (unsigned long)&_ebss - (unsigned long)&_bss_start);
}

謎..
これも解決で,C言語で書くとRAMからデータを読むようなコードを出力するため.よって,ここはアセンブリ言語で書くしかない

実行

再度,以下のmainを実行してみる

main.c
volatile int value = 10;

int main (void)
{
    int i;
    extern char _bootstack;
    static char *ts = &_bootstack;

//    cpdata();

    serial_init();
    char str1[] = {'a','b','\n', '\0'};

    char *s = "hello world\n";
    puts(s);
    puts(str1);
    putxval(value);
    putxval((unsigned long)ts);
    for (i = 0; i < 5; ++i) {
	    puts(s);
    }
    return 0;
}

Screen Shot 2019-06-20 at 9.28.37.png

成功してる!

この実装は,以下のコマンドで試すことができる.

>git clone https://github.com/hiro4669/iosv.git
>cd iosv
>git branch memmap_ver1 origin/memmap_ver1
>git checkout memmap_ver1
>cd mammap
>make
>make write
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?