|
○ はじめに
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 ) に対応させます。