各 OSの IPヘッダ構造体を見ていると, attribute packedを使っているのと
使っていないのがあってなんでだ、って思って生成するコードを調べてみた。
attribute packedをつけているのは NetBSD, FreeBSD.
つけてないのが Linux, OpenBSDです.
各 OSの定義
サンプルコード
全部だと長いので頭のほうだけ取り出したものを例に示します。
struct packed_ip { unsigned char ip_hl:4, ip_v:4; unsigned char ip_tos; unsigned short ip_len; } __attribute__((__packed__)); struct not_packed_ip { unsigned char ip_hl:4, ip_v:4; unsigned char ip_tos; unsigned short ip_len; }; int get_ip_len_packed (struct packed_ip *a) { return a->ip_len; } int get_ip_len_not_packed (struct not_packed_ip *a) { return a->ip_len; }
これをコンパイルします。わかりやすいように mipsのクロスコンパイラを
使いました。GCC version 4.6.1
% mipsel-linux-elf-gcc -c -O2 align_attribute.c
逆アセンブル結果した結果は以下のようになりました。
00000000 <get_ip_len_packed>: 0: 90820003 lbu v0,3(a0) 4: 90830002 lbu v1,2(a0) 8: 00021200 sll v0,v0,0x8 c: 03e00008 jr ra 10: 00431025 or v0,v0,v1 00000014 <get_ip_len_not_packed>: 14: 94820002 lhu v0,2(a0) 18: 03e00008 jr ra 1c: 00000000 nop
ともに unsigned short型のメンバ変数を読み出しているだけ
なのですが、packedの方は 4命令使っています。2バイトのデータを
1バイトずつ読み出し、シフトし、orを取っています。一方 packed
しない方は half word load一回だけです。
参考までに armでの結果も示します。
00000000 <get_ip_len_packed>: 0: e5d03002 ldrb r3, [r0, #2] 4: e5d00003 ldrb r0, [r0, #3] 8: e1830400 orr r0, r3, r0, lsl #8 c: e12fff1e bx lr 00000010 <get_ip_len_not_packed>: 10: e1d000b2 ldrh r0, [r0, #2] 14: e12fff1e bx lr
armでも同様です。orrの列は orと shiftを同時にやっているっぽい.
考察
このような命令が生成される具体的な理由はわからないですが、
packedをつけた場合は, データ構造が整列されていないものと想定して
コンパイラはコード生成を行っているものと考えられます。
もしデータが整列していない場合, (shortが奇数バイトに配置されている等)
half word loadを行うと alignment例外が発生して, ユーザランドなら
終了, カーネルなら最悪 panicという自体になります。ですが, 上記の
ように 1バイトずつ読めば alignment例外が発生することはありません.
そのあたりのことを考慮しているのだと考えられます。
ネットワークプロトコルのようなぎゅうぎゅうに詰まったデータだと
packedを付けないといけないのかなと思ってしまうのですが、それで
結構性能やコードサイズを犠牲にしているのではないかと思いました。
OpenBSDや Linuxがつけていなくていないのは, そのあたりのことも
いくらか考慮されているのかもしれません。(属性をサポートしていない
コンパイラを考慮しているだけかもしれないですが・・・)
packedをつけた場合のコード効率の改善
FreeBSDでは packedをつけたものだと効率が悪いということで、
packedに加えて aligned attributeも付けられています。その場合の
例を示します。
struct packed_aligned_ip { unsigned char ip_hl:4, ip_v:4; unsigned char ip_tos; unsigned short ip_len; } __attribute__((__packed__)) __attribute__((aligned(4))); int get_ip_len_packed_aligned (struct packed_aligned_ip *a) { return a->ip_len; }
00000020 <get_ip_len_packed_aligned>: 20: 94820002 lhu v0,2(a0) 24: 03e00008 jr ra 28: 00000000 nop
ARMの場合は
00000018 <get_ip_len_packed_aligned>: 18: e1d000b2 ldrh r0, [r0, #2] 1c: e12fff1e bx lr
このようになり, packedをつけていない場合とどうようの
コードになります。アラインメントされているいう情報から
このようなコードが生成されたのでしょう.
packedをつけたデータが必ず nバイトのアラインメントにある
場合であれば, aligned属性も合わせてつけた方がよいかと思い
ます。FreeBSDについてはつけるべきでないと考えているの
ですが、確証が得られていないのでそれはもう少し調べてから
書くことにします。
まとめ
attribute packedをつけたデータ構造について生成されるコード
について述べました。