レベル: 中級 Anand Santhanam, Software engineer, IBM Global Services
2003年 9月 23日 リリースが迫っている新しい安定なカーネルにより、Linuxの採用がさらに加速されると見られています。新カーネルはより多様なプロセッサーに対し、より信頼性が高く、よりスケーラブルになっています。ここではコード・サンプルを挙げながら、新しくなった部分を大小取り混ぜ、いくつか見ていこうと思います。
1991年にLinus Torvaldsが基本的なスケジューラーとIPC、それにメモリ管理アルゴリズムを持つだけの極めて質素なv 0.1をリリースしてから、Linuxカーネルの開発は大発展をとげてきました。Linuxは今や市場に挑戦する、また真剣な検討に値する代替オペレーティング・システムとなっています。政府の主要機関やIT企業が次々とLinuxに向かい始めています。Linuxは今や組み込みデバイスからS/390まで、腕時計から大規模なエンタープライズ・サーバーまでを動かすものとなっています。
Linux 2.6はLinuxカーネル開発サイクルの次期メジャー・リリースで、無数の組み込みデバイスへのサポートと同時に、ハイエンドのエンタープライズ・サーバーのパフォーマンス向上をねらった強力なフィーチャーを含んでいます。(参考文献でJoseph PranevichのWonderful World of Linuxへのリンクも見てください。大型、小型、複数プロセッサーに対するLinux 2.6でのサポートに関する、より詳しい分析がなされています。)
この記事では上級Linux ユーザーを対象に、Linux 2.6のキー・フィーチャーのいくつかを分析し、ドライバー開発者にも関心がありそうな変更点についても述べてみます。
Linux 2.6 ハイライト
Linux 2.6は組み込みシステム、エンタープライズ・サーバーのどちらに対しても大きく進化しています。新フィーチャーが目標としているのは、ハイエンドのマシンではパフォーマンス向上、スケーラビリティ、スループット、SMPマシンに対するNUMAサポートなどであり、組み込みシステムの世界では、(ハードウェア制御のメモリ管理システムが無い)MMUレス・システムを含む、新しいアーキテクチャーやプロセッサー・サポートの追加となっています。そしていつものように、デスクトップユーザーの要求に応えて、オーディオやマルチメディアの新しいドライバーも追加されています。
この記事ではLinux 2.6の最も注目すべきフィーチャーを解析しますが、ここに挙げるものの他にもカーネル・コアのダンプの改善、高速mutexサポート、I/Oサブシステムの改善等々、多くの重要な変更がなされています。これらの一部はサイドバーに要約されており、他の資料についても可能な限り参考文献にリンクをつけました。
新スケジューラー
Linux 2.6のカーネルにはIngo Molnarの開発による新しいスケジューラー・アルゴリズムが使われています。O(1)と呼ばれるこのアルゴリズムにより、高負荷時のパフォーマンスが目に見えてよくなっているはずで、多数プロセッサーに対するスケーラビリティも向上しています。
2.4のスケジューラーにあるタイムスライスの再計算アルゴリズムでは、全プロセスはタイムスライスを使い尽くした後で、新しいタイムスライスを再計算する必要があります。そのため多数のプロセッサーを持つシステムでは、各プロセスがそれぞれのタイムスライスを完了し、(新しいタイムスライスの)再計算を待つ間、大部分のプロセッサーはアイドル状態となってしまうのです。これはSMP効率に大きく影響します。それだけではなく、アイドル状態のプロセッサーが、タイムスライスをまだ使い尽くしていない、(プロセッサーがビジーな)待ちプロセスの実行を始めてしまい、プロセッサー間でプロセスがバウンシングする(跳び回る)ことになってしまうのです。優先度の高いプロセスやインタラクティブなプロセスでバウンシングが起きてしまうと、全体のシステム・パフォーマンスが影響を受けることになります。
新しいスケジューラーはタイムスライスをCPU毎に分配し、グローバルな同期や再計算ループをなくすことで、こうした問題に対応しています。このスケジューラーでは、ポインターでアクセスされる2つの優先配列、つまりactive配列とexpired配列を使います。active配列にはCPUに組み合わされたすべてのタスクを含み、タイムスライスが残っています。expired配列にはタイムスライスを終了した全タスクが並べ替えられ、リストになって入っています。すべてのアクティブ・タスクが使い切られると配列へのアクセスポインタは切り替わり、(即実行可能なタスクを持つ)expired配列がactive配列になり、空となったactive配列が、終了タスクに対する新しい配列となります。配列のインデックスは64ビットのビットマップに保存され、最高優先度のタスクを見つけるのが非常に簡単になります。
新しいスケジューラーにはbigrunqueue_lockはもうありません。プロセッサー単位でのrun queue/lock機構を守っているので、2つのプロセッサー上で2つのプロセスが全く並列にスリープ状態になったり、スリープから回復したり、コンテキスト・スイッチしたりできるのです。再計算ループ(プロセスに対してタイムスライスを再計算します)とグッドネス・ループは無くなり、wakeup()とschedule()にはO(1)アルゴリズムが使われます。
新しいスケジューラーの主な利点は次のようなものです
-
SMP効率: 処理すべき仕事があれば、全プロセッサーが動作する。
-
待ちプロセス: どのプロセスもプロセッサーに処理されず長時間放って置かれることはなく、またどのプロセスも不合理に長時間CPUを占有しない。
-
SMP親和性: プロセッサーは一つのCPUに忠義を守り、あちこちのCPUの間で跳び回ることがない。
-
優先順位: 重要度の低いタスクは優先度を下げて起動する(逆も同じ)。
-
負荷均衡: スケジューラーは、プロセッサーの処理能力以上の負荷となるプロセスについてはどんなものであれ、優先度を下げる。
-
インタラクティブ・パフォーマンス: 新しいスケジューラーにより、たとえ高負荷状態であっても、ユーザーによるマウスクリックやキー押しに対するシステムの反応時間は通常負荷時以上とは感じられない。
カーネル・プリエンプション
カーネルのプリエンプション・パッチは2.5シリーズと統合され、そのまま2.6になります。これはユーザーとインタラクティブにやり取りするアプリケーションやマルチメディア・アプリケーションなどでは応答時間を大幅に下げることになるはずです。このフィーチャーはリアルタイム・システムや埋め込みデバイスに特に有効です。
2.5のカーネル・プリエンプションはRobert Loveによるものです。(2.4カーネルを含む)以前のバージョンでは、(システムコールでカーネル・モードに入ったユーザータスクも含め)カーネル・モードで実行しているタスクをプリエンプトすることは(そのタスクが自主的にCPUを放棄しない限り)できませんでした。
カーネル2.6ではカーネルはプリエンプション可能です。重要なユーザー・アプリケーションは実行し続けられるように、カーネル・タスクはプリエンプトできるようになっています。こうすることでシステムのユーザー応答が劇的に向上し、ユーザーはキー押しやマウスのクリックに対する反応がずっと早くなったと感じられるようになります。
もちろん、カーネルコードの全部がプリエンプトできるわけではありません。重要な部分はプリエンプションに対してロックされています。ロックすることでCPU毎のデータ構造、CPU状態とも、常にプリエンプションから確実に保護されることになります。
CPU毎データ構造の問題(SMPシステムでの)を、コードの一部で示します。
リスト1 カーネル・プリエンプション問題のあるコード
int arr[NR_CPUS];
arr[smp_processor_id()] = i;
/* kernel preemption could happen here */
j = arr[smp_processor_id()] /* i and j are not equal as
smp_processor_id() may not be the same */
|
この状況ではカーネル・プリエンプションが指定された点で起きたとすると、再スケジュール時にそのタスクは何か他のプロセッサーに割り当てられていたはずです。その場合、smp_processor_id()は異なった値を返していたはずです。
この状況はロックにより防ぐことができます。
FPUモードも、CPU状態をプリエンプションから保護すべき例です。カーネルが浮動小数点命令を実行しているときにはFPU状態は保存されません。もしここでプリエンプションが起きると、再スケジュール時に、FPU状態はプリエンプション前とは全く違ったものになってしまいます。ですからFPUコードは常にカーネル・プリエンプションに対してロックされている必要があります。
ロックを行うには、重要な部分をプリエンプション不可にし、後でその部分を再びプリエンプション可にします。2.6カーネルではプリエンプション可・不可にするために、次のような#definesが用意されています。
-
preempt_enable()
-- プリエンプト・カウンタを減らす。
-
preempt_disable()
-- プリエンプト・カウンタを増やす。
-
get_cpu()
--preempt_disable()をコールし、続いてsmp_processor_id()をコールする。
-
put_cpu()
-- プリエンプションを再び可にする。
これらの定義を使うと、リスト1は次のように書き換えられます。
リスト2 プリエンプションに対するロックのあるコード
int cpu, arr[NR_CPUS];
arr[get_cpu()] = i; /* disable preemption */
j = arr[smp_processor_id()];
/* do some critical stuff here */
put_cpu() /* re-enable preemption */
|
preempt_disable()/enable()コールはネストされていることに注意してください。つまりpreempt_disable()がn回呼ばれるとプリエンプションはn番目のpreempt_enable()があって初めてプリエンプション可になる、という風に動きます。
スピン・ロックが保持されている場合には、暗黙でプリエンプションは不可とされます。例えばspin_lock_irqsave()をコールすると、preempt_disable()がコールされることで暗黙でプリエンプションが禁止されます。そしてspin_unlock_irqrestore()をコールするとpreempt_enable()がコールされ、再度プリエンプション可になります。
スレッド・モデルとNPTLサポートの改善
2.5のカーネルにはスレッド・パフォーマンス改善にかなりの努力が払われています。2.6でのスレッド・モデルの改善もIngo Molnarによるものです。2.6のスレッド・モデルは1:1スレッド・モデル(一本のユーザー・スレッドに対して一本のカーネル・スレッド)に基づいており、新しいNative Posix Threading Library (NPTL)をカーネルでサポートしています。NPTLはUlrich Drepperの協力により、Molnarが開発したものです。
Other notable changes in the 2.6 kernel
-
ファイルシステム
拡張属性、POSIXアクセス・コントロール・リストのサポートを含め、ext2/ext3ファイルシステムが改善されました。また、NTFSドライバーも書き改められ、(リエントラントに対して安全な)SMPや4KB以上のクラスタその他もサポートされるようになりました。IBMのJFS (journaling file system)やSGIのXFSも2.6に統合されました。
-
オーディオ
ALSA (Advanced Linux Sound Architecture)と言われる、Linuxの新しいオーディオ・アーキテクチャーがようやく取り入れられました。制限が多かった、古いOSS (Open Sound System)アーキテクチャーに代わるものとして、デスクトップ・ユーザーには朗報です。新アーキテクチャーはUSBオーディオやMIDIデバイス、全二重再生などをサポートしています。MP3他のオーディオファイルの再生は昔とは大違いと言うわけです。
-
バス
SCSI/IDEサブシステムは大幅に書き改められ、ドライバーの一部は現在もテストの最中か、仕上げにかかっているところです。
-
電源管理
ACPI (Advanced Configuration and Power Interface)がサポートされ、CPUスケーリング(電力節約のため、負荷に応じてCPUクロック周波数を可変する機能)やソフトウェア・サスペンド(現在もテスト中)ができるようになります。
-
ネットワークとIPSec
カーネルにIPSec (IP セキュリティ)サポート及び、例えばIPペイロード圧縮のような各種RFCが追加されました。カーネル内HTTPサーバーのkttpdは無くなりました。IPSecフィーチャーではカーネルが提供する、新しい暗号化APIを使用しています。この暗号化APIにはMD4やMD5 DESなどのような、よく使われるアルゴリズムが含まれています。新しい、NFSv4 (Network File System)クライアント・サーバーのサポートも追加されています。
-
ユーザー・インターフェース層
2.6ではフレーム・バッファ/コンソール層にも手が加えられています。そのためfbsetやfbdeslのような、ユーザー空間フレーム・バッファ・ツールの更新も必要かもしれません。また、多様な新デバイス(タッチスクリーンから点字デバイス、ちょっと変わったマウスの類まで)がヒューマン(対人)・インターフェース層でサポートされるようになります。
スレッド操作はスピードが向上しているはずです。2.6カーネルでは任意の数のスレッド、(IA32で)20億PIDを扱えます。
もう一つの変更はTLS(Thread Local Storage)システム・コールが導入されたことです。これでスレッド・レジスタとして使用できる、一つ以上のGDT(Global Descriptor Table)エントリーが割り付けできるようになります。GDTはCPU毎、そのエントリーはスレッド毎です。この結果、生成されるスレッドの数の制限無しに1:1スレッド・モデルができるようになります(新カーネル・スレッドはユーザー・スレッド毎に生成されるため)。2.4カーネルではプロセッサー当たり8,192スレッドまでしかできませんでした。
クローン・システム・コールはスレッド生成を最適化するように拡張されています。CLONE_PARENT_SETIDフラグが立っていると、カーネルはスレッドIDを与えられたメモリ位置に保存します。また、CLONE_CLEARIDが立っていると、スレッド終了でメモリ位置をクリアします。これにより、ユーザーレベルでのメモリ管理が未使用のメモリブロックを認識しやすくなります。また、シグナルセーフにスレッド・レジスターをローディングできるようにもなりました。pthread_joinにより、カーネルのスレッドIDでFutex(fast user space mutex)が行われます。(futexについては参考文献を見てください。)
POSIXシグナル処理はカーネル空間で行われます。シグナルはプロセス上の空きスレッドに送られます:fatalなシグナルは全プロセスを終了させます。停止・継続シグナルも全プロセスに影響しますが、これによりマルチスレッド・プロセスが可能になります。
exitシステムコールの新しい変化形が導入されました。exit_group()と呼ばれるこのシステムコールは、プロセスを完全に終了させ、そのプロセスのスレッドもすべて終了させます。さらにO(1)アルゴリズムの導入によりexit処理が改善され、10万スレッドのプロセスを終了するのに2秒しかかからなくなりました(2.4カーネルでは同じ処理に15分かかったのと比較してみてください)。
procファイルシステムは全スレッドではなく、元スレッドだけをレポートするように変更されています。これにより/procレポートの速度が低下することがなくなりました。カーネルは、他の全スレッドが終了するまで元スレッドが残るように保証しています。
VMの変更
VMではRik van Rielのr-map(reverse mapping、逆マッピング)が取り入れられています。これにより、ある特定負荷の下ではVMの振る舞いが大幅に改善されるはずです。
逆マッピングのテクニックを理解するために、Linuxの仮想メモリシステムの基本をざっと復習してみましょう。
Linuxカーネルは仮想メモリモードで動作します。各仮想ページに対応して物理ページ・メモリがあります。仮想ページと物理ページ間のアドレス変換はハードウェアのページ・テーブルにより行われます。ある仮想ページに対し、ページ・テーブルのエントリーはその仮想ページに対応する物理ページを指すか、ページが存在しない(ページ・フォールトを意味する)場合には存在しないことを指します。ただし、この仮想ページ対物理ページ・マッピングは必ず一対一とは限りません。複数の仮想ページ(異なるプロセス間で共有されているページ)が同じ物理ページを指すこともあり得ます。こうした場合カーネルが、ある物理ページを開放しようとすると面倒なことになります。カーネルは物理ページを指すものを探すべく、全プロセスのページ・テーブルのエントリーを横断的に見なければならず、しかもリファレンス・カウントが0になったときにしか物理ページを開放できないからです。カーネルにはどのプロセスがそのページを実際に指しているかを知るすべが無いので、カーネルは全プロセスのページ・テーブルをスキャンすることになりますが、当然ながら非常に時間と手間がかかります。これは高負荷時にはVMの速度を目に見えて遅らせることになります。
逆マッピング・パッチはpte_chainと呼ばれるデータ構造をstructページ(物理ページ構造)に導入することで、この問題に対応しています。pte_chainはページのPTEを指すリンクを持った簡単なリストで、ある特定のページを指すPTEのリストを返します。ページの開放が突然簡単になってしまったわけです。
ところがこのやり方ですと、ポインターのオーバーヘッドが大きくなります。システム上の各structページがpte_chainのために新たなstructを持たなければなりません。物理ページが64KBで256MBのシステムで、64KB * (sizeof(struct pte_chain))だけの余計なメモリがこの目的のために割り当てられる必要があるわけです。かなり大きな数字と言えるでしょう。
これに対処するためにstructページからwait_queue_head_tフィールド(ページへの排他的アクセスに使用される)を除くことを含め、いくつかのテクニックが使われています。この待ちキューは時々しか使われないので、rmapパッチではそれよりも小さな(ハッシュ・キューを使用した待ちキュー方式が)正しい待ちキューを見つけるのに使われています。
オーバーヘッドの問題はありますが、rmapパッチは(特にハイエンドのシステムで高負荷時に)2.4カーネルのVMシステムよりも明らかに良くなっています。
Linux 2.6へのドライバー移植
2.6カーネルはドライバー開発者にとって、かなり重要な変更を多数含んでいます。このセクションでは2.4カーネルから2.6カーネルへのドライバー移植に関して、重要な点をいくつか取り上げてみます。
まず、2.4に比べカーネルのビルド・システムが改善され、ビルドが早くなっています。2.4よりも改善されたグラフィックのツールmake xconfig(Qtライブラリが必要)とmake gconfig(GTKライブラリが必要)が追加されました。
2.6のビルド・システムの特徴をいくつか次に挙げます
- 単に
makeが出されるだけで、arch-zImageとモジュールをデフォルトで生成する。
- 並行
makeにはmake -jNがお勧めです。
-
makeはデフォルトでは冗長ではない(これを変更するにはKBUILD_VERBOSE=1かmake V=1を使います)。
-
make subdir/はsubdir/内及びそれ以下の全ファイルをコンパイルします。
- サポートされているmakeのターゲットは
make helpで見ることができます。
-
make depはどの段階でも走らせる必要はありません。
カーネル・モジュール・ローダーも2.5で完全に作り直されました。つまりモジュール構築の機構が2.4と比べて大きく違うと言うことです。モジュールのローディング、アンローディングに、新しいモジュール・ユーティリティの一式が必要になります(そのダウンロードについては参考文献を見てください)。2.4の古いmakefilesは2.6では動きません。
新しいカーネル・モジュール・ローダーはRusty Russelの作です。カーネル・ビルド機構を利用し、.oモジュール・オブジェクトの代わりに.ko (kernel objectカーネル・オブジェクト)モジュール・オブジェクトを生成します。カーネル・ビルド・システムはまずモジュールをコンパイルし、次にvermagic.oにリンクします。これにより、使用されたコンパイラのバージョン、カーネルのバージョン、カーネル・プリエンプションが使われたかどうか、等々の情報を含んだ特別なセクションが、オブジェクト・モジュールに生成されます。
では簡単なモジュールをコンパイルしてロードするのに、新しいカーネル・ビルド・システムがどう使われるのか、ちょっと例をとり上げて分析してみましょう。とり上げるのは「hello world」モジュールで、init_module、cleanup_moduleの代わりにmodule_init、module_exitを使う必要がある点を除けば、2.4のモジュール・コードと似ています。(カーネル2.4.10モジュール以降ではこのinit_module、cleanup_moduleの機構を使っています。) モジュール名はhello.cでMakefileは以下の通りです
リスト3. ドライバーmakefileの例
KERNEL_SRC = /usr/src/linux
SUBDIR = $(KERNEL_SRC)/drivers/char/hello/
all: modules
obj-m := module.o
hello-objs := hello.o
EXTRA_FLAGS += -DDEBUG=1
modules:
$(MAKE) -C $(KERNEL_SRC) SUBDIR=$(SUBDIR) modules
|
このmakefileはカーネル・ビルド機構を使ってモジュールを生成しています。コンパイルされたモジュールはmodule.koと呼ばれ、hello.cをコンパイルし、vermagicにリンクすることで得られます。KERNEL_SRCはカーネルのソース・ディレクトリ、SUBDIRはモジュールのあるディレクトリを指します。EXTRA_FLAGSはコンパイル時に必要なフラグどれかを指します。
新しいモジュール(module.ko)ができると、そのモジュールを新しいモジュール・ユーティリティでロードしたりアンロードしたりできるようになります。2.4で使われていた古いモジュール・ユーティリティでは、2.6のカーネルモジュールをロード/アンロードできません。デバイスがオープンされた時に、対応するモジュールは誰も使っていないのが確認されたのでアンロード中、という場合にはレースが発生し得ますが、新しいモジュール・ローディング・ユーティリティは、このレースが起きる可能性を最小にするように動作します。こうしたレース状態が起きる理由の一つには、モジュール使用カウントがモジュール・コード自体の中で(MOD_DEC/INC_USE_COUNT経由により)操作されることが挙げられます。
2.6ではモジュールがこの参照カウントの増減をする必要がなくなっており、代わりにモジュール・コード外でされるようになっています。モジュールを参照しようとするコードはtry_module_get(&module)を呼ばねばならず、それに成功したところでモジュールにアクセスできるようになります。モジュールがアンロード中であれば、このコールは失敗します。これに対応して、モジュールへの参照はmodule_put()を使って公開できます。
メモリ管理の変更
メモリ・プールはスリープ無しでメモリ割り付けを満足するために、2.5の開発過程で追加されました。概念としてはメモリのプールをあらかじめ割り付けておき、実際に必要になるまで保存しておくということです。mempool_create()コール(linux/mempool.hが含まれているはずです)でmempoolが生成されます。
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data);
ここでmin_nrはあらかじめ割り付けられた、必要なオブジェクトの数であり、alloc_fnとfree_fnはポインターで、mempool機構が提供する、標準のオブジェクトの割り付け/割り付け解除ルーチンをポイントします。型としては
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data); typedef void (mempool_free_t)(void *element, void *pool_data);
pool_dataは割り付け/割り付け解除機能に使われるポインターであり、gfp_maskはその割り付けフラグです。割り付け機能は__GFP_WAITフラグが指定されない限り、スリープ状態にはなりません。
プール内での割り付け、割り付け解除は下記により行われます
void *mempool_alloc(mempool_t *pool, int gfp_mask); void mempool_free(void *element, mempool_t *pool);
mempool_alloc()はオブジェクトを割り付けます。mempoolアロケーターがメモリを提供できないときは、あらかじめ割り付けられたプールが使われます。
mempoolはmempool_destroy()でシステムに戻されます。
メモリ割り付けに関してmempoolが導入されたのに加え、2.5カーネルでは通常のメモリ割り付けに関して3つの新しいGFPフラグが導入されています。
-
__GFP_REPEAT -- ページ・アロケーターにもっとメモリを探すよう命令します。メモリ割り付け失敗があると、このフラグの使用は減るはずです。
-
__GFP_NOFAIL -- メモリ割り付け失敗を許さない。このフラグは、要求を満足するために呼び出し元がスリープさせられてしまうので、完了するのに長くかかる可能性があります。
-
__GFP_NORETRY -- 失敗した割り付けは再度試行されないことを保証し、失敗状態がレポートとして呼び出し元に返されます。
メモリ割り付けに関する変更とは別に、remap_page_range()コール(ページをユーザー空間にマッピングするのに使われる)も少し手直しされています。2.4と比べると、新たに別のパラメーターをとるようになっています。通常の4つのパラメーター(開始、終了、サイズ、保護フラグ)の前に、最初のパラメーターとして、仮想メモリ部分(virtual memory area, VMA)ポインターを追加する必要があります。
ワークキュー・インターフェース
ワークキュー・インターフェースはタスクキュー・インターフェース(カーネル・タスクのスケジュールに使用される)を置き換えるものとして、2.5の開発で導入されました。各ワークキューは関連付けされた専用のワーカー・スレッドを持ち、ラン・キューにあるタスクはすべて、プロセスのコンテキストで実行されます(従ってスリープに入ることも許される)。ドライバーは自身のワークキューを生成、使用することもできるし、カーネルにあるものを使用することもできます。ワークキューは次のようにして生成されます。
struct workqueue_struct *create_workqueue(const char *name);
ここでnameはワークキューの名前です。
ワークキュー・タスクはコンパイル時または実行時に初期化されます。また、work_struct構造と呼ばれる構造の中に組み込まれている必要があります。ワークキュー・タスクはコンパイル時に下記で初期化されます。
DECLARE_WORK(name, void (*function)(void *), void *data);
ここでnameはwork_struct名、functionはタスクがスケジュールされたときに起動されるファンクション、dataはそのファンクションへのポインタです。
ワークキュー・タスクは実行時に次のように初期化されます。
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
ワークキューへのジョブのキューイング(work_struct構造型のワークキュー・ジョブ/タスク)は次のファンクション・コールで行われます。
int queue_work(struct workqueue_struct *queue, struct work_struct *work); int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
ワークキューに置かれているエントリーが実際に実行される前に、jiffy(最小時間単位)での最低限の遅延が確実に入るように、queue_delayed_work()の遅延が入っています。
ワークキューに置かれたエントリーは、関連付けされたワーカー・スレッドにより、(負荷やインタラプトにより変動する)任意の時間に、または遅延時間後に実行されます。ワークキューに置かれたエントリーのうち異様に実行時間が長くかかるものはどれも、下記で取り消されます。
int cancel_delayed_work(struct work_struct *work);
もし取り消しコールが返ったときにエントリーが実行中の場合、そのエントリーは実行を続けますが、再度キューに加えられることはありません。ワークキューのエントリーは下記で掃き出し消去されます。
void flush_workqueue(struct workqueue_struct *queue);
また、ワークキューは下記で破棄されます。
void destroy_workqueue(struct workqueue_struct *queue);
全ドライバーにカスタムのワークキューが必要なわけではなく、カーネルが提供するデフォルトのワークキューを使うこともできます。デフォルトのワークキューは多くのドライバーで共有するため、エントリーを実行するのに時間がかかります。それを緩和するためワーカー機能の遅延は最小限にとどめるか、または全く使わないようにすべきです。
重要な点として、デフォルトのキューは全ドライバーが使えますが、カスタム定義したワークキューはGPLライセンスのあるドライバーでないと使えません。
-
int schedule_work(struct work_struct *work); -- ワークキューにエントリーを追加します。
-
int schedule_delayed_work(struct work_struct *work, unsigned long delay); -- ワークキューのエントリーを追加し実行を遅延させます。
キューに置かれたすべてが実行されるまで待つ、flush_scheduled_work()機能もあります。この機能はアンロード時、モジュールに呼ばれる必要があります
インタラプト・ルーチンの変更
2.5のインタラプト・ハンドラーの内部には多くの変更がなされましたが、大部分の変更は普通のドライバー開発者に影響を与えるようなものではありません。ただ、デバイスドライバーに影響するような重要な変更も少しはあります。
インタラプト・ハンドラー機能はirqreturn_t型の戻りコードを持つようになっています。この変更はLinusが導入したものですが、そのインタラプトが本当にインタラプトとして意図されたものかどうかを、インタラプト・ハンドラーが汎用IRQ層に知らせます。これは擬似インタラプトを捕えるために行われます。擬似インタラプトというのは特にPCIの線で共有されているような場合、ドライバーが誤ってインタラプト・ビットを立ててしまったり、単にハードウェアがおかしくなってインタラプトが出続けてしまうもので、どのドライバーでもどうにもできないものです。2.6では、デバイスがインタラプトをかける時には、そのドライバーはIRQ_HANDLEDを返し、無関係の時にはIRQ_NONEを返す必要があります。もしインタラプトが来続け、しかもそのデバイスに対して登録されたハンドラーが無い場合には(例えば全ドライバーがIRQ_NONEを返したときなど)、カーネルはそのデバイスからのインタラプトをブロックします。ドライバーがそのインタラプトを実際に操作しているのにIRQ_NONEを返すとバグになってしまうので、ドライバーのIRQルーチンはデフォルトでIRQ_HANDLEDを返す必要があります。新しいインタラプト・ハンドラーはこんな風です
リスト4. 2.6インタラプト・ハンドラー擬似コード
irqreturn_t irq_handler(...) {
..
if (!(my_interrupt)
return IRQ_NONE; // not our interrupt
...
return IRQ_HANDLED; // return by default
}
|
cli()、sti()、save_flags()、restore_flags()等はどれも避け、インタラプトをローカルに(そのプロセッサー内で)禁止するのにはlocal_save_flags()やlocal_irq_disable()を代わりに使うことになっていることには注意してください。全プロセッサーに対してインタラプトを禁止することはできません。
統一デバイスモデル
2.5の開発で最も注目すべきものの一つに統一デバイスモデルがあります。デバイスモデルは、データ構造の数を維持することでデバイスの全体的なアーキテクチャーとシステムのオーバーレイを表します。その利点としてはデバイスに対する電力管理やデバイス関連のタスク(下記の追跡を含む)の管理が容易になることが挙げられます。
- システム上に存在するデバイスと、そのデバイスが接続されているバス。
- ある特定の瞬間における、デバイスの電力状態。
- システムが認識しているデバイスドライバーと、そのドライバーが制御するデバイス。
- システムのバス構造:どのデバイスがどのバスに接続されているか、またどのバスが相互連結されているか(例えばUSB - PCIインターコネクトなど)。
- システム上に存在するデバイスのクラス(クラスにはディスク、パーティションなどを含みます)。
2.5カーネルの開発でデバイスドライバーに関連したものとしては他に
-
malloc.hは削除されました。(メモリ割り付けに)<linux/malloc.h>を含むコードは今後<linux/slab.h>を使う必要があります。
- HZ値はx86アーキテクチャーに対して1000に増やされました。HZ値の変更によりjiffy(最小時間単位)変数が急速にオーバーフローするのを避けるため、
jiffies_64と言われる新しいjiffyカウンターが導入されました。
-
ndelay()と言われる、新しい遅延機能が導入され、ナノ秒単位でのウェイトがかけられるようになりました。
- 頻繁にアクセスされるデータ(ポインター無し)の、小さな一部をロックするために
seqlock()と言われる新しい型のロックが導入されました。
- 2.6カーネルはプリエンプトできるので、コードの一部でプリエンプトされてはならない部分を保護するために、ドライバーでは
preempt_disable()やpreempt_enable()を使う必要があります。(IRQを禁止すると暗黙にプリエンプションを禁止することになります。)
- 2.5では非同期I/Oが追加されました。これはユーザー・プロセスが複数のI/O操作を開始でき、それらが完了するのを待つ必要が無いことを意味します。キャラクタードライバー用に、非同期APIが導入されました。
- 2.5ではブロック層が大幅に変更されています。つまりブロック・デバイスのドライバーは、2.4のときから再設計する必要があると言うことです。
- システムのデバイスモデルのユーザー空間を表すsysfsが2.5で導入されました。/sysにマウントされます。
まとめ
2.4に比べ、Linux 2.6では余りにも多くの変更があるので、カーネルの世界では新リリースが実は3.0になるだろうという話が行き交っています。それについてはLinusが最終的に判断するでしょうし、公式リリースは2003年11月にまで早まるかもしれません。バージョン番号がどうあれ、新しいカーネルは確実に2.4よりも速くスケーラブルであり、より多岐に渡るプラットフォームやアーキテクチャーで、より安定に動作するようになります。
Linusは世界中のテスターたちにバグ取りと問題点のレポートを呼びかけており、ディストリビューションの維持管理者に2.6バージョンをダウンロードするように要求しています。下記の参考文献にダウンロードやインストールのリンクがありますので、参加してみたい読者は試してみてください。
参考文献
著者について  | |  | Anand K. Santhanamはインド・マドラス大学工学部でコンピューターサイエンスの学位を取得。インドにあるIBM Global Services (Software Labs)に1999年より在籍。IBMのLinuxグループの一員で、ARM-Linux、character/Xベースのデバイスドライバー、組み込みシステムでの電源管理、PCIデバイスドライバー、Linuxでのマルチスレッド・プログラムに従事してきました。OSの内部構造、ネットワーキング等にも関心を持っています。 |
記事の評価
|