追加読書

追加読書

私はこの本を読みました:https://lwn.net/Kernel/LDD3/。ここで、著者は装置ファイルを文字装置、ブロック装置、ネットワーク装置の3種類に区分する。第1章6ページで、私は次の事実を見つけました。

キャラクターデバイス

文字(char)デバイスは、バイトストリーム(ファイルなど)でアクセスできるデバイスです。文字ドライバはこの動作を実装します。これらのドライバは通常、少なくとも、およびopenシステムコールを実装します。テキストコンソール()とシリアルポート(および同様のポート)は、ストリーム抽象化でよく表現できるため、文字デバイスの例です。 Charデバイスは、などのファイルシステムノードを介してアクセスされます。closereadwrite/dev/console/dev/ttyS0/dev/tty1/dev/lp0

それでは、charデバイスとこのデバイスの違いは何ですか?ファイルシステムノード?私をさらに混乱させるのは、ls -la /dev/これが次のようなことです。ファイルシステムノードcharデバイスとしても利用可能です(説明はacで始まります)。

私の考えでは、この本では、文字デバイスをハードウェア間の1対1の対応と呼んだようです。ファイルシステムノードソフトウェア抽象化として。これについて良い資料をいただきありがとうございます。

答え1

それでは、charデバイスとこのデバイスの違いは何ですか?ファイルシステムノード

私はこの質問を「文字デバイスドライバと文字デバイスファイルの違いは何ですか?」と解釈します。

キャラクターデバイスドライバーバイトストリームで動作するカーネルソフトウェアで、バイトストリームでも動作する一部のハードウェアとの通信によく使用されます。

キャラクターデバイス文書ファイルシステムのファイルです。デバイスファイルには、ファイルに関連付けられている文字デバイスドライバについて学ぶためにカーネルで使用されるメタデータが関連付けられています。文字デバイスファイル(実際にはすべてのデバイスファイル)には、プライマリデバイス番号とセカンダリデバイス番号の2つのメタデータがあります。出力を見ると、メジャー/マイナー番号が表示されますls -l。たとえば、文字デバイスファイルを考えてみましょう/dev/null

$ ls -l /dev/null
crw-rw-rw- 1 root root 1, 3 Jun  6 14:30 /dev/null

2番目のルートに続くものに注意してください1, 3。これはメジャー(1)とマイナー(3)の数字です。プロセスがデバイスファイルと対話すると、カーネルはキーデバイス番号を使用してそのファイルのI / Oを処理するカーネルデバイスドライバを識別します。メジャー番号1の文字デバイスはメモリデバイスに関連付けられていますmajor.h

./include/uapi/linux/major.h:#define MEM_MAJOR      1

単一デバイスドライバはしばしば複数のデバイスを「駆動」することができる。セカンダリデバイス番号は、ユーザーが動作している特定のデバイスをカーネルに通知します。たとえば、次の文字デバイスファイルはすべて、メイン番号は同じですが、マイナー番号は異なります。

# ls -l /dev/zero /dev/mem /dev/null /dev/full /dev/random /dev/urandom /dev/kmsg
crw-rw-rw- 1 root root 1,  7 Jun  6 14:30 /dev/full
crw-r--r-- 1 root root 1, 11 Jun  6 14:30 /dev/kmsg
crw-r----- 1 root kmem 1,  1 Jun  6 14:30 /dev/mem
crw-rw-rw- 1 root root 1,  3 Jun  6 14:30 /dev/null
crw-rw-rw- 1 root root 1,  8 Jun  6 14:30 /dev/random
crw-rw-rw- 1 root root 1,  9 Jun  6 14:30 /dev/urandom
crw-rw-rw- 1 root root 1,  5 Jun  6 14:30 /dev/zero

次のソースコードの断片は、Linux 5.4.32のマニュアルから入手したものですdrivers/char/mem.c

上記の出力から、lsすべてのファイルのキー番号が1であることがわかります。これにより、同じカーネルデバイスドライバがこれらのファイルを開いて/読み書きするすべてのプロセスに対するI / O要求に応答することがわかります。カーネルのソースコードでは、メモリデバイスドライバがこれらのファイルすべてのI / Oを処理するのに役立つことがわかります。

static const struct memdev {
        const char *name;
        umode_t mode;
        const struct file_operations *fops;
        fmode_t fmode;
} devlist[] = {
#ifdef CONFIG_DEVMEM
         [1] = { "mem", 0, &mem_fops, FMODE_UNSIGNED_OFFSET },
#endif
#ifdef CONFIG_DEVKMEM
         [2] = { "kmem", 0, &kmem_fops, FMODE_UNSIGNED_OFFSET },
#endif
         [3] = { "null", 0666, &null_fops, 0 },
#ifdef CONFIG_DEVPORT
         [4] = { "port", 0, &port_fops, 0 },
#endif
         [5] = { "zero", 0666, &zero_fops, 0 },
         [7] = { "full", 0666, &full_fops, 0 },
         [8] = { "random", 0666, &random_fops, 0 },
         [9] = { "urandom", 0666, &urandom_fops, 0 },
#ifdef CONFIG_PRINTK
        [11] = { "kmsg", 0644, &kmsg_fops, 0 },
#endif
};

配列インデックス(括弧内の数字)は、関連ファイルの補助装置番号と一致します。

次に、キャラクターデバイスファイルの1つを使用するプロセスの例を見てみましょう。以下を含むシェルスクリプトがある場合:

echo "hello" > /dev/null

その後、スクリプトはopen()文字デバイスファイルです/dev/null。カーネルはこれが/dev/null文字デバイスであることを知り、ファイルに関連付けられたキー番号とセカンダリ番号を確認します。メジャー番号 1 を確認するので、open()メジャー番号 1 (メモリデバイス) の操作を処理する文字デバイスドライバに要求をルーティングします。オープン呼び出しを処理するメモリデバイスドライバの関数で終了します。

static int memory_open(struct inode *inode, struct file *filp)
{
        int minor;
        const struct memdev *dev;

        minor = iminor(inode);
        if (minor >= ARRAY_SIZE(devlist))
                return -ENXIO;

        dev = &devlist[minor];
        if (!dev->fops)
                return -ENXIO;

        filp->f_op = dev->fops;
        filp->f_mode |= dev->fmode;

        if (dev->fops->open)
                return dev->fops->open(inode, filp);

        return 0;
}

次に、関数memory_open()はマイナー番号を使用して、以前devlistに見た配列のインデックスを指定します。デバイスに特殊open()機能がある場合はその機能を呼び出し、それ以外の場合はデバイスnullに特殊機能がない場合は0を返しますopen()

最終的に、プロセスは、write()開かれたファイルに関連するファイル記述子に「hello」を書き込む呼び出しを実行します。同様に、カーネルは開かれたファイルがメジャー番号1とマイナー番号3の文字デバイスに関連付けられていることを知っているので、ファイルをwrite()メジャーデバイスタイプ1(メモリデバイス)のドライバにルーティングします。マイナー番号3のデバイスは、I / O処理のための機能セットを登録します(ここnull_fops)。

         [3] = { "null", 0666, &null_fops, 0 },

構造null_fopsには次の関数ポインタが含まれています。

static const struct file_operations null_fops = {
        ...
        .write          = write_null,
        ...
};

したがって、write()メインデバイス番号が1でマイナーデバイス番号が3の文字デバイスファイルを呼び出すと、write_null()この関数の実装は次のようになります。

static ssize_t write_null(struct file *file, const char __user *buf,
                          size_t count, loff_t *ppos)
{
        return count;
}

このwrite_null()関数は、何も実行せず、バイトが正常に書き込まれたことをcount示すために返されますcount(書き込みで期待される動作/dev/null)。

まとめると、キャラクターデバイス文書メタデータ(プライマリおよびマイナーデバイス番号)を含みます。プロセスが文字デバイスファイルでI / Oを実行すると、カーネルはこのメタデータを使用して正しい文字デバイスを見つけます。ドライバーファイルへのI / O要求はカーネルによって処理されます。

答え2

通常、ノードは、すべてのグラフの頂点の一般的な名前にすることができます。ファイルシステムの文脈では、ファイルとディレクトリは自然にノードです(ファイル名はグラフの端になる可能性があります)。ファイルデータを保持するデータ構造は「i」とも呼ばれます。」。

mknod()デバイス用の特殊ファイルを生成するための呼び出しコンテキストを除いて、「i」なしでファイルを通常の「ノード」として参照することはほとんどありません。

デバイスファイルはデータを含まない特別なファイルですが、代わりにこれらのデバイスファイルへのアクセスを処理するカーネルドライバを識別する数値があります。

ドライバは、便利なタスクを実行するカーネルのソフトウェアの一部です。物理ハードウェア(シリアルポートなど)へのアクセスを提供したり/dev/ttyS0、提供しない場合があります/dev/zero

実際のデバイスはもちろん、物理ハードウェア、コネクタ、チップです。

もちろん、混乱を招くために、これらの「デバイスファイル」を単に「デバイス」と呼びます。

したがって、/dev/ttyS0注:マイナーデバイス番号が64:0の文字特殊ファイルであるファイルシステムノードは、シリアルドライバによって処理されるデバイスとして識別され、一部の物理デバイスにアクセスし、16550 UARTに似ている可能性があります。チップ。

答え3

装置は、メジャー番号とマイナー番号の組み合わせと呼ぶことができる。この組み合わせは固定されており、変更できません。この組み合わせはデバイスファイルの一部です。アプリケーションはデバイスファイルを開き、対応する番号の組み合わせを介してデバイスに接続します。

メジャー番号とマイナー番号とは異なり、デバイスファイル名は理論的にランダムに指定されます。/dev/sdaカーネルデバイス名を指すファイルを作成できますsdb

答え4

Dalton氏の回答が長かったにもかかわらず、この本はまだあなたを混乱させます。この本はあなたを誤解しました。本にこのような内容があります。しなければならない説明する:

カーネル仮想端末、パラレルポート、シリアルポートは、ストリーム抽象化でよく表現できるため、文字デバイスの例です。文字デバイスは、/dev/tty1(最初​​のカーネル仮想端末を開く)、/dev/lp0(最初のパラレルポートを開く)、/dev/ttyS0(最初のシリアルポートを開く)などのファイルシステムノードを介して開きます。

実際、それはまったく議論されてはいけません/dev/console。どちらも/dev/tty複雑すぎるため、基本的な例としては使用できません。 (M. Daltonは賢明にそれを使用していないことに注意してください。)また、文字デバイスを開くためにも使用されますが、何ですか?文字デバイスは、前述のKVT、シリアルポート、およびパラレルポートよりも複雑です。具体的には、コンソールにはファイルシステムノード名のように見えますが、それ以外の2番目の名前セットが導入されました。

追加読書

関連情報