私はこの本を読みました:https://lwn.net/Kernel/LDD3/。ここで、著者は装置ファイルを文字装置、ブロック装置、ネットワーク装置の3種類に区分する。第1章6ページで、私は次の事実を見つけました。
キャラクターデバイス
文字(char)デバイスは、バイトストリーム(ファイルなど)でアクセスできるデバイスです。文字ドライバはこの動作を実装します。これらのドライバは通常、少なくとも、および
open
システムコールを実装します。テキストコンソール()とシリアルポート(および同様のポート)は、ストリーム抽象化でよく表現できるため、文字デバイスの例です。 Charデバイスは、などのファイルシステムノードを介してアクセスされます。close
read
write
/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要求はカーネルによって処理されます。