簡単なキャラクタデバイスをつくる

[| ]  最終更新: 2023/02/14 18:34:11

概要

ここでは 実際に簡単なキャラクタデバイスをつくってみます。さしあたって、 に対応します。最初は無難に open・close から。徐々に増やしてきます。

ここでキャラクタ(型)デバイスとはなんぞや、ということです。 Linux ではデバイスはキャラクタ型とブロック型があります。 キャラクタ型が1バイト単位の細かい読み書きが可能なのに対して、ブロック型はブロックというデータの塊を単位に読み書きします。 ブロック型のデバイスは mount することでファイルシステムに組み込むことが可能で、またディスクキャッシュも働きます。 ただ、ちょっと難しいのでここではおいておきます。 実際にハードウェアを操作するときに、ブロック型の必要性があることはほとんどないと思います。


開く・閉じる

サンプルソースと実行例

まずはサンプルのソースと実行例から。
(ソースは例によって手抜きなので "//コメント" です。)

// gcc -c octest.c -Wall -Wstrict-prototypes -O -pipe -m486
// mknod /dev/octest c 60 0;  chmod a+rw /dev/octest  

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/string.h>


static int devmajor=60;
static char *devname="octest";
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
#endif

// Linux 2.0/2.2 共通
static int octest_open(struct inode * inode, struct file * file)
{
  printk("oc_open:");
  printk("   file->f_version     : %lu \n",file->f_version);
  printk("   MINOR(inode->i_rdev): %d \n",MINOR(inode->i_rdev));
  MOD_INC_USE_COUNT;
  return 0;
}

// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int octest_close(struct inode * inode, struct file * file)
#else
static void octest_close(struct inode * inode, struct file * file)
#endif
{
  printk("oc_close:");
  printk("   file->f_version     : %lu \n",file->f_version);
  MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= 0x020100
  return 0;
#endif
}

#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations octest_fops = {    // Linux 2.2.10 より
  NULL,         // loff_t  llseek(struct file *, loff_t, int)
  NULL,         // ssize_t read(struct file *, char *, size_t, loff_t *)
  NULL,         // ssize_t write(struct file *, const char *, size_t, loff_t *)
  NULL,         // int     readdir(struct file *, void *, filldir_t)
  NULL,         // 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 *)
  octest_open,  // int     open(struct inode *, struct file *)
  NULL,         // int     flush(struct file *)
  octest_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  // LINUX_VERSION_CODE < 0x20100: Linux 2.0.x
static struct file_operations octest_fops = {    // Linux 2.0.36 より
  NULL,         // int  lseek(struct inode *, struct file *, off_t, int)
  NULL,         // int  read(struct inode *, struct file *, char *, int)
  NULL,         // int  write(struct inode *, struct file *, const char *, int)
  NULL,         // int  readdir(struct inode *, struct file *, void *, filldir_t)
  NULL,         // 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 *)
  octest_open,  // int  open(struct inode *, struct file *)
  octest_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)
{
  printk("install '%s' into major %d\n",devname,devmajor);
  if(register_chrdev(devmajor,devname,&octest_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");
    }
};
% gcc -c octest.c -Wall -Wstrict-prototypes -O -pipe -m486
# mknod /dev/octest c 60 0              アクセス用の
# chmod a+rw /dev/octest                特殊ファイルをつくる
# insmod octest
% cat /dev/octest
cat: /dev/octest: Invalid argument      read できなかったので文句をいう
# rmmod octest
# dmesg | tail
install 'octest' into major 60
oc_open:   file->f_version     : 25546 
   MINOR(inode->i_rdev): 0 
oc_close:   file->f_version     : 25546 
remove 'octest' from major 60
# 
急にソースが長くなりましたが、よくみると file_operations なる構造体の定義が大きいだけで、中身は難しいことありません。 しかも、Linux 2.0/2.2 どちらの系列でも対応できるようにしているせいで、長さが倍近くなっています。 どちらかにのみ対応すればいいのであれば、#ifdef な部分を削ってしまえば短くなります。

実行例のところで % で始まる行は一般ユーザでも実行可能、# で始まる行は root でなければ実行できないところです。

なお、とりあえず、メジャー番号(後述)を60にしていますが、もしかすると、ほかのデバイスがこの番号を使っているかも知れません。 念のため、 /proc/devices を見てみて、60 という数字があるようなら、ソースの devmajormknod するときの 60 を別の数字(61など)に変えてください。

解説

まず、ドライバをつくったら、その登録をしなければ、Linuxに認知してもらえません。 また、登録したまま rmmod してカーネルからモジュールを取り払うと、すでに関数などがなくなったところにカーネルがアクセスしてしまって、大変なことになります。 そのため、init_module(), cleanup_moduleは、ドライバとしての機能を登録・登録解除を行う必要があります。 それらを行うのが

include/linux/fs.h
int register_chrdev(unsigned int, const char *, struct file_operations *);
int unregister_chrdev(unsigned int major, const char * name);
です。両関数の最初の2つの引数は同じ値で、前者がメジャー番号、後者がデバイスを示す文字列(適当)です。

デバイスの識別にはメジャー番号・マイナー番号がつかわれます。 メジャー番号は登録されたドライバを区別する番号で、マイナー番号はそれぞれのドライバが使える番号です。 たとえば、IDEのデバイスは /dev/hda(ドライブ全部) /dev/hda1(第一パーティション)... と幾つか種類がありますが、すべてメジャー番号3のデバイスでマイナー番号を0,1,2.. と区別することで、ドライバ側でどこが要求されたかを知ることができます。 マイナー番号を使う例はあとで述べることにします。

register_chrdev の3つめの引数は、デバイスに対する操作をどの関数が受け持つかを定める一覧表です。 多くはシステムコールと対応していて open, close(release), read, write などがあります。 上の例でもその一覧を定義しています。項目は多いのですが、サポートしないものは NULL にするため、非常に無駄に見えます。 が、NULL は NULL で大切で、Linux内部で、NULL なら自動的にエラー処理やデフォルト処理をするようになっています。 上の例では open と close のみ定義しています。これらの関数はとりあえず、printk するだけです。

octest_open, octest_closeには、

MOD_INC_USE_COUNT;
MOD_DEC_USE_COUNT;
なる記述がみられます。 これらは、「モジュールの利用度数」を増やしたり、減らしたりするものです(初期値0)。 だれかが使用中(open してcloseしてない(含 プロセスの終了))にドライバが無くなってしまってはこまるので、このような仕組みがあります。 この利用度数が0でなければ除去できない仕組みのため、開くときに増やして、閉じるときに減らす、などの組合わせで使用中は0より大きくするとよいでしょう。 ただし、誤ってINC したのに DEC しそこねたりすると、除去できないモジュールになってしまうので要注意です。

次に実行例の説明ですが、モジュールのところでの例に加え、

# mknod /dev/octest c 60 0
が増えています。 これは一度つくってしまえば、2度目以降はいらないのですが(やるとエラーがでます)、1回は必要です。 UNIXの類い(MS-DOSも一部まねてますが)はデバイスもファイルシステムの一部にするという特徴があります。 そのためのアクセスする口をつくるのが mknod です。 この例では「/dev/octest がアクセスされたら メジャー60 マイナー0のキャラクタ型デバイスをアクセスする」という定義を行っています。 逆に、(60,0) の部分がそのままなら、どこでつくっても、いくつつくってもかまいません。 mknod ~/auau 60 0 とすれば、ホームディレクトリの auau をアクセスしたときにデバイスが参照されることでしょう。 ただ、伝統的に /dev/ に集めてあります(もっとも、今回のようなテスト用途だと /dev/ につくる理由は全くありません)。

動作の流れ

実際の動作についてまとめておきます。

こまかいことをいうと、実際には read でエラーを起こした段階でrelease されてしまうようです。 また、いくらプロセス側からcloseが呼ばれても、release は一度しかよばれないように、設計されてます(そうでないと困ります:-))。

読む

サンプルソースと実行例

先ほどの例に read が加わります。

// gcc -c ocrtest.c -Wall -Wstrict-prototypes -O -pipe -m486
// mknod /dev/ocrtest c 60 0;  chmod a+rw /dev/ocrtest  
// mknod /dev/ocrtest10 c 60 10;  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>
#else
// Linux 2.0.x では memcpy_tofs/_fromfs だった関数が
// Linux 2.1.x 以降で copy_to/from_user に 変更。なので合わせる
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="ocrtest";
static char *message="linux drivers!\n";
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
MODULE_PARM(message, "s");
#endif

static int linecount;

// Linux 2.0/2.2 共通
static int ocrtest_open(struct inode * inode, struct file * file)
{
  printk("ocr_open:");
  printk("   file->f_version     : %lu \n",file->f_version);
  printk("   MINOR(inode->i_rdev): %d \n",MINOR(inode->i_rdev));
  MOD_INC_USE_COUNT;
  linecount=5+MINOR(inode->i_rdev);
  return 0;
}

// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int ocrtest_close(struct inode * inode, struct file * file)
#else
static void ocrtest_close(struct inode * inode, struct file * file)
#endif
{
  printk("ocr_close:");
  printk("   file->f_version     : %lu \n",file->f_version);
  MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= 0x020100
  return 0;
#endif
}

// read はバージョン依存
#if LINUX_VERSION_CODE >= 0x020100
static int ocrtest_read(struct file * file, 
			char * buff, size_t count, loff_t *pos)
#else
static int ocrtest_read(struct inode * inode,struct file * file,
			char * buff,int count)
#endif
{
  int len;

  if(linecount==0) return 0;  // ネタギレ :参照 open()
  linecount--;

  len=strlen(message);
  if(len>count) len=count;
  copy_to_user(buff,message,len);
  printk("ocr_read:");
  printk("   file->f_version     : %lu \n",file->f_version);
  printk("   requested: %d bytes   returned: %d bytes\n",count,len);
  return len;
}

#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations ocrtest_fops = {    // Linux 2.2.10 より
  NULL,         // loff_t  llseek(struct file *, loff_t, int)
  ocrtest_read, // ssize_t read(struct file *, char *, size_t, loff_t *)
  NULL,         // ssize_t write(struct file *, const char *, size_t, loff_t *)
  NULL,         // int     readdir(struct file *, void *, filldir_t)
  NULL,         // 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 *)
  ocrtest_open, // int     open(struct inode *, struct file *)
  NULL,         // int     flush(struct file *)
  ocrtest_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 ocrtest_fops = {    // Linux 2.0.36 より
  NULL,         // int  lseek(struct inode *, struct file *, off_t, int)
  ocrtest_read, // int  read(struct inode *, struct file *, char *, int)
  NULL,         // int  write(struct inode *, struct file *, const char *, int)
  NULL,         // int  readdir(struct inode *, struct file *, void *, filldir_t)
  NULL,         // 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 *)
  ocrtest_open, // int  open(struct inode *, struct file *)
  ocrtest_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)
{
  printk("install '%s' into major %d\n",devname,devmajor);
  if(register_chrdev(devmajor,devname,&ocrtest_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");
    }
};

% gcc -c ocrtest.c -Wall -Wstrict-prototypes -O -pipe -m486
# mknod /dev/ocrtest c 60 0; chmod a+rw /dev/ocrtest
# mknod /dev/ocrtest10 c 60 10; chmod a+rw /dev/ocrtest10
# insmod ocrtest
% cat /dev/octest
linux drivers!
linux drivers!
linux drivers!
linux drivers!
linux drivers!
# cat /dev/ocrtest10
linux drivers!
         :   全部で15個
linux drivers!
# rmmod ocrtest
# insmod ocrtest message='another message '
% cat /dev/octest
another message another message another message another message another message
# rmmod ocrtest
増えたのは のみです。

解説

基本的な動作は先ほどの例と変わりません。 ただ、 cat がread システムコールをつかったときに、ちゃんと何かかえってくる、というあたりが変わってます。

Linux のバージョンによって微妙に動作が異なりますが、read のすべき仕事は

「プロセスが指定した領域に要求された大きさを上限として、データをコピーする」
「コピーした量を return する」
ことです。ここで大事な問題があります。 プロセスから指定される領域は、プロセスの動作しているメモリ空間でのアドレスであり、カーネルの動作しているメモリ空間とは異なる、という点です。 このあたりはやっかいな話なのですが、プロセスでつかっているポインタの示すアドレスが、そもそも物理的なメモリのアドレスではないことによります。
そのようなわけで、データのコピーには特別な手順が必要です。それには以下の関数を使用します。
 Linux2.0:include/asm/segment.h 
memcpy_tofs   (void *to, const void *from, unsigned long n);
memcpy_fromfs (void *to, const void *from, unsigned long n);
 Linux2.2:include/asm/uaccess.h 
copy_to_user  (void *to, const void *from, unsigned long n);
copy_from_user(void *to, const void *from, unsigned long n);
形はmemcpyとおなじで、名前だけ違います。 Linux のバージョン間でも名前だけが違います。 "to"の関数はユーザ空間(プロセスの動作しているメモリ)へ、"from"の関数はユーザ空間からデータの転送をします。 read はユーザ空間へ送るのが目的なので、"to" の関数をつかいます。 ここではソースを共通にするために、Linux 2.0 の場合にはcopy_to_user, copy_from_user を定義して、copy_to_userを使うようにしています。

なお、return する値が 0 の場合、ふつうはデータの終点と見なします。 負の値を返した場合、その絶対値がC言語では errno に設定されます。 読み取りエラーなどの状況を EBUSY, EINVAL などで返すなどの応用が可能です。

動作解説

このデバイスは open してから close するまで、read が来る度に同じメッセージをわたします。回数は5回限定です。 おまけとして、マイナー番号が0で無い場合に、「5+マイナー番号」回出力するようにしてあります(変数 linecount 参照)。 問題点として、回数を共通の変数にしているので、同時に複数の open や read があった場合、回数の処理が変になることが容易に予想がつきます。 この対策として

があります。後者のためには、いくつかの手段があります。 file->f_pos はもともと、ファイルの読み書き位置、すなわちlseek, fseekで指定される位置を保持するために用意された 変数なのですが、この例のように、読む度同じデータを返す、様な場合には本来の目的に使わないので、流用できます。
file->private_data は自前の作業領域用構造体などを確保した場合に、それを保持しておくためなどに使うことの出来る汎用ポインタです。 これは3つめの対策案に使用できます。 ただ、ポインタとはいえ、所詮は数値なので:-) キャストして流用することは不可能じゃありません。
3つめの方法が正当な方法で、多くのドライバでみられる方法です。 大抵、 file->private_data で情報(へのポインタ)を保持しますが、適当に配列をつくる、という方法もあります。 そのときの目印として、 file->f_version を使用できます。この数字は open するたび異なる数値が設定されるので、識別子としてつかえます。

あとは前の例とかわりません。つぎは write を組み込みます。


書く

サンプルソースと実行例

こんどは書込みを実装します。上の例に付け加えてもいいのですが、 分かりにくくなるので、また別のサンプルをつくります。

// gcc -c ocwtest.c -Wall -Wstrict-prototypes -O -pipe -m486
// mknod /dev/ocwtest c 60 0;  chmod a+rw /dev/ocwtest  

#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>
#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="ocwtest";
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
#endif

// Linux 2.0/2.2 共通
static int ocwtest_open(struct inode * inode, struct file * file)
{
  printk("ocw_open:\n");
  MOD_INC_USE_COUNT;
  return 0;
}

// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int ocwtest_close(struct inode * inode, struct file * file)
#else
static void ocwtest_close(struct inode * inode, struct file * file)
#endif
{
  printk("ocw_close:\n");
  MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= 0x020100
  return 0;
#endif
}

// write はバージョン依存
#if LINUX_VERSION_CODE >= 0x020100
static int ocwtest_write(struct file * file, 
			 const char * buff, size_t count, loff_t *pos)
#else
static int ocwtest_write(struct inode * inode,struct file * file,
			   const char * buff,int count)
#endif
{
  int len;
  char k_buff[50];

  len=49;
  if(len>count) len=count;
  copy_from_user(k_buff,buff,len);
  k_buff[len]='\0';
  printk("ocw_write:\n");
  printk("   offered: %d bytes   obtained: %d bytes\n",count,len);
  printk("   message: '%s'\n",k_buff);
  return len;
}

#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations ocwtest_fops = {    // Linux 2.2.10 より
  NULL,         // loff_t  llseek(struct file *, loff_t, int)
  NULL,         // ssize_t read(struct file *, char *, size_t, loff_t *)
  ocwtest_write,// ssize_t write(struct file *, const char *, size_t, loff_t *)
  NULL,         // int     readdir(struct file *, void *, filldir_t)
  NULL,         // 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 *)
  ocwtest_open, // int     open(struct inode *, struct file *)
  NULL,         // int     flush(struct file *)
  ocwtest_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 ocwtest_fops = {    // Linux 2.0.36 より
  NULL,         // int  lseek(struct inode *, struct file *, off_t, int)
  NULL,         // int  read(struct inode *, struct file *, char *, int)
  ocwtest_write,// int  write(struct inode *, struct file *, const char *, int)
  NULL,         // int  readdir(struct inode *, struct file *, void *, filldir_t)
  NULL,         // 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 *)
  ocwtest_open, // int  open(struct inode *, struct file *)
  ocwtest_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)
{
  printk("install '%s' into major %d\n",devname,devmajor);
  if(register_chrdev(devmajor,devname,&ocwtest_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");
    }
};
# mknod /dev/ocwtest c 60 0
# insmod ocwtest
# echo -n "abcdefghijklmnopqrstuvwxyz" > /dev/ocwtest
# rmmod ocwtest
# dmesg | tail
install 'ocwtest' into major 60
ocw_open:
ocw_write:
   offered: 26 bytes   obtained: 26 bytes
  message: 'abcdefghijklmnopqrstuvwxyz'
ocw_close:
remove 'ocwtest' from major 60

# insmod ocwtest
# echo -n "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" > /dev/ocwtest
# rmmod ocwtest
# dmesg | tail
install 'ocwtest' into major 60
ocw_open:
ocw_write:
   offered: 52 bytes   obtained: 49 bytes
  message: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW'
ocw_write:
   offered: 3 bytes   obtained: 3 bytes
  message: 'XYZ'
ocw_close:
remove 'ocwtest' from major 60
# 
前の例から read がなくなって、write ができ、実行例も echo からリダイレクトで投げ込むようになりました。

解説

read までやってしまうと、writeも簡単です。 ひとつ恐いといえば、ユーザ空間にコピーを失敗してもなんとかなりそうですが、write の場合は copy_from_userでカーネル空間にコピーしてきますので、まちがったら、大変なことになりかねない、ということでしょうか。

とりたてて、特別な点はありません。write の場合も受け取ったバイト数を返します。


まとめ

ここでは 簡単なキャラクタデバイスをつくるべく、open, close, read, writeにしぼって、実際例を題材に解説しました。 さて、これらでなにができるでしょう?
じつはこれだけあれば、簡単なデバイスドライバはつくることができます。 デバイスドライバの仕事は、ユーザプロセスからの要求をうけとって、ユーザが普段さわれないハードウェアにアクセスし、読み書きを行うことです。 read, write はべつにファイルのように連続したデータを扱うことを強制するわけではなく、毎回、特定の構造体を単位に読み書きしても何ら問題ないわけです。 そのときは、プロセス側で read(),write() するときに構造体のサイズを渡せばいいだけです。

今回解説した open, close, read, write はいわば、カーネル内部のデバイスドライバと それを利用するユーザ空間のプロセスとの通信手段、といえるでしょう。



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