(H8移植編その2第19回)エンディアン対応をしておこう

2012/04/09

あなたは 人目のお客様です.

突然だがKOZOSのTCP/IPプロトコルスタックはエンディアンの考慮をしていない.

このままだと別アーキテクチャに移植する際に支障があるだろう. まあとはいっても組込み向けプロセッサの多くはビッグエンディアンなので あまり気にすることはないような気もするが,ちょっと別件でエンディアン対応 する必要があったので,まとめてエンディアン対応しておこう.

エンディアンについてはググればいっぱい説明は出てくるだろうからここでは 説明しないが,ネットワーク・エンディアンはビッグエンディアンで H8もビッグエンディアンだ.なのでH8で動かすぶんには,ネットワーク上の データをそのまま読んで差し支えない(だから今までサボっていた). しかしリトルエンディアンのプロセッサだと,エンディアンをひっくり返して データを読み書きする必要がある. まあ代表的なのはx86とARMだが,きちんとエンディアン対応しておくことは, プログラムの見通しを良くする上でも悪いことではない (パケット上のデータを扱っているのか,数値としてのデータを扱っているのかが はっきりするからだ).

一般的な対応としては,htonl()とかntohl()とかを実装して,パケットの読み書きは そういった関数を通して行うことになる.で,リトルエンディアンのアーキテクチャ ではバイト列をひっくり返して返すようにこれらの関数を実装しておけば, とりあえずはエンディアン対応ができる.

ただ,リトルエンディアンでは単にバイト列をひっくり返して,ビッグエンディアン ではそのままみたいにして #ifdef で切替える,というのではなんだかイマイチだ.

というわけでどちらのエンディアンでも #ifdef による切替え無しで同一コードで 動作するようなバイエンディアンの書き方で対応することにする. やりかたとしてはバイト列をひっくり返すのではなく,シフト演算を使って足し込む ような実装にする.たとえば以下のような実装だ. ちなみにKOZOSでは本来のntohl()と名前がぶつからないようにするため, 「ntoh4()」という名前にしてある.

uint32 ntoh4(uint32 n)
{
  uint8 *p = (uint8 *)&n;
  return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
このようにシフト演算を使えば,リトルエンディアンでもビッグエンディアンでも 同じソースコードで正常に動作する.関数自体は lib.c に追加してあるので, まあ詳しくはそちらを参照してほしい.

で,実装したのが以下.

エンディアン対応だが,パケットの読み書き部分に ntoh4() や hton4() を通すように するだけだ.KOZOSのプロトコルスタックでは, プロトコルヘッダの構造体は「XX_header」,編数名は「XXhdr」のような名前で 統一しているのと,将来的にエンディアン対応することも見越して書いてある (パケットのデータを読み書きする箇所を分散させず,いったん読んだ値を netbuf上に保持して受け渡すようにしている)のと, そもそもソースコードの絶対量が少ないので,対応はそれほど たいへんではない(各スタックがせいぜい数百行なので,ソースコード全チェックが パッとできるのがとても楽だ).まあ変更点は,前回からの差分をとってみれば わかるだろう(たいして多くはない).

ちょっと実機で動作確認していないのだが,まあたぶん動くのではなかろうか.


メールは kozos(アットマーク)kozos.jp まで