LECTURE 10 タスクレット

○ はじめに

LECTURE 9 簡単な割り込みハンドラ で、割り込みハンドラの登録/実装方法について説明しました。その際、割り込みハンドラで長時間の処理を行わない様にすること/長時間の処理が必要な場合は割り込みハンドラからタスクレットと呼ばれる機能を起動することと書きました。

タスクレットとは予めカーネルに登録しておくことで、カーネルに余裕がある時にそのタスクを実行してもらうための機能です。即座に制御を返さなければならない様な関数において、タスクの予約を行い自身はすぐ終了することで、その後カーネルがバックグラウンドでタスクを実行してくれます。

ここでは、割り込みハンドラからタスクレットを予約しますが、別に割り込みハンドラから起動しなくてはならないわけではありません。write 等でデバイスに対するコマンド発行を予約しておき、write 自身はすぐ終了する、という使い方をすることもできます。

○ サンプルプログラム

LECTURE 8 I/O ポートの制御 で作成した自作電子回路を使って、割り込みハンドラからタスクレットを起動するサンプルプログラムtasklet.c です。はじめに のページから、全てのサンプルプログラムを 固めた tar ボールをダウンロードすることもできます。

プッシュボタンを押して離した瞬間に割り込みが発生し、タスクレットが起動します。そのタスクレットの中で一番下の LED の状態を反転することで、プッシュボタンを押す度に一番下の LED が点灯/消灯を繰り返します。割り込みハンドラで直接状態を設定していたのを、タスクレットで行う様にした点が異なります。

タスクレット起動時にはカーネルログに

<6>module [tasklet.o] : Tasklet fire-led being executed.

の様に出力されています。

--- tasklet.c ---

    1    #include <asm/io.h>          /* outb(), inb() */
    2    #include <linux/interrupt.h> /* tasklet */
    3    #include <linux/ioport.h>    /* check_region(), request_region(), release_region() */
    4    #include <linux/kernel.h>    /* printk() */
    5    #include <linux/module.h>
    6    #include <linux/sched.h>     /* task_struct */
    7    #include <linux/spinlock.h>
    8    
    9    
   10    MODULE_LICENSE( "GPL" );
   11    
   12    
   13    #define IOPORT_BASE     0x378
   14    #define IOPORT_RANGE    3
   15    
   16    #define IOPORT_DATA     ( IOPORT_BASE + 0 )
   17    #define IOPORT_CONTROL  ( IOPORT_BASE + 2 )
   18    
   19    #define IRQ_NUMBER      7
   20    
   21    #define DATA_LED_3      2
   22    #define DATA_PULLUP     3
   23    #define IRQ_ENABLE      4
   24    
   25    
   26    static char* devname  = "tasklet";
   27    static char* msg      = "module [tasklet.o]";
   28    
   29    static unsigned int initial_data;
   30    static unsigned int initial_control;
   31    
   32    static void tasklet_body( unsigned long value );
   33    DECLARE_TASKLET( tasklet_fire_led, tasklet_body, ( unsigned long )NULL );
   34    
   35    static spinlock_t tasklet_spinlock;
   36    
   37    
   38    /*
   39     * 割り込みハンドラ
   40     */
   41    static void
   42    tasklet_interrupt( int irq, void* dev_id, struct pt_regs* regs )
   43    {
   44        tasklet_schedule( &tasklet_fire_led );
   45    }
   46    
   47    
   48    /*
   49     * LED 操作用タスクレット実体
   50     */
   51    void 
   52    tasklet_body( unsigned long value )
   53    {
   54        unsigned long flags;
   55        unsigned int  data;
   56    
   57        printk( KERN_INFO "%s : Tasklet fire-led being executed.\n", msg );
   58    
   59        spin_lock_irqsave( &tasklet_spinlock, flags );
   60    
   61        data  = inb( IOPORT_DATA );
   62        data ^= ( 1 << DATA_LED_3 );
   63        outb( data, IOPORT_DATA );
   64    
   65        spin_unlock_irqrestore( <tasklet_spinlock, flags );
   66    }
   67    
   68    
   69    /*
   70     * モジュールの初期処理
   71     * insmod 時に呼ばれる
   72     */
   73    int 
   74    init_module( void )
   75    {
   76        unsigned int data;
   77    
   78        if ( request_region( IOPORT_BASE, IOPORT_RANGE, devname ) == NULL ) {
   79            printk( KERN_INFO "%s : request_region returned NULL\n", msg );
   80            return -EBUSY;
   81        }
   82    
   83    
   84        if ( request_irq( IRQ_NUMBER, tasklet_interrupt, 0, devname, NULL ) ) {
   85            printk( KERN_INFO "%s : request_irq failed\n", msg );
   86            release_region( IOPORT_BASE, IOPORT_RANGE );
   87            return -EBUSY;
   88        }
   89    
   90        spin_lock_init( &tasklet_spinlock );
   91    
   92        initial_data    = inb( IOPORT_DATA );
   93        initial_control = inb( IOPORT_CONTROL );
   94        rmb();
   95    
   96        data = inb( IOPORT_DATA );
   97        data |= ( 1 << DATA_PULLUP );
   98        outb( data, IOPORT_DATA );
   99        mb();
  100    
  101        data = inb( IOPORT_CONTROL );
  102        data |= ( 1 << IRQ_ENABLE );
  103        outb( data, IOPORT_CONTROL );
  104        mb();
  105        
  106        printk( KERN_INFO "%s : loaded  into kernel\n", msg );
  107        return 0;
  108    }
  109    
  110    
  111    /*
  112     * モジュールの解放処理
  113     * モジュールの参照数が 0 であれば、rmmod 時に呼ばれる
  114     */
  115    void 
  116    cleanup_module( void )
  117    {
  118        free_irq( IRQ_NUMBER, NULL );
  119        tasklet_kill( &tasklet_fire_led );
  120    
  121        outb( initial_control, IOPORT_CONTROL );
  122        outb( initial_data,    IOPORT_DATA    );
  123    
  124        release_region( IOPORT_BASE, IOPORT_RANGE );
  125    
  126        printk( KERN_INFO "%s : removed from kernel\n", msg );
  127    }
  128    
  129    
  130    /* End of tasklet.c */

○ サンプルプログラムの説明

では、サンプルプログラムについて説明していきます。パラレルポートの I/O 制御関連については LECTURE 9 簡単な割り込みハンドラirq_io.h および irq_io.c の説明を参照してください。

  • 1 〜 7 行目

    必要なヘッダファイルをインクルードしています。2 行目では、 タスクレットを使うために必要なヘッダファイル linux/interrupt.h をインクルードしています。

  • 32 〜 33 行目

    32 行目の static void tasklet_body( unsigned long value ) は、 タスクレットの実処理を行う関数のプロトタイプです。 33 行目で、DECLARE_TASKLET によってタスクレットを宣言しています。

    #include <linux/interrupt.h>
    DECLARE_TASKLET( tasklet_name, tasklet_function, data );
    
    

    tasklet_name はタスクレットにつける名称、tasklet_function は タスクレットの実処理を行う関数のポインタ、data は tasklet_function に渡される unsigned long 値を指定します。これにより、tasklet_name を使ってタスクレットを起動することができます。

    なおタスクレットの宣言には、宣言はするけど起動しても実行されない 無効状態で宣言する DECLARE_TASKLET_DISABLED というマクロもあります。 引数については、DECLARE_TASKLET と同じです。

  • 38 〜 45 行目 ( tasklet_interrupt )

    プッシュボタンスイッチ押下状態から離した際に実行される、割り込みハンドラです。 自身では何もしておらず、44 行目の tasklet_schedule によってタスクレット を予約しています。

    #include <linux/interrupt.h>
    void tasklet_schedule( struct tasklet_struct* t );
    
    

    DECLARE_TASKLET で指定した tasklet_name を引数に指定します。 これにより、登録したタスクが以後一度だけ実行されることが保証されます。 なお、カーネルに余裕がある時間帯に実行されるため、確実に何時実行されるか 明言することはできません。なお、タスクレット実処理の実行は、 タスクレットを予約した CPU 上で行われることが保証されます。

    なお、タスクレットには有効/無効状態が存在し、linux/interrupt.h でプロトタイプが宣言されている以下の関数によって状態を切替えることができます。

    関数名 説明
    void tasklet_disable( struct tasklet_struct* t ); 指定したタスクレットを無効状態にする。これにより、tasklet_schedule での予約ではできるが、有効状態になるまで実行されない。
    void tasklet_enable( struct tasklet_struct* t ); 指定されたタスクレットを有効状態にする。

  • 48 〜 66 行目 ( tasklet_body )

    割り込みハンドラから予約されるタスクレットの実処理部です。 電子回路上の一番下の LED の状態を反転しています。 よって、プッシュボタンが押下された状態から離す度に一番下の LED が点灯/消灯を繰り返します。

    なお spin_lock_irqsave / spin_unlock_irqrestore でスピンロック を取得した状態で LED の操作を行っていますが、タスクレット処理中に別の CPU において割り込みハンドラ経由でタスクレットが実行される可能性があるため、 スピンロックによって排他処理を行っています。割り込みを止める必要は別にないので、 普通の spin_lock / spin_unlock でいいと思います。。。

  • 111 〜 127 行目 ( cleanup_module )

    119 行目で、tasklet_kill により予約済みのタスクレットが終了するのを 待ち合わせています。これにより、カーネルモジュール削除後にカーネル がタスクレットを実行しようとしてハングアップすることを防いでいます。

    #include <linux/interrupt.h>
    void tasklet_kill( struct tasklet_struct* t );
    
    

    tasklet_kill を使う場合は、以下の点に注意してください。

    • タスクレット処理中に再度タスクレットを tasklet_schedule によって予約している場合、 停止フラグ等で再予約しない様にした上で tasklet_kill を実行してください ( カーネルタイマーを使う のサンプルプログラムにおける timerstop の様なフラグを使う )。でなければ、tasklet_kill が永遠に戻って来なくなってしまいます。

    • タスクレットが予約されていてかつ無効になっている状態で tasklet_kill を実行しないでください。tasklet_kill は、予約されているタスクレット が実行されるのを待って戻りますが、無効になっていると実行されないため、 永遠に戻って来なくなってしまいます。

以上で tasklet.c の解説は終わりです。