GAIO CLUB

2004年05月02日

 初めての組み込みLinuxドライバ開発

GAIO CLUB 特集
GAIO CLUB【2004/5月号】
組み込みLinuxでハードウエアをコントロール
はじめてのLinuxドライバ
μITRONとの比較によるLinuxドライバの仕組み早わかり

組み込み機器へ採用されるLinux

Linuxは、マルチタスク、仮想メモリ、共有ライブラリ、メモリ管理、TCP/IP等ネットワーク機能を持ち、従来のUNIXと同じコマンド、プログラミング・インターフェースを持つUNIXライクなOSです。ほとんどのLinuxシステムはPCで使用されていますが、組み込みシステムとしても信頼性が高く、便利で安全なシステムといえます。

この約10年の間に、一般にシステムの小型化が進みました。これに伴って、組み込みシステムも小型化され、ネットワーク接続の実装などにより、システムがより複雑化し、オペレーティング・システム(OS)の機能が求められるようになりました。そこで組み込みシステム上で動作するOSとしてLinuxが登場したわけです。

組み込みシステム上で動作するOSとして、Linuxには以下のようなメリットがあります。

・ ソースが入手可能
・ ロイヤリティフリー
・ デバイスドライバが豊富
・ ネットワークプロトコルスタックやミドルウェアが豊富
・ 安定しているOS
・正式にサポートされているCPUアーキテクチャが豊富 – x86、Alpha、SPARC、68K、PowerPC、ARM、SH、MIPSなど

RTOSとLinuxの違い

組み込みシステムは、周辺ハードウエアとマイコンが密接に関連しています。ハードウエアをコントロールするためには、デバイスドライバを作成しなければなりません。μITRONなどのRTOSでは、アプリケーションが直接ハードウエアにアクセスできる構造となっているため、デバイスドライバの定義自体がなく、標準的なAPIも用意されていません。μITRONなどのRTOSは、アプリケーションからハードウエアをアクセスする自由度は高いですが、悪く言えば野放し状態であり、アプリケーションの1つのバグにより、システムを止めてしまうことも頻繁に起こります。
  • 一方、Linuxは、ユーザーモードとカーネルモードの2つの動作モードを持ち、ユーザーモードとして動作するアプリケーションは、直接ハードウエアにアクセスすることができない構造となっています。アプリケーションは、標準的なAPIを通じてデバイスドライバを動作させ、ハードウエアに間接的にアクセスする仕組みを取っています。これにより、ハードウエアをアプリケーションから完全に隠蔽した構造となり、アプリケーションの可搬性を高くしています。
  • RTOSとLinuxの違い

Linuxのデバイスドライバとは

Linuxのデバイスドライバは、OSの起動時にメモリ上に読み込まれて常駐し、周辺装置からのハード的な割り込みによって駆動される、OSに付属する一種のサブルーチン群です。

Linuxのカーネル自体はきわめてシンプルなマイクロカーネル・アーキテクチャによるものです。Linuxの基本的な機能であるネットワークおよびファイルシステムなどは、モジュール形式でカーネル最上部に置かれています。

これに追加されるドライバーは、実行時にロード可能なモジュールとして作成されます。カーネルと一緒にコンパイルされて1つのオブジェクトとして組み込まれる「スタティックリンク方式」と、実行時に必要に応じてメモリへ展開する「ローダブル方式」があります。(後半で取り上げます。)
Linuxのデバイスドライバとは

周辺ハードウエアをファイルとしてアクセス

Linuxに関連するデバイスには、主に次のようなものがあります。

キャラクタ型デバイス

これは、「シーケンシャルアクセス」、「バイト単位の入出力」の様な、1バイト単位のデータの入出力を行うデバイスです。

ブロック型デバイス

これは、「ランダムアクセス」、「ブロック単位の入出力」の様な、ある決められたブロック単位でのデータの入出力を行うデバイスです。主にストレージなどで使用します。「ファイルシステムを構成できるデバイス」とも言うことができます。


Linuxでは、これらの全てのデバイスを、ファイルシステムの1ファイルとして扱います。つまり、デバイスを認識して、初期化する作業を、ファイルをopen する作業として行い、デバイスからデータを読み込む作業では、ファイルをread する作業として行うと言うことです。

上位アプリケーションは、システムコールというOSとのインターフェースを使ってファイル単位の入出力要求を行います。アプリケーションからはデバイスドライバの存在は見えず、唯一、「/dev/xxx」というデバイスを仮想化したデバイススペシャルファイルが見えるだけです。

アプリケーションがデバイスへのアクセス要求を出した場合、カーネルは、デバイススペシャルファイルで定義された番号を用いて、実際のドライバ関数を示すデバイスドライバテーブルを検索します。この番号は、「メジャー番号」と呼ばれており、デバイス毎に割り当てるものです。
また、メジャー番号の他に、「マイナー番号」があります。これは、例えばシリアルデバイスの様に、COM1、COM2など、ポートだけが異なり、同一のデバイスドライバを使用する場合に使用されます。

マイナー番号も、デバイススペシャルファイルに記述されていますが、この番号は、カーネルがデバイスドライバを呼び出す際に、引数としてそのままドライバへ渡されます。ドライバ側では、マイナー番号をポート番号として扱い、呼び出されたポート番号を判別するために使用できます。この定義は、ドライバ側で自由に決めることができます。
ブロック型デバイス

アプリケーションがデバイスのデータを読み取る処理の流れ

デバイスドライバは以下の3つの部分から構成されます。

初期化ルーチン

これは、OSの起動時にカーネルから1度だけコールされる部分で、ハードウェアの存在チェックを行った後、自分自身をデバイスドライバとしてOSに認識させます。

トップハーフルーチン(Top Half)

これは、上位から(間接的に)コールされる部分で、アプリケーションがopen,read のシステムコールを発行すると、カーネルから当該関数がコールされます。カーネルからの周辺装置との入出力要求を実行し、その結果をカーネルに返します。

ボトムハーフルーチン(Bottom Half)

ハードウェア割り込みによって駆動されるハンドラ部分です。


例えば、read()メソッドにより、外部のハードウエアから情報を読み取る場合、アプリケーションは標準APIをコールします。これを受けて、システムコールライブラリのread()メソッドがコールされ、データ転送の要求をi/oデバイスに対して発行します。通常は、メモリ空間にマッピングされたi/oデバイスのレジスタに、要求の書き込みを行うことで発行が行われます。

i/oデバイスは、要求されたデータをデータ読み出しのためのレジスタ、あるいはメモリ空間にセットし、準備が完了すると割り込みにより、カーネルへ通知を行います。

通知を受けたカーネルは、トップハーフルーチンを呼び出しi/oデバイスが準備したデータの読み出しを行います。実際は、処理をボトムハーフルーチンに委ね、ここがi/oデバイスのレジスタをアクセスして、データを取得します。同期転送の場合、read()メソッドの中では、処理の終了を待つ「interrupible_sleep_on()」がコールされています。この場合は、このボトムハーフルーチンの終了を受けて、カーネルが待ちの解除を行います。
ボトムハーフルーチン(Bottom Half)

デバイスドライバの組み込み

作成したデバイスドライバを使用するための手順について説明します。まず、デバイスドライバをカーネルに登録する必要があります。この登録とは、デバイススペシャルファイルに記述した「メジャー番号」と、そのデバイス操作のための関数を結びつける事を言います。
  • Linuxではドライバモジュールがロードされたとき(insmodコマンドが実行されたとき)、予め決められている初期化関数init_module()がコールされます。この中に、カーネルに対してドライバを登録するための関数 register_chrdev()を記述します。
  • デバイスドライバの組み込み
  • この引数にあるfile_operations構造体とは、デバイスを操作するための決められたメソッド関数へのポインタを保持するものです。Linux2.4では、以下のようなメソッドが定義されています。
  • デバイスドライバの組み込み
以下の4つは、デバイス操作のための主要なメソッドの内容です。

open

デバイスを扱うときに最初に実行される関数で、タイマー等、ドライバで使用するリソースの初期を行います。

release

デバイスがクローズされるときに呼び出されます。

read

デバイスからデータを取り出すために使用します。通常は、copy_to_userを使用してカーネル領域からユーザー領域にデータを転送します。処理が成功した場合には読み出したバイト数を戻り値として返します。

write

データをデバイスに送るために使用します。通常は、copy_from_userを使用してユーザー領域からカーネル領域にデータを転送します。処理が成功した場合、書き込んだバイト数を戻り値として返します。
デバイス操作のための主要なメソッド

カーネルへの割り込みハンドラ登録

  • 前述のように、i/oデバイスが動作を完了して、その終了をカーネルに通知するためには、割り込みが使用されます。この割り込みを受けて、デバイスドライバのトップハーフルーチンを起動するために、割り込みハンドラの登録を行う必要があります。
  • カーネルへの割り込みハンドラ登録
  • 登録には標準のAPIが用意されています。割り込みハンドラへのポインタ引数には、デバイスドライバのトップハーフルーチンを設定します。
  • カーネルへの割り込みハンドラ登録

カーネルへのデバイスドライバの組み込み

デバイスドライバを組み込んで使用する方法として、Linuxでは2通りの方法が提供されています。
  • 1つは、デバイスドライバを、カーネルにスタティックリンクしてしまう方法です。これは、カーネルのコンパイル時に、ドライバ部分も合わせてコンパイルし、1つのオブジェクトとしてリンクしてしまう方法です。

    カーネルブート時に、デバイスドライバの登録と初期化の処理さえ行えば、直ぐに使用可能になります。
  • カーネルへのデバイスドライバの組み込み
この方法では、スタティックなメモリ領域にドライバが常駐することになり、安全性の高い組み込み方法ですが、メモリを多く消費してしまう欠点があります。μITRONや通常のRTOSのほとんどは、この方法で組み込みを行っています。
  • もう1つの方法として、Linuxには「ローダブルモジュール方式」がサポートされています。これは、必要に応じてメモリへロードして使用し、不要になったら、メモリから取り除くことができるものです。普段使用しないドライバモジュールは、メモリへ常駐させる必要が無いため、最小限のメモリでシステムを動作させることが可能になります。
  • カーネルへのデバイスドライバの組み込み
Linuxには、ローダブルモジュールを使用するためのコマンドが用意されています。
ローダブルモジュールを使用するためのコマンド

LEDドライバの組み込み実行例

では、LEDの点灯を制御するドライバを組み込んで、アプリケーションから利用する方法をご覧頂きます。ここでは、コンソールからのコマンドにより、ローダブルモジュールとして、コンパイル後のLEDドライバモジュール「led_driver.o」を組み込みます。
まず、ドライバモジュールのファイルパスを指定して組み込みます。ドライバが初期化されると、応答メッセージ「hello!LED_DRIVER」を返すようになっています。
LEDドライバの組み込み実行例
組み込まれたかをコマンドにより、確認してみます。これには、lsmodコマンドを使用します。
LEDドライバの組み込み実行例
メジャー番号を調べてみましょう。catコマンドにより、組み込まれたデバイスをリスト出力してみます。メジャー番号10に組み込まれていることが分かります。
LEDドライバの組み込み実行例

組み込まれたドライバを使用する

LEDドライバは正しく組み込まれました。では、このLEDを操作するアプリケーションを作成してみます。このLEDをアクセスするためのデバイススペシャルファイルは、mkmodコマンドを使用して作成されます。このファイルは、/dev ディレクトリにリストされますが、仮想的なファイルであり、実体は持ちません。(下図A参照)
デバイススペシャルファイルの作成
アプリケーションでは、このデバイススペシャルファイルをオープンして、LEDを点灯するための値(0x80)を書き込みます。操作は、通常のファイル操作と同じであり、open、write、close の各標準APIを使用して行います。この操作のソースコードを下に示します。(下図B参照)
LEDを点灯するアプリケーション例

まとめ

このように、Linuxではデバイスをファイルの1つとして扱い、標準のAPIにより操作する方法を採っています。この仕組みにさえ従えば、容易にデバイスドライバを作成できることが体感頂けたかと思います。


人気のコラム

最新のコラム