勘違いから、誤った情報を流してしまいましたこと、ご容赦下さい。
(前から、なにか引っ掛かる気がしていたのですが、記事執筆中に不安なところを残さないようにと再チェックしていて気づきました...)
もし、待つときに、それだけを待てばいいような場合、
それに対して、
以上のうち、ブロックとselectについてこのページでは触れます。 どちらも
今回はわりと長めの作りで、本格的になっています(とはいえ、穴はありそうですが)。特徴は
// gcc -c blktest.c -Wall -Wstrict-prototypes -O -pipe -m486
// mknod /dev/ocrtest c 60 0; chmod a+rw /dev/ocrtest
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/string.h>
#if LINUX_VERSION_CODE >= 0x020100
#include <asm/uaccess.h>
#include <linux/poll.h> // 注意
#else
static inline unsigned long
copy_to_user(void *to, const void *from, unsigned long n)
{
memcpy_tofs(to,from,n);
return 0;
}
static inline unsigned long
copy_from_user(void *to, const void *from, unsigned long n)
{
memcpy_fromfs(to,from,n);
return 0;
}
#endif
static int devmajor=60;
static char *devname="blktest";
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
#endif
// 読み書き領域 本当は kmalloc で取った方がたぶんいいけど、横着します。
#define MAXMLEN 8192
static char message[MAXMLEN]; // メッセージ read で読み, write で書く
static int mlen=0; // 長さ。最長 MAXMLEN
static unsigned long read_jiffies=1; // 読んだ時間
static unsigned long write_jiffies=0; // 書いた時間
static unsigned long read_fversion=0; // 読んだやつ
static unsigned long write_fversion=0;// 書いたやつ
// ファイルごとの管理領域
typedef struct
{
unsigned long f_version; // 識別用 f_version
struct wait_queue *wait; // 休眠待機用領域
int sleep_mode; // 0: 起きてる 1: 読み込み待ち 2: 書き込み待ち
} FileInfo;
#define MAXFILE 3 // 同時アクセス数
FileInfo fi[MAXFILE];
// Linux 2.0/2.2 共通
static int blktest_open(struct inode * inode, struct file * file)
{
int i;
printk("blktest_open: (%ld)\n",file->f_version);
for(i=0;i<MAXFILE;i++)
{
if(fi[i].f_version==0) break;
}
if(i==MAXFILE)
{
printk("blktest_open: busy\n");
return -EBUSY;
}
fi[i].f_version=file->f_version;
fi[i].sleep_mode=0;
fi[i].wait=NULL;
file->private_data=fi+i; // プライベートデータに構造体ポインタ設定
printk("%d\n",i);
MOD_INC_USE_COUNT;
file->f_pos=0; // 読み出し 書き込み 位置を 0
return 0;
}
// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int blktest_close(struct inode * inode, struct file * file)
#else
static void blktest_close(struct inode * inode, struct file * file)
#endif
{
int i;
printk("blktest_close: (%ld)\n",file->f_version);
MOD_DEC_USE_COUNT;
if(file->f_version==read_fversion)
{
read_jiffies=jiffies; // 読んで閉じた時間
if(read_jiffies==write_jiffies) read_jiffies++; // はまり防止
for(i=0;i<MAXFILE;i++)
if((fi[i].f_version)&&(fi[i].sleep_mode&2)) // 書き待ちを
{
wake_up_interruptible(&(fi[i].wait)); // 起こす
fi[i].sleep_mode&=~2;
}
}
if(file->f_version==write_fversion)
{
write_jiffies=jiffies; // 書いて閉じた時間
if(read_jiffies==write_jiffies) write_jiffies++; // はまり防止
for(i=0;i<MAXFILE;i++)
if((fi[i].f_version)&&(fi[i].sleep_mode&1)) // 読み待ちを
{
wake_up_interruptible(&(fi[i].wait)); // 起こす
fi[i].sleep_mode&=~1;
}
}
((FileInfo *)(file->private_data))->f_version=0;
((FileInfo *)(file->private_data))->sleep_mode=0;
#if LINUX_VERSION_CODE >= 0x020100
return 0;
#endif
}
// read はバージョン依存
#if LINUX_VERSION_CODE >= 0x020100
static int blktest_read(struct file * file,
char * buff, size_t count, loff_t *pos)
#else
static int blktest_read(struct inode * inode,struct file * file,
char * buff,int count)
#endif
{
int len;
// ブロック
if((file->f_pos==0)&&(read_jiffies>=write_jiffies)) // 読んだ方が最近
{
((FileInfo *)(file->private_data))->sleep_mode=1;
interruptible_sleep_on(&(((FileInfo *)(file->private_data))->wait));
if(read_jiffies>write_jiffies)
{
printk("blktest: read: woken up ealier!(signal?)\n");
return -EINTR;
}
}
len=mlen-file->f_pos; // のこり
if(len>count) len=count;
if(len<0) return -EINVAL;
copy_to_user(buff,message+file->f_pos,len);
printk("blktest_read: (%ld) %d bytes (at %d)\n", // 本当は loff_t だと
file->f_version,len,(int)(file->f_pos)); // long long
file->f_pos+=len;
read_fversion=file->f_version;
return len;
}
// write はバージョン依存
#if LINUX_VERSION_CODE >= 0x020100
static int blktest_write(struct file * file,
const char * buff, size_t count, loff_t *pos)
#else
static int blktest_write(struct inode * inode,struct file * file,
const char * buff,int count)
#endif
{
int len;
// ブロック
if((file->f_pos==0)&&(write_jiffies>=read_jiffies)) // 書いた方が最近
{
((FileInfo *)(file->private_data))->sleep_mode=2;
interruptible_sleep_on(&(((FileInfo *)(file->private_data))->wait));
if(write_jiffies>read_jiffies)
{
printk("blktest: write: woken up ealier!(signal?)\n");
return -EINTR;
}
}
len=MAXMLEN-file->f_pos; // のこり
if(len<=0) return -ENOSPC; // いっぱい
if(len>count) len=count;
if(len<0) return -EINVAL;
copy_from_user(message+file->f_pos,buff,len);
printk("blktest_write: (%ld) %d bytes (at %d)\n",
file->f_version,len,(int)(file->f_pos));
file->f_pos+=len;
mlen=file->f_pos;
write_fversion=file->f_version;
return len;
}
// poll / select : 2.2 では poll. 2.0 では select
#if LINUX_VERSION_CODE >= 0x020100
static unsigned int blktest_poll(struct file *file,
struct poll_table_struct *ptab)
{
unsigned long mask=0;
poll_wait(file,&(((FileInfo *)(file->private_data))->wait),ptab);
if(read_jiffies>write_jiffies)
mask|=POLLOUT|POLLWRNORM; // 書き込みOK
else
((FileInfo *)(file->private_data))->sleep_mode|=2;
if(read_jiffies<write_jiffies)
mask|=POLLIN|POLLRDNORM; // 読み込みOK
else
((FileInfo *)(file->private_data))->sleep_mode|=1;
return mask;
}
#else // 2.0 系用
static int blktest_select(struct inode *inode, struct file *file,
int sel_type, select_table *stab)
{
if(sel_type==SEL_OUT)
{
if(read_jiffies>write_jiffies)
return 1; // 書き込みOK
((FileInfo *)(file->private_data))->sleep_mode|=2;
select_wait(&(((FileInfo *)(file->private_data))->wait),stab);
return 0;
}
if(sel_type==SEL_IN)
{
if(read_jiffies<write_jiffies)
return 1; // 読み込みOK
((FileInfo *)(file->private_data))->sleep_mode|=1;
select_wait(&(((FileInfo *)(file->private_data))->wait),stab);
return 0;
}
return 0;
}
#endif
// lseek はバージョン依存
#if LINUX_VERSION_CODE >= 0x020100
static loff_t blktest_lseek(struct file *file, loff_t offset, int whence)
#else
static int blktest_lseek(struct inode *inode, struct file *file,
off_t offset, int whence)
#endif
{
switch(whence)
{
case 0: // SEEK_SET
if(offset>MAXMLEN) return -EINVAL;
if(offset<0) return -EINVAL;
file->f_pos = offset;
return file->f_pos;
case 1: // SEEK_CUR
if(offset+file->f_pos>MAXMLEN) return -EINVAL; // オーバー
if(offset+file->f_pos<0) return -EINVAL; // 負もあり得る
file->f_pos += offset;
return file->f_pos;
case 2:
if(offset+MAXMLEN>MAXMLEN) return -EINVAL;
if(offset+MAXMLEN<0) return -EINVAL;
file->f_pos=offset+MAXMLEN;
return file->f_pos;
default:
return -EINVAL;
}
}
#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations blktest_fops = { // Linux 2.2.10 より
blktest_lseek,// loff_t llseek(struct file *, loff_t, int)
blktest_read, // ssize_t read(struct file *, char *, size_t, loff_t *)
blktest_write,// ssize_t write(struct file *, const char *, size_t, loff_t *)
NULL, // int readdir(struct file *, void *, filldir_t)
blktest_poll, // u. int poll(struct file *, struct poll_table_struct *)
NULL, // int ioctl(struct inode *, struct file *, u.int, u.long)
NULL, // int mmap(struct file *, struct vm_area_struct *)
blktest_open, // int open(struct inode *, struct file *)
NULL, // int flush(struct file *)
blktest_close,// int release(struct inode *, struct file *)
NULL, // int fsync(struct file *, struct dentry *)
NULL, // int fasync(int, struct file *, int)
NULL, // int check_media_change(kdev_t dev)
NULL, // int revalidate(kdev_t dev)
NULL, // int lock(struct file *, int, struct file_lock *)
};
#else
static struct file_operations blktest_fops = { // Linux 2.0.36 より
blktest_lseek,// int lseek(struct inode *, struct file *, off_t, int)
blktest_read, // int read(struct inode *, struct file *, char *, int)
blktest_write,// int write(struct inode *, struct file *, const char *, int)
NULL, // int readdir(struct inode *, struct file *, void *, filldir_t)
blktest_select,// int select(struct inode *, struct file *, int, select_table *)
NULL, // int ioctl(struct inode *, struct file *, u.int, unsigned long)
NULL, // int mmap(struct inode *, struct file *, struct vm_area_struct *)
blktest_open, // int open(struct inode *, struct file *)
blktest_close,// void release(struct inode *, struct file *)
NULL, // int fsync(struct inode *, struct file *)
NULL, // int fasync(struct inode *, struct file *, int)
NULL, // int check_media_change(kdev_t dev)
NULL, // int revalidate(kdev_t dev)
};
#endif
int init_module(void)
{
int i;
for(i=0;i<MAXFILE;i++) fi[i].f_version=0; // 未使用マーク
printk("install '%s' into major %d\n",devname,devmajor);
if(register_chrdev(devmajor,devname,&blktest_fops)) // 登録
{
printk("device registration error\n");
return -EBUSY;
}
return 0;
}
void cleanup_module(void)
{
printk("remove '%s' from major %d\n",devname,devmajor);
if (unregister_chrdev(devmajor,devname))
{
printk ("unregister_chrdev failed\n");
}
};
// select.c gcc -o select select.c
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc,char **argv)
{
int fd,r;
char c;
char *message="test message\n";
int mode=0;
struct timeval tv;
fd_set fds;
if((argc>1) && (argv[1][0]=='w'))
mode=1;
fd=open("/dev/blktest",O_RDWR);
if(fd==0)
{ printf("cannnot open /dev/blktest\n"); return 1; }
printf("device size: %d\n",(int)lseek(fd,0,SEEK_END));
lseek(fd,0,SEEK_SET); // lseek をつかって領域サイズ取得
while(1)
{
FD_ZERO(&fds); // select の準備
FD_SET(fd,&fds);
tv.tv_sec=1; tv.tv_usec=0; // タイムアウト1秒
if(mode)
r=select(fd+1,NULL,&fds,NULL,&tv); // 書きselect
else
r=select(fd+1,&fds,NULL,NULL,&tv); // 読みselect
if(r<0)
{ printf("select returned with signal or error\n"); break; }
if(r==0)
{ putchar('.'); fflush(stdout); continue; }
if((r==1)&&(FD_ISSET(fd,&fds)))
{
if(mode)
{
printf("write operation prepaired\n");
write(fd,message,strlen(message));
// close して 初めて読んだことになるから 一旦 close
close(fd); fd=open("/dev/blktest",O_RDWR);
}
else
{
printf("read operation prepaired\n");
read(fd,&c,1);
close(fd); fd=open("/dev/blktest",O_RDWR);
}
}
}
close(fd);
return 0;
}
今回はタイミングが関係するだけに、実行結果をうまくお見せできません。
% gcc -c blktest.c -Wall -Wstrict-prototypes -O -pipe -m486 # insdev blktest /dev/blktest % cat てきとうなファイル > /dev/blktest で書き込み % cat /dev/blktest で読み込み % ./select (w) で読み(書き)待機 ..read operation prepaired < 1秒1つ . がでる。別のところで .read operation prepaired 書き込むと準備完了メッセージがでるなお、select と select w を同時に実行すると
今回はややこしいので 要素毎に解説します。
ブロックを実現するには、まず、「準備ができている・できていない」をはっきりと評価する必要があります。 これはselectでも一緒です。 準備が出来ていない場合には休眠させ、待機させます。 それは
struct wait_queue *q; interruptible_sleep_on(struct wait_queue ** q);を使用します。 wait_queue は休眠しているプロセスの情報を起こすときに使えるようにするための変数で、プログラムをつくる場合には struct wait_queue * で変数を宣言し、そのポインタ (&q)を interruptible_sleep_on に渡します。 interruptible がない sleep_on もありますが、違いは signal で目覚めるかどうか、です。
なんらかの要因で、この休眠を解きたい場合には
wake_up_interruptible(struct wait_queue ** q);を使います。wait_queue には sleep_on したのと同じ変数(のポインタ)を与えます。
このように、なにかを待つために sleep_on して、待っているものを起こすのに wake_up をつかう、その情報は wait_queue * で渡す、というのが、待機の基本です。
select, poll の場合は実際に休眠するかどうかは、カーネルのほうでやってくれます。 そのため、
void poll_wait(struct file * file, struct wait_queue ** wait, poll_table *p); void select_wait(struct wait_queue ** wait, select_table * p);を使い、もし、休眠が必要ない場合、 wait_queue * で用意した変数の後始末をしてくれます(結局sleepしなかったときに、間違ってwake_upしても問題にならないように処理してくれるようです)。
起こす点についてはブロックと同じで
select などの動作を見やすく/確認しやすくしようとしたら、プログラムがやたらと大きくなってしまいましたが、要は「寝せて」「起こす」です。