キャラクターデバイスまたはキャラクタースペシャルファイルはどのように機能しますか?


22

キャラクタースペシャルファイルを理解しようとしています。ウィキペディアから、これらのファイルは、一度に1文字ずつデータを送信するデバイスの「インターフェースを提供する」ことを理解しています。私の理解では、システムはデバイスドライバーを直接呼び出すのではなく、何らかの方法でキャラクターデバイスを呼び出します。しかし、ファイルはこのインターフェースをどのように提供しますか?システムコールを変換する実行可能ファイルですか?誰かが何が起こっているのか説明できますか。

回答:


19

それらは実際にはまさに-インターフェースです。「メジャー」および「マイナー」番号でエンコードされ、カーネルへのフックを提供します。

キャラクターデバイスとブロックデバイスの2つのフレーバーがあります(まあ、3つですが、名前付きパイプはこの説明の範囲外です)。

ブロックデバイスは、出力をバッファリングし、後で取得するためにデータを保存できるストレージデバイスである傾向があります。

キャラクターデバイスは、オーディオやグラフィックカードのようなもの、またはキーボードやマウスのような入力デバイスです。

いずれの場合も、カーネルが(ブート時またはudevなどのプログラムを介して)正しいドライバーをロードすると、さまざまなバスをスキャンして、そのドライバーで処理されるデバイスが実際にシステムに存在するかどうかを確認します。その場合、適切なメジャー/マイナー番号で「リッスン」するデバイスをセットアップします。

(たとえば、システムによって検出された最初のオーディオカードのデジタル信号プロセッサは14/3のメジャー/マイナーペアを取得し、2番目は14,35などを取得します。)

major 14 minor 3とマーク/devdspれたキャラクターデバイスとしてnamedにエントリを作成するのはudev です。

(Linuxの非常に古いバージョンまたは最小フットプリントバージョンでは、/dev/動的にロードされず、すべての可能なデバイスファイルが静的に含まれる場合があります。)

次に、ユーザー空間プログラムが適切なメジャー/マイナー番号で「キャラクタースペシャルファイル」としてマークされたファイルにアクセスしようとすると(たとえば、オーディオプレーヤーがデジタルオーディオを送信しようとしている場合/dev/dsp)、カーネルはこのデータが必要であることを認識しますメジャー/マイナー番号が添付されているドライバーを介して送信されます。おそらく、ドライバーはそれをどう処理するかを知っているという。


1
1.メジャー/マイナー番号はポートに似ていますか?
bernie2436

2.では、プログラムがファイルにアクセスすると、カーネルはこれらの特別なインターフェースを読み取り、プログラムが特定のデバイスから割り込みを受け取るべきかどうかを学習しますか?例:プログラムがワードファイルを開く場合、キャラクターデバイスの特殊ファイルを読み取り、プログラムがキーボード入力に応答する必要があることを確認しますか?
bernie2436

1)やや。それは貧乏人のアナロジーですが、そうでしょう。
シャドゥール

2
2)抽象化の3つまたは4つの層がそこにありません。テキストファイルを開くプログラムは、キーボードデバイスが何であるかを認識せず、気にしません。基盤となるハードウェアとの通信は、ターミナルエミュレーター(コンソールモードの場合)またはXイベントレイヤー(グラフィックスモードの場合)を介して行われ、どちらもキーボードや他のドライブをリッスンして、 、もしあれば、プログラムに渡す。ここでは、かなり複雑な多層システムをまとめています。一般的にはX Window Systemを読むとよいでしょう。
シャドゥール

1
また、UN * Xのフレーバーには、ストレージデバイス用のキャラクタースペシャルファイルがあることに注意してください。特殊ファイルの読み取りまたは書き込みは、デバイス上の一連のブロックの読み取りまたは書き込みに変わります。(FreeBSDの最近のバージョンでは、これらはストレージデバイス用の唯一の特殊ファイルです。ブロック特殊ファイルはありません。)

10

すべてのファイル、デバイスまたはその他は、VFS内で6つの基本操作をサポートします。

  1. 開いた
  2. 閉じる
  3. 読む
  4. 書きます
  5. シーク
  6. 教えて

さらに、デバイスファイルはI / Oコントロールをサポートしているため、最初の6ではカバーされていないその他のさまざまな操作が可能です。

キャラクタースペシャルの場合、seekとtellはストリーミングインターフェースをサポートしているため実装されていません。つまり、シェルでリダイレクトを使用して行われるように、直接読み取りまたは書き込みを行います。

echo 'foo' > /dev/some/char
sed ... < /dev/some/char

6

最小限の実行可能なfile_operations

最小限の例を見ると、すべてが明らかになります。

主なアイデアは次のとおりです。

  • file_operations syscallに関連する各ファイルのコールバックが含まれます
  • mknod <path> c <major> <minor> それらを使用するキャラクターデバイスを作成します file_operations
  • デバイス番号を動的に割り当てる文字デバイス(競合を避けるための標準)で、番号を見つけます cat /proc/devices

character_device.ko カーネルモジュール:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h> /* register_chrdev, unregister_chrdev */
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include <uapi/linux/stat.h> /* S_IRUSR */

#define NAME "lkmc_character_device"

MODULE_LICENSE("GPL");

static int major;

static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    size_t ret;
    char kbuf[] = {'a', 'b', 'c', 'd'};

    ret = 0;
    if (*off == 0) {
        if (copy_to_user(buf, kbuf, sizeof(kbuf))) {
            ret = -EFAULT;
        } else {
            ret = sizeof(kbuf);
            *off = 1;
        }
    }
    return ret;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = read,
};

static int myinit(void)
{
    major = register_chrdev(0, NAME, &fops);
    return 0;
}

static void myexit(void)
{
    unregister_chrdev(major, NAME);
}

module_init(myinit)
module_exit(myexit)

ユーザーランドテストプログラム:

insmod /character_device.ko
dev="lkmc_character_device"
major="$(grep "$dev" /proc/devices | cut -d ' ' -f 1)"
mknod "/dev/$dev" c "$major" 0
cat /dev/lkmc_character_device
# => abcd
rm /dev/lkmc_character_device
rmmod character_device

GitHub QEMU + Buildrootアップストリームを実際に実行するボイラープレート付き:

より複雑な例:


これはとても助かりました!ただ一つの質問、これが正確に何をするのか*off = 1;、そしてなぜそれが設定されているの1か?
SilverSlash

1
@SilverSlashその値はread、同じopen(ファイル記述子への複数の呼び出しにわたって渡されます。ドライバーは、それで何でもできます。通常のセマンティックは、読み取られたバイト数を含めることです。この例ではしかし、私たちは単純なセマンティックを持っている:0最初の読み取りのために、1後の最初の読み取り。実行して、printkまたはGDBステップをデバッグしてみてください。
Ciro Santilli新疆改造中心法轮功六四事件

4

「一度に文字」は誤った呼び名です(文字デバイスは必ずしもシークアンドテルをサポートしないという考えと同じです)。実際、「一度にブロック」(つまり、テープドライブ*などの厳密にレコード指向)デバイス、キャラクターデバイスでなければなりません。キャラクターデバイスは必然的にシーク不可能でなければならないという考えもあります-キャラクターデバイスドライバーは完全なfile_operationsは、デバイスが操作をサポートするかどうかに応じてllseekを自由に定義できる構造を定義します。ほとんどの人が例として考えているキャラクターデバイスは、null、urandom、TTYデバイス、サウンドカード、マウスなどです。これらは、それらのデバイスが何であるかの詳細のためにすべて探せませんが、/ dev / vcs、/ dev / fb0 、および/ dev / kmemもキャラクターデバイスであり、すべてシーク可能です。

前述したように、キャラクターデバイスドライバーは、誰かがファイルで呼び出したいすべての操作(シーク、読み取り、書き込み、ioctlなど)の関数ポインターを持つfile_operations構造体を定義します。このデバイスファイルを開いた状態で実行されます。したがって、読み取りと書き込みは、引数を使用して必要な処理を実行できます。大きすぎる書き込みの受け入れを拒否したり、適切な内容のみを書き込むことができます。要求されたバイト数全体ではなく、1つのレコードに対応するデータのみを読み取ることができます。

では、ブロックデバイスとは何ですか?基本的に、ブロックデバイスはディスクドライブです。他の種類のデバイス(ramdiskやループバックなどの仮想ディスクドライブを除く)は、ブロックデバイスではありません。ユーザープロセスから/ dev / sdaなどにアクセスしている場合でも、キャラクターデバイスとは異なる方法で、I / O要求システム、ファイルシステム層、バッファー/キャッシュシステム、および仮想メモリシステムに統合されます。。ページが例外として言及している「未加工デバイス」でさえ、キャラクターデバイスですです。

*一部のUNIXシステムは、現在「固定ブロックモード」と呼ばれるものを実装しています。これにより、カーネルグループと分割I / O要求は、ディスクドライブの場合とほぼ同じ方法で構成済みのブロック境界に適合します-ブロックとしてデバイス。キャラクタデバイスは、「可変ブロックモード」に必要です。このモードでは、1回のwrite(2)呼び出しが1つのブロックを書き込み、1回のread(2)呼び出しが1つのブロックを返すため、ユーザープログラムからブロック境界が保持されます。モード切り替えは、個別のデバイスファイルではなくioctlとして実装されるようになったため、キャラクターデバイスが使用されます。シークにはバイト数ではなくレコード数のカウントが含まれ、ネイティブシーク操作はioctlとして実装されるため、可変レコードテープドライブはほとんど「シーク不可」です。


1

キャラクターデバイスは、カーネルモジュール(またはカーネル自体)によって作成できます。デバイスが作成されると、作成者はopen、readなどの標準呼び出しを処理する実装する関数へのポインターを提供します。次に、Linuxカーネルはこれらの関数をキャラクターデバイスに関連付けます。たとえば、ユーザーモードアプリケーションがread()キャラクタデバイスファイルの関数である場合、syscallになり、カーネルはこの呼び出しをドライバーの作成時に指定された読み取り関数にルーティングします。キャラクターデバイスの作成に関するステップバイステップチュートリアルがここにあります。サンプルオブジェクトを作成し、デバッガーを使用してデバイスオブジェクトを作成する方法とハンドラーが呼び出されるタイミングを理解する手順を作成できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.