カーネル API: 第 2 回、遅延可能な関数、カーネルのタスクレット、およびワークキュー

Linux 2.6 でのボトムハーフの紹介

頻繁に発生するスレッド化操作のために、Linux® カーネルではタスクレットとワークキューを提供しています。遅延可能な機能を実装するタスクレットとワークキューは、ドライバーとして今まで使用されてきたボトムハーフ・メカニズムの代わりとなるものです。この記事では、タスクレットとワークキューがカーネルでどのように使用されているのかを探り、タスクレット API とワークキュー API のそれぞれを使って遅延可能な関数を作成する方法を紹介します。

M. Tim Jones, Independent author

M. Tim JonesM. Tim Jones は組み込みソフトウェアのエンジニアであり、『Artificial Intelligence: A Systems Approach』、『GNU/Linux Application Programming』現在、第 2 版です) や『AI Application Programming』(こちらも現在、第 2 版です)、それに『BSD Sockets Programming from a Multilanguage Perspective』などの著者でもあります。技術的な経歴は静止軌道衛星用のカーネル開発から、組み込みシステム・アーキテクチャーやネットワーク・プロトコル開発まで、広範にわたっています。また、コロラド州ロングモン所在のEmulex Corp. の顧問エンジニアでもあります。


developerWorks 貢献著者レベル

2010年 3月 02日

Tim とつながるには

Tim は developerWorks で人気の高いお馴染みの著者の 1 人です。Tim が書いたすべての developerWorks の記事を閲覧してみてください。また、My developerWorks では、Tim のプロフィールを調べることや、彼やその他の著者、そして他の読者とつながることができます。

この記事では、カーネル (具体的には、2.6.27.14 Linux カーネル) のコンテキストの間で処理を遅延させる手法について説明します。これらの手法は Linux カーネルに固有のものですが、その背後にある概念は、アーキテクチャーの観点からも役に立つ概念です。例えば、作業のスケジューリングに従来使用されているスケジューラーの代用として、これらの概念を従来の組み込みシステムに実装することもできます。

関数を遅延させるためにカーネルで使われている手法を探る前に、まずはその解決すべき対象となっている問題の背景を説明しておきます。オペレーティング・システムでハードウェア・イベント (ネットワーク・アダプターでのパケット受信など) による割り込みが発生すると、割り込みコンテキストで処理が開始されます。通常は、割り込みによってかなりの量の作業が発生するものです。この作業の一部は割り込みコンテキストで処理され、残りの作業はソフトウェア・スタックに渡されて処理されることになります (図 1 を参照)。

図 1. トップハーフおよびボトムハーフの処理
3 つのプロセッサー共有レベルを示す図。上位層ドライバー、ボトムハーフ、トップハーフがあります。

では、割り込みコンテキストで行う処理の量はどれくらいにするべきなのでしょう?割り込みコンテキストで行う処理に伴う問題は、この処理が行われている間、割り込みの一部、あるいはすべてが無効になってしまうと、他のハードウェア・イベントの処理待ち時間が長くなってしまうという点です (さらに、処理の振る舞いにも変化が生じることになります)。したがって、割り込みコンテキストで処理する作業は最小限に抑え、作業の一部を (プロセッサーを有効に共有できる可能性の高い) カーネルのコンテキストで処理するようにしなければなりません。

図 1 に示されているように、割り込みコンテキストで行われる処理はトップハーフと呼ばれ、割り込みの発生による処理のうち、割り込みコンテキストからカーネルのコンテキストに渡されて行われる処理はボトムハーフと呼ばれます (この場合、ボトムハーフによる以降の処理は、トップハーフがスケジューリングします)。カーネルのコンテキストで行われるということは、つまり、割り込みが可能であるということです。割り込みが可能であれば、時間に依存しない作業を遅らせ、頻繁に発生する割り込みイベントを即座に処理できるため、パフォーマンスが向上する結果となります。

ボトムハーフの歴史概略

Linux カーネルのバージョン

この記事でのタスクレットとワークキューの説明には、バージョン 2.6.27.14 の Linux カーネルを用いています。

Linux はスイス・アーミー・ナイフのように多機能になっていく傾向がありますが、遅延機能にしても例外ではありません。カーネル 2.3 以降、32 の静的に定義されたボトムハーフ一式を実装する softirq が使用できるようになっています。これらのボトムハーフは (動的な新しいメカニズムとは異なり)、コンパイル時に静的要素として定義されます。softirq はかつて、カーネル・スレッドのコンテキストで最も優先される処理 (ソフトウェア割り込み) に使用されていました。softirq 機能のソースは、./kernel/softirq.c で参照することができます。Linux カーネル 2.3 では softirq に加え、タスクレットも導入されています (./include/linux/interrupt.h を参照)。softirq をベースにしたタスクレットでは、遅延可能な関数を動的に作成することができます。さらに Linux カーネル 2.5 では、ワークキューが導入されました (./include/linux/workqueue.h を参照)。ワークキューでは、処理を割り込みコンテキストで行うのではなく、カーネル・プロセスのコンテキストで行うように遅延させることができます。

ここからは、作業遅延、タスクレット、そしてワークキューの動的メカニズムを探っていきます。


タスクレットの紹介

softirq は当初、さまざまなソフトウェア割り込みの振る舞いをサポートする 32 の softirq エントリーのベクターとして設計されたものですが、現在 softirq で使用されているベクターは 9 つしかありません。その 1 つが TASKLET_SOFTIRQ です (./include/linux/interrupt.h を参照)。softirq は今でもカーネル内にありますが、新しい softirq ベクターを割り当てるよりは、タスクレットとワークキューを使用することをお勧めします。

タスクレットは、関数を登録し、後で実行されるようにスケジューリングする遅延方式です。トップハーフ (割り込みハンドラー) は作業のうちのわずかな量を処理した後、残りの作業をボトムハーフで行うようにタスクレットをスケジューリングします。

リスト 1. タスクレットの宣言およびスケジューリング
/* Declare a Tasklet (the Bottom-Half) */
void tasklet_function( unsigned long data );

DECLARE_TASKLET( tasklet_example, tasklet_function, tasklet_data );

...

/* Schedule the Bottom-Half */
tasklet_schedule( &tasklet_example );

タスクレットが実行される CPU は、そのタスクレットがスケジューリングされた CPU に限られます。ある特定のプロセッサーの複数の CPU 上で同じタスクレットが同時に実行されることは決してありません。ただし、異なる複数のタスクレットをそれぞれに異なる CPU で同時に実行することは可能です。

タスクレットは tasklet_struct 構造体で表現されます (図 2 を参照)。この構造体に、タスクレットを管理および保守するために必要なデータ (状態、atomic_t による有効化/無効化、関数ポインター、データ、およびリンク・リスト参照) が組み込まれます。

図 2. tasklet_struct 構造体の構成要素
tasklet_struct のコード構造図

マシンがソフト割り込みによって過負荷状態になると、softirq メカニズムにより、あるいは場合によっては ksoftirqd (CPU ごとのカーネル・スレッド) により、タスクレットがスケジューリングされます。次のセクションでは、タスクレット・アプリケーション・プログラミング・インターフェース (API) で使用できる各種の関数について詳しく説明します。

組み込みシステムからの遺産

タスクレットとワークキューの背後にある概念には、組み込みシステムから引き継がれている部分があります。組み込みシステムの多くには従来からのスケジューラーがなく、(入力/出力 (I/O) または内部処理によって駆動される) 作業遅延が行われるだけです。スケジューラーがない代わりに、システム内の他の要素が後で処理をするようにスケジューリングする手段として、割り込みとアプリケーションでは作業を遅延させます。この方式では、スケジューラーが (ハンドラー関数に作業をフィードする) ワークキューあるいは (タスクレットの作業処理能力を示す) ビット・マスクのプロセッサーになります。

タスクレットの API

タスクレットを定義するには、DECLARE_TASKLET というマクロを使用します (リスト 2 を参照)。このマクロでは、提供された情報 (タスクレット名、関数、およびタスクレット固有のデータ) で tasklet_struct が初期化されるだけにすぎません。デフォルトではタスクレットが有効になります。つまり、タスクレットをスケジューリングできるということです。タスクレットをデフォルトで無効として宣言することも可能で、それには DECLARE_TASKLET_DISABLED マクロを使用します。無効として宣言すると、tasklet_enable 関数を呼び出さなければ、タスクレットをスケジューリングすることができなくなります。タスクレットをスケジューリング可能、スケジューリング不可という点で有効または無効にするには、tasklet_enable 関数、tasklet_disable 関数をそれぞれ使用することができます。また、tasklet_struct をユーザーが提供したタスクレットのデータで初期化するための tasklet_init 関数もあります。

リスト 2. タスクレットの作成および有効化/無効化関数
DECLARE_TASKLET( name, func, data );
DECLARE_TASKLET_DISABLED( name, func, data);
void tasklet_init( struct tasklet_struct *, void (*func)(unsigned long),
			unsigned long data );
void tasklet_disable_nosync( struct tasklet_struct * );
void tasklet_disable( struct tasklet_struct * );
void tasklet_enable( struct tasklet_struct * );
void tasklet_hi_enable( struct tasklet_struct * );

無効化関数は 2 つあり、どちらもタスクレットの無効化を要求しますが、タスクレットが終了した後にリターンするのは、tasklet_disable 関数だけです (tasklet_disable_nosync はタスクレットが終了する前にリターンする場合があります)。無効化関数を使用すると、タスクレットは有効化関数が呼び出されるまでマスクされます (つまり、実行されないということです)。有効化関数も同じく 2 つあります。1 つは標準優先度のスケジューリング用 (tasklet_enable)、もう 1 つは高優先度のスケジューリング用 (tasklet_hi_enable) です。標準優先度のスケジューリングはTASKLET_SOFTIRQ レベルの softirq によって行われ、高優先度のスケジューリングは HI_SOFTIRQ レベルの softirq によって行われます。

有効化関数に標準優先度用と高優先度用があるのと同じく、スケジューリング関数にも標準優先度のスケジューリング用と高優先度のスケジューリング用があります (リスト 3 を参照)。関数はそれぞれに特定の softirq ベクター (標準優先度の場合はtasklet_vec、高優先度の場合は tasklet_hi_vec) でタスクレットをキューに入れます。高優先度ベクターのタスクレットが最初に処理された後、標準ベクターのタスクレットが処理されるという仕組みです。各 CPU がそれぞれに独自の標準優先度および高優先度の softirq ベクターを保持することに注意してください。

リスト 3. タスクレットのスケジューリング関数
void tasklet_schedule( struct tasklet_struct * );
void tasklet_hi_schedule( struct tasklet_struct * );

作成済みのタスクレットを停止するには、tasklet_kill 関数を使用することができます (リスト 4 を参照)。tasklet_kill 関数はタスクが再度実行されないようにし、タスクレットの実行が現在スケジューリングされている場合には、その実行が完了するまで待ってからタスクレットをキルします。tasklet_kill_immediate を使用するのは、特定の CPU が停止状態にある場合のみです。

リスト 4. タスクレットのキル関数
void tasklet_kill( struct tasklet_struct * );
void tasklet_kill_immediate( struct tasklet_struct *, unsigned int cpu );

この API を見るとわかるように、タスクレット API は単純で、実装するのも簡単です。タスクレット・メカニズムの実装は、./kernel/softirq.c および ./include/linux/interrupt.h で調べることができます。

単純なタスクレットの例

一例として、単純なタスクレット API の使い方を見てください。リスト 5 に示されているように、関連データ (my_tasklet_function および my_tasklet_data) を使用してタスクレット関数が作成された後、その関数を使って、DECLARE_TASKLET により新規タスクレットが宣言されます。モジュールが挿入されると、タスクレットが将来の何らかの時点で実行可能になるようにスケジューリングされます。モジュールがアンロードされるときには、タスクレットがスケジューリング可能な状態でなくなるように tasklet_kill 関数が呼び出されます。

リスト 5. カーネル・モジュールのコンテキストでの単純なタスクレットの例
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");

char my_tasklet_data[]="my_tasklet_function was called";

/* Bottom Half Function */
void my_tasklet_function( unsigned long data )
{
  printk( "%s\n", (char *)data );
  return;
}

DECLARE_TASKLET( my_tasklet, my_tasklet_function, 
		 (unsigned long) &my_tasklet_data );

int init_module( void )
{
  /* Schedule the Bottom Half */
tasklet_schedule( &my_tasklet );

  return 0;
}

void cleanup_module( void )
{
  /* Stop the tasklet before we exit */
tasklet_kill( &my_tasklet );

  return;
}

ワークキューの紹介

ワークキューは比較的最近の遅延メカニズムであり、バージョン 2.5 の Linux カーネルで追加されました。ワークキューは、タスクレットのように 1 回限りの遅延をスケジューリングする方式ではなく、汎用的な遅延メカニズムであるため、ワークキューのハンドラー関数はスリープに入ることもできます (タスクレットのモデルでは不可能です)。ワークキューの場合、待ち時間がタスクレットより長くなることもありますが、作業遅延のための API はタスクレットよりも充実しています。遅延はかつてタスク・キューが keventd によって管理していましたが、今ではカーネルの events/X というワーカー・スレッドが管理するようになっています。

ワークキューは、機能をボトムハーフに任せるための汎用メソッドを提供します。コアとなるのはワークキュー (workqueue_struct 構造体) です。この構造体に、対象となる作業が配置されます。作業は work_struct 構造体によって表現され、この構造体が遅延対象の作業と、使用する遅延関数を識別します (図 3 を参照)。そしてevents/X カーネル・スレッド (CPU ごとに 1 つ) がワークキューから作業を抽出し、(work_struct 構造体に含まれるハンドラー関数の指定に従って) ボトムハーフ・ハンドラーのうちの1 つを起動します。

図 3. ワークキューを支えるプロセス
プロセスのフローチャート。左から右の順で、割り込みハンドラー、work_struct 構造体、workqueue_struct構造体、events/X、ハンドラー関数が示されています。

work_struct によって使用するハンドラー関数が示されることから、ワークキューを使用して、さまざまなハンドラーを対象に作業をキューに入れることができます。次のセクションでは、ワークキューの具体的な API 関数について見ていきます。

ワークキューの API

ワークキュー API は、主に多数のオプションがサポートされていることから、タスクレットより多少複雑です。まずはワークキューについて詳しく説明した上で、実際の応用例を紹介します。

図 3 からわかるように、ワークキューのコアとなる構造体はキュー自体です。この構造体を使用して、トップハーフからボトムハーフで実行するために遅延される作業がキューに入れられます。ワークキューが create_workqueue というマクロによって作成されると、workqueue_struct 参照が返されます。このワークキューを後で削除しなければならなくなった場合には、destroy_workqueue 関数の呼び出しを使用することができます。

struct workqueue_struct *create_workqueue( name );
void destroy_workqueue( struct workqueue_struct * );

ワークキューを介してやり取りされる作業は、work_struct 構造体によって定義されます。一般に、ユーザーが作業を定義するときには、この構造体が最初の要素となります (後でその例を記載します)。ワークキュー API には、(割り当てられたバッファーから) 作業を初期化するための関数が 3 つ用意されています (リスト 6 を参照)。そのうちの 1 つ、INIT_WORK マクロは、(ユーザーから渡された) ハンドラー関数に必要となる初期化および設定を行います。残りの INIT_DELAYED_WORKINIT_DELAYED_WORK_DEFERRABLE の 2 つのマクロは、ワークキューに入れられる前に作業を遅延しなければならない場合に使用することができます。

リスト 6. 作業の初期化マクロ
INIT_WORK( work, func );
INIT_DELAYED_WORK( work, func );
INIT_DELAYED_WORK_DEFERRABLE( work, func );

作業構造体が初期化されたら、次は作業をワークキューに入れます。それには、いくつかの方法があります (リスト 7 を参照)。その 1 つは、queue_work を使用して単純に作業をワークキューに入れるという方法です (これにより、作業が現行の CPU に結び付けられます)。あるいは、ハンドラーを実行する CPU を、queue_work_on を使って指定するという方法もあります。残りの 2 つの関数は、遅延された作業に対して同じ機能を提供します (構造体の中に、work_struct 構造体と作業遅延のタイマーがカプセル化されます)。

リスト 7. ワークキューの関数
int queue_work( struct workqueue_struct *wq, struct work_struct *work );
int queue_work_on( int cpu, struct workqueue_struct *wq, struct work_struct *work );

int queue_delayed_work( struct workqueue_struct *wq,
			struct delayed_work *dwork, unsigned long delay );

int queue_delayed_work_on( int cpu, struct workqueue_struct *wq,
			struct delayed_work *dwork, unsigned long delay );

グローバルなカーネル全体のワークキューを使用することもできます。カーネル全体のワークキューを処理するための関数は 4 つあります。これらの関数 (リスト 8 を参照) は、ワークキュー構造体を定義する必要がないという点を除けば、リスト 7 に記載した関数と同じ働きをします。

リスト 8. カーネル全体のワークキューの関数
int schedule_work( struct work_struct *work );
int schedule_work_on( int cpu, struct work_struct *work );

int scheduled_delayed_work( struct delayed_work *dwork, unsigned long delay );
int scheduled_delayed_work_on( 
		int cpu, struct delayed_work *dwork, unsigned long delay );

ワークキューに入れられた作業をフラッシュまたはキャンセルするために使用できる数々のヘルパー関数もあります。特定の作業項目をフラッシュし、作業が完了するまでブロックするには、flush_work を呼び出します。flush_workqueue を呼び出すと、特定のワークキューに入れられたすべての作業を完了することができます。いずれの場合も、呼び出し側は操作が完了するまでブロックされます。カーネル全体のワークキューをフラッシュするには、flush_scheduled_work を呼び出してください。

int flush_work( struct work_struct *work );
int flush_workqueue( struct workqueue_struct *wq );
void flush_scheduled_work( void );

作業がまだハンドラーで実行されていない場合は、その作業をキャンセルすることができます。cancel_work_sync を呼び出すと、キューに入れられた作業が終了されるか、またはコールバックが終了するまでブロックされます (ハンドラーで作業が進行中の場合)。作業が遅延されているとしたら、cancel_delayed_work_sync の呼び出しを使用することができます。

int cancel_work_sync( struct work_struct *work );
int cancel_delayed_work_sync( struct delayed_work *dwork );

最後に、work_pending または delayed_work_pending を呼び出すことによって、(まだハンドラーによって実行されていない) 作業項目が保留状態にあるかどうかを調べることもできます。

work_pending( work );
delayed_work_pending( work );

以上が、ワークキュー API のコアです。ワークキュー API の実装は ./kernel/workqueue.c にあり、API の定義は ./include/linux/workqueue.h にあります。続いて、単純なワークキュー API の例を紹介します。

単純なワークキューの例

以下の例で、コアとなるワークキュー API 関数をいくつか抜粋して説明します。タスクレットの例と同様、説明を単純にするために、この例はカーネル・モジュールのコンテキストで実装します。

まずは、ボトムハーフを実装するために使用する作業構造体およびハンドラー関数に注目してください (リスト 9 を参照)。このリストでまず目に留まるのは、ワークキュー構造体参照 (my_wq) の定義と、my_work_t の定義でしょう。my_work_t の型定義には、先頭に work_struct 構造体、そして作業項目を表す整数が含まれます。ハンドラー (コールバック関数) は work_struct ポインターから my_work_t 型を間接参照します。作業項目 (構造体の整数) を出力した後、作業ポインターが解放されます。

リスト 9. 作業構造体とボトムハーフ・ハンドラー
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>

MODULE_LICENSE("GPL");

static struct workqueue_struct *my_wq;

typedef struct {
  struct work_struct my_work;
  int    x;
} my_work_t;

my_work_t *work, *work2;


static void my_wq_function( struct work_struct *work)
{
  my_work_t *my_work = (my_work_t *)work;

  printk( "my_work.x %d\n", my_work->x );

  kfree( (void *)work );

  return;
}

リスト 10 は、create_workqueue API 関数を使ってワークキューの作成を開始する init_module 関数です。ワークキューの作成が正常に完了した時点で、(kmalloc によって割り当てられた) 2 つの作業項目を作成します。作成された作業項目のそれぞれが INIT_WORK によって初期化され、作業が定義されると、queue_work の呼び出しによってワークキューに入れられます。(ここでシミュレートしているプロセスの場合) これでトップハーフ・プロセスは完了です。作業は後で、ハンドラーによって処理されることになります (リスト 10 を参照)。

リスト 10. ワークキューと作業の作成
int init_module( void )
{
  int ret;

  my_wq = create_workqueue("my_queue");
  if (my_wq) {

    /* Queue some work (item 1) */
    work = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);
    if (work) {

INIT_WORK( (struct work_struct *)work, my_wq_function );

      work->x = 1;

      ret = queue_work( my_wq, (struct work_struct *)work );

    }

    /* Queue some additional work (item 2) */
    work2 = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);
    if (work2) {

INIT_WORK( (struct work_struct *)work2, my_wq_function );

      work2->x = 2;

      ret = queue_work( my_wq, (struct work_struct *)work2 );

    }

  }

  return 0;
}

リスト 11 に、最後の要素を記載します。このリストでは、モジュールのクリーンアップで特定のワークキューをフラッシュした後 (これにより、ハンドラーが作業の処理を完了するまでワークキューはブロックされます)、ワークキューを破棄します。

リスト 11. ワークキューのフラッシュおよび破棄
void cleanup_module( void )
{
flush_workqueue( my_wq );

destroy_workqueue( my_wq );

  return;
}

タスクレットとワークキューの違い

この記事ではタスクレットとワークキューについて簡単に説明しただけですが、この説明から、トップハーフからボトムハーフに作業を任せる 2 つの方式を理解していただけたはずです。タスクレットは待ち時間の少ない単純明快なメカニズムとなる一方、ワークキューは複数の作業項目をキューに入れられる、柔軟な API となります。いずれも割り込みコンテキストから作業を遅延するという点では同じですが、完了するまで実行するというやり方でアトミックに動作するのはタスクレットだけです。ワークキューでは、必要に応じてハンドラーがスリープに入ることも許されます。両方とも作業遅延に役立つ手法なので、どちらの手法を選択するべきかは、対処するべきそれぞれのニーズによって決まります。


さらに詳しく調べてください

この記事で説明した作業遅延の手法は、Linux カーネルで従来使用されてきた手法、そして現在使用されている手法です (今回はタイマーについては説明しませんでしたが、今後の記事で取り上げるつもりです)。これらの手法は確かに新しいものではありませんが (実際、過去に別の形で存在していました)、Linux や他の環境でも役に立つ興味深いアーキテクチャー・パターンを表しています。softirq からタスクレットへ、そしてワークキューから遅延ワークキューへと、Linux はカーネルのあらゆる領域で進化し続けるとともに、ユーザー空間で一貫した互換性のあるエクスペリエンスを提供しています。

参考文献

学ぶために

製品や技術を入手するために

  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

  • My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=483092
ArticleTitle=カーネル API: 第 2 回、遅延可能な関数、カーネルのタスクレット、およびワークキュー
publish-date=03022010