LECTURE 4 ブロックI/O

○ はじめに

multiproc.c では、read 時に読み込めるデータが存在しない、あるいは write 時に書き込むスペースが存在しない場合には即座に処理終了していました。

今回はその様な場合に即座に終了するのではなく、他のプロセスによるデータの読み書きが行われるまで待機するブロック I/O を実装します。

○ サンプルプログラム

ブロック I/O ( read / write ) に対応したサンプルプログラム block.c です。 はじめに のページから、 全てのサンプルプログラムを 固めた tar ボールをダウンロードすることもできます。

echo "HELLO WORLD" > /dev/chardev

の様にして書き込みを行った際に内部バッファに空きが無いと、他プロセスで

cat /dev/chardev
HELLO WORLD

の様にして内部バッファの内容を取り出すまで、書き込みを行おうとしたプロセスは待ち状態になります。内部バッファの内容が取り出されて空きが出来ると同時に、書き込みプロセスが動作し始めます。

反対に、cat 等で読み込もうとした際に内部バッファにデータが無い場合、読み込みプロセスは待ち状態になります。内部バッファにデータが書き込まれると同時に、読み込みプロセスが動作し始めます。

C 言語等で非ブロック指定で /dev/chardev を open した場合は、上記の様な場合に待ち状態になるのではなく、即座に読み込み、書き込み処理が終了します。その際 read / write システムコールは -1 を返し、errno には EBUSY が設定されます。

--- block.c ---

    1    #include <asm/semaphore.h>
    2    #include <asm/uaccess.h>   /* copy_from_user, copy_to_user */
    3    #include <linux/errno.h>
    4    #include <linux/fs.h>      /* inode, file, file_operations */
    5    #include <linux/kernel.h>  /* printk() */
    6    #include <linux/module.h> 
    7    #include <linux/wait.h>    /* wait_queue_head_t */
    8    #include <linux/sched.h>   /* wait_event_interruptible, wake_up_interruptible */
    9    
   10    
   11    MODULE_AUTHOR( "Ryo" );
   12    MODULE_DESCRIPTION( "Sample character device driver." );
   13    MODULE_LICENSE( "GPL" );
   14    
   15    
   16    static int   devmajor = 77;
   17    static char* devname  = "chardev";
   18    static char* msg      = "module [block.o]";
   19    
   20    
   21    #define MAXBUF 32
   22    static unsigned char devbuf[ MAXBUF ];
   23    static int buf_pos;
   24    
   25    static struct semaphore chardev_sem;
   26    static wait_queue_head_t write_q;
   27    static wait_queue_head_t read_q;
   28    
   29    
   30    /*
   31     * ログ出力フォーマット用関数
   32     */
   33    static void 
   34    log_format( char* body )
   35    {
   36        if ( current != NULL ) {
   37            printk( KERN_INFO "%s : PID = %-5i cmdline = \"%-10s\" : %s\n", 
   38                msg, current->pid, current->comm, body );
   39        }
   40        else {
   41            printk( KERN_INFO "%s : %s\n", msg, body );
   42        }
   43    }
   44    
   45    
   46    /*
   47     * open()
   48     */
   49    static int 
   50    chardev_open( struct inode* inode, struct file* filp )
   51    {
   52        log_format( "open()  called" );
   53        return 0;
   54    }
   55    
   56    
   57    /*
   58     * release()
   59     */
   60    static int 
   61    chardev_release( struct inode* inode, struct file* filp )
   62    {
   63        log_format( "close() called" );
   64        return 0;
   65    }
   66    
   67    
   68    /*
   69     * read()
   70     * ユーザプロセスに対して内部バッファの内容を転送する。
   71     * 転送した内容は内部バッファから消える。
   72     */
   73    static ssize_t 
   74    chardev_read( struct file* filp, char* buf, size_t count, loff_t* pos )
   75    {
   76        int copy_len;
   77        int i;
   78    
   79        log_format( "read()  called" );
   80    
   81        if ( down_interruptible( &chardev_sem ) ) {
   82            log_format( "down_interruptible for read failed" );
   83            return -ERESTARTSYS;
   84        }
   85    
   86        while ( buf_pos == 0 ) {
   87            up( &chardev_sem );
   88    
   89            if ( filp->f_flags & O_NONBLOCK ) {
   90                log_format( "read return because O_NONBLOCK is specified" );
   91                return -EAGAIN;
   92            }
   93    
   94            log_format( "read process going to sleep" );
   95    
   96            if ( wait_event_interruptible( read_q, ( buf_pos != 0 ) ) )
   97                return -ERESTARTSYS;
   98    
   99            if ( down_interruptible( &chardev_sem ) ) {
  100                log_format( "down_interruptible for read failed" );
  101                return -ERESTARTSYS;
  102            }
  103        }
  104    
  105    
  106        log_format( "read process going to read" );
  107    
  108        if ( count > buf_pos )
  109            copy_len = buf_pos;
  110        else
  111            copy_len = count;
  112    
  113        if ( copy_to_user( buf, devbuf, copy_len ) ) {
  114            up( &chardev_sem );
  115            log_format( "copy_to_user failed" );
  116            return -EFAULT;
  117        }
  118    
  119        *pos += copy_len;
  120    
  121        for ( i = copy_len; i < buf_pos; i ++ )
  122            devbuf[ i - copy_len ] = devbuf[i];
  123    
  124        buf_pos -= copy_len;
  125    
  126        up( &chardev_sem );
  127        wake_up_interruptible( &write_q );
  128    
  129        return copy_len;
  130    }
  131    
  132    
  133    /*
  134     * write()
  135     * ユーザプロセスから転送された内容を内部バッファに書き込む。
  136     */
  137    static ssize_t 
  138    chardev_write( struct file* filp, const char* buf, size_t count, loff_t* pos )
  139    {
  140        int copy_len;
  141    
  142        log_format( "write() called" );
  143    
  144        if ( down_interruptible( &chardev_sem ) ) {
  145            log_format( "down_interruptible for write failed" );
  146            return -ERESTARTSYS;
  147        }
  148    
  149        while ( buf_pos == MAXBUF ) {
  150            up( &chardev_sem );
  151    
  152            if ( filp->f_flags & O_NONBLOCK ) {
  153                log_format( "write return because O_NONBLOCK is specified" );
  154                return -EAGAIN;
  155            }
  156    
  157            log_format( "write process going to sleep" );
  158    
  159            if ( wait_event_interruptible( write_q, ( buf_pos != MAXBUF ) ) )
  160                return -ERESTARTSYS;
  161    
  162            if ( down_interruptible( &chardev_sem ) ) {
  163                log_format( "down_interruptible for write failed" );
  164                return -ERESTARTSYS;
  165            }
  166        }
  167    
  168    
  169        log_format( "write process going to write" );
  170    
  171        if ( count > ( MAXBUF - buf_pos ) )
  172            copy_len = MAXBUF - buf_pos;
  173        else
  174            copy_len = count;
  175    
  176        if ( copy_from_user( devbuf + buf_pos, buf, copy_len ) ) {
  177            up( &chardev_sem );
  178            log_format( "copy_from_user failed" );
  179            return -EFAULT;
  180        }
  181    
  182        *pos    += copy_len;
  183        buf_pos += copy_len;
  184    
  185        up( &chardev_sem );
  186        wake_up_interruptible( &read_q );
  187    
  188        return copy_len;
  189    }
  190    
  191    
  192    static struct file_operations chardev_fops = 
  193    {
  194        owner   : THIS_MODULE,
  195        read    : chardev_read,
  196        write   : chardev_write,
  197        open    : chardev_open,
  198        release : chardev_release,
  199    };
  200    
  201    
  202    /*
  203     * モジュールの初期処理
  204     * insmod 時に呼ばれる
  205     */
  206    int 
  207    init_module( void )
  208    {
  209        if ( register_chrdev( devmajor, devname, &chardev_fops ) ) {
  210            log_format( "register_chrdev failed" );
  211            return -EBUSY;
  212        }
  213    
  214        sema_init( &chardev_sem, 1 );
  215    
  216        init_waitqueue_head( &write_q );
  217        init_waitqueue_head( &read_q );
  218    
  219        log_format( "loaded  into kernel" );
  220        return 0;
  221    }
  222    
  223    
  224    /*
  225     * モジュールの解放処理
  226     * モジュールの参照数が 0 であれば、rmmod 時に呼ばれる
  227     */
  228    void 
  229    cleanup_module( void )
  230    {
  231        if ( unregister_chrdev( devmajor, devname ) ) {
  232            log_format( "unregister_chrdev failed" );
  233            /* 対策無し ... */
  234        }
  235    
  236        log_format( "removed from kernel" );
  237    }
  238    
  239    
  240    /* End of block.c */

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

では、サンプルプログラムについて説明していきます。

block.c は、前回の multiproc.c を基本として、ブロック用の処理を追加しています。ここでは変更点について説明します。

  • 7 〜 8 行目

    プロセスを待ち状態にするために必要なヘッダファイルをインクルードしています。

  • 26 〜 27 行目

    プロセスを待ち状態にするための待ち列と呼ばれるデータ構造 wait_queue_head_t 型変数を宣言しています。write_q は書き込み プロセスを待ち状態にするための待ち列で、read_q は読み込みプロセス を待ち状態にするための待ち列です。待ち列については後述します。

  • 30 〜 43 行目

    ログ出力フォーマットを揃えるために、専用の関数を用意しました。 current->pid / current->comm という変数を使用していますが、current は現在実行中のユーザプロセスに関する情報を参照するための変数で struct task_struct 構造体を差すポインタです。linux/sched.h を インクルードすることで参照できます。

    pid メンバは名前の通りプロセス ID を表す整数値で、comm は コマンド名を表す文字列です。これらをログに出力する様にしています。

  • 68 〜 130 行目 ( chardev_read )
    • 86 行目 〜 103 行目

      セマフォを取得した直後の処理です。ユーザプロセスに返せる データが存在しない場合 ( buf_pos == 0 )、デッドロックが生じない様 にまずセマフォを解放します。その後、デバイスに対して非ブロック指定 されている場合は、待ち状態にせずにその時点で終了します。

      非ブロック指定されているかどうかは、chardev_read に引数として 渡される struct file 構造体へのポインタ filp を使い、f_flags メンバを調べます。このフラグに O_NONBLOCK が設定されている場合 は非ブロック指定されています。

      プロセスを待ち状態にするために、wait_event_interruptible を 実行します。

      #include <linux/sched.h>
      int wait_event_interruptible( wait_queue_head_t queue, int condition );
                

      wait_queue_head_t は linux/wait.h で定義されている待ち列と 呼ばれるデータ構造で、イベントの通知を待つプロセスのリストを 格納するために使われます。 block.c では static 変数として read / write 用の待ち列が宣言されています。 ここでは、読み込みプロセスを待ち状態にするため read_q を使って います。

      この待ち列は初期化して使用しなければなりません。ここでは init_module 内で init_waitqueue_head によって初期化していますが、 静的な変数として用意する場合は DECLARE_WAIT_QUEUE_HEAD マクロ によって初期化をコンパイル時に行うことができます。

      #include <linux/wait.h>
      init_waitqueue_head( wait_queue_head_t* queue_p );
      DECLARE_WAIT_QUEUE_HEAD( wait_queue_head_t queue );
                

      wait_event_interruptible は、condition に指定された式が 0 である場合は現在のプロセスを待ち状態にします。待ち状態が解除 されるとまた condition の判定を行い、0 であれば再度プロセスを 待ち状態にします。condition が非 0 あるいはシグナルによって 割り込まれた場合に、wait_event_interruptible は終了します。 condition が非 0 で終了した場合は 0 を返します。

      wait_event_interruptible が 0 を返さなかった場合は、 シグナルによって割り込まれたとみなして -ERESTARTSYS を返しています。 0 を返した場合は再度セマフォを取得し、セマフォ取得前に別 プロセスによってデータが消費されていないかどうかを確認するために while ループの最初に戻ります。

    • 127 行目

      読み込み処理が終わった後、待ち状態になっている書き込みプロセス を実行可能状態にするために wake_up_interruptible を実行します。

      #include <linux/sched.h>
      wake_up_interruptible( wait_queue_head_t* queue );
                

      使用している待ち列は書き込みプロセスの待ち列 write_q です。 これを実行しないと、待ち状態になっている書き込みプロセスは 永遠に待ち状態になってしまうため注意して下さい。

  • 133 〜 189 行目 ( chardev_write )

    chardev_read 同様、プロセスを待ち状態にするための処理を行っています。 chardev_read と異なる点は、ブロック判定用の条件文が異なる / wait_event_interruptible で使用している待ち列が write_q である / wake_up_interruptible で使用している待ち列が read_q である、 といった点です。

  • 202 〜 221 行目 ( init_module )

    待ち列初期化用の処理 init_waitqueue_head が追加されています。

    #include <linux/wait.h>
    init_waitqueue_head( wait_queue_head_t* queue_p );
              

以上で block.c の解説は終わりです。次回は、O_NONBLOCK 指定をせずに非ブロック I/O を行いたい場合に使用するシステムコール select ( poll ) に対応させます。