デバイス・ドライバ

情報学類 オペレーティングシステムII					2007年02月06日

                                       筑波大学システム情報工学研究科
				       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2006/2007-02-06
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■復習

オペレーティング・システム は、2つのインタフェースを、うまくつなぐもの。

■デバイス

周辺装置。コンピュータの箱の中に内蔵されている部品やケーブルで外で接続 する部品。普通は、CPU とメモリ以外のもの。

アプリケーションからの使い方は、2種類

直接
デバイスをデバイスとしてつかう
間接
デバイスをファイル・システムのデータを保存するために使う

◆ブロック型と文字型

Unix での分類。
ブロック型
ファイル・システムのデータを保存するために使う。 主にハードディスク、CD-ROM、USBメモリ・ディスクなど。 その名の通り, 512バイト, あるいは1024バイトといった決められた大きさ(ブ ロック長)単位で入出力を行う。
文字型
ブロック型以外( 名前にまどわされてはいけない)。 入出力の単位は、バイト単位のものや、その他、デバイスに依存したブロック 単位のものがある。

◆デバイス・ファイル

Unix では、アプリケーション・プロセスからは、デバイスが「ファイル」とし て見える。 ファイルは、デバイス・ファイル、または、特殊ファイルと呼ばれる。 デバイス・ファイルもまた、ブロック型と文字型がある。

デバイス・ファイルは、2つの番号で区別される。

メジャー番号(major number)
デバイス(ドライバ)の種類を表す
マイナー番号(minor number)
同一種類のデバイスで、区別する。

デバイス・ファイルは、主に、/dev というディレクトリ以下に作られ る(他の場所にも作れる)。

◆ブロック型デバイス

ls -l で見ると、先頭に b と表示される。
% ls -l /dev/disk* [←]
brw-r-----   1 root  operator   14,   0 Dec 26 13:57 /dev/disk0
br--r-----   1 root  operator   14,   1 Dec 26 13:57 /dev/disk0s1
brw-r-----   1 root  operator   14,   2 Dec 26 13:57 /dev/disk0s3
brw-r-----   1 root  operator   14,   3 Dec 26 13:57 /dev/disk1
br--r-----   1 root  operator   14,   4 Dec 26 13:57 /dev/disk1s1
br--r-----   1 root  operator   14,   5 Dec 26 13:57 /dev/disk1s2
br--r-----   1 root  operator   14,   6 Dec 26 13:57 /dev/disk1s3
br--r-----   1 root  operator   14,   7 Dec 26 13:57 /dev/disk1s5
br--r-----   1 root  operator   14,   8 Dec 26 13:57 /dev/disk1s6
br--r-----   1 root  operator   14,   9 Dec 26 13:57 /dev/disk1s7
brw-r-----   1 root  operator   14,  10 Dec 26 13:57 /dev/disk1s9
% []
普通のファイルなら容量が表示される所に、メジャー番号(この例では14)とマ イナー番号(この例では0から10)が表示される。

ブロック型デバイスファイルは、主にファイルシステムをマウントする時に使う。

◆ブロック型デバイスの利用

ユーザ・プロセスからは、主に、ファイル・システムへのアクセスを通じて間 接的に使われる。直接的には、あまり使われない。マウント・システム・コー ルくらい。
int mount(const char *source, const char *target, 
    const char *filesystemtype, unsigned long mountflags, const void *data);

◆文字型デバイス

ls -l で見ると、先頭に c と表示される。
% ls -l cu.Bluetooth-*  [←]
crw-rw-rw-   1 root  wheel    9,   3 Dec 26 13:57 cu.Bluetooth-Modem
crw-rw-rw-   1 root  wheel    9,   1 Dec 26 13:57 cu.Bluetooth-PDA-Sync
% []

◆文字型デバイスの利用

ユーザ・レベルからは、次のような主にシステム・コールで操作する。
int open(const char *pathname, int flags);
int close(int fildes);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int ioctl(int fildes, int request, ... /* arg */);
ioctl() の第2引数、第3引数は、デバイス依存。

◆生のデバイス(raw device)

伝統的な Unix (BSD含む)には、ブロック型デバイスに、対応する文字型デバイ スがある。これを「生のデバイス(raw device)」と呼ぶ。

生のデバイスでは、読み書きすると必ずハードウェアのレベルでも入出力が生 じる。ブロック型デバイスをアクセスすると、キャッシングが行われる。

生のデバイスの利用方法

生のデバイスのファイル名には、先頭に r を付ける習慣がある。

% ls -l /dev/{r,}disk0 [←]
brw-r-----   1 root  operator   14,   0 Dec 26 13:57 /dev/disk0
crw-r-----   1 root  operator   14,   0 Dec 26 13:57 /dev/rdisk0
% []
                                                     (MacOSX での例)
Linux には、「生のデバイス」の概念が希薄。明示的には存在しない。

◆いろいろなデバイス

ファイル名は、システムによって異なる。

◆ソフトウェアのデバイス

対応するハードウェアが存在しない。Unix のカーネルがソフトウェア的に作り 出したもの。

制御端末 /dev/tty
どのプロセスから見ても、自分自身の制御端末がアクセスできる。
コンソール /dev/console
コンピュータ全体のエラーメッセージを表示する端末。
空デバイス /dev/null)
読み込むと EOF(end-of-file)、書き込むと黙って吸い込むデバイス。 0 バイトの入力が必要な時、出力が不要な時に使う。
疑似端末 /dev/tty[pqrs][0-9], /dev/pty[pqrs][0-9]
ssh によるログインや端末プログラム(xtermなど)で ハードウェアの端末デバイスのエミュレーションを行うためのデバイス。 たとえば、/dev/ttyp0 と同じ記号と番号の /dev/ptyp0 が 対になっており、 /dev/ttyp0 に書き込まれたシェルなどの出力が、 /dev/ptyp0 を見ている sshdxterm に読み込まれる。
カーネルログ /dev/klog
カーネルのエラーメッセージを syslogd が吸い上げる
乱数 /dev/random, /dev/urandom
乱数生成器。

■デバイスドライバ

デバイス・ドライバ(device driver)は、カーネル内で動作するモジュール。 デバイス(ハードウェア)を操作して入出力を行う。

新しくハードウェアを開発したら、オペレーティング・システムで定めた仕様 に従ったデバイス・ドライバを開発する。

図? ユーザ・プロセス、システム・コール、デバイス・ドライバ、ハードウェア

図? デバイス・ドライバとオペレーティング・システム本体のインタフェース

◆register_chrdev()

デバイス・ドライバの利用、オブジェクト指向/抽象データ型のよい見本。

Linux で、文字型のデバイス・ドライバをカーネルに登録するには、 register_chrdev() という関数を使う。struct file_operations にある関数を 定義し、register_chrdev() を呼ぶ。

/* fs/char_dev.c */
int register_chrdev(unsigned int major, const char *name,
                    const struct file_operations *fops);

/* linux/fs.h */
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*dir_notify)(struct file *filp, unsigned long arg);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};
ファイルを開く時に、struct file の f_op というフィールドは、デバイスご とのstruct file_operations へポインタで初期化される。以後、 f->f_op->read(...) のようにアクセスされる。

◆/dev/urandom

乱数生成。
/* drivers/char/random.c */

struct file_operations urandom_fops = {
        .read  = urandom_read,
        .write = random_write,
        .ioctl = random_ioctl,
};

static ssize_t
urandom_read(struct file * file, char __user * buf,
                      size_t nbytes, loff_t *ppos)
{
        return extract_entropy_user(&nonblocking_pool, buf, nbytes);
}

static ssize_t
random_write(struct file * file, const char __user * buffer,
             size_t count, loff_t *ppos)
{
...
        const char __user *p = buffer;
        size_t c = count;
        while (c > 0) {
                bytes = min(c, sizeof(buf));
                bytes -= copy_from_user(&buf, p, bytes);
                if (!bytes) {
                        ret = -EFAULT;
                        break;
                }
                c -= bytes;
                p += bytes;
                add_entropy_words(&input_pool, buf, (bytes + 3) / 4);
        }
        if (p == buffer) {
                return (ssize_t)ret;
        } else {
...
        }
}

static int
random_ioctl(struct inode * inode, struct file * file,
             unsigned int cmd, unsigned long arg)
{
        int size, ent_count;
        int __user *p = (int __user *)arg;
        int retval;

        switch (cmd) {
        case RNDGETENTCNT:
                ent_count = input_pool.entropy_count;
                if (put_user(ent_count, p))
                        return -EFAULT;
                return 0;
        case RNDADDTOENTCNT:
...
        default:
                return -EINVAL;
        }
}

◆register_blkdev()/blk_init_queue()

ブロック型デバイスの登録は、2段階。
int register_blkdev(unsigned int major, const char *name)

typedef struct request_queue request_queue_t;
typedef void (request_fn_proc) (request_queue_t *q);
request_queue_t *blk_init_queue(request_fn_proc *, spinlock_t *);

struct request_queue
{
...
        request_fn_proc         *request_fn;
...
};

blk_init_queue() で、request関数を登録する。オペレーティング・システム の上位層は、必要に応じてデバイス・ドライバの request 関数を呼び出す。

void __generic_unplug_device(request_queue_t *q)
{
...
        q->request_fn(q);
}
入出力の記述には、struct bio を使う。仮想記憶と統合的に使える。

◆考え方

伝統的な Unix では、strategy という。 要求の内容は、以下のようなもの

実際には、割込みを使う。request_irq() などで、あらかじめ割り込み処理ハ ンドラを登録しておく。 デバイス・ドライバでの要求の扱い方

■ネットワーク・デバイス

ブロック型との違い
struct net_device
{
...
        int                     (*init)(struct net_device *dev);
				// 統計
        struct net_device_stats* (*get_stats)(struct net_device *dev);
				// 送信開始
        int                     (*hard_start_xmit) (struct sk_buff *skb,
                                                    struct net_device *dev);
        void                    (*destructor)(struct net_device *dev);
				// ifconfig up
        int                     (*open)(struct net_device *dev);
				// ifconfig down
        int                     (*stop)(struct net_device *dev);
				// ヘッダを作る
        int                     (*rebuild_header)(struct sk_buff *skb);
				// MAC アドレスの変更
        int                     (*set_mac_address)(struct net_device *dev,
                                                   void *addr);
				// 送信失敗
        void                    (*tx_timeout) (struct net_device *dev);
};
受信は、この関数の表にはない。割り込みから起動される。
Last updated: 2007/02/06 03:18:03
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>