ブロックとSelect

最終更新: 2003/09/01 00:42:30

ご注意

これまで ここのページに書いてある通りにドライバを作ってきていて、動いていたので安心していたのですが、wait_queue の使い方に多少の誤りがあることが分かりました。 この通りでも動作はしますが、真似なさらないことを推奨します。 いずれ、このページは書き換える予定ですが、現在時間が取れず、しばらく先になりそうです。 最近執筆したLinuxJapan誌8月号の記事(配布資料置き場にPDFあり)では、正しいと考えられる使い方で記載しましたので、お急ぎ?の方はこちらをご覧下さい。

勘違いから、誤った情報を流してしまいましたこと、ご容赦下さい。
(前から、なにか引っ掛かる気がしていたのですが、記事執筆中に不安なところを残さないようにと再チェックしていて気づきました...)


データの待機

時として、ユーザのプロセスで、デバイスの準備待ちをしたい場合があります。 普段使っている用途ですと、キーボードやマウスの操作待ちであったり、通信の結果待ちであったり、様々です。 一方、制御ハードの待機というと、外部データの読み込み待ちや出力用FIFOの空き待ち、画像取り込みのタイミング待ちなどやはり様々です。

もし、待つときに、それだけを待てばいいような場合、

などの案があります。前者は「ブロック」と呼ばれています。 デバイスの状況はいつでも最新のものが読みたいけど、時々発生するイベントも待ちたい、というような場合は後者が有力でしょう(read write は即時リクエストを実行して戻るように実装)。 本当はfcntl で設定するBLOCK/NONBLOCKをつかうのがいいのかも知れませんが。
いずれにせよ、ドライバ内部で待っている場合には、以前に述べたように休眠状態などを使うことでシステムを止めないようにしなければなりません。

それに対して、

といった場合には select システムコールが便利です。 selectシステムコールは意味としては「準備が出来たデバイスを選ぶ」といったところでしょうか。 複数のファイル記述子(open()で得られる値)と最大待ち時間を指定して呼ぶと、どれかの「読み・書き(・特殊)」の準備が出来た段階で帰ってきます。 読み書きはそれぞれ独立に待機できますし、同時に複数の準備ができた場合には、複数まとめてかえってきます。 同様なシステムコールに poll もあります(Linux 2.2 ではpoll に指向しているようですし、SunOS5では select がpollをつかっているようです)。
蛇足ですが、ユーザインターフェイスとしてメジャーなGTKでは、これをサポートしているため、デバイスの準備を待ちつつ、GUIもこなすようなプログラムを書く場合に非常に有益です。

以上のうち、ブロックとselectについてこのページでは触れます。 どちらも

という2つの機能からなります。 ブロックがreadやwriteの中で一旦停止させるのに対し、selectはOS側で他のデバイスとともにチェックして、動作する、という点のみ異なります。

サンプルプログラムと解説

動作

今回はわりと長めの作りで、本格的になっています(とはいえ、穴はありそうですが)。特徴は

です。

ソース

// 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 のみそ

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しても問題にならないように処理してくれるようです)。

起こす点についてはブロックと同じで wake_up_interruptible()を使います。

select などの動作を見やすく/確認しやすくしようとしたら、プログラムがやたらと大きくなってしまいましたが、要は「寝せて」「起こす」です。


まとめ

はじめにも述べましたが、ブロックも select も「待機」を実現する機能です。 一般的なUNIXのデバイスとしては、なんらかのデータがくるのを待つのに使われることが多いですが、ハードウェアの制御においては、なんらかのトリガを拾うことにも使用可能です。 実際、以前開発した、Digital Parallel I/O ボードのドライバでは、割り込みをselectで受けられるようにして(割り込み関数内でwake_up する)、ユーザプロセスで割り込みを扱うことを可能にしました。 また、ポーリングでハードの状態を監視し、それに応じてwake_up をかけるなど、使い道は様々です。 signal も同じように使えそうなところですので、こちらもそのうちやってみることにしましょう。


熊谷正朗 [→連絡]
東北学院大学 工学部 機械知能工学科 RDE
[| ]