Linux Kernel で提供される標準ライブラリはおおよそアプリケーション向けの glibc に近い機能が提供されています。加えてビット操作と kernel 内ならではのアルゴリズム群があります。アルゴリズム群は「こんなのも有るのか」程度に知っておくと良いでしょう。アプリケーション向けに作られたライブラリが移植されていることもあるので、欲しいと思ったら Linux Kernel ソースコード検索 から探してみるのも良いでしょう。
要注意なことは、アプリケーションプログラムでは普通に除算 '/'、剰余 '%' 演算子を用いた式を long long 型に対して書けば計算可能なのに対し Linux Kernel 内では linux/math64.h からインクルードされる関数群を使用して関数呼び出し(あるいはマクロ呼び出し)の形で計算する必要があることです。
双方向リンクリストを操作するマクロです。リストを辿るループ処理を構成する list_for_each_entry() などの巧妙なマクロが定義されています。
機能単位 | 主なヘッダやソース | 備考 |
リスト操作 | linux/list.h | リンクリストメンバーからそれを保持する構造体へ変換するマクロ list_entry() は container_of() と同じです。リスト操作に関連するならば list_entry() を使用して下さい。 |
整数とビットに対するアトミック操作の機能群です。Tasklet, Work Queue などを含み、複数のスレッドや割り込みコンテキストと共有する変数、お互いの同期を取るための状態変数を操作するために使用します。ソースコード中で a++ や a |= FLAG_MASK といった式を書いたときに 変数 a は複数スレッドや割り込みコンテキストから参照・更新されるのか常に気にする必要が有ります。もし、参照・更新されるのなら spin_lock やアトミック操作が必要である可能性が高いです。
機能単位 | 主なヘッダやソース | 備考 |
整数 | linux/atomic.h asm/atomic.h | asm/atomic.h はプロセッサ毎に定義されています。asm/atomic.h は linux/atomic.h で include されます。 |
ビット | asm/bitops.h | asm/bitops.h はプロセッサ毎に定義されています。ヘッダファイルに atomic かどうか書かれています。atomic な関数・マクロは set_bit(), clear_bit(), clear_bit_unlock(), change_bit(), test_and_set_bit(), test_and_set_bit_lock(), test_and_clear_bit(), test_and_change_bit(), test_bit() atomic 関数と混在可 です。asm/bitops.h は linux/bitops.h で include されます。 |
バリア指示 barrier() とバリア同期 mb(), rmb(), wmb() の関数またはマクロ定義です。バリア指示はコンパイラに対して、barrier() を超えてメモリアクセス順を変更しないようにします。バリア同期は同期関数の前までに行ったメモリ操作 mb(), メモリ読みだし rmb(), メモリ書き込み wmb() を済ませるよう指示します。インラインアセンブラなどによってロート・ストアキューまたはキャッシュに保留されたトランザクションをメモリに反映します。Documentation/memory-barriers.txt を参照して下さい。
プラットホーム(SoC 内部に構成された回路ブロックやバス、基板・バックプレーン・インターコネクトで接続されたプロセッサ、チップセット、バスブリッジなどで構成されるアーキテクチャ)によっては各回路(特にバスブリッジやインターコネクト)のリクエストキューにバストランザクションがバリア指示・同期を行っても滞留する場合もあります。プラットホームのデータシートを参照して意図通りにバリア指示・同期ができているか確認して下さい。
機能単位 | 主なヘッダやソース | 備考 |
バリア | linux/compiler.h asm/barrier.h | linux/compiler.h は linux/kernel.h より include されます。asm/barrier.h は linux/atomic.h から include されます。 |
複数の処理を一貫して行いたい場合に使う関数とマクロ群です。Spin Lock は複数のバリエーションがあり、割り込みコンテキストと共に使える機能もあります。ロックを獲得できるまで CPU が Spin (空走ループ)します。短時間で済む処理をロックして行うのに適しています。空走時間が長くなる場合 CPU の処理能力を無駄に消費します。
Semaphore, Mutex は呼び出したプロセスやスレッドなどを中断して他に実行を譲れる状況で使用します。割り込みコンテキストの中から使うことは出来ません。ロックを獲得できないならば、他に実行を譲ります。
機能単位 | 主なヘッダやソース | 備考 |
Spin Lock | linux/spinlock.h | 使い分けのドキュメントは Documentation/locking/spinlocks.txt, Documentation/DocBook/kernel-locking/index.html にあります。ドキュメントの冒頭にあるように spin_lock_irqsave(), spin_unlock_irqrestore() の組が最も汎用的にどんな場所でも使えます。使い分けが難しいと感じるならばこの組み合わせから使ってみて下さい。 |
Semaphore | linux/semaphore.h | |
Mutex | linux/mutex.h | Documentation/locking/mutex-design.txt, Documentation/locking/rt-mutex-design.txt にドキュメントがあります。 |
条件が成立するまで待つ機能、完了を待つ機能があります。別スレッドや work queue に処理を任せたあと完了を待ったり、割り込みが発生を条件に処理を進める場合に使用します。
機能単位 | 主なヘッダやソース | 備考 |
Condition | linux/wait.h | ドキュメントは Wait queues and Wake events です。説明の中に Wait Queue を初期化する関数 init_waitqueue_head() の説明がありません。これはソースコードを見て用法を理解して下さい。初期化が済んだ後の典型的な流れは wait_event_interruptible() で条件待ちをし、条件成立をさせたならば wake_up() で起床させます。wait_event_*, wake_up_* とも変化型が多くあります目的に合う関数またはマクロを選んで下さい。 |
Completion | linux/completion.h | ドキュメントは Documentation/scheduler/completion.txt です。古いドライバでは linux/semaphore.h にある down(), up() を使って完了待ち合わせを実現しています。今はこのような実装は書き直されているはずです。 |
デバイスから来る割り込みを受ける関数を登録するための定義群です。ドライバやデバイスが外された時は登録抹消します。割り込みコントローラの制御は SoC ベンダーあるいはボードベンダーによって実装されています。割り込みハンドラに必要最低限の処理は割り込み要因を解消することです。解消しない場合は割り込み処理が再割り込みによって永遠に継続したり、2 度目の割り込みが発生しないなどの事態になります。
機能単位 | 主なヘッダやソース | 備考 |
IRQ class | linux/interrupt.h | ドキュメントは Documentation/DocBook/genericirq/index.html です。デバイスドライバでよく使われる request_irq() は Documentation/driver-model/design-patterns.txt にデザインパターンとして出ています。多くのドライバソースコードを読んで用法を習得できるでしょう。 割り込み番号(IRQ number)は SoC ベンダーが開発・実行環境を整備しているならば gpio_to_irq() 関数を使ってチップのピン番号から IRQ 番号へ変換します。linux/gpio.h をインクルードすれば gpio_to_irq() を使えるようになります。GPIO (汎用入出力ピン)を識別するマクロ定義は各プラットホーム毎に違うので arch/processor 以下の開発対象に関係したソースを読んでみてマクロ定義の一覧を含んだヘッダファイルあるいはデバイスのピン番号とソースコード中の GPIO ピン番号の対応を把握して下さい。 |
制御対象のデバイスはプロセッサの処理速度に比べ動作が遅いことがあります。コマンド発行間隔、要求してから結果が得られるまでの待ち時間が必要な場合が有ります。Sleep, Delay, Timer はこれらのタイミングを合わせるための機能です。注意点はこれらの待ち時間は長くなる方向でぶれます。平行して隣接する機能ブロックを操作している場合、処理や制御の重なりで思わぬ挙動を示す場合が有ります。
機能単位 | 主なヘッダやソース | 備考 |
Sleep | linux/delay.h | sleep の代表的な関数は msleep(), usleep_range() です。実行中のタスクを再スケジュールします。CPU 資源を無駄にしません。割り込み処理中では使えません。 単に別の task に実行を渡す(いわゆる 0 秒待ちをする)場合は schedule() を使用します。 |
Delay | linux/delay.h | delay 関数は ndelay(), udelay(), mdelay() があります。これらは CPU を空走させて時間待ちを実現します。割り込みハンドラの中でも使えます。しかし、割り込み応答性能を悪化させます。 |
Timer | linux/timer.h linux/hrtimer.h | ドキュメントは Delaying, scheduling, and timer routines です。代表的な関数は add_timer(), mod_timer(), del_timer_sync() です。 |
Delayed Work | linux/workqueue.h | 遅延実行機能付きの Work Queue です。指定時間が経過した後に呼び出される関数の中で msleep() などのコンテキストスイッチの可能性を伴う関数を使用できるので Timer より使い勝手が良いです。INIT_DELAYED_WORK(), schedule_delayed_work() などの用例を参考にして下さい。 |
Kernel 内で刻まれている時刻を取得する機能群です。jiffies は 1/HZ 秒単位 で進む時刻です。ミリ秒オーダーの精度です。より高精度の時刻を必要とする場合は、local_clock(), sched_clock() を使用して下さい。これらは Kernel が起動されてから刻まれる時刻です。現実世界の時刻と相対的な時間差があります。CPU が完全に停止した suspend 中には刻まれず止まっています。
機能単位 | 主なヘッダやソース | 備考 |
jiffies | linux/jiffies.h | jiffies の時間的前後関係を判定する場合は、linux/jiffies.h に定義された比較関数・マクロを使用して下さい。jiffies と時間を変換するには HZ を直接使うより msecs_to_jiffies(), jiffies_to_msecs() などを使用することをお勧めします。マイクロ秒、ナノ秒単位への変換関数 linux/jiffies.hに定義されています。 |
Time | linux/time.h linux/timekeeping.h linux/sched.h | linux/sched.h の中に local_clock(), sched_clock() などの時刻関数が入っています。 |
アプリケーションで使用している malloc(), free() の代わりになる機能です。動的なメモリ確保を必要とする場合、ほぼ kzalloc(), kfree() の 2 つで十分に対応できます。
生成したデバイスコンテキスト、デバイスに対するコマンドパケットなどの生存期間はデバイスが突然外されたり、コンテキストが複数のリンクリスト等に属する場合、特定の操作に合わせた単純な allocate - free の関係では扱いきれない場合が有ります。リファレンスカウンタ (ドキュメントは Documentation/kref.txt、ヘッダファイルは linux/kref.h) が使えないか、検討して下さい。
機能単位 | 主なヘッダやソース | 備考 |
任意サイズ | linux/slab.h | アロケーターは SLAB, SLUB, SLOB の 3 つがあります。どれを選んでもインクルードするファイルは linux/slab.h です。一つの構造体を複数回確保する様な状況(固定した任意サイズ x N 個)に対して kmem_cache_create(), kmem_cache_init(), kmem_cache_alloc(), kmem_cache_free() 等とこれらの周辺機能を使うと、キャッシュラインを意識しアクセス速度を早めた領域を確保できます。 |
ページ単位 | linux/gfp.h linux/mm.h | ページ確保: linux/gfp.h:alloc_page() 、ページ・アドレス取得: linux/mm.h:page_address() となっています。 |
おそらく DMA 転送を主導するようなドライバを書く機会は少ないと思います。ほぼこのような処理は SoC デバイスメーカーや開発プラットホームを作ったメーカーが提供しています。最も使うのは User - Kernel Copy だと思います。ioctl(), read(), write() に対応する処理を実装すると、アプリケーションプログラムへ(から)データを転送する処理が伴います。このデータ転送は(実質的な実装がそうであっても) memcpy() を使ってはいけません。
機能単位 | 主なヘッダやソース | 備考 |
User - Kernel Copy | asm/uaccess.h linux/uaccess.h | asm/uaccess.h は linux/uaccess.h で include されます。copy_to_user(), copy_from_user() などはアーキテクチャに最適な実装がされることがあります。ioctl() の実装などで構造体の各メンバーを順にアクセスする場合は、構造体全体のアクセス可能性を access_ok() で確認してから進めるのが良いでしょう。 |
Virt - Phy map convert | asm/io.h linux/io.h mach/hardware.h | asm/io.h は linux/io.h で include されます。virt_to_phys(),phys_to_virt() などはアーキテクチャやプラットホームに最適な実装がされることがあります。 ioremap(), iounmap() などを使って物理アドレスと仮想アドレスの対応付けをします。一部のプラットホームでは内蔵された機能ブロックの仮想アドレス割り付けを単純化していて、軽量な実装の IO_ADDRESS() マクロなどを経由して物理アドレスから仮想アドレスへ変換出来るよう便宜が図られています。arch/processor/ 以下に格納されたプラットホーム毎の初期化処理やデバイス登録処理を読んでみて機能ブロックに対する仮想アドレスの扱いかたを把握しておくと良いでしょう。 |
DMA mapping, Address convert, Scatter Gather | asm/dma-mapping.h asm-generic/dma-mapping-common.h | asm/dma-mapping.h, asm-generic/dma-mapping-common.h は linux/dma-mapping.h で include されます。 アーキテクチャ、プラットホームによっては DMA アドレスと物理アドレスが違っています。phys_to_dma(), dma_to_phys() 関数が用意されています。 |
SoC 内蔵の機能ブロック、PCI 接続のカードに乗っているデバイスはメモリにマップされたレジスタやメモリ空間があります。これらをアクセスするために用意された関数、あるいはマクロ群です。C 言語の '*' (間接演算子) を使ったアクセスは禁止されていません。しかし、関数やマクロには volatile を付加したり、メモリバリアを行ったりデバイスをアクセスする作法として必要な処理が盛り込まれています。
機能単位 | 主なヘッダやソース | 備考 |
Read-Write | asm/io.h | asm/io.h は linux/io.h で include されます。readb(), writeb(), readw(), writew(), readl(), writel() などメモリにマップされたデバイスをアクセスするための関数がアーキテクチャやプラットホームに最適化されています。 |
スレッド・軽量処理
機能単位 | 主なヘッダやソース | 備考 |
kthread | linux/kthread.h | |
work queue | linux/workqueue.h | |
tasklet | linux/interrupt.h | ヘッダファイルは linux/interrupt.h です。この中の tasklet_init() をはじめとする関数群です。 |
接続切断通知
機能単位 | 主なヘッダやソース | 備考 |
UEvent | linux/kobject.h | kobject_uevent(), kobject_uevent_env() 関数で kobject (ユーザー・ランド視点で /sys 以下のノード) から uevent を発行します。 |
External Connector (Switch) | linux/extcon.h | Android で switch と呼ばれていた抽象化デバイスです。Android も extcon に移行しています。コネクタの接続切断、キーボードとして扱わないボタン押しなどを検出して通知します。 |
リファレンスカウンタ
機能単位 | 主なヘッダやソース | 備考 |
kref | linux/kref.h | Reference Counter を構成するライブラリです。生成したインスタンスの生存期間を参照の有無によって決定する場合に使います。kref_init(), kref_get(), kref_put() が代表的関数です。 |
Kernel Object
機能単位 | 主なヘッダやソース | 備考 |
KObject, Kset | linux/kobject.h | kobject(kernel object) のインスタンスを直接生成する機会は殆ど無いと思います。kobject を利用する場面は、kobject を参照するポインタを使って /sys (sysfs) の下にあるデバイスやドライバのノードに特殊なノードを作ったり、uevent を発行するのが主な場面になるでしょう。 |
ノード形成
機能単位 | 主なヘッダやソース | 備考 |
VFS(character) | linux/fs.h linux/cdev.h | alloc_chrdev_region(), register_chrdev(), cdev_alloc(), cdev_init(), cdev_add() 辺りから使用例を探ると良いでしょう。 |
VFS(block) | linux/fs.h linux/genhd.h | register_blkdev(), blk_register_region(), alloc_disk(), add_disk() 辺りから使用例を探ると良いでしょう。 |
SCSI | scsi/scsi.h scsi/scsi_host.h scsi/scsi_driver.h scsi/scsi_device.h | block device として登録するならば SCSI device として登録した方が良い場合もあります。SCSI 周りは記述が多くなるので別記検討中です。 |
procfs | linux/proc_fs.h | |
sysfs | linux/sysfs.h linux/device.h linux/moduleparam.h | DEVICE_ATTR() は linux/device.h にあります。module_param() は linux/moduleparam.h にあります。 |
debugfs | linux/debugfs.h |
モジュールロード、アンロード、シンボル解決
機能単位 | 主なヘッダやソース | 備考 |
Module insmod rmmod | kernel/module.c | System Call として関数は実装されています。Kernel 内部から呼び出すには symbol を リンクできるように修正する必要が有ります。 |
Symbol lookup | linux/kallsyms.h linux/license.h linux/module.h | シンボルのアドレスを取得する symbol_get() と symbol_put() は linux/module.h に有ります。シンボルの解決を使うと static link された Kernel からモジュールの関数を呼び出すことができるようになります。ただし、GPL の抜け穴になるような作り込みは慎みましょう。 |
基本的なドライバ
機能単位 | 主なヘッダやソース | 備考 |
Null, Zero, Full, Mem drivers | drivers/char/mem.c | /dev/null, /dev/zero, /dev/full, /dev/mem の実装です。単純なキャラクタ型デバイスを実装したドライバです。単純ながら興味深い実装になっています。 |
基本的なファイルシステムノード
機能単位 | 主なヘッダやソース | 備考 |
pipe | fs/pipe.c | poll(select)の実装、ioctl FIONREAD の実装 は単純な挙動で特別なデバイスも無い環境でも試せるので参考になるでしょう。 |
eventfd | fs/eventfd.c | eventfd, signalfd, timerfd それぞれで system call を実装し、file descriptor を API として提供しています。独自の Kernel API を必要としているならば読んでみるのも良いでしょう。 |
signalfd | fs/signalfd.c | |
timerfd | fs/timerfd.c |
デバイス登録
機能単位 | 主なヘッダやソース | 備考 |
platform | linux/platform_device.h | platform_device_register() にてプラットホーム・デバイスを登録します。プラットホームデバイスは SoC に内蔵された IP ブロックが主に対象となります。ただし、suspend、resume、接続、切断 動作に関して何らかの上流バスの支配あるいは管轄下にある場合は、そのバスのデバイスとして登録します。 |
HID | linux/hid.h | hid_allocate_device(), hid_add_device() |
I2C | linux/i2c.h | I2C 関連の基礎的な用語 Algorithm, Adapter, Driver, Client については Documentation/i2c/summary にて解説されています。全般的なドキュメントは Documentation/i2c/ 以下のファイルと API の解説 Documentation/DocBook/device-drivers/i2c.html を参照してください。 多くの場合 i2c_register_board_info() を使ってプラットホームの初期化処理にてデバイスを登録します。動的な登録は i2c_new_device() で行います。 |
PCI | linux/pci.h | PCI 接続のデバイス登録は SoC メーカーあるいは開発環境を提供したメーカーがすでに初期化処理の中で行うよう実装しているはずです。pci_common_init(), pcibios_scan_root(), pci_bus_add_devices() 辺りからコードを追跡して確認してください。それでも意図して登録する場合は pci_bus_add_device() の用例を参考にしてください。 |
SDIO | linux/mmc/host.h | SDIO デバイスは自動でデバイスが登録されます。登録の流れは mmc_detect_change(), (delayed work を挟み) mmc_rescan(), mmc_rescan_try_freq(), mmc_attach_sdio(), mmc_attach_sd(), mmc_attach_mmc(), mmc_add_card() の様になります。 |
SPI | linux/spi/spi.h | spi_register_board_info() を使ってプラットホームの初期化処理にてデバイスを登録します。動的な登録は spi_alloc_device(), spi_add_device() で行います。 |
USB | linux/usb.h linux/usb/hcd.h | 接続処理(デバイス登録)は自動で行われます。usb_new_device(), hub_port_connect(), usb_hub_create_port_device() とその呼び出し元のコードを探索してみてください。 |
ドライバ登録
機能単位 | 主なヘッダやソース | 備考 |
platform | linux/platform_device.h | platform_driver_register(), module_platform_driver() |
I2C | linux/i2c.h | i2c_add_driver(), module_i2c_driver() |
PCI | linux/pci.h | pci_register_driver(), module_pci_driver() |
SDIO | linux/mmc/mmc.h linux/mmc/core.h linux/mmc/card.h linux/mmc/sdio.h linux/mmc/sdio_ids.h linux/mmc/sdio_func.h | ドライバ登録は sdio_register_driver() で行います。左の列に挙げたヘッダファイルは一部だけ使用する場合もあります。実装する機能と照らし合わせ include/linux/mmc の下のヘッダファイル群を取り込んで下さい。 |
SPI | linux/spi/spi.h | spi_register_driver(), module_spi_driver() |
USB | linux/usb.h | usb_register(), module_usb_driver() |
クラスデバイス登録
機能単位 | 主なヘッダやソース | 備考 |
Input, HID | linux/input.h linux/hid.h | 組み込みでは input_allocate_device(), input_register_device() のほうをよく使うと思います。Documentation/DocBook/device-drivers/input_subsystem.html を参照してください。関数名はデバイス登録を思わせる名前です。実質はドライバ登録に当たります。同じドライバソースの中にキー入力を報告(伝達)する関数 input_report_key(), input_sync() が見つかると思います。 HID 系の API は hid_allocate_device(), hid_add_device() などです。Documentation/hid/hid-transport.txt を参照してください。 hid_register_driver(), module_hid_driver() |