一般来讲,在驱动模块的开发阶段,通常是将模块编译成.ko文件,再使用html
sudo insmod module.ko
或者linux
depmod -a modprobe module
将模块加载到内核,相对而言,modprobe要比insmod更加智能,它会检查并自动处理模块的依赖,而insmod出现依赖问题时仅仅是告诉你安装失败,本身想办法吧。网络
这一章节咱们并不关注模块的运行时加载,咱们要讨论的是将模块编译进内核。架构
在学习内核的Makefile规则的时候就能够知道,将驱动程序编译成模块时,只须要使用:分布式
obj-m += module.o
指定相应的源代码(源代码为module.c)便可,因此不少朋友就简单地得出结论:若是要将模块编译进内核,只要执行下面的的指令就能够了:工具
obj-y += module.o
事实上,这样是行不通的,要明白怎么将驱动程序编译进内核,咱们仍是得先了解linux源码的编译规则。性能
关于linux源码的编译规则和部分细节能够查看个人另外一篇博客linux内核Makefile概览
本篇博客的全部实验基于arm平台,beagle bone开发板,内核版本为4.14.79学习
注:在如下的讨论中,目标主机和本机指加载运行驱动程序的机器,是开发的对象。而开发机指只负责编译的机器,通常指PC机。测试
在对驱动程序进行编译时,通常会有两种不一样的作法:code
直接在目标主机上编译是比较方便的作法,本机编译本机运行。
一般,本机系统中通常不会自带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端构建编译环境以获取更好的编译性能。
选择在开发机上编译而不是本机编译时,须要注意的一点就是:一般嵌入式开发都是基于arm、mips等嵌入式架构,而PC经常使用X86架构,在编译时就不能使用开发机上的gcc编译器,由于开发机上编译器是针对开发平台(X86),而非运行平台(arm、mips),因此须要使用交叉编译工具链,同时在编译时指定运行的主机平台。
指令是这样的:
make arch=arm CROSS_COMPILE=$COMPILE_PATH/$COMPILE_TOOL
也能够在makefile中给相应的arch和CROSS_COMPILE变量赋值,直接执行make指令便可。
显然,这种交叉编译方式是对linux内核源码的完整编译,主要生成这一些目标文件:
在上文中有提到,目标主机中,linux的启动镜像放置在/boot目录下,因此若是咱们须要替换linux的镜像,须要替换/boot目录下的如下两个文件:
在主机中,模块通常被放置在/lib/modules目录中,若是交叉编译出的版本与本机中模块版本不一致,将没法识别,因此编译出的模块也须要替换。
根据上文,能够得出的结论是:在试图将驱动程序编译进内核时,咱们须要编译完整的linux内核源码以生成相应的镜像文件,而后将其替换到目标主机的/boot目录下便可。
那么,怎样将驱动的源码C文件编译进内核呢?
这个问题得从makefile的执行流程提及:
首先,若是你有基本的linux内核编译经验,就知道在编译linux源码前,须要进行config(配置),以决定哪些部分编译进内核、哪些部分编译成模块,
一般使用make menuconfig,不一样的config方式一般只是选择界面的不一样,其中稍微特殊的make oldconfig则是沿用以前的配置。
在配置完成以后将生成一个.config文件,makefile根据.config文件选择性地进入子目录中执行编译工做。
流程基本如上所说,可是咱们须要知道更多的细节:
答案是这样的:
整个linux内核的编译都是采用一种分布式的思想,须要添加一个驱动到模块中,咱们须要作的事情就是:
将驱动源文件放在内核对应目录中,通常的驱动文件放在drivers目录下,字符设备放在drivers/char中,块设备就放在drivers/blok中,文件的位置遵循这个规律,若是是单个的字符设备源文件,就直接放在drivers/char目录下,若是内容较多足以构成一个模块,则新建一个文件夹。
若是是新建文件夹,须要修改上级目录的Makefile和Kconfig,以将文件夹添加到整个源码编译树中。
执行make menuconfig,执行make
将生成的新镜像以及相应boot文件拷贝到目标主机中,测试。
beagle bone的启动文件包括:vmlinuz、System.map,编译出的模块文件为modules
上文中提到,在添加源码时,通常会须要一个Kconfig文件,这样就能够在make menuconfig时对其进行配置选择,在对一个源文件进行描述时,遵循相应的语法。
在这里介绍一些经常使用的语法选项:
source:至关于C语言中的include,表示包含并引用其余Kconfig文件
新建一个条目,用法:
source drivers/xxx/Kconfig config TEST bool "item name" depends on NET select 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:*
表示当前模块须要依赖另外一个选项,若是另外一个选项没有没选择编译,当前条目选项不会出如今窗口中
select:*
一样是依赖相关的选项,表示当前选项须要另外其余选项的支持,若是选择了当前选项,那么须要支持的那些选项就会被强制选择编译。
help:
容许添加一些提示信息
**
用法:
menu "Test option" ... endmenu
这一对关键词建立一个选项目录,该选项目录不能被配置,选项目录中能够包含多个选项
至关于menu+config,此选项建立一个选项目录,并且当前选项目录可配置。
梳理了整个添加的流程,接下来就以一个具体的示例来进行详细的说明。
背景以下:
鉴因而字符设备,因此将源文件目录放置在$KERNEL_ROOT/drivers/char/下面。
若是是块设备,就会被放置在block下面,可是这并非绝对的,相似USB为字符设备,可是独立了一个文件出来。
放置后目标文件位置为:$KERNEL_ROOT/drivers/char/cdev_test
在$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文件的做用就是:
在上文中还提到,Kconfig分布式地存在于子目录下,同时须要注意的是,在编译时,配置工具并不是无差异地进入每一个子目录,收集全部的Kconfig信息,而是遵循必定的规则递归进入。
那么,既然是新建的目录,怎么让编译器知道要进入到这个子目录下呢?答案是,在上级目录的Kconfig中包含当前路径下的Kconfig文件。
打开char目录下的Kconfig文件,而且在文件的靠后位置添加:
source "drivers/char/xillybus/Kconfig"
就把新的Kconfig文件包含到系统中检索目录中了,那么drivers/char/又是怎么被检索到的呢?
就是在drivers的Kconfig中添加drivers/char/目录下的Kconfig索引,以此类推。
在$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变量为y,cdev_test/Makefile就会被调用。
生成配置的部分完成,就须要在menuconfig菜单中进行配置,执行:
make menuconfig
进入目录选项Device Driver --> Character devices--->cdev test dir.
而后按'y'选中模块cdev test support
保存退出,而后执行编译:
make
将vmlinuz(zImage)、System.map拷贝到目标主机的/boot目录下。
在编译生成的modules拷贝到目标主机的/lib/modules目录下。
须要注意的是:启动文件也好,模块也好,在目标板上极可能文件名为诸如vmlinuz-$version,会包含版本信息,须要将文件名修改为一致,否则没法启动。对于模块而言,就是相应模块没法加载。
最后一步就是验证本身的驱动程序是否被编译进内核,若是被编译进内核,驱动程序中的module_init()程序将被系统调用,完成一些开发者指定的操做。
这一部分的验证操做就是各显身手了。
***
好了,关于linux将驱动程序编译进内核的讨论就到此为止啦,若是朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言
原创博客,转载请注明出处!
祝各位早日实现项目丛中过,bug不沾身.