Quels sont les pilotes :
Le pilote encapsule les opérations du périphérique matériel sous-jacent et fournit des sockets de fonction aux couches inférieures.
Catégorie d'équipement :
Le système Linux divise les appareils en trois catégories : les appareils de caractère, les appareils de bloc et les appareils réseau.
Périphérique de caractère : fait référence à un appareil qui ne peut lire et écrire qu'octet par octet. Il ne peut pas lire de manière aléatoire certaines données dans la mémoire vidéo de l'appareil. Les données doivent être lues dans l'ordre. Les périphériques de caractères sont des périphériques orientés fluxDéveloppement de pilotes Linux Les périphériques de caractères courants incluent les souris, les claviers, les ports série, les consoles et les périphériques LED. Appareil bloc : fait référence à un appareil capable de lire une certaine quantité de données à partir de n'importe quel emplacement de l'appareil. Les périphériques bloqués incluent les disques durs, les disques, les clés USB, les cartes SD, etc. Périphérique réseau : un périphérique réseau peut être un périphérique matériel, tel qu'une carte réseau ; mais il peut également s'agir d'un périphérique purement logiciel, tel qu'une prise de bouclage (lo). Une prise réseau est responsable de l'envoi et de la réception des paquets de données. Cognition pilotée :
Regardez d’abord une image qui décrit le processus et aide à comprendre le conducteur.
Profil utilisateur :
État du noyau :
Tableau de pilotes : gérez les pilotes de tous les périphériques, ajoutez-les ou recherchez-les. L'ajout se produit après avoir compilé le pilote et l'avoir chargé dans le noyau. La recherche appelle le pilote et l'espace utilisateur de la couche application utilise la fonction open pour rechercher. L'ordre dans lequel le pilote est inséré dans la matrice est récupéré par le numéro de périphérique, c'est-à-dire que le numéro de périphérique majeur et le numéro de périphérique mineur peuvent non seulement distinguer différents types de périphériques et différents types de périphériques, mais peuvent également être chargés. le pilote dans une certaine position dans le tableau, qui est présenté ci-dessous. Le développement du code du pilote n'est rien de plus que l'ajout du pilote (ajout du numéro de périphérique, du nom du périphérique et de la fonction du pilote de périphérique) et de l'appel du pilote.
Ajouté :
Chaque appel système correspond à un numéro d'appel système, et le numéro d'appel système correspond à la fonction de traitement correspondante dans le noyau. Tous les appels système sont déclenchés via l'interruption 0x80. Lors de l'utilisation d'un appel système, le numéro d'appel système est transmis au noyau via le registre eax. Les paramètres d'entrée de l'appel système sont transmis tour à tour au noyau via ebx, ecx... Tout comme la fonction, la valeur de retour de. l'appel système est stocké dans eax, et tous les paramètres doivent être obtenus auprès d'eax. Comment supprimer le pilote de périphérique de caractère
.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 visualisées comme des 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 ?
Ajouté :
(1) Lorsque la fonction open ouvre un fichier de périphérique, vous pouvez connaître le type de périphérique à utiliser (périphérique de caractère ou périphérique de bloc) en fonction des informations décrites par la structure structinode correspondant au fichier de périphérique, et une structure de fichier struct sera être attribué.
(2) Selon le numéro de périphérique enregistré sur la structure structinode, 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 structcdev. Cette structure décrit toutes les informations du périphérique de caractère, dont la plus importante est la prise de fonction opérationnelle du périphérique de caractère.
(3) Après avoir trouvé la structure structcdev, le noyau Linux enregistrera la première adresse de l'espace mémoire vidéo où se trouve la structure structcdev dans le membre i_cdev de la structure structinode, et enregistrera l'adresse du socket d'opération de fonction enregistrée dans la structure structcdev dans la structure structfile dans le membre f_ops.
(4) Une fois la tâche terminée, la couche VFS renverra un descripteur de fichier (fd) à l'application. Ce fd correspond à la structure structfile. Ensuite, l'application de couche inférieure peut trouver le fichier struct via fd, puis trouver la fonction socket file_operation qui exploite le périphérique de caractères dans le fichier struct.
Parmi eux, cdev_init et cdev_add ont déjà été appelés dans la fonction d'entrée du pilote, complétant respectivement la liaison du périphérique de caractère et du socket d'opération de la fonction file_operation, et enregistrant le pilote de caractère dans le noyau.
Recommandations vidéo associées
Adresse d'apprentissage gratuite : Développement Linux C/C++ (front-end/audio et vidéo/jeu/embarqué/réseau haute performance/stockage/infrastructure/sécurité)
Vous avez besoin du matériel d'apprentissage de l'architecte de serveur Linux C/C++ et ajoutez qun579733396 pour les obtenir (les matériaux incluent C/C++, Linux, la technologie golang, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, méthode de saisie CDNlinux, P2P, K8S, Docker, TCP/IP, interpréteur, DPDK, ffmpeg, etc.), partage gratuit
La relation entre les périphériques de caractères, les pilotes de périphériques de caractères et les programmes de l'espace utilisateur qui accèdent au périphérique
Comme le montre la figure, la structure cdev est utilisée dans le noyau Linux pour décrire les périphériques de caractères, et son membre dev_t est utilisé pour définir le numéro de périphérique (divisé en numéros de périphérique majeurs et mineurs) afin de déterminer le caractère unique du périphérique de caractères. Définissez les fonctions de socket fournies par le pilote de périphérique de caractère à VFS via ses file_operations membres, telles que common open(), read(), write(), etc.
Dans le pilote de périphérique de caractères Linux, la fonction de chargement de module obtient le numéro de périphérique de manière statique ou dynamique via register_chrdev_region() ou alloc_chrdev_region(), établit la connexion entre cdev et file_operations via cdev_init() et ajoute un cdev au système via cdev_add( ) pour finaliser l'inscription. La fonction de déchargement du module déconnecte cdev via cdev_del() et libère le numéro de périphérique via unregister_chrdev_region().
Le programme de l'espace utilisateur qui accède au périphérique utilise des appels système Linux, tels que open(), read(), write(), pour "appeler" file_operations afin de définir la fonction socket fournie par le pilote de périphérique de caractère à VFS.
Étapes de développement du pilote
Le noyau Linux est composé de différents types de pilotes. Environ 85 % du code source du noyau est le code de différents types de pilotes. Il existe une gamme complète de pilotes dans le noyau, et ils peuvent être modifiés en fonction de pilotes similaires pour correspondre à des cartes spécifiques.
La difficulté d'écrire un pilote n'est pas le fonctionnement spécifique du matériel, mais la compréhension du cadre du pilote existant et l'ajout du matériel à ce cadre.
D'une manière générale, le processus général de compilation d'un pilote de périphérique Linux est le suivant :
Ce qui suit utilise un code-cadre de pilote de périphérique de caractères simple pour développer et compiler le pilote.
Développement de code basé sur un framework de pilotes
Code d'appel inférieur
#include #include #include #include void main() { int fd,data; fd = open("/dev/pin4",O_RDWR); if(fd<0){ printf("open failn"); perror("reson:"); } else{ printf("open successn"); } fd=write(fd,'1',1); }
Code du cadre du pilote
#include //file_operations声明 #include //module_initmodule_exit声明 #include //__init__exit 宏定义声明 #include //classdevise声明 #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_openn");//内核的打印函数和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_writen"); return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, }; 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);//入口,内核加载该驱动(insmod)的时候,这个宏被使用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
On dit que la principale difficulté dans le développement de pilotes est de comprendre le code du framework et d'y ajouter et de modifier des périphériques. Jetons un coup d'œil à la logique de ce framework.
Processus de conception de cadre piloté
1. Déterminez le numéro de l'appareil principal
2. Définissez le type de structure file_operations
3. Implémentez les fonctions drv_open/drv_read/drv_write correspondantes et remplissez la structure file_operations
4. Implémenter la saisie du pilote : Lors de l'installation du pilote, cette fonction de saisie sera appelée pour effectuer le travail :
①Indiquez au noyau la structure file_operations : enregistrez le pilote register_chrdev.
②Créer une classe class_create.
③Créer un appareil device_create.
5. Implémenter l'exportation : lorsque le pilote est désinstallé, cette fonction d'exportation sera appelée pour effectuer le travail :
①Désenregistrer la structure file_operations du noyau : unregister_chrdev.
②Détruisez la classe class_create.
③Détruisez le nœud de périphérique device_destroy.
6. Autres établissements : contrat GPL, chargement du portail
1. Déterminez l'appareil principal et la définition de la variable
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"; //模块名
2. Définissez la structure file_operations et chargez-la dans le tableau de pilotes du noyau
Il s'agit de la structure file_operations dans le noyau Linux
Définir les membres de la structure en fonction de la fonction d'appel sous-jacente
static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, .read= pin4_read, };
3. Implémentez des fonctions telles que le membre de structure pin4_read
static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("pin4_readn"); return 0; } //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_openn");//内核的打印函数和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_writen"); return 0; }
4. Entrée conducteur
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; }
其中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev手动生成设备,除此之外还可以自动生成设备,在dev目录下sudomknod+设备名子+设备类型(c表示字符设备驱动)+主设备号+次设备号。
5、出口
void __exit pin4_drv_exit(void) { device_destroy(pin4_class, devno); class_destroy(pin4_class); unregister_chrdev(major, module_name);//卸载驱动 }
6、GPI合同,入口加载,出口加载
module_init(pin4_drv_init);//入口,内核加载该驱动(insmod)的时候,这个宏被使用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
驱动模块代码编译和测试
编译阶段
驱动模块代码编译(模块的编译须要配置过的内核源码,编译、连接后生成的内核模块后缀为.ko,编译过程首先会到内核源码目录下,读取顶楼的Makefile文件,之后再返回模块源码所在目录。)
#include //file_operations声明 #include //module_initmodule_exit声明 #include //__init__exit 宏定义声明 #include //classdevise声明 #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"; //模块名 static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("pin4_readn"); return 0; } //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_openn");//内核的打印函数和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_writen"); return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, .read= pin4_read, }; 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自动生成设备 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);//入口,内核加载该驱动(insmod)的时候,这个宏被使>用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
将该驱动代码拷贝到linux-rpi-4.14.y/drivers/char目录下文件中(也可选择设备目录下其它文件)
更改该文件夹下Makefile(驱动代码放在那个目录,就更改该目录下的Makefile),将前面的代码编译生成模块,文件内容如右图所示:(-y表示编译进内核,-m表示生成驱动模块,CONFIG_表示是按照config生成的),所以只须要将obj-m+=pin4drive.o添加到Makefile中即可。
回到linux-rpi-4.14.y/编译驱动文件
使用指令:ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-KERNEL=kernel7makemodules进行编译生成驱动模块。
编译生成驱动模块会生成以下几个文件:
.o的文件是object文件,.ko是kernelobject,与.o的区别在于其多了一些sections,例如.modinfo。.modinfosection是由kernelsource里的modpost工具生成的,包括MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_LICENSE,deviceIDtable以及模块依赖关系等等。depmod工具按照.modinfosection生成modules.dep,modules.*map等文件,便于modprobe更便捷的加载模块。
编译过程中,经历了这样的步骤:先步入Linux内核所在的目录,并编译出pin4drive.o文件,运行MODPOST会生成临时的pin4drive.mod.c文件linux 驱动 开发,而后依据此文件编译出pin4drive.mod.o,然后联接pin4drive.o和pin4drive.mod.o文件得到模块目标文件pin4drive.ko,最后离开Linux内核所在的目录。
将生成的.ko文件发送给猕猴桃派:[email protected]:/home/pi
将pin4test.c(下层调用代码)进行交叉编译后发送给猕猴桃派,就可以看见pi目录下存在发送过来的.ko文件和pin4test这两个文件,
加载内核驱动
sudo insmod pin4drive.ko
加载内核驱动(相当于通过insmod调用了module_init这个宏,之后将整个结构体加载到驱动数组中)加载完成后就可以在dev下边见到名子为pin4的设备驱动(这个和驱动代码上面staticchar*module_name="pin4";//模块名这行代码有关),设备号也和代码上面相关。
lsmod查看系统的驱动模块,执行下层代码,赋于权限
查看内核复印的信息,
dmesg |grep pin4
如右图所示:表示驱动调用成功
在装完驱动后可以使用指令:sudormmod+驱动名(不须要写ko)将驱动卸载。
调用流程:
我们下层空间的open去查找dev下的驱动(文件名),文件名背后包含了驱动的主设备号和次设备号,此时用户open触发一个系统调用linux是什么系统,系统调用经过vfs(虚拟文件系统),vfs按照文件名背后的设备号去调用sys_open去判定,找到内核中驱动数组的驱动位置,再去调用驱动上面自己的dev_open函数
为何生成驱动模块须要在虚拟机上生成?猕猴桃派不行吗?
生成驱动模块须要编译环境(linux源码而且编译,须要下载和系统版本相同的Linux内核源代码),也可以在猕猴桃派里面编译,但在猕猴桃派里编译,效率会很低,要特别久。
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!