Attribution du numéro de périphérique du pilote Linux et création automatique de nœuds de périphérique

Numéro de périphérique du pilote Linux

Pour les systèmes Linux, afin d'identifier et de gérer les périphériques, chaque périphérique est marqué d'un numéro unique. Chaque périphérique enregistré dans le noyau nécessite un numéro. Ce numéro est le numéro du périphérique. Afin de subdiviser le numéro du périphérique, il est divisé en numéro d'appareil principal et numéro d'appareil mineur.

Étant donné que la gestion des périphériques Linux est étroitement intégrée au système de fichiers, divers périphériques sont stockés dans /devle répertoire sous forme de fichiers, afin que nous puissions voir le numéro de périphérique de l'appareil en affichant les informations détaillées du fichier.

crw-rw---- 1 root uucp 4, 70 04-14 18:16 ttyS6
crw-rw---- 1 root uucp 4, 71 04-14 18:16 ttyS7
crw-rw---- 1 root tty 7, 0 08-08 18:58 vcs
crw-rw---- 1 root tty 7, 1 08-08 18:58 vcs1

On peut voir que les autorisations des fichiers de périphérique ne rwxsont , mais deviennent un périphérique de caractères dont crwle premier caractère est . cEn même temps, il y a deux autres nombres séparés par des virgules. Ces deux nombres correspondent au numéro de périphérique majeur et au numéro de périphérique mineur de l'appareil. Comme indiqué ci-dessus, 4 et 7 sont respectivement les numéros de périphérique majeurs, et 70, 71, 0 et 1 sont tous des appareils mineurs.

Le numéro d'appareil majeur est utilisé pour distinguer différents types d'appareils, tandis que le numéro d'appareil mineur est utilisé pour distinguer plusieurs appareils du même type (le numéro d'appareil majeur est utilisé pour marquer le type d'appareil et le numéro d'appareil mineur est utilisé pour distinguer des appareils individuels spécifiques dans ce type d'appareil) ).

1. Représentation du numéro d'appareil

Le numéro de périphérique majeur est utilisé pour distinguer différents types de périphériques, tandis que le numéro de périphérique mineur est utilisé pour distinguer plusieurs périphériques du même type. Le numéro de périphérique est représenté en interne dans le noyau Linux comme une valeur du type u32. Le type dev_t est finalement utilisé, comme suit (dans le code source du noyau include/linux/types.h).

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

Et u32 est défini comme unsigned int dans le code source du noyau Linux, comme suit.

typedef unsigned int __u32;
typedef __u32 u32;

Par conséquent, dev_t appartient essentiellement au type unsigned int (c'est-à-dire un type de données de 32 bits). Afin d'exprimer simultanément le numéro de périphérique majeur et le numéro de périphérique mineur, le type dev_t utilise les 12 bits de poids fort du Données de 32 bits pour représenter le numéro de périphérique principal, et les 20 bits de poids faible sont le numéro de périphérique mineur. . Par conséquent, le nombre maximum de numéros de périphérique majeurs peut être 2^12=4096 (0-4095) et le nombre maximum de numéros de périphérique mineurs peut être 2^20=1048576 (0-1048575).

                          dev_t 32 bit
-------------------------------------------------------------------------
| 31 .. MAJOR ... 20 | 19 ................. MINOR ................... 0 |
-------------------------------------------------------------------------

Lorsque le numéro de périphérique majeur est rarement utilisé, il ne peut pas dépasser 4095. Le numéro de périphérique mineur peut généralement être utilisé à volonté, et le numéro de périphérique mineur est généralement suffisant. Bien que ce soit le cas, il est toujours nécessaire d'attribuer le numéro de périphérique pilote et ne le gaspillez pas à volonté.

2. Macro d'opération du numéro de périphérique

Le numéro de périphérique majeur et le numéro de périphérique mineur sont stockés ensemble dans une variable de 32 bits. Afin de faciliter l'extraction ou le réglage des bits correspondants du numéro de périphérique majeur/mineur, certaines définitions de macro sont fournies. Ces définitions de macro seront utilisé lors de l’écriture de pilotes de périphérique, comme suit.

#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)

#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

MINORBITSReprésente un numéro de périphérique mineur de 20 bits, MINORMASKun masque utilisé pour séparer le numéro de périphérique mineur, MAJOR()utilisé pour obtenir le numéro de périphérique majeur de dev_t, MINOR()utilisé pour obtenir le numéro de périphérique mineur de dev_t, MKDEV()utilisé pour combiner le numéro de périphérique majeur et le périphérique mineur. number dans dev_t Type de numéro de périphérique.

3. Attribuer un numéro d'appareil

L'attribution du numéro d'appareil, similaire aux adresses IP, peut être attribuée de manière statique ou dynamique. L'allocation IP statique signifie que nous spécifions nous-mêmes une adresse IP, mais nous ne pouvons pas utiliser une adresse IP déjà utilisée par d'autres appareils. L'allocation IP dynamique signifie que le routeur attribue Nous fournissons une adresse IP qui n'a pas été utilisée par d'autres appareils. L'adresse IP utilisée et l'attribution du numéro de périphérique du pilote suivent également les mêmes règles. L'allocation dynamique consiste à appliquer un numéro de périphérique à partir du noyau Linux.

Lors de l'attribution statique de numéros d'appareil, veillez à ne pas utiliser des numéros d'appareil déjà utilisés par d'autres appareils. En fait, nous utilisons généralement l'allocation dynamique de numéros d'appareil.

4. Allocation statique

(1) L'attribution statique d'un numéro de périphérique est très simple. Il vous suffit de spécifier un numéro de périphérique majeur et un numéro de périphérique mineur lors de l'écriture du pilote, puis d'utiliser la macro d'opération de numéro de périphérique MKDEV() pour construire un numéro de périphérique, et enfin appelez l'interface d'enregistrement register_chrdev_region fournie par le noyau.() peut enregistrer le numéro de périphérique construit auprès du pilote, comme suit.

dev_t dev_id;
dev_id = MKDEV(200, 0);
register_chrdev_region(dev_id, 1, "DRIVER_NAME");

Notez que le numéro de périphérique majeur et le numéro de périphérique mineur déjà utilisés par d'autres périphériques ne peuvent pas être utilisés dans une allocation statique, car cela construirait le même numéro de périphérique que les autres périphériques, ce qui n'est pas autorisé.

(2) Il existe une autre façon d'attribuer statiquement des numéros de périphérique, qui consiste à fournir un seul numéro de périphérique principal. Par exemple, lors de l'enregistrement d'un périphérique à l'aide de la fonction d'enregistrement de périphérique de caractères register_chrdev, comme suit.

file_operations dev_fops;
register_chrdev(200, "DRIVER_NAME", &dev_fops);

Mais il y a un gros problème avec cela. Tous les numéros d'appareil mineurs sous un numéro d'appareil majeur seront utilisés. Par exemple, si le numéro d'appareil majeur de la LED est réglé sur 200, alors tous les numéros d'appareil mineurs dans la plage de 0 à 1048575 sera utilisé par LED. Occupation de l'appareil (c'est un gaspillage de numéros d'appareil mineurs ! Un appareil LED ne doit avoir qu'un seul numéro d'appareil majeur et un numéro d'appareil mineur). Pourquoi ? Regardez simplement le code et vous le saurez.

Recherchez la définition de la fonction register_chrdev() et constatez que la fonction __register_chrdev() est appelée et que le paramètre formel transmet notre numéro de périphérique majeur spécifié statiquement, le numéro de périphérique mineur spécifié obligatoire est 0 et le nombre d'enregistrements de numéros de périphérique est 256. .

static inline int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
{
    
    
    return __register_chrdev(major, 0, 256, name, fops);
}

En regardant la définition de la fonction __register_chrdev(), la fonction __register_chrdev_region() est appelée pour enregistrer de force 256 numéros de périphérique en fonction du numéro de périphérique majeur statique que nous avons défini, du numéro de périphérique mineur spécifié obligatoire 0 et du numéro d'enregistrement 256, comme suit.

int __register_chrdev(unsigned int major, unsigned int baseminor,
              unsigned int count, const char *name,
              const struct file_operations *fops)
{
    
    
    struct char_device_struct *cd;
    struct cdev *cdev;
    int err = -ENOMEM;

    cd = __register_chrdev_region(major, baseminor, count, name);
    if (IS_ERR(cd))
        return PTR_ERR(cd);
    ...
    err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
    ...
    return err;
}

En lisant le code ci-dessus, vous comprendrez pourquoi cette méthode d'allocation statique utilise tous les numéros de périphérique mineurs sous un numéro de périphérique majeur.

5. Allocation dynamique

L'attribution statique des numéros de périphérique nous oblige à vérifier à l'avance les propriétés des fichiers de périphérique dans le système de fichiers racine Linux pour déterminer quels numéros de périphérique ne sont pas utilisés, puis à sélectionner un numéro de périphérique à enregistrer pour notre pilote.

La meilleure façon de résoudre ce problème est d'appliquer au noyau Linux lors de l'utilisation de numéros de périphérique. Appliquez-en autant que nécessaire et le noyau Linux attribuera des numéros de périphérique utilisables à votre pilote.

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name);

dev est utilisé pour recevoir le numéro de périphérique demandé, baseminor spécifie le numéro de périphérique mineur et count spécifie le nombre de numéros de périphérique à demander.

dev_t dev_id;
alloc_chrdev_region(&dev_id, 0, 1, "DRIVER_NAME");

Après avoir demandé le numéro de périphérique, utilisez les macros d'opération de numéro de périphérique MAJOR() et MINOR() pour séparer le numéro de périphérique majeur et le numéro de périphérique mineur du numéro de périphérique à d'autres fins.

int major = MAJOR(dev_id);
int minor = MINOR(dev_id);

6. Fichier de nœud de périphérique

Dans les systèmes Linux, les applications accèdent aux périphériques (pilotes) en accédant aux fichiers de nœuds de périphérique. Comment l'opération d'accès aux fichiers de nœuds de périphérique est-elle mappée à des pilotes spécifiques ? La réponse est le numéro d'appareil que nous attribuons à l'appareil.

Puisque nous attribuons un numéro de périphérique au pilote, tant que le fichier de nœud de périphérique est mappé au numéro de périphérique, cela équivaut au mappage au pilote. C'est le rôle d'attribuer un numéro de périphérique au pilote.

7. Créez manuellement des nœuds de périphérique

Comment mapper le fichier de nœud de périphérique au numéro de périphérique correspondant ? La première méthode consiste mknodà transmettre le numéro de périphérique en tant que paramètre à mknodla commande lors de la création manuelle du fichier de nœud de périphérique à l'aide de la commande, comme suit.

mknod /dev/leddrv c 200 0

Ici, le nom du fichier du nœud de périphérique est "/dev/leddrv", le numéro de périphérique majeur est 200, et le numéro de périphérique mineur est 0. De cette façon, le fichier de nœud de périphérique est mappé au pilote associé au numéro de périphérique. Enfin, l'application peut accéder au pilote correspondant via le fichier de nœud de périphérique. .

fd = open("/dev/leddrv", O_RDWR);

8. Créez automatiquement des nœuds de périphérique

Une condition pour créer manuellement un nœud de périphérique est que nous devons connaître le numéro de périphérique spécifique et transmettre le numéro de périphérique en tant que paramètre au fichier de nœud de périphérique lors de la création du nœud de périphérique.

Cela ne pose aucun problème lorsque le numéro de périphérique est alloué statiquement, mais lorsque le numéro de périphérique est alloué dynamiquement (s'appliquant au noyau), nous ne pouvons pas connaître le numéro de périphérique spécifique à l'avance. À l'heure actuelle, le mappage des périphériques est dans un état incertain, en particulier pour les périphériques dynamiques tels que les périphériques USB. Le mappage des fichiers de nœuds de périphérique avec les périphériques réels (pilotes) est incertain.

À ce stade, le périphérique (pilote) doit créer lui-même le fichier de nœud de périphérique en utilisant le numéro de périphérique attribué lors de l'initialisation.

8.1 Comprendre udev et mdev

udev est un programme utilisateur. Sous Linux, udev peut être utilisé pour créer et supprimer des fichiers de périphérique. udev peut détecter l'état des périphériques matériels du système et créer ou supprimer des fichiers de périphérique en fonction de l'état des périphériques matériels.

modprobePar exemple , après avoir chargé avec succès le module pilote à l'aide de la commande, udev /devcréera automatiquement le fichier de nœud de périphérique correspondant dans le répertoire de rootFS rmmod. Après avoir utilisé la commande pour désinstaller le module pilote, le fichier de nœud de périphérique correspondant dans le répertoire sera automatiquement supprimé. /dev.mdev est une version simplifiée d'udev, utilisée pour une plateforme embarquée avec peu de ressources.

Si vous devez utiliser la fonction de création automatique de nœuds de périphériques, vous devez activer mdev dans le menu de configuration du noyau.

8.2 Création et suppression de classes

Le travail de création automatique de nœuds de périphérique est effectué dans la fonction de saisie du pilote. Généralement, le code lié à la création automatique de nœuds de périphérique est ajouté après la fonction cdev_add.

La première étape consiste à créer une classe à l’aide de la fonction class_create.

struct class * _class =  class_create(THIS_MODULE, "DRIVER_NAME");

Vous pouvez voir la définition de class_create dans le fichier include/linux/device.h, et vous pouvez voir qu'il s'agit d'une fonction de définition de macro, comme suit.

extern struct class * __must_check __class_create(struct module
    *owner, const char *name, struct lock_class_key *key);


#define class_create(owner, name) \
({
      
       \
    static struct lock_class_key __key; \
    __class_create(owner, name, &__key); \
})

Le propriétaire du paramètre formel est généralement THIS_MODULE, name est le nom de la classe et la valeur de retour est un pointeur vers la classe de structure, qui est la classe créée.

Lors de la désinstallation du pilote, vous devez utiliser la fonction class_destroy() pour supprimer la classe. Le paramètre cls spécifie la classe à supprimer. La fonction est définie comme suit.

void class_destroy(struct class *cls);

8.3 Créer un appareil

Dans la deuxième étape, une fois la classe créée, un appareil doit être créé sous cette classe. Pour créer un appareil, utilisez la fonction device_create.

struct device * _device = device_create(_class, NULL, dev_id, NULL, 
                                       "DRIVER_NAME"");

Vous pouvez voir la définition de device_create dans le fichier include/linux/device.h, et vous pouvez voir qu'il s'agit d'une fonction à paramètre variable, comme suit.

struct device *device_create(const struct class *class, 
                             struct device *parent,
                             dev_t devt, void *drvdata, 
                             const char *fmt, ...);

Le paramètre formel class spécifie dans quelle classe le périphérique est créé, parent est le périphérique parent, généralement NULL (c'est-à-dire qu'il n'y a pas de périphérique parent). devt est le numéro de périphérique (numéro de périphérique attribué dynamiquement), drvdata spécifie les données privées que le périphérique peut utiliser et est généralement NULL. fmt spécifie le nom du fichier du nœud de périphérique (si fmt=xxx est défini, le fichier de nœud de périphérique /dev/xxx sera généré). La valeur de retour est le périphérique créé.

Lors de la désinstallation du pilote, vous devez utiliser la fonction device_destroy() pour supprimer le périphérique. La fonction est définie comme suit. Le paramètre class est la classe de l'appareil à supprimer et le paramètre devt spécifie le numéro de l'appareil à supprimer.

void device_destroy(const struct class *class, dev_t devt);

Je suppose que tu aimes

Origine blog.csdn.net/jf_52001760/article/details/133563219
conseillé
Classement