目次: Linux
Linux用のデバイスドライバを書くときはmodule_init(関数名) と書いて、初期化関数を指定します。
ドライバをカーネルモジュール(*.ko)としてビルドしたときは、insmodした時に初期化関数が実行されます。これは想像に難くないですし、初期化関数にprintkでも入れておけば簡単に確認できます。
では…、ドライバをLinuxカーネルに組み込んだ時は、一体いつ初期化処理が実行されるのでしょうか?Linuxには他のドライバも含まれていますが、実行される順序に決まりはあるでしょうか?
わたくし恥ずかしながら、この辺の仕組みを全く理解していなかったので、コードを調べてみました。既に同じような事を調べている人がいる気がしてなりませんが…まあ良いや。
Linuxの初期化は、linux/init/main.cのstart_kernel() から始まります。start_kernel() は山のように初期化を実行していますが、我らがmodule_init() の実行に関係しているのは下記の部分です。
start_kernel()
rest_init()
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
ここでカーネルスレッドが作成され、スレッドのエントリ関数はkernel_init() です。で、続けて追いかけます。
kernel_init()
kernel_init_freeable()
do_basic_setup()
do_initcalls()
do_initcall_level()
do_one_initcall()
initcall_levels[level][n]()
このような順で呼ばれます。このinitcall_levelsが今回の話のキモとなります。initcall_levelsは下記のように定義されています。
typedef int (*initcall_t)(void);
extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
コードを見る限り、二重配列 __initcall_levelsは __initcalln_startという配列を要素として持っていること、__initcall0_start[0], __initcall0_start[1], __initcall0_start[2], ... の順に関数ポインタが先に呼び出され、その後も同様に __initcall1_start, __initcall2_start, ... と続いて __initcall7_startに入っている関数ポインタは後に呼び出されることがわかります。
しかし肝心の __initcall0_startに何の関数ポインタが入るのか?はわかりません。それもそのはず、実はCのコード上に、これら __initcallx_start配列の定義はありません。別の方法で定義されているのです。
この __initcall0_start〜 __initcall7_start配列は、あるマクロで宣言された初期化関数のポインタを全て持つ配列となります。対応関係は下記の通りです。
なぜそうなるか?は後述しますので、とりあえずこんな関係があることだけ頭の隅に置いてください。
カーネルモジュールを作るとき、初期化関数を指定するためのおまじないとしてmodule_init(funcname) と書きますが、これは実はマクロでlinux/include/linux/module.hにて、下記のように定義されています。
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
マクロの定義はmodule_init = __initcall = device_initcallとなっていますので、module_init(funcname) はdevice_initcall(funcname) と書くのと同じです。
最後にmodule_init(funcname) は __define_init(funcname, 6) と定義されています。これもマクロでlinux/include/linux/init.hに下記のように定義されています。
#define __define_initcall(fn, id) \r static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
何だか小難しいですが、簡単に言うとmodule_init(funcname) つまり __define_initcall(funcname, 6) と書いたら、初期化関数funcnameのポインタを値に持つ __initcall_funcname6という名前の変数を宣言し、セクション .initcall6.initに置け、という意味になります。
その変な名前の変数がどこに置かれるのか?は、リンカスクリプトlinux/arch/arm/kernel/vmlinux.ldsに書いてあります。全部は載せられませんので .initcall6.initセクションに言及しているところを見てみます。
.init.data : {
*(.init.data) *(.meminit.data) *(.init.rodata) *(.meminit.rodata) . = ALIGN(8);
__clk_of_table = .;
*(__clk_of_table) *(__clk_of_table_end) . = ALIGN(32);
__dtb_start = .;
*(.dtb.init.rodata) __dtb_end = .;
. = ALIGN(16);
__setup_start = .;
*(.init.setup) __setup_end = .;
__initcall_start = .;
*(.initcallearly.init) __initcall0_start = .;
*(.initcall0.init) *(.initcall0s.init) __initcall1_start = .;
*(.initcall1.init) *(.initcall1s.init) __initcall2_start = .;
*(.initcall2.init) *(.initcall2s.init) __initcall3_start = .;
*(.initcall3.init) *(.initcall3s.init) __initcall4_start = .;
*(.initcall4.init) *(.initcall4s.init) __initcall5_start = .;
*(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .;
*(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .;
*(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; ★★これ★★
*(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init) __con_initcall_end = .;
__security_initcall_start = .;
security_initcall.init) __security_initcall_end = .;
...
※本来は改行が入っていませんが、見やすいように適宜改行を入れています。
私はリンカスクリプトに明るくないので、間違っていたらごめんなさいですが、基本的には書いた順でシンボル(変数や関数など)をオブジェクトファイル内に並べてくれる、という理解でこの辺りは大丈夫なはずです。
先ほどの __define_initcall() マクロの定義から __initcall_funcname6変数は、コンパイラが .initcall6.initというセクションに入れて、オブジェクトファイルを生成します。わかりにくいと思うので、例で説明します。
例えば、リンカが今までシンボルを並べてきた結果、現在のアドレスが0x10000になっていたとします。あと .initcall6.initセクションには __initcall_funcname6と __initcall_func2name6の2つのシンボルが含まれているとします。
リンカはまず「__initcall6_start = .;」を見て、現在のアドレス(ドットは現在のアドレスを表す)に0x10000: __initcall6_startを定義します。現在のアドレスは変わりません。
次に「*(.initcall6.init)」を見て、全オブジェクトファイルの .initcall6.initのシンボルを順番に並べ、現在のアドレスを進めます。つまり0x10000: __initcall_funcname6, 0x10004: __initcall_func2name6が並び、現在のアドレスは0x10008になります。「*(.initcall6s.init)」も同様ですが、何もシンボルが入っていないのでスルーします。
最後に「__initcall7_start = .;」を見て、0x10008: __initcall7_startを定義します。
すなわち下記のようなアドレス、シンボルの関係になります。
- Linuxカーネル
- セクションA
- セクションB
- セクション .init.data
- ...
- 0x10000: __initcall6_start
- .initcall6.init内の全シンボル
(内訳)
- 0x10000: __initcall_funcname6 = funcname() のポインタ
- 0x10004: __initcall_func2name6 = func2name() のポインタ
- 0x10008: .initcall6s.init内の全シンボル
- __initcall7_start
- .initcall7.init内の全シンボル
- ...
- セクションD
...
ここで、序盤の謎であった __initcall6_startの謎が解けます。思い出してほしいのですが、変数 __initcall_xxxx6はdevice_initcall(xxxx, 6) で指定した関数xxxxのポインタを値として持っていました。
リンカスクリプトによって、変数 __initcall_xxxx6の一群を __initcall_funcname6から「連続したアドレスに配置する」ので、C言語から見たときには、__initcall6_startはあたかも関数ポインタの値を持った配列に見えるわけです。
static initcall_t __initcall_funcname6 = funcname;
static initcall_t __initcall_func2name6 = func2name;
initcall_t __initcall6_start[] = {
__initcall_funcname6,
__initcall_func2name6,
...,
};
ただし、C言語ではこのような書き方はできないし、仮に書けたとしても、
&__initcall6_start[0] == &__initcall_funcname6
&__initcall6_start[1] == &__initcall_func2name6
が成り立たないので、厳密に同じことはできません。
この仕組みはC言語だけでは記述するのは恐らく不可能で、リンカとの連携の賜物と言えるでしょう。
論より証拠でLinuxカーネルをビルドして、生成されたvmlinuxをreadelfで調べましょう。
実験に使うカーネルは3.14.44でARM Versatile PBボード向けコンフィグでビルドしたものです。拙作のARM9エミュレータememu(emuemuについてはこちら)の動作確認用に実際に動作させていたバイナリです。
まずは -Sオプションでセクションの一覧を見ます。
$ arm-linux-gnueabihf-readelf -S vmlinux セクションヘッダ: [番] 名前 タイプ アドレスOff サイズES Flg Lk Inf Al ... [21] .init.pv_table PROGBITS c039b818 3a3818 0005cc 00 A 0 0 1 [22] .init.data PROGBITS c039bde8 3a3de8 003f6c 00 WA 0 0 8 [23] .data PROGBITS c03a0000 3a8000 027380 00 WA 0 0 32 ...
アドレスc039bde8からc039ffffまで、サイズ3f6c分のセクション .init.dataが居ることがわかります。vmlinuxをバイナリエディタなどで見る場合はOffの位置、つまり3a3de8を見ると .init.dataのバイナリデータが見られます。
Linuxはビルドしたときに全シンボルのアドレスと、シンボル名の対応表を作成してくれます。アドレスとシンボル名の対応はSystem.mapというファイルに出力されます。
$ grep initcall6 System.map c039fb08 T __initcall6_start $ grep initcall7 System.map c039fc94 T __initcall7_start c039fcb4 t __initcall_deferred_probe_initcall7
System.mapを見てもわかるように、__initcallx_startの軍団は .init.dataセクションの範囲内に全員収まっていることがわかります。
残りの疑問であるinitcall6内の呼び出し順はどうなるか?については、コンパイルされた順番になるようです。
System.mapの __initcall6_start付近を見ると、
c039fb08 T __initcall6_start ★★★arch/arm/ 以下 c039fb08 t __initcall_fpe_init6 ★★★kernel/ 以下 c039fb0c t __initcall_proc_execdomains_init6 c039fb10 t __initcall_ioresources_init6 ... c039fb50 t __initcall_pid_namespaces_init6 c039fb54 t __initcall_utsname_sysctl_init6 ★★★mm/ 以下 c039fb58 t __initcall_init_per_zone_wmark_min6 c039fb5c t __initcall_kswapd_init6 ... c039fb70 t __initcall_slab_proc_init6 c039fb74 t __initcall_cpucache_init6 ★★★fs/ 以下 c039fb78 t __initcall_fcntl_init6 c039fb7c t __initcall_proc_filesystems_init6 ... c039fbbc t __initcall_init_jffs2_fs6 c039fbc0 t __initcall_init_romfs_fs6 ★★★ipc/ 以下 c039fbc4 t __initcall_ipc_init6 c039fbc8 t __initcall_ipc_sysctl_init6 ★★★crypto/ 以下 c039fbcc t __initcall_crypto_algapi_init6 c039fbd0 t __initcall_aes_init6 ★★★block/ 以下 c039fbd4 t __initcall_proc_genhd_init6 c039fbd8 t __initcall_bsg_init6 c039fbdc t __initcall_noop_init6 c039fbe0 t __initcall_deadline_init6 c039fbe4 t __initcall_cfq_init6 ★★★drivers/ 以下 c039fbe8 t __initcall_pl061_gpio_init6 c039fbec t __initcall_fb_console_init6 ... c039fc68 t __initcall_ms_driver_init6 -> module_hid_driverが生成した関数 c039fc6c t __initcall_mr_driver_init6 -> module_hid_driverが生成した関数 ★★★net/ 以下 c039fc70 t __initcall_sock_diag_init6 c039fc74 t __initcall_flow_cache_init_global6 ... c039fc90 t __initcall_packet_init6 c039fc94 T __initcall7_start
このようになっています。linux/Makefileを見てみると、
ifeq ($(KBUILD_EXTMOD),) core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
このように同じ順で並んでいることがわかります。ざっと見た感じだと、サブディレクトリ内のモジュール達(fs, driversの下)も同様に、Makefileに書いた順で初期化されるようです。
要するに、モジュールBがモジュールAに依存している場合、MakefileにはA → Bの順で書かないとなりません。逆に書いてしまうと、モジュールBが先に初期化され、未初期化のモジュールAを呼び出し、カーネルがクラッシュする悲劇が起きます。
この記事にコメントする
「うわ... 私のiPhone 6sバッテリーもたない?」CPU違いの6sでバッテリーベンチをしてみました を読んで。
元々の原価が低いのか、FinFETで首位に立つべく値引きしたか、事情は知りませんが、廉価版のiPhoneほどSamsung製の搭載比率が大きいところを見ると、TSMCよりSamsungの方がAppleの利ザヤがデカいのでしょう。おそらく。
そういえばQualcommはSnapdragon 810(TSMC 20nm)で発熱問題に泣いて、次のSnapdragon 820はSamsung製らしいですね。16nm世代を採用したら次も泣かされそう…。
メモ: 技術系の話はFacebookから転記しておくことにした。
この記事にコメントする
今や映像の圧縮方式として当たり前となりつつあるMPEG4-AVC(※)には、2つの形式があります。
Byte Stream Formatは、俗に言うスタートコード形式(0x000001を目印としてNALUが始まる)で、コンテナを使わない場合、リアルタイム配信(RTSP)、放送系(MPEG2-TSをコンテナ)で良く使われているようです。
利点としては「欠け」に強いことです。何か問題が発生してデータが欠けてしまっても、スタートコードさえ見つかれば、その地点から処理を再開できます。その分、毎度スタートコードを探す処理が必要がある、先頭が欠けても再生できるように重複して情報を入れる必要があるなど、やや非効率的です。
NAL File Formatは、いわゆるサイズ指定形式(数バイトのサイズの後にNALUが始まる)です。ファイル形式(MP4など)に良く使われているようです。
利点としては「効率的」であることです。余分な情報はコンテナに持たせて、ビットストリームからは取り去りますのでサイズが小さくできます。NALUのサイズも予めわかりますから不要なNALUをスキップする際の負荷も低いです。その分、1バイトでも欠けてしまうと、デコードできなくなってしまう弱さもあります。
実はこの2つを変換するのは面倒で、サイズ指定をスタートコードに置き換えるだけではダメです。そんな単純な変換では、上記のような特徴が出せません…。
(※)AVCはAdvanced Video Codecの略です。この規格はISO/IECとITU-Tの共同規格なので、MPEG4-AVCではなくITU-Tの規格名であるH.264と呼ぶ人も多いですね。
でも、自分でそのような処理を書く必要はありません。世の中にはナイスガイ達が居て、変換が可能なツールを公開してくれています。ありがたいことです。
まずはffmpegを使う場合ですが、コーデックは変更しないのでcopyを指定して、普段の利用ではあまり見かけないbsfオプションを使って変換します。
なぜmp4toannexbという名前かと言うと、MPEG4-AVCの規格であるISO/IEC 14496-1 Annex BにByte Stream Formatの規格が示されているからではないか?と思われます。
avconv -i /path/to/hogehoge.mp4 -vcodec copy -bsf:v h264_mp4toannexb hogehoge.h264
Gstreamerを使う場合はh264parseエレメントを使います。ffmpegと比べるとかなり長く感じますが、大したことはしていないです。
例では、改行を入れていますが、実際には改行を入れずに1行で書いてください。もしGstreamer 1.0系を使う場合は、video_00のところをvideo_0と書く必要があったはずです。たぶん。
gst-launch filesrc location=/path/to/hogehoge.mp4 ! qtdemux name=dem dem.video_00 ! h264parse ! video/x-h264,stream-format=byte-stream,alignment=au ! filesink location=/path/to/es/hogehoge.h264
普通に動画を楽しんでいる人には、こんな変換は全く縁がありませんが、私にとっては便利なんです。こんなツール達がオープンソースで公開されているというのは、ありがたいことですなー…。
この記事にコメントする
Visual Studio 2015のコンパイラは、未だにBOMを付けないとUTF-8を認識しません。なぜかShift-JISもコンパイルエラーになります(2013ではなりません)。
MSDNライブラリ(サイトへのリンク)でも「コンパイラおよびリンカーでのUnicodeのサポート(... 中略 ...)BOM付きのUTF-8」などと言いきる始末です。イマイチすぎる…。
Visual StudioのIDEはGitに対応したり、エディタがマルチプラットフォームになったり、コミュニティベース開発に力を入れたり、時代の流れに沿って進化していますが、
コンパイラはダメダメ感が漂います。C++0xの対応はllvm, gccの後塵を拝しワーストです。もはやMicrosoftですら単独でC/C++ コンパイラを開発するのは不可能なのかなあ…?
メモ: 技術系の話はFacebookから転記しておくことにした。
この記事にコメントする
SDガンダム ジージェネレーションフロンティアの話。今までのFR設計イベントをメモしておきます。
第一弾 FRアカツキ(シラヌイ)/ 誘導機動ビーム砲塔システム -- URゲーマルク(覚醒)/マザー&チルド・ファンネル -- URシナンジュ /バズーカ -- URドム/ジェットストリームアタック -- URデスティニーガンダム(最大稼働)/パルマフィオキーナ 第二弾 FR νガンダム/連続格闘 -- URクロスボーン・ガンダムX1改・改/ヒートダガー&スクリュー・ウェッブ -- URサザビー/ファンネル(Ver.2) -- URジ・O(覚醒)/ビーム・ソード/四刀流 -- URアルトロンガンダム/ドラゴンハング 第三弾 FRガンダムアヴァランチエクシア/GNソード/ソードモード -- URストライクルージュ(IWSP)/一斉発射 -- URノイエ・ジール/有線クロー・アーム -- UR ∀ガンダム/核弾頭 -- URケルディムガンダムGNHW(トランザム)/GNライフルビット 第四弾 FRガンダムデルタカイ/プロト・フィン・ファンネル -- URグフ・カスタム/ヒート・ワイヤー -- URガンダムDX(Gファルコン)/ツインサテライトキャノン -- URデストロイガンダム/ツォーンMk2&スーパースキュラ -- URデルタガンダム/ビーム・サーベル/二刀流 第五弾 FRガンダムAGE-2ノーマル/MS/ハイパードッズライフル -- URザクII改/ヒート・ホーク&ダミーバルーン爆弾 -- URトールギスIII/メガキャノン/最大出力 -- URガンダムAGE-1ノーマル/ドッズライフル -- URラフレシア/親バグ&子バグ
これらのイベントは既に終わっていますが、しばらく経つと再実施されるようです。作り逃したFRキャラクターが居ても、後でまた作れるかもしれませんので、材料となるURキャラクターを、間違って処分してしまわないようにメモしておきます。
楽しい点は、小学生の時にハマっていたSDガンダムのカードダス集めの気分が味わえることです。特にFRキャラクター(カードダスのキラキラカードに相当)が当たった時は、出たこれ!って気分で嬉しいですね。
ゲームとしてはつまらないです。もし同じ面を100回以上やる「イベント」を、毎週連発するだけのゲームが面白い!好き!と思う人が居ればオススメしますが…。私は立ちながらか、歩きながらプレイしないと確実に寝ます。
嫌いな点はUIです。いちいちボタンを押さないと進まないのに、ボタンの配置に統一感がありません。片手で遊びづらいです。
この記事にコメントする
Visual Studio Community 2015をインストールしてみたので、適当なプロジェクトを作成して、デバッグしようとすると「アクセスが拒否されました」と怒られました。なんだ、いつのまにこんなことに…?
どうもプロジェクトをsambaの共有ドライブ上に作成していたのが良くないようで、生成されたexeファイルを直接実行しようとしても「アクセスできません」と怒られてしまいます。
Sambaのデフォルトの設定だとWindows側からファイルを作成すると全てのファイルがパーミッション0755で作成されてしまい、実行ファイルでもないファイルに実行権限が付いてしまいます。
これはLinuxからファイルを見る時に非常にウザいので、私の環境ではSambaの設定にcreate mask = 0644を追加しています。しかし、この設定だとexeファイルもパーミッションが0644で作成されてしまいます。
Sambaはパーミッション0644をWindowsのACLに変換するとき「実行権限が無い」と解釈するため、Windows側が「実行してはいけないファイルを実行するな」と怒っているようです。
Linux側から手動でexeファイルのパーミッションを644から755に変更すると、実行できるようになりますが、ビルドの度にパーミッションが644に戻るので、毎回変更しなければならずダルくてやっていられません。
逆にWindows側から実行権を付けようと [プロパティ] - [セキュリティ] - [編集] ボタンから、実行権限を付加しても、ファイルのパーミッションに全く反映されません。
この問題に半日悩んでいたのですが、実は非常に簡単な設定がありました。
Sambaの設定にacl allow execute always = yesを加えれば解決しました。このオプションは名前の通り、ファイルは常に実行可能、と解釈するオプションのようです。
なぜか日本語のヘルプが見あたらないのですが、最近追加されたオプションなのでしょうか?まあ、今となってはどうでも良いですが…。
ちなみにSambaの設定ファイルは /etc/samba/smb.confです。もし現在のSambaの設定の一覧を調べたければtestparmコマンドを使ってください。便利です。
この記事にコメントする
グランフロント大阪(大阪市北区)に、進撃の巨人展に併設の3D VRシアター「哮」を見に来ました。
チケットに書いてあるフォルムからして、ソニーのHMDじゃなさそう、機材はOculus Riftですかね。
上映時間は5分くらいとのこと。見終わったらまた感想でも書きます。
見てきました。ストーリーは無いに等しいです。立体機動装置って何?くらいを知っていれば楽しめると思います。
映像はキレイとは言えませんが、画が動いていれば、荒さは気になりません。
視点の動きは立体機動にしては大人し目ですが、もしミカサやアルミン並の上下動をされたら、メチャクチャ酔うと思います。おそらく物足りない位がちょうど良いのでしょう…。
映画と違うなーと思うところは、やはり枠の概念がないことでしょうか。振り返ると真後ろの景色が見えるのはとても斬新です。
技術面でも、
後者はすぐ解決されると思いますが、何かしらの標準ができるまでは、互換性やフォーマット乱立で揉めそうな予感がします。
YouTubeが一部、全視野対応の動画を配信していた気がしますが、あれ半天だったかな?
あと、見る前は、映画館のように暗い部屋で見るのかと思っていたのですが、写真のように明るい部屋での上映でした。Oculusは視界を覆うゴーグルタイプだから、周りの明るさは関係ないんですなー。
ただ、端から見るとかなり怪しい集団です…。なんだこれ。
メモ: 技術系の話はFacebookから転記しておくことにした。
この記事にコメントする
| < | 2015 | > | ||||
| << | < | 10 | > | >> | ||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| - | - | - | - | 1 | 2 | 3 |
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
25年10月6日
25年10月6日
25年9月29日
25年9月29日
20年8月24日
20年8月24日
16年2月14日
16年2月14日
25年7月20日
25年7月20日
25年7月20日
25年7月20日
25年7月20日
25年7月20日
20年8月16日
20年8月16日
20年8月16日
20年8月16日
24年6月17日
24年6月17日
wiki
Linux JM
Java API
2002年
2003年
2004年
2005年
2006年
2007年
2008年
2009年
2010年
2011年
2012年
2013年
2014年
2015年
2016年
2017年
2018年
2019年
2020年
2021年
2022年
2023年
2024年
2025年
過去日記について
アクセス統計
サーバ一覧
サイトの情報合計:
本日: