|
○ はじめに
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 を基本として拡張していきます。