linux设备驱动程序——将驱动程序编译进内核

更新时间:2023-05-23 20:00:35 阅读: 评论:0

我的一家-主持人陈晨

linux设备驱动程序——将驱动程序编译进内核
2023年5月23日发(作者:泗礁)

linux设备驱动程序——将驱动程序编译进内核

linux驱动程序——将驱动程序编译进内核

模块的加载

通常来说,在驱动模块的开发阶段,⼀般是将模块编译成.ko⽂件,再使⽤

sudo insmod

或者

depmod -a

modprobe module

将模块加载到内核,相对⽽⾔,modprobe要⽐insmod更加智能,它会检查并⾃动处理模块的依赖,⽽insmod出现依赖问题时仅仅是告诉你

安装失败,⾃⼰想办法吧。

将模块编译进内核

这⼀章节我们并不关注模块的运⾏时加载,我们要讨论的是将模块编译进内核。

在学习内核的Makefile规则的时候就可以知道,将驱动程序编译成模块时,只需要使⽤:

obj-m += module.o

指定相应的源代码(源代码为module.c)即可,所以很多朋友就简单地得出结论:如果要将模块编译进内核,只要执⾏下⾯的的指令就可以

了:

obj-y += module.o

事实上,这样是⾏不通的,要明⽩怎么将驱动程序编译进内核,我们还是得先了解linux源码的编译规则。

关于linux源码的编译规则和部分细节可以查看我的另⼀篇博客

本篇博客的所有实验基于平台,开发板,内核版本为

armbeagle bone4.14.79

编译平台

注:在以下的讨论中,⽬标主机和本机指加载运⾏驱动程序的机器,是开发的对象。⽽开发机指只负责编译的机器,⼀般指机。

PC

本机编译

在对驱动程序进⾏编译时,⼀般会有两种不同的做法:

直接在⽬标主机上编译

在其他平台上构建交叉编译环境,⼀般是在PC机上编译出可在⽬标板上运⾏的驱动程序

直接在⽬标主机上编译是⽐较⽅便的做法,本机编译本机运⾏。

通常,本机系统中⼀般不会⾃带linux内核源码的头⽂件,我们需要做的就是在系统中安装头⽂件:

sudo apt-get install linux-headers-$(uname -r)

$(uname -r)获取当前主机运⾏的linux版本号。

有了头⽂件,那么源代码从哪⾥来呢?答案是并不需要源代码,或者说是并不需要C⽂件形式的源代码,⽽是直接引⽤当前运⾏的镜像,在

编译时,将/boot/vmlinuz-$(version)镜像当成库⽂件进⾏链接即可。

值得注意的是,/boot/vmlinuz-$(version)linux启动时读取的镜像,但是在本机中进⾏驱动程序编译的时候并不会影响到这个镜像,换句话

说,即使是指定了obj-y,驱动程序也不会编译到/boot/vmlinuz-$(version)镜像中,⾃然达不到将驱动编译进内核的效果。

交叉编译

本机(⽬标机)编译是⽐较⽅便的,但是⽆法改变⽣成的镜像,当然也可以将源码下载到本机(⽬标机)中进⾏编译,就可以⽣成相应的linux

像。

但是⼀般情况下,在嵌⼊式开发中,不论是⽹络、内存还是执⾏速率,⽬标主机的性能⼀般不会太⾼,如果需要编译完整的源码时,⽤户会

更倾向于在PC端构建编译环境以获取更好的编译性能。

选择在开发机上编译⽽不是本机编译时,需要注意的⼀点就是:通常嵌⼊式开发都是基于armmips等嵌⼊式架构,⽽PC常⽤X86架构,在

编译时就不能使⽤开发机上的gcc编译器,因为开发机上编译器是针对开发平台(X86),⽽⾮运⾏平台(armmips),所以需要使⽤交叉编译

⼯具链,同时在编译时指定运⾏的主机平台。

指令是这样的:

make arch=arm CROSS_COMPILE=$COMPILE_PATH/$COMPILE_TOOL

也可以在makefile中给相应的archCROSS_COMPILE变量赋值,直接执⾏make指令即可。

显然,这种交叉编译⽅式是对linux内核源码的完整编译,主要⽣成这⼀些⽬标⽂件:

⽣成linux的可启动镜像,通常是zImage或者vmlinuz,这是⼀个可boot执⾏的压缩⽂件

伴随着的还有镜像对应的map⽂件,这个⽂件对应镜像中的编译符号以及符号的地址信息

未编译进内核的模块,也就是在配置时被选为M的选项

linux内核头⽂件等等

在上⽂中有提到,⽬标主机中,linux的启动镜像放置在/boot⽬录下,所以如果我们需要替换linux的镜像,需要替换/boot⽬录下的以下两个

⽂件:

linux的可启动镜像,也就是⽣成的zImage或者vmlinuz

.map⽂件

在主机中,模块⼀般被放置在/lib/modules⽬录中,如果交叉编译出的版本与本机中模块版本不⼀致,将⽆法识别,所以编译出的模块也需

要替换。

驱动程序编译进内核

根据上⽂,可以得出的结论是:在试图将驱动程序编译进内核时,我们需要编译完整的linux内核源码以⽣成相应的镜像⽂件,然后将其替换

到⽬标主机的/boot⽬录下即可。

那么,怎样将驱动的源码C⽂件编译进内核呢?

这个问题得从makefile的执⾏流程说起:

make的执⾏

⾸先,如果你有基本的linux内核编译经验,就知道在编译linux源码前,需要进⾏config(配置),以决定哪些部分编译进内核、哪些部分编译

成模块,

通常使⽤make menuconfig,不同的config⽅式通常只是选择界⾯的不同,其中稍微特殊的make oldconfig则是沿⽤之前的配置。

在配置完成之后将⽣成⼀个.config⽂件,makefile根据.config⽂件选择性地进⼊⼦⽬录中执⾏编译⼯作。

流程基本如上所说,但是我们需要知道更多的细节:

make menuconfig执⾏的原理是什么?

顶层makefile是怎样执⾏⼦⽬录中的编译⼯作的?

答案是这样的:

make menuconfig肯定是读取某个配置⽂件来陈列出所有的配置选项,递归地进⼊到⼦⽬录中,发现⼏乎每个⼦⽬录中都有⼀个名为

Kconfig的⽂件,这个⽂件负责配置驱动在配置菜单中的显⽰以及配置⾏为,且Kconfig遵循某种语法,make menuconfig就是读取这些

⽂件来显⽰配置项。

递归地进⼊到每个⼦⽬录中,发现其中都有⼀个makefile中,可以想到,makefile递归地进⼊⼦⽬录,然后通过调⽤⼦⽬录中的

makefile来执⾏各级⼦⽬录的编译,最后统⼀链接。

整个linux内核的编译都是采⽤⼀种分布式的思想,需要添加⼀个驱动到模块中,我们需要做的事情就是:

1. 将驱动源⽂件放在内核对应⽬录中,⼀般的驱动⽂件放在drivers⽬录下,字符设备放在drivers/char中,块设备就放在drivers/blok中,

⽂件的位置遵循这个规律,如果是单个的字符设备源⽂件,就直接放在drivers/char⽬录下,如果内容较多⾜以构成⼀个模块,则新建

⼀个⽂件夹。

2. 如果是新建⽂件夹,因为是分布式编译,需要在⽂件夹下添加⼀个Makefile⽂件和Kconfig⽂件并修改成指定格式,如果是单个⽂件直

接添加,则直接修改当前⽬录下的MakefileKconfig⽂件将其添加进去即可。

3. 如果是新建⽂件夹,需要修改上级⽬录的MakefileKconfig,以将⽂件夹添加到整个源码编译树中。

4. 执⾏make menuconfig,执⾏make

5. 将⽣成的新镜像以及相应boot⽂件拷贝到⽬标主机中,测试。

beagle bone的启动⽂件包括:vmlinuz,编译出的模块⽂件为modules

Kconfig的语法简述

上⽂中提到,在添加源码时,⼀般会需要⼀个Kconfig⽂件,这样就可以在make menuconfig时对其进⾏配置选择,在对⼀个源⽂件进⾏描述

时,遵循相应的语法。

在这⾥介绍⼀些常⽤的语法选项:

source

source:相当于C语⾔中的include,表⽰包含并引⽤其他Kconfig⽂件

config

新建⼀个条⽬,⽤法:

source drivers/xxx/Kconfig

config TEST

bool "item name"

depends on NET

lect NET

help

just for test

config是最常⽤的关键词了,它负责新建⼀个条⽬,对应linux中的编译模块,条⽬前带有选项。

config TEST:

config后⾯跟的标识会被当成名称写⼊到.config⽂件中,⽐如:当此项被选择为[y],即编译进内核时,最后会在.config⽂件中添加这样⼀个

条⽬:

CONFIG_TEST=y

CONFIG_TEST变量被传递给makefile进⾏编译⼯作。

bool "item name"

其中bool表⽰选项⽀持的种类,bool表⽰两种,编译进内核或者是忽略,还有另⼀种选项就是tristate,它更常⽤,表⽰⽀持三种配置选项:

编译进内核、编译成可加载模块、忽略。⽽item name就是显⽰在menu中的名称。

depends on:

表⽰当前模块需要依赖另⼀个选项,如果另⼀个选项没有没选择编译,当前条⽬选项不会出现在窗⼝中

lect

同样是依赖相关的选项,表⽰当前选项需要另外其他选项的⽀持,如果选择了当前选项,那么需要⽀持的那些选项就会被强制选择编译。

help

允许添加⼀些提⽰信息

menu/menuend

⽤法:

menu "Test option"

...

endmenu

这⼀对关键词创建⼀个选项⽬录,该选项⽬录不能被配置,选项⽬录中可以包含多个选项

menuconfig

相当于menu+config,此选项创建⼀个选项⽬录,⽽且当前选项⽬录可配置。

编译⽰例

梳理了整个添加的流程,接下来就以⼀个具体的⽰例来进⾏详细的说明。

背景如下:

⽬标开发板为开源平台beagle bone,基于4.14.79内核版本,arm平台

需要添加的源代码为字符设备驱动,名为cdev_test.c,新建⼀个⽬录cdev_test

字符设备驱动实现的功能:在/dev⽬录下⽣成⼀个basic_demo⽂件,⽤来检测是否成功将源代码编译进内核

将驱动编译进内核

放置⽬录

鉴于是字符设备,所以将源⽂件⽬录放置在$KERNEL_ROOT/drivers/char/下⾯。

如果是块设备,就会被放置在block下⾯,但是这并不是绝对的,类似USB为字符设备,但是独⽴了⼀个⽂件出来。

放置后⽬标⽂件位置为:$KERNEL_ROOT/drivers/char/cdev_test

Kconfig⽂件

$KERNEL_ROOT/drivers/char/cdev_test⽬录下创建⼀个Kconfig⽂件,并修改⽂件如下:

menu "cdev test dir"

config CDEV_TEST

bool "cdev test support"

default n

help

just for test ,hehe

endmenu

根据上⽂中对Kconfig⽂件的语法描述,可以看出,这个Kconfig⽂件的作⽤就是:

1. menuconfig的菜单中,在Device Driver(对应drivers⽬录) ---> Character devices(对应char⽬录)菜单下创建⼀个名为"cdev test dir"

菜单选项,

执⾏效果是这样的

2. "cdev test dir"的菜单选项下创建⼀个"cdev test support"的条⽬,这个条⽬的选项只有两个,[*]表⽰编译进内核和[]表⽰不进⾏编译,

默认选择n,不进⾏编译。

执⾏效果是这样的

3. 选择help可以查看相应的提⽰信息。

在上⽂中还提到,Kconfig分布式地存在于⼦⽬录下,同时需要注意的是,在编译时,配置⼯具并⾮⽆差别地进⼊每个⼦⽬录,收集所有的

Kconfig信息,⽽是遵循⼀定的规则递归进⼊。

那么,既然是新建的⽬录,怎么让编译器知道要进⼊到这个⼦⽬录下呢?答案是,在上级⽬录的Kconfig中包含当前路径下的Kconfig⽂件。

打开char⽬录下的Kconfig⽂件,并且在⽂件的靠后位置添加:

source "drivers/char/xillybus/Kconfig"

就把新的Kconfig⽂件包含到系统中检索⽬录中了,那么drivers/char/⼜是怎么被检索到的呢?

就是在driversKconfig中添加drivers/char/⽬录下的Kconfig索引,以此类推。

Makefile⽂件

$KERNEL_ROOT/drivers/char/cdev_test⽬录下创建⼀个Makefile⽂件,并且编译Makefile⽂件如下:

obj-$(CONFIG_CDEV_TEST) += cdev_test.o

表⽰当前⼦⽬录下Makefile的作⽤就是将cdev_test.c源⽂件编译成cdev_test.o⽬标⽂件。

值得注意的是,这⾥的编译选项中使⽤的是:

obj-$(CONFIG_CDEV_TEST)

⽽⾮

obj-y

如果确定要将驱动程序编译进内核永远不变,那么可以直接写死,使⽤obj-y,如果需要进⾏灵活的定制,还是需要选择第⼀种做法。

CONFIG_CDEV_TEST是怎么被配置的呢?在上⽂提到的Kconfig⽂件编写时,有这么⼀⾏:

config CDEV_TEST

...

Kconfig被添加到配置菜单中,且被选中编译进内核时,就会在$KERNEL_ROOT/.config⽂件中添加⼀个变量:

CONFIG_CDEV_TEST=y

⾃动添加CONFIG_前缀,⽽名称CDEV_TEST则是由Kconfig指定,看到这⾥,我想你应该明⽩了这是怎么回事了。

是不是这样就已经将当前⼦⽬录添加到内核编译树中了呢?其实并没有,就像Kconfig⼀样,Makefile也是分布式存在于整个源码树中,顶层

makefile根据配置递归地进⼊到⼦⽬录中,调⽤⼦⽬录中的Makefile进⾏编译。

同样地,需要修改drivers/char/⽬录下的Makefile⽂件,添加⼀⾏:

obj-$(CONFIG_CDEV_TEST) += cdev_test/

在编译时,如果CONFIG_CDEV_TEST变量为ycdev_test/Makefile就会被调⽤。

make menuconfig中选中

⽣成配置的部分完成,就需要在menuconfig菜单中进⾏配置,执⾏:

make menuconfig

进⼊⽬录选项Device Driver --> Character devices--->cdev test dir.

然后按'y'选中模块cdev test support

保存退出,然后执⾏编译:

make

拷贝到⽬标主机

vmlinuz(zImage)拷贝到⽬标主机的/boot⽬录下。

在编译⽣成的modules拷贝到⽬标主机的/lib/modules⽬录下。

需要注意的是:启动⽂件也好,模块也好,在⽬标板上很可能⽂件名为诸如vmlinuz-$version,会包含版本信息,需要将⽂件名修改成⼀

致,不然⽆法启动。对于模块⽽⾔,就是相应模块⽆法加载。

验证

最后⼀步就是验证⾃⼰的驱动程序是否被编译进内核,如果被编译进内核,驱动程序中的module_init()程序将被系统调⽤,完成⼀些开发者

指定的操作。

这⼀部分的验证操作就是各显⾝⼿了。

好了,关于linux将驱动程序编译进内核的讨论就到此为⽌啦,如果朋友们对于这个有什么疑问或者发现有⽂章中有什么错误,欢迎留⾔

原创博客,转载请注明出处!

祝各位早⽇实现项⽬丛中过,bug不沾⾝.

农业生产工作总结-丞相祠堂何处寻

linux设备驱动程序——将驱动程序编译进内核

本文发布于:2023-05-23 20:00:34,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/zhishi/a/1684843235175982.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:linux设备驱动程序——将驱动程序编译进内核.doc

本文 PDF 下载地址:linux设备驱动程序——将驱动程序编译进内核.pdf

标签:insmod
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 实用文体写作网旗下知识大全大全栏目是一个全百科类宝库! 优秀范文|法律文书|专利查询|