LECTURE 3 複数プロセスからのアクセス

○ はじめに

LECTURE 2 単純なキャラクタデバイスドライバ で説明した chardev.c では、同時に open できるユーザプロセス数を 1 に制限していました。

今回は、この制限を無くして複数ユーザプロセスが同時にデバイスドライバにアクセスできる様にします。この場合は、複数プロセスが read / write に同時にアクセスすることで内部バッファの内容に不整合が生じない様にするため、内部バッファ操作の前後でロックを取得 / 解放する必要があります。

ここでは、この様な競合状態を防ぐためのロックのうち、セマフォと呼ばれるロックを使用する方法について説明します。

○ サンプルプログラム

複数プロセスからのアクセスに対応したサンプルプログラム multiproc.c です。 はじめに のページから、 全てのサンプルプログラムを 固めた tar ボールをダウンロードすることもできます。

ユーザプロセスからアクセスするためのデバイスファイルは、前回と同じ /dev/chardev を使います。これ以降のサンプルでも特に指定が無い場合は同じデバイスファイルを使います。

multiproc.c は、echo や cat で操作する分には特に違いは分かりません。デバイスファイルをオープンしてしばらく処理を続ける様なプロセスの場合には、複数プロセスからデバイスファイルにアクセスできることが分かります。

--- multiproc.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    
    8    
    9    MODULE_AUTHOR( "Ryo" );
   10    MODULE_DESCRIPTION( "Sample character device driver." );
   11    MODULE_LICENSE( "GPL" );
   12    
   13    
   14    static int   devmajor = 77;
   15    static char* devname  = "chardev";
   16    static char* msg      = "module [multiproc.o]";
   17    
   18    
   19    #define MAXBUF 32
   20    static unsigned char devbuf[ MAXBUF ];
   21    static int buf_pos;
   22    
   23    static struct semaphore chardev_sem;
   24    
   25    
   26    /*
   27     * open()
   28     */
   29    static int 
   30    chardev_open( struct inode* inode, struct file* filp )
   31    {
   32        printk( KERN_INFO "%s : open()  called\n", msg );
   33        return 0;
   34    }
   35    
   36    
   37    /*
   38     * release()
   39     */
   40    static int 
   41    chardev_release( struct inode* inode, struct file* filp )
   42    {
   43        printk( KERN_INFO "%s : close() called\n", msg );
   44        return 0;
   45    }
   46    
   47    
   48    /*
   49     * read()
   50     * ユーザプロセスに対して内部バッファの内容を転送する。
   51     * 転送した内容は内部バッファから消える。
   52     */
   53    static ssize_t 
   54    chardev_read( struct file* filp, char* buf, size_t count, loff_t* pos )
   55    {
   56        int copy_len;
   57        int i;
   58    
   59        printk( KERN_INFO "%s : read()  called\n", msg );
   60    
   61        if ( down_interruptible( &chardev_sem ) ) {
   62            printk( KERN_INFO "%s : down_interruptible for read failed\n", msg );
   63            return -ERESTARTSYS;
   64        }
   65    
   66        printk( KERN_INFO "%s : down_interruptible for read successed\n", msg );
   67    
   68        if ( count > buf_pos )
   69            copy_len = buf_pos;
   70        else
   71            copy_len = count;
   72    
   73        if ( copy_to_user( buf, devbuf, copy_len ) ) {
   74            up( &chardev_sem );
   75            printk( KERN_INFO "%s : copy_to_user failed\n", msg );
   76            return -EFAULT;
   77        }
   78    
   79        *pos += copy_len;
   80    
   81        for ( i = copy_len; i < buf_pos; i ++ )
   82            devbuf[ i - copy_len ] = devbuf[i];
   83    
   84        buf_pos -= copy_len;
   85        up( &chardev_sem );
   86        printk( KERN_INFO "%s : buf_pos = %d\n", msg, buf_pos );
   87    
   88        return copy_len;
   89    }
   90    
   91    
   92    /*
   93     * write()
   94     * ユーザプロセスから転送された内容を内部バッファに書き込む。
   95     */
   96    static ssize_t 
   97    chardev_write( struct file* filp, const char* buf, size_t count, loff_t* pos )
   98    {
   99        int copy_len;
  100    
  101        printk( KERN_INFO "%s : write() called\n", msg );
  102    
  103        if ( down_interruptible( &chardev_sem ) ) {
  104            printk( KERN_INFO "%s : down_interruptible for write failed\n", msg );
  105            return -ERESTARTSYS;
  106        }
  107    
  108        printk( KERN_INFO "%s : down_interruptible for write successed\n", msg );
  109    
  110        if ( buf_pos == MAXBUF ) {
  111            up( &chardev_sem );
  112            printk( KERN_INFO "%s : no space left\n", msg );
  113            return -ENOSPC;
  114        }
  115    
  116        if ( count > ( MAXBUF - buf_pos ) )
  117            copy_len = MAXBUF - buf_pos;
  118        else
  119            copy_len = count;
  120    
  121        if ( copy_from_user( devbuf + buf_pos, buf, copy_len ) ) {
  122            up( &chardev_sem );
  123            printk( KERN_INFO "%s : copy_from_user failed\n", msg );
  124            return -EFAULT;
  125        }
  126    
  127        *pos    += copy_len;
  128        buf_pos += copy_len;
  129    
  130        up( &chardev_sem );
  131        printk( KERN_INFO "%s : buf_pos = %d\n", msg, buf_pos );
  132    
  133        return copy_len;
  134    }
  135    
  136    
  137    static struct file_operations chardev_fops = 
  138    {
  139        owner   : THIS_MODULE,
  140        read    : chardev_read,
  141        write   : chardev_write,
  142        open    : chardev_open,
  143        release : chardev_release,
  144    };
  145    
  146    
  147    /*
  148     * モジュールの初期処理
  149     * insmod 時に呼ばれる
  150     */
  151    int 
  152    init_module( void )
  153    {
  154        if ( register_chrdev( devmajor, devname, &chardev_fops ) ) {
  155            printk( KERN_INFO "%s : register_chrdev failed\n" );
  156            return -EBUSY;
  157        }
  158    
  159        sema_init( &chardev_sem, 1 );
  160        printk( KERN_INFO "%s : loaded  into kernel\n", msg );
  161    
  162        return 0;
  163    }
  164    
  165    
  166    /*
  167     * モジュールの解放処理
  168     * モジュールの参照数が 0 であれば、rmmod 時に呼ばれる
  169     */
  170    void 
  171    cleanup_module( void )
  172    {
  173        if ( unregister_chrdev( devmajor, devname ) ) {
  174            printk( KERN_INFO "%s : unregister_chrdev failed\n" );
  175            /* 対策無し ... */
  176        }
  177    
  178        printk( KERN_INFO "%s : removed from kernel\n", msg );
  179    }
  180    
  181    
  182    /* End of multiproc.c */

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

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

multiproc.c は、前回の chardev.c を基本としてアクセス管理用変数によるアクセス数の管理コードを削除し、read / write 時にセマフォを使った排他制御を行っています。よって、変更点について説明します。

  • 1 行目

    排他制御に使用するセマフォについてのヘッダファイルをインクルードしています。

  • 23 行目

    排他制御用のセマフォ構造体 chardev_sem を宣言しています。 セマフォについては後述します。

  • 26 〜 34 行目 ( chardev_open )

    chardev.c から access_num に関するコードを削除しています。

  • 37 〜 45 行目 ( chardev_release )

    chardev_open 同様、access_num に関するコードを削除しています。

  • 48 〜 89 行目 ( chardev_read )

    複数のプロセスから並列に実行される可能性があるため、 内部バッファを操作する前にセマフォと呼ばれるロックを取得しています。

    Linux® でのセマフォは、セマフォの値が 0 より大きいかどうかをまず調べます。 もし 0 より大きければ、セマフォの値を 1 減らします。0 である場合は、 セマフォが解放される(セマフォの値が 1 増える)まで待つことになります。 セマフォが不要になった時点で解放(セマフォの値を 1 増やす)します。

    これらは、down / down_interruptible という関数を実行することで 実現できます。

    #include <asm/semaphore.h>
    void down( struct semaphore* sem );
    int down_interruptible( struct semaphore* sem );
         

    down と down_interruptible の差は、セマフォの取得待ちの間にシグナル によって割り込むことができるかどうかです。シグナルによって割り込めない場合、 仮にセマフォが解放されない場合は取得待ちのままどうすることもできなくなります。 よって通常は down_interruptible を使用します。

    down_interruptible が成功したか失敗した(シグナルによって割り込まれた) かは、down_interruptible が 0 を返した(成功)かそれ以外を返したか によって判断します。シグナルによって割り込まれた場合は、- ERESTARTSYS を返すことでカーネルに割り込まれたことを通知して終了します。

    セマフォの初期値は sema_init という初期化関数によって設定します。

    #include <asm/semaphore.h>
    void sema_init( struct semaphore* sem, int val );
         

    multiproc.c では、init_module 内で値を 1 に初期化しています。 よって multiproc.c では、同時に 1 ユーザプロセスが内部バッファに アクセスできることになります。

    71、81 行目では up 関数によって取得したセマフォを解放しています。

    #include <asm/semaphore.h>
    void up( struct semaphore* sem );
         
  • 92 〜 134 行目 ( chardev_write )

    chardev_read 同様、down_interruptible によってセマフォをまず取得 しています。また、終了時(正常 / 失敗共)には up によってセマフォを 解放しています。

  • 147 〜 163 行目 ( init_module )

    sema_init 関数により、セマフォを初期化しています。1 に初期化する ことで、内部バッファに同時にアクセスできるユーザプロセス数を 1 に制限しています。

以上で multiproc.c の解説は終わりです。以降のキャラクタデバイスドライバのサンプルは multiproc.c を基本として拡張していきます。