Conducteur de caractère de base dans Linux

Conducteur de caractère de base dans Linux
Nous allons passer par la manière Linux de mettre en œuvre le pilote de caractère. Nous allons d'abord essayer de comprendre ce qu'est le pilote de caractère et comment le framework Linux nous permet d'ajouter le pilote de caractère. Après cela, nous ferons l'exemple d'application d'espace utilisateur de test. Cette application de test utilise le nœud de périphérique exposé par le pilote pour écrire et lire les données de la mémoire du noyau.

Description

Laissez-nous commencer la discussion avec le pilote de personnage dans Linux. Le noyau catégorise les pilotes en trois catégories:

Moteurs de personnage - Ce sont les pilotes qui n'ont pas trop de données pour gérer. Peu d'exemples de pilotes de caractère sont le pilote à écran tactile, le pilote UART, etc. Ce sont tous les pilotes de caractère car le transfert de données se fait via le caractère par caractère.

Bloquer les conducteurs - Ce sont les pilotes qui traitent de trop de données. Le transfert de données est effectué en bloc par bloc car trop de données doivent être transférées. L'exemple de pilotes de blocs est SATA, NVME, etc.

Pilotes de réseau - Ce sont les pilotes qui fonctionnent dans le groupe de réseaux de réseaux. Ici, le transfert de données est effectué sous forme de paquets de données. Les conducteurs sans fil comme Atheros sont dans cette catégorie.

Dans cette discussion, nous nous concentrerons uniquement sur le pilote de personnage.

Par exemple, nous prendrons les opérations de lecture / écriture simples pour comprendre le pilote de personnage de base. Généralement, tout pilote de périphérique a ces deux opérations minimales. L'opération supplémentaire pourrait être ouverte, proche, ioctl, etc. Dans notre exemple, notre pilote a la mémoire dans l'espace du noyau. Cette mémoire est allouée par le pilote de périphérique et peut être considérée comme la mémoire de l'appareil car aucun composant matériel n'est impliqué. Le pilote crée l'interface de l'appareil dans le répertoire / dev peut être utilisé par les programmes d'espace utilisateur pour accéder au pilote et effectuer les opérations prises en charge par le pilote. Pour le programme Userspace, ces opérations sont comme toutes les autres opérations de fichiers. Le programme d'espace utilisateur doit ouvrir le fichier de périphérique pour obtenir l'instance de l'appareil. Si l'utilisateur souhaite effectuer l'opération de lecture, l'appel du système de lecture peut être utilisé pour le faire. De même, si l'utilisateur souhaite effectuer l'opération d'écriture, l'appel du système d'écriture peut être utilisé pour réaliser l'opération d'écriture.

Conducteur de personnage

Considérons pour implémenter le pilote de caractère avec les opérations de données de lecture / écriture.

Nous commençons par prendre l'instance des données de l'appareil. Dans notre cas, c'est "struct cdrv_device_data".

Si nous voyons les champs de cette structure, nous avons CDEV, le tampon de périphérique, la taille du tampon, l'instance de classe et l'objet de périphérique. Ce sont les champs minimaux où nous devons implémenter le pilote de caractère. Cela dépend de l'implémentateur des champs supplémentaires qu'il veut ajouter pour améliorer le fonctionnement du conducteur. Ici, nous essayons d'atteindre le fonctionnement minimum.

Ensuite, nous devons créer l'objet de la structure de données de l'appareil. Nous utilisons l'instruction pour allouer la mémoire de manière statique.

struct cdrv_device_data char_device [cdrv_max_minors];

Cette mémoire peut également être allouée dynamiquement avec «Kmalloc». Gardons la mise en œuvre aussi simple que possible.

Nous devons prendre la mise en œuvre des fonctions de lecture et d'écriture. Le prototype de ces deux fonctions est défini par le cadre du pilote de périphérique de Linux. La mise en œuvre de ces fonctions doit être définie par l'utilisateur. Dans notre cas, nous avons examiné ce qui suit:

Lire: L'opération pour obtenir les données de la mémoire du pilote à l'espace utilisateur.

STATIC SSIZE_T CDRV_READ (fichier struct * Fichier, char __User * user_buffer, size_t size, loff_t * offset);

Écrire: l'opération pour stocker les données à la mémoire du pilote à partir de l'espace utilisateur.

STATIC SSIZE_T CDRV_WRITE (fichier struct * Fichier, const char __User * user_buffer, size_t size, loff_t * offset);

Les deux opérations, lecture et écriture, doivent être enregistrées dans le cadre de Struct File_Operations CDRV_FOPS. Ceux-ci sont enregistrés dans le cadre du pilote de périphérique Linux dans le pilote init_cdrv () du pilote. À l'intérieur de la fonction init_cdrv (), toutes les tâches de configuration sont effectuées. Peu de tâches sont les suivantes:

  • Créer une classe
  • Créer une instance de périphérique
  • Allouer un numéro majeur et mineur pour le nœud de l'appareil

L'exemple de code complet pour le pilote de périphérique de caractères de base est le suivant:

#inclure
#inclure
#inclure
#inclure
#inclure
#inclure
#inclure
#define cdrv_major 42
#define CDRV_MAX_MINORS 1
#define buf_len 256
#define CDRV_DEVICE_NAME "CDRV_DEV"
#define cdrv_class_name "cdrv_class"
struct cdrv_device_data
struct cdev cdev;
tampon char [buf_len];
size_t size;
Classe struct * cdrv_class;
Struct Device * CDRV_DEV;
;
struct cdrv_device_data char_device [cdrv_max_minors];
statique ssize_t cdrv_write (fichier struct * fichier, const char __User * user_buffer,
size_t size, loff_t * offset)

struct cdrv_device_data * cdrv_data = & char_device [0];
ssize_t len ​​= min (cdrv_data-> taille - * offset, taille);
printk ("écriture: bytes =% d \ n", taille);
if (Len Buffer + * Offset, User_Buffer, Len))
retour -efault;
* offset + = len;
retour Len;

statique ssize_t cdrv_read (fichier struct * fichier, char __User * user_buffer,
size_t size, loff_t * offset)

struct cdrv_device_data * cdrv_data = & char_device [0];
ssize_t len ​​= min (cdrv_data-> taille - * offset, taille);
if (Len Buffer + * Offset, len))
retour -efault;
* offset + = len;
printk ("lire: bytes =% d \ n", taille);
retour Len;

statique int cdrv_open (struct inode * inode, fichier struct * fichier)
printk (kern_info "cdrv: périphérique open \ n");
retour 0;

statique int cdrv_release (struct inode * inode, fichier struct * fichier)
printk (kern_info "cdrv: périphérique fermé \ n");
retour 0;

const struct file_opérations cdrv_fops =
.propriétaire = this_module,
.Open = CDRV_OPEN,
.lire = cdrv_read,
.write = cdrv_write,
.libération = cdrv_release,
;
int init_cdrv (void)

int count, ret_val;
printk ("init le pilote de caractère de base… start \ n");
ret_val = registre_chrdev_region (mkdev (cdrv_major, 0), CDRV_MAX_MINORS,
"cdrv_device_driver");
if (ret_val != 0)
printk ("registre_chrdev_region (): échoué avec le code d'erreur:% d \ n", ret_val);
return ret_val;

pour (count = 0; count < CDRV_MAX_MINORS; count++)
cdev_init (& char_device [count].cdev, & cdrv_fops);
cdev_add (& char_device [count].cdev, mkdev (cdrv_major, count), 1);
char_device [count].cdrv_class = class_create (this_module, cdrv_class_name);
if (is_err (char_device [count].cdrv_class))
printk (kern_alert "CDRV: la classe de périphérique de registre a échoué \ n");
return ptr_err (char_device [count].cdrv_class);

char_device [count].size = buf_len;
printk (kern_info "classe de périphérique CDRV enregistrée avec succès \ n");
char_device [count].cdrv_dev = device_create (char_device [count].cdrv_class, null, mkdev (cdrv_major, count), null, cdrv_device_name);

retour 0;

void Cleanup_cdrv (void)

int count;
pour (count = 0; count < CDRV_MAX_MINORS; count++)
Device_destroy (char_device [count].cdrv_class et char_device [count].cdrv_dev);
class_destroy (char_device [count].cdrv_class);
cdev_del (& char_device [count].cdev);

unregister_chrdev_region (mkdev (cdrv_major, 0), cdrv_max_minors);
printk ("sortant du pilote de caractère de base… \ n");

module_init (init_cdrv);
module_exit (cleanup_cdrv);
Module_license ("gpl");
Module_author ("Sushil Rathore");
Module_description ("Exemple de pilote de caractères");
Module_version ("1.0 ");

Nous créons un échantillon de MakeFile pour compiler le pilote de base et l'application de test. Notre code de pilote est présent dans CRDV.C et le code d'application de test est présent dans CDRV_App.c.

OBJ-M + = CDRV.o
tous:
Make -c / lib / modules / $ (shell uname -r) / build / m = $ (pwd) modules
$ (Cc) cdrv_app.c -o cdrv_app
faire le ménage:
faire -c / lib / modules / $ (shell uname -r) / build / m = $ (pwd) propre
RM CDRV_APP
~

Une fois l'émission réalisée au Makefile, nous devrions obtenir les journaux suivants. Nous obtenons également le CDRV.ko et exécutable (cdrv_app) pour notre application de test:

root @ haxv-srathore-2: / home / cienauser / kernel_articles # faire
faire -c / lib / modules / 4.15.0-197-Generic / build / m = / home / cienauser / kernel_articles modules
Make [1]: Entrer le répertoire '/ usr / src / linux-headers-4.15.0-197-Générique '
Cc [m] / home / cienauser / kernel_articles / cdrv.o
Modules de construction, étape 2.
Modules modpost 1
CC / Home / Cienauser / Kernel_articles / CDRV.mod.o
Ld [m] / home / cienauser / kernel_articles / cdrv.ko
Make [1]: Laisser le répertoire '/ usr / src / linux-headers-4.15.0-197-Générique '
cc cdrv_app.c -o cdrv_app

Voici l'exemple de code de l'application de test. Ce code implémente l'application de test qui ouvre le fichier de périphérique créé par le pilote CDRV et y écrit les «données de test». Ensuite, il lit les données du pilote et les imprime après avoir lu les données à imprimer sous forme de «données de test».

#inclure
#inclure
#define device_file "/ dev / cdrv_dev"
char * data = "Test data";
char lecture_buff [256];
int main()

int fd;
int rc;
fd = open (device_file, o_wronly, 0644);
if (fd<0)

perror ("Fichier d'ouverture: \ n");
retour -1;

rc = write (fd, data, strlen (data) +1);
if (rc<0)

perror ("Fichier d'écriture: \ n");
retour -1;

printf ("bytes écrit =% d, data =% s \ n", rc, data);
Close (FD);
fd = open (device_file, o_rdonly);
if (fd<0)

perror ("Fichier d'ouverture: \ n");
retour -1;

rc = read (fd, read_buff, strlen (data) +1);
if (rc<0)

perror ("Fichier de lecture: \ n");
retour -1;

printf ("read bytes =% d, data =% s \ n", rc, read_buff);
Close (FD);
retour 0;

Une fois que nous avons toutes les choses en place, nous pouvons utiliser la commande suivante pour insérer le pilote de caractère de base au noyau Linux:

root @ haxv-srathore-2: / home / cienauser / kernel_articles # insmod cdrv.ko
root @ haxv-srathore-2: / home / cienauser / kernel_articles #

Après avoir inséré le module, nous obtenons les messages suivants avec DMESG et obtenons le fichier de périphérique créé dans / dev / dev / cdrv_dev:

root @ haxv-srathore-2: / home / cienauser / kernel_articles # dmesg
[160.015595] CDRV: Chargement du noyau de module hors arbre.
[160.015688] CDRV: Vérification du module Échec: Signature et / ou clé requise manquante - grain de dynamisation
[160.016173] init le pilote de caractère de base… Démarrez
[160.016225] classe de périphérique CDRV enregistrée avec succès
root @ haxv-srathore-2: / home / cienauser / kernel_articles #

Maintenant, exécutez l'application de test avec la commande suivante dans le shell Linux. Le message final imprime les données de lecture du pilote, ce qui est exactement la même que ce que nous avons écrit dans l'opération d'écriture:

root @ haxv-srathore-2: / home / cienauser / kernel_articles # ./ cdrv_app
octets écrits = 10, données = données de test
lire les octets = 10, données = données de test
root @ haxv-srathore-2: / home / cienauser / kernel_articles #

Nous avons quelques impressions supplémentaires dans le chemin d'écriture et de lecture qui peut être vu à l'aide de la commande DMESG. Lorsque nous émettez la commande DMESG, nous obtenons la sortie suivante:

root @ haxv-srathore-2: / home / cienauser / kernel_articles # dmesg
[160.015595] CDRV: Chargement du noyau de module hors arbre.
[160.015688] CDRV: Vérification du module Échec: Signature et / ou clé requise manquante - grain de dynamisation
[160.016173] init le pilote de caractère de base… Démarrez
[160.016225] classe de périphérique CDRV enregistrée avec succès
[228.533614] CDRV: Appareil ouvert
[228.533620] Écriture: octets = 10
[228.533771] CDRV: dispositif fermé
[228.533776] CDRV: Appareil ouvert
[228.533779] Lire: octets = 10
[228.533792] CDRV: dispositif fermé
root @ haxv-srathore-2: / home / cienauser / kernel_articles #

Conclusion

Nous avons parcouru le pilote de caractère de base qui implémente les opérations de base de l'écriture et de la lecture. Nous avons également discuté de l'échantillon MakeFile pour compiler le module avec l'application de test. L'application de test a été écrite et discutée pour effectuer les opérations d'écriture et de lecture de l'espace utilisateur. Nous avons également démontré la compilation et l'exécution du module et de l'application de test avec des journaux. L'application de test écrit quelques octets de données de test, puis les lit en arrière. L'utilisateur peut comparer les deux données pour confirmer le bon fonctionnement du pilote et de l'application de test.