1. 主页 > 用户投稿

linux驱动开发入门与实战(linux驱动开发详解)

今天给各位分享关于linux驱动开发详解(linux驱动开发入门与实战)的知识,希望对你有帮助,现在开始把!

1、最近在搞一个linux项目,主要写一些应用模块,内核及其驱动模块涉及很少。在遇到一些驱动模块的问题时,临时查了一些资料,大致了解了开发驱动模块的基本步骤和常规步骤,又从网上搜集了一些相关资料,所以做了一个简单的总结,记录在这里,以备日后参考,与同事们分享。什么是linux内核驱动模块?Linux内核的整体结构已经非常庞大,包含了很多组件。

2、我们如何在内核中包含所有需要的部分?一种 *** 是将所有需要的函数编译到Linux内核中。这将导致两个问题。首先,生成的内核会非常大。

3、其次,如果我们想在现有内核中添加或删除函数,我们必须重新编译内核。有没有什么机制使得编译后的内核本身不需要包含所有的函数,而是在需要使用这些函数的时候,将相应的代码动态加载到内核中?答案是肯定的,Linux提供了这样一种机制,叫做模块。该模块具有以下特点:模块本身没有编译到内核映像中,内核映像控制着内核的大小。

4、一旦模块被加载,它就和内核的其余部分完全一样了。所以,问题来了。内核驱动模块怎么写?别急,一步步介绍吧。

5、[文章福利]边肖整理了一些我个人认为比较好的linux内核学习书籍和视频资料,分享在群文件里。如有需要,可以私信[内核],免费添加!!!(包括视频教程、电子书、实践项目和代码)

6、首先,从最简单的例子开始。我们先来看最简单的内核模块HelloWorld。

7、#include#includeMODULE_LICENSE("DualBSD/GPL");staticinthello_init(void){printk(KERN_INFO"HelloWorldentern");return0;}staticvoidhello_exit(void){printk(KERN_INFO"HelloWorldexitn");}module_init(hello_init);module_exit(hello_exit);MODULE_AUTHOR("SongBaohua");MODULE_DESCRIPTION("AsimpleHelloWorldModule");MODULE_ALIAS("asimplestmodule");这个最简单的内核模块只包含内核模块加载函数、卸载函数、双BSD/GPL权限的声明和一些描述信息。编译将产生hello.ko目标文件,该文件可以通过命令in *** od加载。/hello.ko并通过命令rmmodhello卸载。

8、加载时会输出HelloWorldenter,卸载时会输出HelloWorldexit。内核中的输出函数是kernel空之间的printk(),而不是users空之间的printf()。作为最基本的内核调试 *** ,printk()类似于printf(),但是可以定义输出级别。

9、检查系统中已加载的模块列表在Linux中,可以使用l *** od命令获取系统中加载的所有模块及其依赖关系,例如:root@imx6:~$l *** odModuleSizeUsedbyhello15680ohci1394327160ide_scsi167080ide_cd393920cdrom369601ide_cdl *** od命令实际读取并分析/proc/modules文件,上述l *** od命令结果对应的/proc/modules文件如下:root@imx6:~$cat/proc/moduleshello15680-Live0xc8859000ohci1394327160-Live0xc88c8000ieee1394944201ohci1394,Live0xc8840000ide_scsi167080-Live0xc883a000ide_cd393920-Live0xc882f000cdrom369601ide_cd,Live0xc8876000内核中已加载模块的信息也存在于/sys/module目录中。加载hello.ko后,内核将包含/sys/module/hello目录,该目录又包含一个refcnt文件和一个sections目录。运行/sys/module/hello目录中的Tree–A以获得以下目录树:root@imx6:~$tree-a.|--refcnt`--sections|--.bss|--.data|--.gnu.linkonce.this_module|--.rodata|--.rodata.str1|--.strtab|--.symtab|--.text`--__versions查看特定模块的详细信息使用modinfo命令可以获取模块的信息,包括模块作者、模块描述、模块支持的参数等。

10、root@imx6:~$modinfohello.kofilename:hello.kolicense:DualBSD/GPLauthor:SongBaohuadescription:AsimpleHelloWorldModulealias:asimplestmodulevermagic:5686gcc-2depends:模块程序的基本结构Linux内核模块主要由以下部分组成:模块加载函数(一般需要)当通过in *** od或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。模块卸载函数(一般需要)当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。模块许可证声明(必须)许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染(kerneltainted)的警告。

11、在Linux6内核中,可接受的LICENSE包括GPL、GPLvGPLandadditionalrights、DualBSD/GPL、DualMPL/GPL和Proprietary。大多数情况下,内核模块应遵循GPL兼容许可权。Linux6内核模块最常见的是以MODULE_LICENSE(DualBSD/GPL)语句声明模块采用BSD/GPL双LICENSE。

12、模块参数(可选)模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。模块导出符号(可选)内核模块可以导出符号(symbol,对应于函数或变量),这样其它模块可以使用本模块中的变量或函数。模块作者等信息声明(可选)用于申明模块作者的相关信息,一般用于备注作者姓名、邮箱等。

13、正在加载内核模块的函数模块加载函数Linux内核模块加载函数要用__initlogo声明,模块加载函数的典型形式如下:staticint__initinitialization_function(void){/*初始化代码*/}module_init(initialization_function);模块加载函数必须以module_init(函数名)的形式指定。它返回一个整数值,如果初始化成功,它应该返回0。初始化失败时,应该返回错误代码errno【在Linux内核中,错误代码errno是负值,在头文件linux/errno.h中定义,包含-ENODEV和-ENOMEM等符号值】。

14、总是返回相应的错误代码是一个非常好的习惯,因为只有这样,用户程序才能使用perror等 *** 将其转换成有意义的错误消息字符串。在Linux6内核中,可以使用request_module(constchar*fmt,…)函数加载内核模块,驱动开发者可以调用request_module(module_name);或者request_module("char-major-%d-%d",major(dev),minor(dev));这种灵活的方式加载其他内核模块。注意:在Linux中,所有标识为__init的函数都放在该节中。

15、init.text当它们连接时。此外,所有__init函数还在该节中保存一个函数指针。initcall.init在初始化过程中,内核会通过这些函数指针调用这些__init函数,初始化后释放init段(包括。

16、初始化文本,..内核模块的卸载函数Linux内核模块加载函数要用__exitlogo声明,典型的模块卸载函数如下:staticvoid__exitcleanup_function(void){/*释放代码*/}module_exit(cleanup_function);模块卸载函数在模块卸载时执行,不返回值。必须以module_exit(函数名)的形式指定。一般来说,模块卸载功能必须执行与模块加载功能相反的功能,例如:若模块加载函数注册了XXX,则模块卸载函数应该注销XXX;若模块加载函数动态申请了内存,则模块卸载函数应释放该内存;若模块加载函数申请了硬件资源(中断、DMA通道、I/O端口和I/O内存等)的占用,则模块卸载函数应释放这些硬件资源;若模块加载函数开启了硬件,则卸载函数中一般要关闭之;和__init一样,__exit也可以让相应的函数在运行后自动回收内存。

17、实际上,__init和__exit都是宏,它们的定义是:#define__init__attribute__((__section__(".init.text")))#ifdefMODULE#define__exit__attribute__((__section__(".exit.text")))#else#define__exit__attribute_used____attribute__((__section__(".exit.text")))#endif数据也可以定义为__initdata和__exitdata,它们是:#define__initdata__attribute__((__section__(".init.data")))和#define__exitdata__attribute__((__section__(".exit.data")))内核模块的参数传递我们可以用module_param(参数名,参数类型,参数读/写权限)来定义一个模块的参数。例如,下面的代码定义了一个整数参数和一个字符指针参数:staticchar*book_name="深入浅出Linux设备驱动";staticintnum=4000;module_param(num,int,S_IRUGO);module_param(book_name,charp,S_IRUGO);在加载内核模块时,用户可以以in *** ode(或modprobe)模块名参数名=参数值的形式向模块传递参数。否则,参数将使用模块中定义的默认值。

18、参数可以是byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool或invbool(布尔的逆)。模块编译时,module_param中声明的类型将与变量定义的类型进行比较,以确定是否一致。模块加载后,以该模块名称命名的目录将出现在/sys/module/目录中。

19、当参数读写权限为0时,表示该参数在sysfs文件系统中没有对应的文件节点。如果该模块中存在参数读/写权限不为0的命令行参数,则该模块的目录下也会出现参数目录,其中包含一系列以参数名称命名的文件节点。这些文件的权限值是传递给module_param()的参数读/写权限,以及此外,模块还可以有module_param_array(数组名、数组类型、数组长度、参数的读写权限)形式的参数数组。

20、从0版到10版,数组长度的变量名必须赋给数组长度,从10版开始,数组长度变量的指针必须赋给数组长度,在不需要保存实际输入数组元素个数的情况下,可以设置为NULL。当运行in *** od或modprobe命令时,应该使用逗号来分隔输入数组元素。内核模块的符号导出该模块可以使用以下宏将符号导出到内核符号表中:EXPORT_SYMBOL(符号名);EXPORT_SYMBOL_GPL(符号名);导出的符号可以被其他模块使用,使用前声明即可。

21、EXPORT_SYMBOL_GPL()仅适用于具有GPL许可证的模块。下面的代码给出了一个内核模块的例子,这个内核模块派生出整数加减函数的符号(这些派生出的符号没有实际意义,只是为了演示)。#include#includeMODULE_LICENSE("DualBSD/GPL");intadd_integar(inta,intb){returna+b;}intsub_integar(inta,intb){returna-b;}EXPORT_SYMBOL(add_integar);EXPORT_SYMBOL(sub_integar);内核模块的信息声明在Linux内核模块中,我们可以使用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE和MODULE_ALIAS来声明模块的作者、描述、版本、设备表和别名,例如:MODULE_AUTHOR(author);MODULE_DESCRIPTION(description);MODULE_VERSION(version_string);MODULE_DEVICE_TABLE(table_info);MODULE_ALIAS(alternate_name);对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE。

22、内核模块的编译 *** 我们可以为代码清单1的模板编写一个简单的Makefile:你好并使用以下命令编译HelloWorld模块:make-C/usr/src/Linux-2.6.15.5/M=/driver_study/模块如果您当前位于模块所在的目录中,以下命令等效于上述命令:make–C/usr/src/Linux-2.6.15.5M=$(pwd)模块c指定Linux内核源代码的目录,M=指定hello.c和Makefile所在的目录。编译结果如下:root@imx6:~$make-C/usr/src/linux-5/M=/driver_study/module *** ake:Enteringdirectory`/usr/src/linux-5'CC/driver_study/hello.o/driver_study/hello.c:18:35:warning:nonewlineatendoffileBuildingmodules,stageMODPOSTCC/driver_study/hello.mod.oLD/driver_study/hello.komake:Leavingdirectory`/usr/src/linux-5'可以看到,在编译过程中,有这样几个步骤:首先,进入Linux内核所在的目录,编译hello.o文件。运行MODPOST会生成一个临时的hello.mod.c文件,然后根据这个文件编译hello.mod.o,再连接hello.o和hello.mod.o文件得到模块目标文件hello.ko,最后离开Linux内核所在的目录。

23、示例分析现在让我们定义一个有两个参数的模块,观察模块加载和不加载时的输出。#include#includeMODULE_LICENSE("DualBSD/GPL");staticchar*book_name="dissectingLinuxDeviceDriver";staticintnum=4000;staticintbook_init(void){printk(KERN_INFO"bookname:%sn",book_name);printk(KERN_INFO"booknum:%dn",num);return0;}staticvoidbook_exit(void){printk(KERN_INFO"Bookmoduleexitn");}module_init(book_init);module_exit(book_exit);module_param(num,int,S_IRUGO);module_param(book_name,charp,S_IRUGO);MODULE_AUTHOR("SongBaohua,author@linuxdriver.cn");MODULE_DESCRIPTION("AsimpleModulefortestingmoduleparams");MODULE_VERSION("V0");运行in *** odbook.ko命令加载上述模块,对应的输出是模块中的默认值。您可以通过查看/var/log/messages日志文件来查看内核的输出:root@imx6:~$tail-n2/var/log/messagesJul201:03:10localhostkernel:<6>bookname:dissectingLinuxDeviceDriverJul201:03:10localhostkernel:booknum:4000当用户运行命令in *** odbook.kobook_name='goodbook'num=5000时,输出是用户传递的参数:root@imx6:~$tail-n2/var/log/messagesJul201:06:21localhostkernel:<6>bookname:GoodBookJul201:06:21localhostkernel:booknum:5000摘要本文主要阐述了Linux内核模块的概念和基本编程 *** 。

24、内核由加载/卸载函数、功能函数和一系列声明组成。它可以以参数或导出符号的形式传递给其他模块。由于Linux设备驱动是以内核模块的形式存在的,所以要编写任何类型的设备驱动都需要掌握以上内容。

25、在设备驱动的具体开发中,将驱动编译成模块也是很有工程意义的,因为如果将正在开发的驱动直接编译成内核,在开发过程中会不断修改驱动代码,需要编译内核,重启Linux,而如果编译成模块,只需要rmmod和in *** od,开发效率大大提高。

好了,linux驱动开发详解(linux驱动开发入门与实战)的知识介绍就到这里,本文到此结束!

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 203304862@qq.com

本文链接:https://jinnalai.com/n/194825.html

联系我们

在线咨询:点击这里给我发消息

微信号:

工作日:9:30-18:30,节假日休息