JNAではまる、その2。__attribute__((__packed__))のマッピングをする。
さらに2時間ほどはまった。。。第一回は以下から。
JNAではまる。Javaからepollを使いたい。 - StoryEdit 開発日誌
さて、やりたいことはepollをJNAから使うってことだが、いかんせん、JNA初心者には学ぶことがおおい。
そもそもバインドしたい構造体をみてみる。
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ } __attribute__ ((__packed__));
最初は"union"を見逃していたため、後に苦労したが、この構造体のバインドは以下の順番で処理した。
- 構造体のマッピング
- 構造体のネストのマッピング
- void*のマッピング
- unionのマッピング
- __attribute__(((__packed__)))のマッピング (はてな文法のため括弧ひとつ多い)
5つ目のpackedされたデータのマッピングは情報がなかなか見つからなかったのでここについて書くことにする。
__attribute__(((__packed__)))って?
CPUは現在、モバイル機器を除いて、64bitCPUが主流だ。プログラマが考えなければいけないのは、主にレジスタ、パイプライン、キャッシュなどであるが、これらがすべて64bit単位で動く。Cコンパイラはある構造体をコンパイルするときに、CPUからアクセスしやすいように、構造体のアラインメントを行う。これは、CPUからアクセスする単位である8バイトの先頭にデータがくるように、すべてのデータをそろえた方が、アクセス効率が良く、全体的にパフォーマンスがあがるからだ。
このため、8バイトの乗数未満のデータはすべて8バイト単位に整列させられる。しかし、構造体のネストや配列化がおこる場合、このアラインメントはメモリ効率を落としてしまう。特にCPUキャッシュはサイズが小さいため、なるべく多くの情報をのせた方がいい。したがって、コンパイラはすべてのソフトウェアを対象に性能をあげるためにアラインメントをおこなうが、パフォーマンスクリティカルなソフトウェア(カーネルなど)は、キャッシュサイズを意識したプログラミングが行われる。そして__attribute__(((__packed__)))は、この最適化を行うための指示で、コンパイラによるアラインメントを抑止する。上のepoll_data_tは実際は12バイトのデータを持つが16バイトにアラインメントされるところを、最小の12バイトで作る。
__attribute__(((__packed__)))のmapping
JNAで構造体をマッピングするとき、デフォルトではアラインメントは勝手にプラットフォームにあわせて行われる。しかし、このアラインメントの種類をかえるメソッドが、Structure.setAlignTypeである。
従って、以下のようにバインドする。
// epoll bindings public static class EpollData extends Union { public int fd; public long u64; // we don't need this, but make size 8. public static class ByReference extends EpollData implements Structure.ByReference {} public static class ByValue extends EpollData implements Structure.ByValue {} } public static class EpollEvent extends Structure { public EpollEvent() { super(); setAlignType(Structure.ALIGN_NONE); // to make it packed } public int events; public EpollData data; public static class ByReference extends EpollEvent implements Structure.ByReference { } public static class ByValue extends EpollEvent implements Structure.ByValue { } }
Structure.ALIGN_NONEはアライメントなし。今回はStructure.GNU_Cでも良い。