Was sind die Treiber:
Der Treiber kapselt die Vorgänge des zugrunde liegenden Hardwaregeräts und stellt Funktions-Sockets für die unteren Schichten bereit.
Ausrüstungskategorie:
Das Linux-System unterteilt Geräte in drei Kategorien: Zeichengeräte, Blockgeräte und Netzwerkgeräte.
Zeichengerät: Bezieht sich auf ein Gerät, das nur Byte für Byte lesen und schreiben kann. Es kann nicht zufällig bestimmte Daten im Videospeicher des Geräts lesen. Zeichengeräte sind Stream-orientierte Geräte (Linux-Treiberentwicklung). Zu den gängigen Zeichengeräten gehören Mäuse, Tastaturen, serielle Schnittstellen, Konsolen und LED-Geräte. Blockgerät: Bezieht sich auf ein Gerät, das eine bestimmte Datenmenge von jedem Ort auf dem Gerät lesen kann. Zu den Blockgeräten gehören Festplatten, Festplatten, USB-Flash-Laufwerke, SD-Karten usw. Netzwerkgerät: Ein Netzwerkgerät kann ein Hardwaregerät sein, beispielsweise eine Netzwerkkarte; es kann aber auch ein reines Softwaregerät sein, beispielsweise ein Loopback-Socket (lo). Ein Netzwerk-Socket ist für das Senden und Empfangen von Datenpaketen verantwortlich. Getriebene Erkenntnis: Schauen Sie sich zunächst ein Bild an, das den Vorgang beschreibt und hilft, den Fahrer zu verstehen.
Benutzerprofil:
Kernelstatus:
Treiber-Array: Verwalten Sie die Treiber aller Geräte, fügen Sie sie hinzu oder suchen Sie sie. Das Hinzufügen erfolgt, nachdem wir den Treiber kompiliert und in den Kernel geladen haben. Die Suche ruft den Treiber auf, und der Benutzerbereich der Anwendungsschicht verwendet die Öffnungsfunktion zum Suchen. Die Reihenfolge, in der der Treiber in das Array eingefügt wird, wird anhand der Gerätenummer ermittelt. Das heißt, die Hauptgerätenummer und die Nebengerätenummer können nicht nur verschiedene Gerätetypen und Gerätetypen unterscheiden, sondern auch laden Der Treiber wird an einer bestimmten Position im Array platziert, was im Folgenden vorgestellt wird. Die Entwicklung des Treibercodes ist nichts anderes als das Hinzufügen des Treibers (Hinzufügen der Gerätenummer, des Gerätenamens und der Gerätetreiberfunktion) und das Aufrufen des Treibers.
Hinzugefügt:
Jeder Systemaufruf entspricht einer Systemaufrufnummer, und die Systemaufrufnummer entspricht der entsprechenden Verarbeitungsfunktion im Kernel. Alle Systemaufrufe werden über den Interrupt 0x80 ausgelöst. Bei Verwendung eines Systemaufrufs wird die Systemaufrufnummer über das EAX-Register an den Kernel übergeben. Die Eingabeparameter des Systemaufrufs werden wiederum über ebx, ecx ... an den Kernel übergeben. Genau wie bei der Funktion ist der Rückgabewert von Der Systemaufruf wird in eax gespeichert und alle Parameter müssen von eax abgerufen werden. So entnehmen Sie den Zeichengerätetreiber
Funktionsprinzip des Zeichengerätetreibers In der Linux-Welt ist alles eine Datei und alle Hardwaregeräteoperationen werden als Dateioperationen auf der Anwendungsebene visualisiert. Wir wissen, dass die Anwendungsschicht, wenn sie auf ein Hardwaregerät zugreifen möchte, den der Hardware entsprechenden Treiber aufrufen muss. Es gibt so viele Treiber im Linux-Kernel. Wie kann eine Anwendung den zugrunde liegenden Treiber genau aufrufen?
Hinzugefügt:
(1) Wenn die Öffnungsfunktion eine Gerätedatei öffnet, können Sie den Typ des zu bedienenden Geräts (Zeichengerät oder Blockgerät) anhand der Informationen ermitteln, die durch die der Gerätedatei entsprechende Structinode-Struktur beschrieben werden, und eine Strukturdateistruktur wird dies tun zugeteilt werden.
(2) Anhand der in der Structinode-Struktur aufgezeichneten Gerätenummer kann der entsprechende Treiber gefunden werden. Hier nehmen wir Zeichengeräte als Beispiel. Im Linux-Betriebssystem verfügt jedes Zeichengerät über eine structcdev-Struktur. Diese Struktur beschreibt alle Informationen des Zeichengeräts. Die wichtigste davon ist der Betriebsfunktionssockel des Zeichengeräts.
(3) Nachdem die structcdev-Struktur gefunden wurde, zeichnet der Linux-Kernel die erste Adresse des Videospeicherbereichs auf, in dem sich die structcdev-Struktur im i_cdev-Mitglied der structinode-Struktur befindet, und zeichnet die in der structcdev-Struktur aufgezeichnete Funktionsoperations-Socket-Adresse auf in der Strukturdateistruktur.
(4) Wenn die Aufgabe abgeschlossen ist, gibt die VFS-Schicht einen Dateideskriptor (fd) an die Anwendung zurück. Dieses fd entspricht der Strukturdateistruktur. Dann kann die Anwendung der unteren Ebene die Strukturdatei über fd finden und dann den Funktionssocket file_operation finden, der das Zeichengerät in der Strukturdatei betreibt.
Unter diesen wurden cdev_init und cdev_add bereits in der Eingabefunktion des Treibers aufgerufen, um die Bindung des Zeichengeräts und des Funktions-Sockets der Dateioperation abzuschließen und den Zeichentreiber beim Kernel zu registrieren.
Ähnliche Videoempfehlungen
Kostenlose Lernadresse: Linux C/C++-Entwicklung (Frontend/Audio und Video/Spiele/eingebettet/Hochleistungsnetzwerk/Speicher/Infrastruktur/Sicherheit)
Sie benötigen Lernmaterialien für C/C++-Linux-Serverarchitekten und fügen qun579733396 hinzu, um sie zu erhalten (Materialien umfassen C/C++, Linux, Golang-Technologie, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, Streaming-Medien, CDNlinux-Eingabemethode, P2P, K8S, Docker, TCP/IP, Interpreter, DPDK, ffmpeg usw.), kostenlose Freigabe
Die Beziehung zwischen Zeichengeräten, Zeichengerätetreibern und User-Space-Programmen, die auf das Gerät zugreifen
Wie in der Abbildung gezeigt, wird die cdev-Struktur im Linux-Kernel zur Beschreibung von Zeichengeräten verwendet, und ihr Mitglied dev_t wird zum Definieren der Gerätenummer (unterteilt in Haupt- und Nebengerätenummern) verwendet, um die Einzigartigkeit des Zeichengeräts zu bestimmen. Definieren Sie die Socket-Funktionen, die der Zeichengerätetreiber VFS über seine Mitgliedsdateien file_operations bereitstellt, wie z. B. allgemeines open(), read(), write() usw.
Im Linux-Zeichengerätetreiber erhält die Modulladefunktion die Gerätenummer statisch oder dynamisch über register_chrdev_region() oder alloc_chrdev_region(), baut die Verbindung zwischen cdev und file_operations über cdev_init() auf und fügt dem System über cdev_add( ), um die Registrierung abzuschließen. Die Funktion zum Entladen des Moduls meldet cdev über cdev_del() ab und gibt die Gerätenummer über unregister_chrdev_region() frei.
Das User-Space-Programm, das auf das Gerät zugreift, verwendet Linux-Systemaufrufe wie open(), read(), write(), um file_operations „aufzurufen“, um die Socket-Funktion zu definieren, die der Zeichengerätetreiber für VFS bereitstellt.
Schritte zur Treiberentwicklung
Der Linux-Kernel besteht aus verschiedenen Treibertypen. Etwa 85 % des Kernel-Quellcodes besteht aus Code verschiedener Treibertypen. Im Kernel gibt es eine ganze Reihe von Treibern, die auf der Grundlage ähnlicher Treiber geändert werden können, um sie an bestimmte Boards anzupassen.
Die Schwierigkeit beim Schreiben eines Treibers liegt nicht in der spezifischen Funktionsweise der Hardware, sondern darin, das Framework des vorhandenen Treibers herauszufinden und die Hardware zu diesem Framework hinzuzufügen.
Im Allgemeinen ist der allgemeine Prozess zum Kompilieren eines Linux-Gerätetreibers wie folgt:
Im Folgenden wird ein einfacher Zeichen-Gerätetreiber-Framework-Code zum Entwickeln und Kompilieren des Treibers verwendet.
Codeentwicklung basierend auf dem Treiber-Framework
Unterer Aufrufcode
#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); }
Treiber-Framework-Code
#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");
Es wird gesagt, dass die Hauptschwierigkeit bei der Treiberentwicklung darin besteht, den Framework-Code zu verstehen und Geräte darauf hinzuzufügen und zu ändern. Werfen wir einen Blick auf die Logik dieses Frameworks.
Gesteuerter Framework-Designprozess
1. Bestimmen Sie die Hauptgerätenummer
2. Definieren Sie den Strukturtyp file_operations
3. Implementieren Sie die entsprechenden drv_open/drv_read/drv_write und andere Funktionen und füllen Sie die file_operations-Struktur aus
4. Treibereintrag implementieren: Bei der Installation des Treibers wird diese Eingabefunktion aufgerufen, um die Arbeit auszuführen:
①Teilen Sie dem Kernel die Struktur file_operations mit: Registrieren Sie den Treiber register_chrdev.
②Klasse erstellen class_create.
③Gerät erstellen device_create.
5. Export implementieren: Wenn der Treiber deinstalliert wird, wird diese Exportfunktion aufgerufen, um die Arbeit auszuführen:
①Registrieren Sie die file_operations-Struktur vom Kernel: unregister_chrdev.
②Zerstöre die Klasse class_create.
③Zerstören Sie den Geräteknoten device_destroy.
6. Andere Einrichtungen: GPL-Vertrag, Portalladung
1. Bestimmen Sie das Hauptgerät und die Variablendefinition
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. Definieren Sie die file_operations-Struktur und laden Sie sie in das Kernel-Treiber-Array
Dies ist die file_operations-Struktur im Linux-Kernel
Strukturelemente entsprechend der zugrunde liegenden aufrufenden Funktion definieren
static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, .read= pin4_read, };
3. Implementieren Sie Funktionen wie das Strukturmitglied 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. Fahrereingang
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内核源代码),也可以在猕猴桃派里面编译,但在猕猴桃派里编译,效率会很低,要特别久。
Das obige ist der detaillierte Inhalt vonDie Reihenfolge, in der zugrunde liegende Hardwaregerätetreiber in die verknüpfte Liste (Treiber) eingefügt werden.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!