Comment fonctionnent les périphériques de caractères ou les fichiers spéciaux de caractères?

22

J'essaie de comprendre les fichiers spéciaux de caractères. D'après wikipedia , je comprends que ces fichiers "fournissent une interface" pour les appareils qui transmettent des données un caractère à la fois. Ma compréhension est que le système appelle en quelque sorte le périphérique de caractères au lieu d'appeler directement le pilote de périphérique. Mais comment le fichier fournit-il cette interface? Est-ce un exécutable qui traduit l'appel système? Quelqu'un peut-il expliquer ce qui se passe.

bernie2436
la source

Réponses:

19

Ce ne sont en fait que des interfaces. Encodés par un nombre "majeur" et "mineur", ils fournissent un crochet au noyau.

Ils existent en deux versions (enfin, trois, mais les canaux nommés sont hors de portée de cette explication pour l'instant): les périphériques de caractères et les périphériques de blocs.

Les périphériques de bloc ont tendance à être des périphériques de stockage, capables de mettre en tampon la sortie et de stocker des données pour une récupération ultérieure.

Les périphériques de caractères sont des éléments tels que des cartes audio ou graphiques, ou des périphériques d'entrée comme le clavier et la souris.

Dans chaque cas, lorsque le noyau charge le bon pilote (soit au démarrage, soit via des programmes comme udev ), il analyse les différents bus pour voir si des périphériques gérés par ce pilote sont réellement présents sur le système. Si tel est le cas, il configure un appareil qui «écoute» le numéro majeur / mineur approprié.

(Par exemple, le processeur de signal numérique de la première carte audio trouvée par votre système obtient la paire de nombres majeurs / mineurs de 14/3; la seconde obtient 14,35, etc.)

Il appartient à udev de créer une entrée dans /devnamed en dsptant que périphérique de caractère marqué majeur 14 mineur 3.

(Dans les versions beaucoup plus anciennes ou à empreinte minimale de Linux, il se /dev/peut qu'il ne soit pas chargé dynamiquement mais contienne simplement tous les fichiers de périphérique possibles de manière statique.)

Ensuite, lorsqu'un programme de l'espace utilisateur essaie d'accéder à un fichier marqué comme un `` fichier spécial de caractères '' avec le numéro majeur / mineur approprié (par exemple, votre lecteur audio tente d'envoyer de l'audio numérique à /dev/dsp), le noyau sait que ces données doivent être transmis via le conducteur auquel est attaché un numéro majeur / mineur; on peut supposer que le conducteur sait à son tour quoi en faire.

Shadur
la source
1
1. Les nombres majeurs / mineurs sont-ils donc analogues aux ports?
bernie2436
2. Ainsi, lorsque des programmes accèdent à un fichier, le noyau lit ces interfaces spéciales pour savoir si le programme doit obtenir des interruptions d'un périphérique particulier? Ex: si un programme ouvre un fichier de mots, il lit le fichier spécial de périphérique de caractères pour savoir que le programme doit répondre à la saisie au clavier?
bernie2436
1) Un peu . C'est l'analogie d'un pauvre homme mais ça fera l'affaire.
Shadur
2
2) Il vous manque environ trois ou quatre couches d'abstraction. Le programme avec lequel vous ouvrez un fichier texte ne connaît ni ne se soucie du périphérique clavier. La communication avec le matériel sous-jacent se fait soit via l'émulateur de terminal (si vous êtes en mode console), soit via la couche d'événements X (si vous êtes en mode graphique), qui écoutera le clavier et d'autres lecteurs et décidera de quoi , le cas échéant, à transmettre au programme. Je résume ici un système multicouche assez complexe; vous feriez bien de lire sur le système X Window en général.
Shadur
1
Notez également que, sur certaines versions d'UN * X, il existe des fichiers spéciaux de caractères pour les périphériques de stockage; une lecture ou une écriture dans le fichier spécial se transforme en lecture ou en écriture sur une séquence de blocs sur l'appareil. (Dans les versions récentes de FreeBSD, ce sont les seuls fichiers spéciaux pour les périphériques de stockage; il n'y a pas de fichiers spéciaux de bloc.)
10

Chaque fichier, périphérique ou autre, prend en charge 6 opérations de base dans le VFS:

  1. Ouvrir
  2. proche
  3. Lis
  4. Écrire
  5. Chercher
  6. Dire

De plus, les fichiers de périphérique prennent en charge le contrôle des E / S, ce qui permet d'autres opérations diverses non couvertes par le premier 6.

Dans le cas d'un caractère spécial, la recherche et la révélation ne sont pas implémentées car elles prennent en charge une interface de streaming . Autrement dit, lire ou écrire directement comme cela se fait avec la redirection dans le shell:

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

file_operationsExemple exécutable minimal

Une fois que vous voyez un exemple minimal, tout devient évident.

Les idées clés sont:

  • file_operations contient les rappels pour chaque appel système lié au fichier
  • mknod <path> c <major> <minor> crée un dispositif de caractères qui utilise ceux file_operations
  • pour les périphériques de caractères qui allouent dynamiquement les numéros de périphérique (la norme pour éviter les conflits), recherchez le numéro avec cat /proc/devices

character_device.ko module du noyau:

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

Programme de test Userland:

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 en amont avec passe-partout pour le faire fonctionner:

Exemples plus complexes:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
la source
C'était super utile merci! Juste une question, qu'est-ce que cela fait exactement *off = 1;et pourquoi est-il réglé sur 1?
SilverSlash
1
@SilverSlash, cette valeur est transmise sur plusieurs readappels au même open(descripteur de fichier. Le conducteur peut en faire ce qu'il veut. La sémantique habituelle est de contenir le nombre d'octets lus. Dans cet exemple cependant, nous avons juste une sémantique plus simple: 0pour la première lecture, 1après la première lecture. Essayez de l'exécuter et mettez une étape printk ou GDB à le déboguer.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
4

"Caractère à la fois" est un terme impropre (tout comme l'idée que les dispositifs de caractères ne prennent pas nécessairement en charge la recherche et la révélation). En fait, les périphériques "bloc à la fois" (c'est-à-dire strictement orientés vers l'enregistrement, comme un lecteur de bande *) doivent être des périphériques de caractères. Il en va de même pour l'idée qu'un périphérique de caractères doit nécessairement être impossible à rechercher - les pilotes de périphériques de caractères définissent une file_operationsstructure complète qui est libre de définir llseek ou non selon que le périphérique prend en charge l'opération. Les appareils de caractères que la plupart des gens considèrent comme des exemples sont null, urandom, les appareils TTY, la carte son, la souris, etc. et / dev / kmem sont également des périphériques de caractères et ils sont tous recherchables.

Comme je l'ai mentionné, un pilote de périphérique de caractères définit une structure file_operations qui a des pointeurs de fonction pour toutes les opérations que quelqu'un pourrait vouloir appeler sur un fichier - rechercher, lire, écrire, ioctl, etc. - et ceux-ci sont chacun appelés une fois lorsque l'appel système correspondant est exécuté avec ce fichier de périphérique ouvert. Et lire et écrire peuvent donc faire tout ce qu'ils veulent avec leurs arguments - ils peuvent refuser d'accepter une écriture trop grande ou seulement écrire ce qui convient; il ne peut lire que les données correspondant à un enregistrement plutôt que le nombre entier d'octets demandé.

Alors, qu'est-ce qu'un périphérique bloc, alors? Fondamentalement, les périphériques de bloc sont des unités de disque. Aucun autre type de périphérique (à l'exception des lecteurs de disques virtuels , comme le disque virtuel et le bouclage) n'est un périphérique bloc. Ils sont intégrés dans le système de demande d'E / S, la couche du système de fichiers, le système de tampon / cache et le système de mémoire virtuelle d'une manière qui ne le sont pas, même lorsque vous accédez par exemple à / dev / sda à partir d'un processus utilisateur. . Même les «périphériques bruts» que cette page mentionne comme exception sont des périphériques de caractères .

* Certains systèmes UNIX ont implémenté ce qui est maintenant appelé "mode de bloc fixe" - qui permet au groupe du noyau et de diviser les demandes d'E / S de s'adapter aux limites de bloc configurées de la même manière que pour les unités de disque - en tant que bloc dispositif. Un dispositif de caractères est nécessaire pour le "mode bloc variable", qui préserve les limites des blocs du programme utilisateur, car un seul appel en écriture (2) écrit un bloc et un seul appel en lecture (2) renvoie un bloc. Étant donné que le changement de mode est désormais implémenté sous la forme d'un ioctl plutôt que d'un fichier de périphérique distinct, un périphérique de caractères est utilisé. Les lecteurs de bande à enregistrement variable sont pour la plupart "non recherchables" car la recherche implique le comptage d'un certain nombre d'enregistrements plutôt que d'un certain nombre d'octets, et l'opération de recherche native est implémentée comme un ioctl.

Aléatoire832
la source
1

Les périphériques de caractères peuvent être créés par des modules du noyau (ou le noyau lui-même). Lorsqu'un périphérique est créé, le créateur fournit des pointeurs vers des fonctions qui implémentent des appels standard tels que open, read, etc. Le noyau Linux associe ensuite ces fonctions au périphérique de caractères, par exemple lorsqu'une application en mode utilisateur appelle read () sur un fichier de périphérique de caractères, il en résultera un appel système, puis le noyau acheminera cet appel vers une fonction de lecture spécifiée lors de la création du pilote. Il y a un tutoriel étape par étape sur la création d'un périphérique de personnage ici , vous pouvez créer un exemple de projet et l'étape à travers celui-ci à l'aide d'un débogueur pour comprendre comment l'objet périphérique est créé et quand les gestionnaires sont appelés.

bazis
la source