新千葉 ガーベージ・コレクション

FPGA マガジンやインターフェースで書けなかったこと等をちょぼちょぼ書いてます。@ryos36

C++ を embedded な環境でつかう

結局、素の C++ を使うのがベスト。うっかりすると素ではなくていろんなものに依存するので注意。たとえば arm-xilinx-linux-gnueabi-gcc/g++ これは当然、Linux 環境に依存する。だから単純に printf とかすると Linux の /usr/include/stdio.h の printf を使おうとしたりする。
arm-xilinx-eabi-gcc/g++ 。これは(xilinxが) newlib + libstdc++ に(意図的に)依存させて作っている。したがって、printf とかすると newlib の stdio.h の printf を使おうとしたりする。なので、newlib が要求する Unix システムコール(もどき)のシミュレーションを(自分で作ったライブラリを)用意する必要がある。例えば open とか read/write とか close とか。xilinx はこれを Standalone と称して用意している。さらに、リンク時には、”コンパイラを作った時の”環境に依存する crt (C RunTime ?) をリンクする。xilinx は作った時に何も考えていなかったのか、空の crt がリンクされる。crt にはシステムの初期化用の _init が含まれる。
embedded な環境で使うには -nostdlib を指定すると、crt やら libc libstdc++ やらをリンクしない。
g++ でコンパイルするとデフォルトではデストラクタの登録に __cxa_atexit を使おうとする。cxa はどうやら c++(cxx) abi の略のようだ。g++ では -fno-use-cxa-atexit を指定すると cxa_atexit を使わないオブジェクトを生成することが出来る。__aeabi_atexit が undefine の時はこれで”一時的に”解決する。全部解決したと思わない方がよい。RTOS などの設計にもよるが、いつデストラクタを呼ぶのかはちゃんと考えないといけない。タスクやスレッドが終わった時にどうするのか?が主な注意点だろう。これは RTOS の設計。
libc を自分で設計するならリエントラントに設計する必要があるかどうか?それらの情報どうするかは考えないといけない。g++ は exception を使う設定にしておくと、__gxx_personality_v0 と __cxa_end_cleanup の実装を要求される。これはいずれも exception ハンドラに関連する関数。特に __gxx_personality_v0 は try catch を使うなら実装が必須。最近では setjmp/longjump は使わないみたいだ(未確認)。下のblogが役に立ちそう。

g++の例外を捕まえよう!!(素手で)

exception を使わないなら(不便になるけど)-fno-exceptions を使えばよい。結局、g++ ではオプションで次の設定をしておけば(コンパイラ自身が持っている環境を無視するので)だいだい使える。

-nostdinc -nostdlib -fno-use-cxa-atexit -fno-exceptions

さぁこれで vx-schemeコンパイルがどこでもできるんじゃないかな?

やってはいけないこと

そんでもって -nostdlib を選択したということはコンパイラが持つ libc や libstdc++ を使わないということ。なので、-lc なんかしちゃだめ。-lm も当然ダメ。中途半端にライブラリをリンクすると dso_handle がないとか言われる。あと __impure_ptr 。これは newlib の環境内のリエントラントな構造を保つ大域変数。つまり newlib が前提。newlib を前提としてコンパイルされた libstdc++ は exception の回収にこのポインタを使う。だから、そんなことが起こったらきっと環境が間違っている。exception を使わないことがわかっているなら -fno-exceptions で回避できる。もし、使うなら、自分で unwind するための関数である 、__gxx_personality_v0 と __cxa_end_cleanup の実装が必要。
まちがっても、いろんなものを”くっつけて”解決しようとしないように。私は dummy.c というファイルを作って、知らない関数や変数をいっぱい定義して回避している”プロジェクト”を見たことがある。当然、崩壊してたね。まぁ趣味ならいい気はする。

new や delete

これをつかうと動的にメモリ確保開放を必要とする。RTOS で使うときは注意。動的にメモリ獲得するということは結局ガベージコレクション(的な現象)を生む。これはスクリプト言語を使わなくても malloc を使えば起こるということ。malloc の実装にもよるけど、メモリが足りなくなったらエラーになるならまだよいが、他のタスクが解放してくれるのを待つという実装になっていると RTOS の意味がなくなる場合(ケースバイケースだが)がある。だからやってはいけないというわけではなくて、注意。
まぁ TCP/IP つかっちゃうと結局動的にメモリ確保をするので(mbuf とかね)ここも本来注意なんだけど、、、最近では気にする人はいなくなっちゃたね。
new や delete を使うと operator が足りないと言われるので(言われなかったら何か怪しい libstdc++ をリンクしているぞ。危険)自分で実装する。malloc で実装してもよいし、簡単に自分で RTOSAPI を使ったり、単純な配列を使ったりしてもよい。要は delete と連動していればよい。
あと、うろ覚えだけど、クラスに new や delete を関連付けることが出来たような、、、、
placement new と delete(2)

placement new

RTOS ではこちらがおすすめ。new や delete のオペレータを使わない。
placement new と delete(1)

自分専用の gcc を作る

何度かトライしたけどめちゃめんどくさい。みんないい加減にやっている(やっていた)。しかし、Android や Yocto がうまくコンパイラコンパイルを隠ぺいして開発環境を自動的に作ってくれる環境を作ったので、みんな何も考えずにできるようになったね。ぱちぱち。2000 の初めにはなんか自分で gcc をつくらなきゃいけなかったので、毎晩、nightly build を追ってた。特に sh。
Linux 用のコンパイラを作る手順はざっと書くと次のような感じ

  • build 用のコンパイラを作る(i386 -> i386 ってことね。最近では x64 -> x64 かな)
  • cross 用のコンパイラをとりあえず作る。たとえば arm-unkown-eabi-gcc みたいなもの。newlib に依存してもしなくてもよい。これはいずれ捨てる。
  • arm-unkown-eabi-gccLinux カーネルコンパイルする。これで Linux の一時環境が出来た。この時点でやろうと思えば Linux のアプリは作れる。APILinuxシステムコールだけだが。ここで用意されるのは Linux が用意する include の類でこれをどっかクロスコンパイラ用 root にインストールする。
  • libc をつくる。Linux の環境とコンパイラがあれば libc が作れる。作れるはずなのだが、、、libc を作るときに libc 入りのコンパイラを要求されたり、例によって途中でバージョンの依存関係でエラーになったりと、苦労する。jこれを Linux の クロスコンパイラ用 root にインストール。ここを明示的に参照することで Linux アプリが書ける。(はず)
  • Linux の クロスコンパイラ用 root を元に cross 用のコンパイラを作り直す。arm-linux-gnueabi-gcc みたいもの。このクロスコンパイラは include とか lib とかはこのクロスコンパイラ用の root を"自動的に"参照することになる。
  • 出来たコンパイラで次々とライブラリをコンパイル・インストールする。そうすると、クロスコンパイラ用 root がいつのまにか Linux の root のようになる。これをファイルシステムにすればよい。これはオールドスタイルの root fs だ。
  • 出来た クロスコンパイラ用 root は実は i386 で動くクロスコンパイラが出来てたりする。だから、これをファイルシステムにしてしまうと i386 オブジェクトが含まれることになる。そこで、deploy 用の root fs を別に作る。ここにこつこつライブラリなどをインストールすれば本当の root fs ができる。

これを gcc のバージョン違いやいろんなライブラリを作るときのエラーの回避なんかをしてると、らちが明かない。Yocto は上を全部自動でやってくれる。そして、注目したいところだけ、自分でつくりこめばいいのだ。これは楽だね。

基本的には RTOS も上の手順を踏めば、正しく標準ライブラリを組み込んだ gcc ができる(はず)。

ARM の EABI とか newlib とか

あまりまとまっていないけど最近の成果をメモ。わすれちゃうからね。

XilinxSDK 用の gccgdb だけ再コンパイルしようとしたけどだめだった。もう全部コンパイルしないといけないみたい。結構めんどくさい。とはいえ、シェルプロがあるようなのでそれを一発動かせばよさそうであるのも事実。この SDK 用の gcc では g++ が使える。特に arm-xilinx-eabi- の方はどうしているのだろうとちょっと調べてみた。普通と言えば普通なんだけど、newlib を使っている。newlib を使うと、open や write などの Unixシステムコールが用意されているものとして libc を作ってくる。あとはこれらのシステムコールをシミュレーションすれば(用意すれば)c++ はできあがる。実際に、SDK では普通に c++ を選択できる。Eclispe での設定は次のようになっている。

-Wl,--start-group,-lxil,-lgcc,-lc,-lstdc++,--end-group
  • lstdc++ が指定されているが、 -nostdlib は指定されていない。つまり crt0 なんかがくっつく。crt0 はほとんど何もしていない。だから本来は crt0 なんかくつけないように -nostdlib を使用するべきなのだ。がそうはなっていない。理由は恐らく _init の存在。なぜか _init は crtbegin の中にあったりする。この _init はシステムの初期化なので本来は OS が持っていなければならないはずだ。そこが手抜きされている。っていうかなにリンクされているかわからないからほんとはすべて自分でコントロールすべきだよ。

まぁそんなこんなで newlib が要求するシステムコールは bsp 内の ps7_cortexa9_0/libsrc/standalone_v5_3/src にある。write なんてかなり手抜き。まぁいいのか。

リンカーで特徴的なのは .ctors とか .dtors で(コンストラクタとデストラクタかな)、さらに初期化ルーチンの preinit_array とinit_array 。C だけならあまりこの辺は気にしなくてよいのだけど、c++ を使うなら、この preinit_array と init_array に登録された関数をちゃんと呼ばないといけない。newlib では misc の init.c で __libc_init_array を呼べばよい。実際、SDK の main の前でよばれている。(xil-crt0.S)。もー crt0.S を用意するなら -nostdlib を設定すればいいのに、、、この __libc_init_array から _init が呼ばれているが、これは main に入る前の _init なんで(くりかえすけど) OS が用意するもの。SDK は用意しないで gcc の crt をリンクすることで解決している。まぁ弊害はないからいいのか?

c++ でめんどくさいのはグローバル変数のオブジェクト。これは gcc のバージョンにもよるが、4.9.1 だと

__static_initialization_and_destruction_0(int, int)

という関数が出来た。init_array 各関数をコールすることで呼ばれる。そして、このとき cxa_atexit 経由でデストラクタが登録される。いま検索したら

-fno-use-cxa-atexit

というオプションをつけると登録されないらしい。なんだよ~~~。意味がなくなりつつあるけど、続けて書くよ。

つまり終了処理の時にデストラクタを呼ぶ。終了処理は exit の時だけではなく、動的ライブラリのアンロードの時も呼ばれる。動的ライブラリを使っていないのに動的ライブラリの事を気にしなくてはいけなくなるのよ(あーでも -fno-use-cxa-atexit を使うと解決するみたいよ)。

動的ライブラリは各ライブラリに handle が割り当てられて、本体も仮想的に動的ライブラリ扱いされるので、main に dso_handle が必要になる。そして、デストラクタ登録のために cxa_atexit が呼ばれる。これらは newlib に用意されているから本来あまり気にしなくてよい。cxa_atexit などの必要な関数というのは EABI で決められているらしい。

あと、もう一つ、gcc は void *_impure_ptr; も用意しなければならない。これは再入可能な環境を保持していくもののようで、 newlib は reentrant な環境も用意していているので、newlib を使うとばっちりそろっている。

ということで、SDK ちょっとへんだけど普通に g++ が使えるという話。めでたしめでたし。

tkernel みたいな

tkernel みたいなのはちょっとめんどくさくなってくる。どういうわけか malloc を持っている。まぁそもそも libc とはなじまないから -nostdlibc だよね。最初は -nostdlibc を削除して SDK のように -lc をつかった。はい。できました。でも、これはよくなくて、crtX.o とか atexit とか、あまり tkernel の OS の事を考えてないものがくっつく。malloc なんてリンク順でかろうじて tkernel のが呼ばれる。危ない。

だから、この手の RTOS はやっぱり -nostdlibc で自前で crtX.o や libc をつくらないといけない。

_impure_ptr は newlib にその構造体が定義されていて、それを指すポインタになっている。 ./libsupc++/vterminate.cc から参照されている。システムでどのように reent の構造を用意するかということみたいなので、とりあえず、変数だけを用意しておく。(おい!! > 自分)

なんとなくだが、libstdc++ のコンパイルに失敗している気がするぞ。たしかに configure に --with-newlib がある。

usb_modeswitch と FS01BU

FS01BU は普通に USB Host につなぐと 1c9e:98ff で Mass Storage と認識される。usb-modeswitch を使うとモードを変えることが出来る。usb_modeswitch の本家からusb-modeswitch-data をとってくると 1c9e:98ff のデータがある。それを使う。

> usb_modeswitch -v 0x1c9e -p 0x98ff -c usb_modeswitch.d/1c93:98ff

これで vendor 固有のやりとりをして 1c9e:6801 に変更してくれる。1c9e:6801 になったら、今度は、modprobe で usb_serial を”無理やり" わりあてる。そのまえに 1c9e:6801 のエンドポイントを簡単に見てみる。(lsusb -v で詳しく見れるはず)。lsusb -v の出力ではないけど、自分で解析した結果は次の通り。

inteface:00 ff ff ff
   81: bulk
   01: bulk
inteface:01 ff ff ff   
   82: interrupt
   83: bulk
   02: bulk
inteface:02 ff ff ff
   84: bulk
   03: bulk
inteface:03 08 06 50
   04: bulk
   85: bulk

みたいな感じ。ff ff ff は USB のクラス等なのだが未定義ということになる。08 06 50 は Mass Storage。そこで、modprobe で usb serial を指定すると、よくわからないものすべてを usb serial に割り当てるようだ。kernel のソースを読むと vendor id と product id しかみていない。つまり、順に未定義の inteface すべてに usb serial を割り当てる。結果として ttyUSB0, ttyUSB1, ttyUSB2 ができる。素性のわかっている 08 06 50 には usb serial は割り当てないようだ。

usb serial の general に vendor id と product id 以上の割り当てポリシーに関する解像度はないので、すべてが usb serial になってしまう。ttyUSB2 は AT コマンドを受け付けるようなので (cu -l /dev/ttyUSB2 で確認可能。ATE1 でecho。ATI で情報が得られる)
、適当に遊べる。atd は使えなかった(そういうものじゃないらしい)。

じゃ、あとの interface 0 と 1 をアサインした ttyUSB0 と ttyUSB1 の役割は何なのだろうという疑問がわく。82 が interrupt なので、データの送受信は 82 02 でやるのかな?

ということで、ubuntu を upgrade するとうまくいくという記述をちらほら見かけますが、これは恐らく、usb_modeswitch のデータが更新されたためですね。

メモ:DNS の設定で警告

権威DNSサーバーの設定不備による情報流出の危険性と設定の再確認について(2016年1月12日公開)
jprs.jp から警告を受けたので

options に allow-transfer を追加

        allow-transfer { 192.168.0.XX; };

セカンダリだけにトランスファをするのが正しいらしい。

FS01BUからSIMカードが外せなかった、、、

検索すると
FS01BUからSIMカードが外せなかったので、分解して取り出す – まつぼ x Web
がひかかった。ここまでわかれば分解しなくても外せそう。ということで、ノートを切って隙間に差し込むと nanosim のひっかかりがなくなり、ちゃんと外せました。分解しなくても大丈夫。

CPS のメモ

CPS で APPLY が出てくることろで、割り込みをチェックして、割り込みルーチンに飛ばす。そして、その中でレジスタの切り替えとコンテキストの切り替えをすれば、VM 上でうまくマルチタスクが出来そう。割り込み性能が落ちる。がそれはそれ、そもそも VM で CPU をシミュレーションしているからなのであって、最終的に VM じゃなくて FPGA 上で走るリアルなVM(なにいっているんだか)にすれば解決する、、、、きがするぞ。

vm と割り込み

VM をつくっているひとは割り込みについてどう実装しているのだろう?実際の CPU の動きは割り込みがあがると別のアドレスにジャンプしたりするわけだが、VM ではどう実装すればよいのだ?単純にはどっかのフラグを"毎回"見て必要があればジャンプすればよい。しかし、これでは効率が悪すぎるだろう。折角(おれおれVMを)スレッデッドにした意味がなくなるのではないか?
Qemu は確か、ブロックごとにわけて、ブロック処理毎に割り込みを見ていたと思う。
タスク切り替えやエクセプションも問題になりそう。