Wie funktionieren Zeichengeräte- oder Zeichenspezialdateien?

22

Ich versuche, Charakterspezialdateien zu verstehen. Aus Wikipedia geht hervor , dass diese Dateien "eine Schnittstelle" für Geräte bieten, die Daten zeichenweise übertragen. Mein Verständnis ist, dass das System irgendwie das Zeichengerät aufruft, anstatt den Gerätetreiber direkt aufzurufen. Aber wie bietet die Datei diese Schnittstelle? Ist es eine ausführbare Datei, die den Systemaufruf übersetzt? Kann jemand erklären, was los ist.

bernie2436
quelle

Antworten:

19

Sie sind eigentlich genau das - Schnittstellen. Codiert durch eine "Major" - und eine "Minor" -Nummer bieten sie einen Hook für den Kernel.

Sie gibt es in zwei Varianten (nun, drei, aber Named Pipes sind im Moment nicht Gegenstand dieser Erklärung): Character Devices und Block Devices.

Blockgeräte sind in der Regel Speichergeräte, die in der Lage sind, Ausgaben zu puffern und Daten für den späteren Abruf zu speichern.

Zeichengeräte sind Dinge wie Audio- oder Grafikkarten oder Eingabegeräte wie Tastatur und Maus.

In jedem Fall durchsucht der Kernel beim Laden des richtigen Treibers (entweder beim Booten oder über Programme wie udev ) die verschiedenen Busse, um festzustellen, ob Geräte, die von diesem Treiber behandelt werden, tatsächlich auf dem System vorhanden sind. In diesem Fall wird ein Gerät eingerichtet, das die entsprechende Haupt- / Nebennummer abhört.

(Beispielsweise erhält der digitale Signalprozessor der ersten von Ihrem System gefundenen Audiokarte das Major / Minor-Zahlenpaar von 14/3; der zweite erhält 14,35 usw.)

Es liegt an udev, einen Eintrag in /devnamed dspals Zeichengerät mit der Bezeichnung major 14 minor 3 zu erstellen .

(In deutlich älteren Versionen oder Versionen mit minimalem Speicherbedarf von Linux wird /dev/möglicherweise nicht dynamisch geladen, sondern enthält nur statisch alle möglichen Gerätedateien.)

Wenn dann ein Userspace-Programm versucht, auf eine Datei zuzugreifen, die als 'Character Special File' mit der entsprechenden Major / Minor-Nummer markiert ist (z. B. Ihr Audio-Player, der versucht, digitales Audio zu senden /dev/dsp), weiß der Kernel, dass diese Daten benötigt werden über den Fahrer übertragen werden, an den die Haupt- / Nebennummer angehängt ist; vermutlich weiß besagter fahrer was man damit wiederum macht.

Shadur
quelle
1
1. Sind Major / Minor-Nummern also analog zu Ports?
Bernie2436
2. Wenn Programme auf eine Datei zugreifen, liest der Kernel diese speziellen Schnittstellen, um zu erfahren, ob das Programm von einem bestimmten Gerät Interrupts empfängt. Beispiel: Wenn ein Programm eine Word-Datei öffnet, liest es die Sonderdatei des Zeichengeräts, um zu wissen, dass das Programm auf Tastatureingaben reagieren soll.
Bernie2436
1) etwas . Es ist die Analogie eines armen Mannes, aber es wird reichen.
Shadur
2
2) Ihnen fehlen dort etwa drei oder vier Abstraktionsebenen. Das Programm, mit dem Sie eine Textdatei öffnen, kennt das Tastaturgerät weder und kümmert sich auch nicht darum. Die Kommunikation mit der zugrunde liegenden Hardware erfolgt entweder über den Terminalemulator (wenn Sie sich im Konsolenmodus befinden) oder über die X-Ereignisebene (wenn Sie sich im Grafikmodus befinden), von der eine auf die Tastatur und andere Laufwerke hört und entscheidet, welche , wenn überhaupt, an das Programm weiterzuleiten. Ich fasse hier ein ziemlich komplexes Mehrschichtsystem zusammen. Sie sollten sich allgemein über das X Window System informieren.
Shadur
1
Beachten Sie auch, dass es in einigen UN * X-Versionen Zeichenspezialdateien für Speichergeräte gibt. Ein Lese- oder Schreibzugriff auf die spezielle Datei wird zu einem Lese- oder Schreibzugriff auf eine Folge von Blöcken auf dem Gerät. (In neueren Versionen von FreeBSD sind dies die einzigen speziellen Dateien für Speichergeräte; es gibt keine speziellen
10

Jede Datei, jedes Gerät oder sonstige Element unterstützt 6 grundlegende Vorgänge innerhalb des VFS:

  1. Öffnen
  2. Schließen
  3. Lesen
  4. Schreiben
  5. Suchen
  6. Sagen

Darüber hinaus unterstützen Gerätedateien die E / A-Steuerung, die andere verschiedene Vorgänge ermöglicht, die von den ersten 6 nicht abgedeckt werden.

Im Fall eines speziellen Zeichens wird das Suchen und Sagen nicht implementiert, da es eine Streaming-Schnittstelle unterstützt . Das heißt, direktes Lesen oder Schreiben wie bei der Umleitung in der Shell:

echo 'foo' > /dev/some/char
sed ... < /dev/some/char
Ignacio Vazquez-Abrams
quelle
6

Minimal lauffähiges file_operationsBeispiel

Sobald Sie ein minimales Beispiel sehen, wird alles offensichtlich.

Die Schlüsselideen sind:

  • file_operations enthält die Rückrufe für jeden dateibezogenen Systemaufruf
  • mknod <path> c <major> <minor> erstellt ein Zeichengerät, das diese verwendet file_operations
  • Finden Sie für Zeichengeräte, die Gerätenummern dynamisch zuweisen (die Norm zur Vermeidung von Konflikten), die Nummer mit cat /proc/devices

character_device.ko Kernel-Modul:

#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)

Userland Testprogramm:

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 upstream mit Boilerplate, um es tatsächlich auszuführen:

Komplexere Beispiele:

Ciro Santilli ist ein Schauspieler
quelle
Das war super hilfreich, danke! Nur eine Frage, was genau macht das *off = 1;und warum ist es geplant 1?
SilverSlash
1
@SilverSlash Dieser Wert wird über mehrere readAufrufe an denselben open(Dateideskriptor übergeben. Der Fahrer kann damit machen, was er will. Die übliche Semantik besteht darin, die Anzahl der gelesenen Bytes zu enthalten. In diesem Beispiel haben wir jedoch nur eine einfachere Semantik: 0für das erste Lesen 1nach dem ersten Lesen. Versuchen Sie, es auszuführen und einen printk- oder GDB-Schritt zum Debuggen zu erstellen.
Ciro Santilli
4

"Character at a time" ist eine falsche Bezeichnung (ebenso wie die Vorstellung, dass Zeichengeräte das Suchen und Erkennen nicht unbedingt unterstützen). In der Tat, „Block zu einer Zeit“ (also streng satzorientierte, wie beispielsweise ein Bandlaufwerk *) Geräte müssen Zeichenvorrichtungen sein. Die Idee ist also, dass ein Zeichengerät unbedingt nicht suchbar sein muss - Zeichengerätetreiber definieren eine vollständige file_operationsStruktur, die frei definiert werden kann, je nachdem, ob das Gerät die Operation unterstützt oder nicht. Die Zeichengeräte, die die meisten Leute als Beispiele ansehen, sind null, urandom, TTY-Geräte, Soundkarten, Mäuse usw., die alle aufgrund der Besonderheiten dieser Geräte nicht zu finden sind, aber / dev / vcs, / dev / fb0 , und / dev / kmem sind ebenfalls Zeichengeräte und können gesucht werden.

Wie bereits erwähnt, definiert ein Zeichengerätetreiber eine file_operations-Struktur, die Funktionszeiger für alle Vorgänge enthält, die für eine Datei ausgeführt werden sollen (Suchen, Lesen, Schreiben, Ioctl usw.). Diese werden jeweils beim entsprechenden Systemaufruf einmal aufgerufen wird bei geöffneter Gerätedatei ausgeführt. Und Lesen und Schreiben kann daher mit seinen Argumenten tun, was immer es will - es kann sich weigern, ein zu großes Schreiben zu akzeptieren oder nur das zu schreiben, was passt; Es kann nur die Daten lesen, die einem Datensatz entsprechen, und nicht die gesamte angeforderte Anzahl von Bytes.

Was ist also ein Blockgerät? Blockgeräte sind grundsätzlich Festplatten. Keine andere Art von Gerät (mit Ausnahme von virtuellen Laufwerken wie Ramdisk und Loopback) ist ein Blockgerät. Sie sind in das E / A-Anforderungssystem, die Dateisystemschicht, das Puffer- / Cache-System und das virtuelle Speichersystem so integriert, wie es Zeichengeräte nicht tun , selbst wenn Sie von einem Benutzerprozess aus auf z. B. / dev / sda zugreifen . Sogar die "Raw-Geräte", die auf dieser Seite als Ausnahme erwähnt werden, sind Zeichengeräte .

* Einige UNIX-Systeme implementierten den sogenannten "Fixed-Block-Modus", mit dem die Kernel-Gruppen- und E / A-Anforderungen so aufgeteilt werden können, dass sie den konfigurierten Blockgrenzen in etwa wie bei Festplatten entsprechen - als Block Gerät. Für den "Variablen-Block-Modus" wird ein Zeichengerät benötigt, das die Blockgrenzen aus dem Anwenderprogramm beibehält, da ein einzelner Write (2) -Aufruf einen Block schreibt und ein einzelner Read (2) -Aufruf einen Block zurückgibt. Da die Modusumschaltung jetzt als ioctl und nicht als separate Gerätedatei implementiert ist, wird ein Zeichengerät verwendet. Bandlaufwerke mit variablen Datensätzen sind meistens "nicht suchbar", da das Suchen das Zählen einer Anzahl von Datensätzen anstelle einer Anzahl von Bytes umfasst und die native Suchoperation als ioctl implementiert ist.

Random832
quelle
1

Zeichengeräte können von Kernelmodulen (oder vom Kernel selbst) erstellt werden. Wenn ein Gerät erstellt wird, stellt der Ersteller Zeiger auf Funktionen bereit, die Standardaufrufe wie open, read usw. verarbeiten. Der Linux-Kernel ordnet diese Funktionen dann dem Zeichengerät zu, z. B. wenn eine Anwendung im Benutzermodus read () aufruft. Wenn eine Zeichengerätedatei bearbeitet wird, führt dies zu einem Systemaufruf, und der Kernel leitet diesen Aufruf an eine Lesefunktion weiter, die beim Erstellen des Treibers angegeben wurde. Es gibt ein Schritt- für -Schritt - Tutorial eine Zeichengerät zum Erstellen von hier können Sie ein Beispielprojekt und den Schritt durch sie schaffen einen Debugger zu verstehen , wie das Geräteobjekt wird erstellt und wenn die Handler aufgerufen werden.

Bazis
quelle