Maison >Tutoriel système >Linux >Linux Intermédiaire - 'Pilote' Connaissances de bas niveau qui doivent être apprises pour contrôler le matériel
Augmenter la notoriété
Le pilote encapsule les opérations du périphérique matériel sous-jacent et fournit des interfaces de fonction à la couche supérieure.
Classification des appareils : Le système Linux divise les appareils en 3 catégories : Appareils de caractère, appareils de blocage et appareils réseau.
Donnons un exemple pour expliquer le processus global d'appel
open("/dev/pin4",O_RDWR);
et appelons pin4 sous /dev pour l'ouvrir de manière lisible et inscriptible. **== Lorsque la couche supérieure open est appelée au noyau, une interruption logicielle se produit. Le numéro d'interruption est 0X80, qui est saisi depuis l'espace utilisateur ==**system_call
(fonction du noyau) et system_call trouvera le numéro de périphérique souhaité en fonction du nom de périphérique /dev/pin4. sys_open
dans VFS, sys_open se trouvera dans la liste des pilotes, et trouvez le pilote basé sur le numéro d'appareil majeur et numéro d'appareil mineur La fonction d'ouverture dans la broche 4, Notre ouverture dans la broche 4 consiste à faire fonctionner le registre
Insérer la description de l'image ici
«
Lorsque nous écrivons un pilote, nous faisons simplement Ajouter un pilote : À quoi sert l'ajout d'un pilote ?
- Nom de l'appareil
- Numéro d'appareil
- Fonction de pilote de périphérique (registre d'exploitation pour piloter le port IO)
”
==En résumé==Si vous souhaitez ouvrir dev
ci-dessouspin4
pin, le processus est : dev
下面的pin4
引脚,过程是:用户态调用open(“/de/pin4”,O_RDWR
),对于内核来说,上层调用open函数会触发一个软中断(系统调用专用,中断号是0x80,0x80代表发生了一个系统调用),系统进入内核态,并走到system_call
,可以认为这个就是此软中断的中断服务程序入口,然后通过传递过来的系统调用号来决定调用相应的系统调用服务程序(在这里是调用VFS
中的sys_open
)。sys_open
会在内核的驱动链表里面根据设备名和设备号查找到相关的驱动函数(每一个驱动函数是一个节点
Appel en mode utilisateur ouvert
"/de/pin4",O_RDWR
), pour le noyau Pour Par exemple, appeler la fonction open depuis la couche supérieure déclenchera une interruption logicielle (spécial pour les appels système, le numéro d'interruption est 0x80, 0x80 signifie qu'un appel système a eu lieu), system_call
, vous pouvez considérer cela comme ceci L'entrée dans le programme de service d'interruption de l'interruption logicielle, puis détermine d'appeler le programme de service d'appel système correspondant via le numéro d'appel système transmis (ici, il est appelé<code style="font-size : 14px;remplissage : 2px 4px en VFS
;border-radius : 4px;margin : 0 2px;font-family : Operator Mono, Consolas, Monaco, Menlo, monospace;color: #10f5d6c">sys_open ). sys_open trouvera la fonction de pilote appropriée dans la liste des pilotes du noyau en fonction du nom du périphérique et du numéro de périphérique (<code style="font-size: 14px;padding: 2px 4px; border-radius: 4px; margin: 0 2px ; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;color: #10f5d6c">Chaque fonction du pilote est un nœud
), **==La fonction du pilote contient du code pour contrôler le port IO via des registres, puis peut contrôler le port IO pour implémenter les fonctions associées ==**.
2. Explication détaillée de chaque composant”
«
Profil utilisateur :
open,read,write,fork,pthread,socket
. Bibliothèque C (doit être incluse dans la bibliothèque standard Linux) : Il s'agit de Clibary, qui fournit une interface permettant au programme de contrôler le travail du noyau. L'appel est encapsulé et implémenté ici, et est appelé par l'application écrite. .
«
État du noyau :
🎜”🎜🎜
Lorsque les utilisateurs souhaitent utiliser un certain périphérique matériel, ils ont besoin d'un pilote de périphérique en mode noyau,puis de faire fonctionner le matériel, comme mentionné dans l'article précédentwiringPi库
, fournit une interface permettant aux utilisateurs de contrôler les périphériques matériels, s'il n'y a pas de bibliothèque câblagePi, vous devez implémenter vous-même les fonctions de la bibliothèque câblagePi, c'est-à-dire écrire vous-même le pilote de périphérique. De cette façon, lorsque nous obtenons un autre type de carte, nous pouvons également terminer le développement.
Tout sous Linux est un fichier. Tous les types de fichiers et d'appareils (tels que la souris, le clavier, l'écran, la mémoire flash, la mémoire, la carte réseau, comme le montre la figure ci-dessous) sont tous des fichiers, doncpuisqu'il s'agit de fichiers, vous pouvez utiliser les fonctions d'exploitation de fichiers pour faire fonctionner ces appareils .
J'ai une question : comment les fonctions d'exploitation de fichiers telles que l'ouverture et la lecture savent-elles à quel périphérique matériel se trouve le fichier ouvert ? ①Entrez le nom du fichier correspondant dans la fonction ouvrir pour contrôler l'appareil correspondant. ②Pass ==numéro d'appareil (numéro d'appareil majeur et numéro d'appareil mineur)==. De plus, nous devons également comprendre l'emplacement de ces pilotes et comment les implémenter Chaque périphérique matériel correspond à un pilote différent (ces pilotes sont implémentés par nous-mêmes).
La gestion des appareils Linux est étroitement intégrée au système de fichiers Divers appareils sont stockés dans le répertoire /dev sous forme de fichiers, appelés ==device files==*. Les applications peuvent ouvrir, fermer, lire et écrire ces fichiers de périphérique, et effectuer des opérations sur le périphérique, tout comme si vous utilisiez des fichiers de données ordinaires. **Afin de gérer ces appareils, le système numérote les appareils**,*Chaque numéro d'appareil est divisé en ==numéro d'appareil majeur== et ==numéro d'appareil mineur==* (comme indiqué dans la figure ci-dessous : ). *****Le numéro d'appareil principal** 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. Pour les appareils couramment utilisés, Linux a des numéros conventionnels. Par exemple, le numéro de périphérique principal d'un disque dur est 3. ****Un périphérique de caractère ou un périphérique de bloc a un numéro de périphérique majeur et un numéro de périphérique mineur. ==Le numéro d'appareil majeur et le numéro d'appareil mineur sont collectivement appelés numéro d'appareil==**.
«
Le numéro principal de l'appareil est utilisé pour représenter un pilote spécifique.
Le numéro de périphérique mineur est utilisé pour représenter chaque périphérique utilisant ce pilote.”
Par exemple, un système intégré dispose de deux indicateurs LED et les lumières LED doivent être allumées ou éteintes indépendamment. Ensuite, vous pouvez écrire un pilote de périphérique de caractère pour une lumière LED et enregistrer son numéro de périphérique majeur en tant que périphérique n° 5, et ses numéros de périphérique mineurs en tant que 1 et 2 respectivement. Ici, les numéros de périphérique secondaire représentent respectivement deux voyants LED.
==Liste liée des pilotes==
«
Gérez les pilotes pour tous les appareils, ajoutez ou recherchez
appelle le pilote et l'espace utilisateur de la couche application utilise la fonction open pour le trouverL'ajout
添加
是发生在我们编写完驱动程序,加载到内核。查找
se produit une fois que nous avons fini d'écrire le pilote et de l'avoir chargé dans le noyau.Rechercher
. L'ordre dans lequel le pilote est inséré dans la liste chaînée est récupéré par le numéro d'appareil, c'est-à-dire que le numéro d'appareil majeur et le numéro d'appareil mineur peuvent non seulement distinguer différents types d'appareils et différents types d'appareils , mais aussi charger le pilote à une certaine position dans la liste chaînée , le développement du code du pilote présenté ci-dessous n'est rien de plus que ajouter le pilote (ajouter le numéro de périphérique, le nom de l'appareil et la fonction du pilote de périphérique) et
appeler. le chauffeur.
”
Ajouté :
Principe de fonctionnement du pilote de périphérique de caractère Dans le monde Linux, tout est un fichier et toutes les opérations sur les périphériques matériels seront résumées en opérations sur les fichiers au niveau de la couche application. On sait que si la couche application veut accéder à un périphérique matériel, elle doit appeler le pilote correspondant au matériel. Il y a tellement de pilotes dans le noyau Linux. Comment une application peut-elle appeler avec précision le pilote sous-jacent ?
==Connaissances incontournables :==🎜🎜struct inode
structure Cette structure enregistre toutes les informations du fichier, telles que le type de fichier, les droits d'accès, etc. /dev
目录或者其他如/sys
de la couche application. struct file
structure au niveau de la couche VFS pour décrire le fichier ouvert. (1) Lorsque la fonction open ouvre un fichier de périphérique, vous pouvez connaître le type de périphérique à utiliser ensuite (périphérique de caractère ou périphérique de bloc) en fonction des informations décrites par la structure struct inode correspondant au fichier de périphérique, et une structure sera également allouée.
(2) Selon le numéro de périphérique enregistré dans la structure struct inode, le pilote correspondant peut être trouvé. Ici, nous prenons comme exemple les dispositifs de caractères. Dans le système d'exploitation Linux, chaque périphérique de caractères possède une structure struct cdev. Cette structure décrit toutes les informations du dispositif de caractère, dont la plus importante est l'interface de fonction d'exploitation du dispositif de caractère.
(3) Après avoir trouvé la structure struct cdev, le noyau Linux enregistrera la première adresse de l'espace mémoire où se trouve la structure struct cdev dans le membre i_cdev de la structure struct inode, et enregistrera l'adresse de l'interface d'opération de fonction enregistrée dans le Structure struct cdev. Dans le membre f_ops de la structure de fichier struct.
(4) Une fois la tâche terminée, la couche VFS renverra un descripteur de fichier (fd) à l'application. Ce fd correspond à la structure du fichier struct. Ensuite, l'application de couche supérieure peut trouver le fichier struct via fd, puis trouver l'interface de fonction file_operation pour faire fonctionner les périphériques de caractères dans le fichier struct.
Parmi eux, cdev_init et cdev_add ont été appelés dans la fonction d'entrée du pilote, complétant respectivement la liaison du périphérique de caractère et de l'interface d'opération de la fonction file_operation, et enregistrant le pilote de caractère dans le noyau.
#include #include #include #include void main() { int fd,data; fd = open("/dev/pin4",O_RDWR); if(fdprintf("open fail\n"); perror("reson:"); } else{ printf("open successful\n"); } fd=write(fd,'1',1); }
-Pilote du noyau **==Le cadre de pilote de périphérique de caractères le plus simple==** :
#include //file_operations声明 #include //module_init module_exit声明 #include //__init __exit 宏定义声明 #include //class devise声明 #include //copy_from_user 的头文件 #include //设备号 dev_t 类型声明 #include //ioremap iounmap的头文件 static struct class *pin4_class; static struct device *pin4_class_dev; static dev_t devno; //设备号,devno是用来接收创建设备号函数的返回值,销毁的时候需要传这个参数 static int major =231; //主设备号 static int minor =0; //次设备号 static char *module_name="pin4"; //模块名 //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_open\n"); //内核的打印函数和printf类似 return 0; } //led_write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) { printk("pin4_write\n"); //内核的打印函数和printf类似 return 0; } //将上面的函数赋值给一个结构体中,方便下面加载到到驱动链表中去 static struct file_operations pin4_fops = { //static防止其他文件也有同名pin4_fops //static限定这个结构体的作用,仅仅只在这个文件。 .owner = THIS_MODULE, .open = pin4_open, .write = pin4_write, }; /* 上面的代码等同于以下代码(但是在单片机keil的编译环境里面不允许以上写法): 里面的每个pin4_fops结构体成员单独赋值 static struct file_operations pin4_fops; pin4_fops.owner = THIS_MODULE; pin4_fops.open = pin4_open; pin4_fops.write = pin4_write; */ //static限定这个结构体的作用,仅仅只在这个文件。 int __init pin4_drv_init(void) //真实的驱动入口 { int ret; devno = MKDEV(major,minor); //2. 创建设备号 ret = register_chrdev(major, module_name,&pin4_fops); //3. 注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中 pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev下自动生成设备,创建一个类 pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件,先有上面那一行代码,创建一个类然后这行代码,类下面再创建一个设备。 return 0; } void __exit pin4_drv_exit(void) { device_destroy(pin4_class,devno);//先销毁设备 class_destroy(pin4_class);//再销毁类 unregister_chrdev(major, module_name); //卸载驱动 } module_init(pin4_drv_init); //入口,内核加载驱动的时候,这个宏(不是函数)会被调用,去调用pin4_drv_init这个函数 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
Créer manuellement le nom de l'appareil
sudo mknod +设备名字 +设备类型(c表示字符设备驱动) +主设备号+次设备号
b : créer un fichier spécial bloc (bufferisé). c, u : crée un fichier spécial de caractères (sans tampon). p : créez un FIFO, Pour supprimer le nom de l'appareil créé manuellement, il suffit de rm. Comme le montre l'image ci-dessous : Ouvrez un périphérique via le programme de couche supérieure. S'il n'y a pas de pilote, une erreur sera signalée lors de l'exécution. Dans le pilote du noyau, le système de couche supérieure appelle open,wirte
函数会触发sys_call
、sys_call会调用sys_open,
和sys_write
, sys_open et sys_write transmettent le numéro majeur de périphérique. et placez le périphérique dans la liste des pilotes du noyau Recherchez le pilote et exécutez l'ouverture et l'écriture à l'intérieur Pour que l'ensemble du processus se déroule sans problème, nous devons d'abord préparer le pilote (fichier du pilote de périphérique).
Les fichiers du pilote de périphérique ont un cadre fixe :
module_init(pin4_drv_init);
//入口 去调用 pin4_drv_init
Fonctionint __init pin4_drv_init(void)
//Entrée réelle du conducteurdevno = MKDEV(major,minor);
// Créer un numéro d'appareilregister_chrdev(major, module_name,&pin4_fops);
//Enregistrez le pilote et dites au noyau d'ajouter la structure préparée ci-dessus à la liste chaînée du pilote du noyaupin4_class=class_create(THIS_MODULE,"myfirstdemo");
//Générer automatiquement l'appareil sous dev by code et créer une classepin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);
//Créer un fichier d'appareil. /dev
un fichier supplémentaire que notre couche supérieure pourra ouvrirsudo mknod +设备名字 +设备类型(c表示字符设备驱动) +主设备号+次设备号
Compilation du code du module pilote (la compilation du module nécessite un code source du noyau configuré. Le suffixe du module du noyau généré après la compilation et la connexion est **.ko
. Le processus de compilation ira d'abord dans le répertoire du code source du noyau, lira le niveau supérieur Makefile, puis retournez ensuite dans le répertoire où se trouve le code source du module) : **
#include //file_operations声明 #include //module_init module_exit声明 #include //__init __exit 宏定义声明 #include //class devise声明 #include //copy_from_user 的头文件 #include //设备号 dev_t 类型声明 #include //ioremap iounmap的头文件 static struct class *pin4_class; static struct device *pin4_class_dev; static dev_t devno; //设备号 static int major =231; //主设备号 static int minor =0; //次设备号 static char *module_name="pin4"; //模块名 //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_open\n"); //内核的打印函数和printf类似 return 0; } //read函数 static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos) { printk("pin4_read\n"); //内核的打印函数和printf类似 return 0; } //led_write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) { printk("pin4_write\n"); //内核的打印函数和printf类似 return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open = pin4_open, .write = pin4_write, .read = pin4_read, }; //static限定这个结构体的作用,仅仅只在这个文件。 int __init pin4_drv_init(void) //真实的驱动入口 { int ret; devno = MKDEV(major,minor); //创建设备号 ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中 pin4_class=class_create(THIS_MODULE,"myfirstdemo");//让代码在dev下自动>生成设备 pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件 return 0; } void __exit pin4_drv_exit(void) { device_destroy(pin4_class,devno); class_destroy(pin4_class); unregister_chrdev(major, module_name); //卸载驱动 } module_init(pin4_drv_init); //入口,内核加载驱动的时候,这个宏会被调用,去调用pin4_drv_init这个函数 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
/SYSTEM/linux-rpi-4.19.y/drivers/char
将以上代码复制到一个文件中,然后下一步要做的是就是:将上面的驱动代码编译生成模块,再修改Makefile。(你放那个文件下,就改哪个文件下的Makefile)obj-m += pin4drive.o
添加到Makefile中即可。下图:Makefile文件图
.ko
文件发送给树莓派**然后回/SYSTEM/linux-rpi-4.19.y
下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
进行编译生成驱动模块。然后将生成的.ko
文件发送给树莓派:scp drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi
编译生成驱动模块会生成以下几个文件:
.o
的文件是object文件,.ko
是kernel object,与.o的区别在于其多了一些sections,比如.modinfo
。.modinfo section
是由kernel source里的modpost工具生成的, 包括MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, device ID table以及模块依赖关系等等。depmod 工具根据.modinfo section生成modules.dep, modules.*map等文件,以便modprobe更方便的加载模块。“
- Pendant le processus de compilation, nous avons suivi les étapes suivantes :
- Entrez d'abord dans le répertoire où se trouve le noyau Linux et compilez le fichier pin4drive.o
- L'exécution de MODPOST générera un fichier temporaire pin4drive.mod.c, puis compilera pin4drive.mod.o basé sur ce fichier,
- Connectez ensuite les fichiers pin4drive.o et pin4drive.mod.o pour obtenir le fichier cible du module pin4drive.ko,
- Quittez enfin le répertoire où se trouve le noyau Linux.
”
Compilez de manière croisée pin4test.c (code d'appel de la couche supérieure) et envoyez-le au Raspberry Pi. Vous pouvez voir qu'il y a deux fichiers envoyés sous le répertoire pi, comme le montre la figure ci-dessous : .ko文件
和pin4test
.
sudo insmod pin4drive.ko
加载内核驱动(相当于通过insmod调用了module_init这个宏,然后将整个结构体加载到驱动链表中) 加载完成后就可以在dev
下面看到名字为pin4
lsmod
sudo chmod 666 /dev/pin4
pin4test
En surface, il n'y a aucune sortie d'informations. En fait, il y a des informations d'impression dans le noyau, mais elles ne peuvent pas être vues par la couche supérieure. Si vous souhaitez afficher les informations imprimées par le noyau , vous pouvez utiliser la commande : dmesg |grep pin4
. Comme le montre l'image ci-dessous : cela signifie que l'appel du chauffeur a réussipin4test
表面上看没有任何信息输出,其实内核里面有打印信息只是上层看不到如果想要查看内核打印的信息可以使用指令:dmesg |grep pin4
Après avoir installé le pilote, vous pouvez utiliser la commande :
Pourquoi le module pilote généré doit-il être généré sur une machine virtuelle ? Le Raspberry Pi ne fonctionne-t-il pas ?
La génération du module pilote nécessite un environnement de compilation (code source Linux et compilation, vous devez télécharger le code source du noyau Linux qui est le même que la version du système. Il peut également être compilé sur le Raspberry Pi, mais compilé sur le). Raspberry Pi sera très inefficace et nécessitera très longtemps . Cet article parle de la compilation locale du pilote Raspberry Pi.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!