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