第1个linux驱动___编译空壳驱动程序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第1个linux驱动___编译空壳驱动程序相关的知识,希望对你有一定的参考价值。

在上一篇博文中我们已经了解到整个系统中各个层次之间是如何协调如何工作的,应用层发出的命令经由内核下达到驱动层,从而达到操作硬件层的目的。


我们先回顾上上篇博文最后成型的“空壳驱动程序”first_drv.c中的代码:

#include <linux/module.h>
#include <linux/init.h>

static int __init first_drv_init(void)
{   
    printk(KERN_INFO"hello world!\n");
    return 0;
}

static int __exit first_drv_exit(void)
{   
    printk(KERN_INFO"goodbye world...\n");
    return 0;
}

module_init(first_drv_init);
module_exit(first_drv_exit);


在之前的博文中我说过,这个驱动程序还存在一个不足之处,我们在下一篇博文再去优化。现在先来对这个驱动程序进行编译吧,怎么编译呢,Source Insight可没有提供这样的一个编译按钮,怎么办呢?


我们之前是通过CuteFTP远程登陆到linux虚拟机中,将/work/my_drivers/first_drv/1th/first_drv.c拖拽到D:\work\my_drivers\first_drv\1th\目录下,然后用windows上的Source Insight打开first_drv.c,把上述代码写到first_drv.c中,但是接下来如何编译代码?


在进行单片机或嵌入式开发时,我们往往采用“交叉编译”的方法来进行程序的编译,如在keil平台上为51单片机编写出led.c程序,我们只需要点击keil的编译按钮,这个编译器软件就会自动运行编译程序对led.c进行编译,生成二进制文件led.bin后,就可以烧录到单片机中去运行,像这种在windows环境中运行的编译程序能够编译出在另一个平台(如单片机)中运行的程序,这个过程就叫做“交叉编译”。


我们的目标平台51单片机中运行一个编译器来编译led.c显然不太现实,那么对嵌入式开发而言也是一样,我们通常采用在虚拟机中使用交叉编译工具(如arm-linux-gcc)来针对arm环境编译出bin文件,再将bin文件烧录到arm开发板中去运行。


我们可以在ubuntu命令行中使用交叉编译工具链中的arm-linux-gcc等命令来对已经写好的程序进行编译,但为了方便操作,我们通常使用Makefile文件来承担编译等工作。


你最好对Makefile的一些基本语法有所了解,可以自己去网上查找资料学习,切记不要深入,Makefile的语法可以讲一本书,但我们目前只需要一些最基础的语法就可以了。


通过CuteFTP将first_drv.c拖拽到ubuntu的/work/my_drivers/first_drv/1th目录下,在SecureCRT终端(通过SSH2协议远程登陆到ubuntu)中,我们进入到/work/my_drivers/first_drv/1th/


然后执行:vi Makefile


切换到文本模式,输入以下内容:


KERN_DIR = /work/kernel/linux-2.6.22.6


all:

    make -C $(KERN_DIR) M=`pwd` modules 


obj-m += first_drv.o


注意,缩进的行都是缩进了一个TAB键,与四个空格同宽,但不能敲四个空格代替,必须敲一个TAB键。

在你对Makefile的语法稍作了解以后,我们来逐行进行分析:


第一行:KERN_DIR = /work/kernel/linux-2.6.22.6


其实其实计算机语言之间都是相通的,KERN_DIR就是一个变量,这个变量的值初始化为:/work/kernel/linux-2.6.22.6


那么/work/kernel/linux-2.6.22.6这个路径有什么含义呢,我们使用Makefile是为了便于编译驱动程序,那么为什么Makefile里第一句就得提到这个路径呢?这路径有什么特殊的吗?


其实,这个路径是我们的内核源码树所在的路径,在我们首先在www.kernel.org(linux内核下载官网)下载2.6.22.6版本的内核(其实下载哪个版本无所谓),解压后可以在目录下看到arch、block、...、usr这些众多目录,linux内核的源码文件都分布在这些目录下。


现在你相信了在ubuntu的/work/kernel/linux-2.6.22.6目录下放着linux内核的众多源码文件,

在目录下编译内核后生成的uImage也就是我们烧写到arm开发板上的uImage。


也就是说,编译出的能在开发板上运行的uImage时所使用的内核源码,与我们Mkefile中使用的/work/kernel/linux-2.6.22.6这个路径下的内核源码是同一个东西。


其实,不仅是编译uImage,我们编译驱动程序first_drv.c也要用到这个内核源码中的文件,只有当我们在编译uImage和编译驱动程序时,都使用同一个内核源码,那么uImage和驱动程序才能匹配。如果我们编译驱动程序first_drv.c使用了这个版本的内核源码,而编译uImage使用了另一个版本的内核源码,那么我们的驱动模块就无法被注册到内核中,导致该驱动模块无法在开发板的内核中工作。


因此可以猜到,我们一定会指引Makefile去/work/kernel/linux-2.6.22.6目录下的内核源码来对驱动程序first_drv.c进行编译。现在,我们继续分析:


all:

    make -C $(KERN_DIR) M=`pwd` modules

    

all表示目标,而all后面的冒号之后本应该跟上依赖,但是这里我们省去了。

由于这是Makefile的第一个目标,我们只要在/work/my_drivers/first_drv/1th目录下执行make,就相当于调用了Makefile中的make -C $(KERN_DIR) M=`pwd` modules 

两个反引号加上pwd(即`pwd`)表示:pwd不是变量,而是一个命令,而linux环境中pwd用于打印出当前所在目录,加上前面我们对KERN_DIR的分析,这一长串命令就可以进行如下替换:


make -C $(KERN_DIR) M=`pwd` modules 

make -C /work/kernel/linux-2.6.22.6 M=/work/my_drivers/first_drv/1th modules 


对比绿色部分可以发现M= `pwd`已经变成了M=/work/my_drivers/first_drv/1th


我们是在/work/my_drivers/first_drv/1th目录下执行了一个make命令,所以这就是我们当前所在的目录。


不要被这一长串五颜六色的命令吓倒了,我们逐个地来分析。


第一个命令make和最后一个参数modules应该放在一起,就是make modules,这表示我们是要使用这一行命令编译出一个模块。


如何根据什么东西来编译呢,-C /lib/modules/2.6.31-23-generic/build,表示进入/lib/modules/2.6.31-23-generic/build目录下去读取内核源码的Makefile,利用内核源码的Makefile中的规则来对我们的驱动程序first_drv.c进行编译。


而绿色部分:M=/work/my_drivers/first_drv/1th表示编译完成后返回到/work/my_drivers/first_drv/1th目录下,继续执行Makefile中剩下的部分。


而实际编译过程中,first_drv.c可以被编译为很多格式的文件,你到底是要生成哪一种格式文件呢?我们是要把c文件编译成一个可以被安装到内核上去的驱动模块,我们写的Makefile中最后一行的obj-m += first_drv.o的意思就是把根据first_drv.o文件编译出一个模块(first_drv.ko)。


但是对于我们所写的这空壳驱动程序first_drv.c来说,它的任务是打印出hello world,这显然不适合运行在开发板上,我们希望可以在ubuntu命令行中看到驱动程序打印出hello world,那么请你思考一下,ubuntu是linux的发行版,也就是一个linux操作系统,它也有自己的内核,该内核也有一个确定的版本号,那么如果我们要让驱动程序能够在ubuntu中运行并且打印出hello world,那么编译驱动程序程序时所使用的内核源码肯定得和ubuntu的内核源码“门当户对”咯!


或者说,要想使得我们编译出的驱动模块能够被ubuntu的内核认可,我们最好使用ubuntu本身的内核源码文件来对驱动程序first_drv.c进行编译,这样才能得到可以在ubuntu环境中运行的first_drv.ko,打印出想要的hello world。


那么ubuntu本身的内核源码文件在哪儿呢?


在ubuntu命令行中输入:ls /lib/modules/


在我的ubuntu系统中打印出:2.6.31-14-generic  2.6.31-23-generic


这是两个文件夹,装着不同版本的内核源码文件,一个是2.6.31-14版本,另一个是2.6.31-23版本,ubuntu本身是基于哪个版本的呢?


"uname"是linux的一个命令,可以执行"man uname"得知"uname"的作用是"print the kernel release",也就是打印内核版本号。


执行uname -r打印出:2.6.31-23-generic


因此2.6.31-23就是我们ubuntu的内核版本号。


执行:cd 2.6.31-23-generic/build/


可以看到与/work/kernel/linux-2.6.22.6目录下的那些文件一样。


在Mkaefile中#表示该行内容被注释,我们将已经有的内核源码树KERN_DIR = /work/kernel/linux-2.6.22.6注释如下:


#KERN_DIR = /work/kernel/linux-2.6.22.6


然后添加如下两行:


KERN_VER = $(shell uname -r)

KERN_DIR = /lib/modules/$(KERN_VER)/build


这两行是什么意思呢,这涉及Makefile的语法,不过你应该可以大致猜测上面两行相当于:


KERN_DIR = /lib/modules/$(shell uname -r)/build


之前已经得知执行uname -r后会得到:2.6.31-23-generic


那么替换进去就得到了以下这个赋值:


KERN_DIR = /lib/modules/2.6.31-23-generic/build


这正好就是我所使用的ubuntu的内核源码树的绝对路径。


执行ls /lib/modules/2.6.31-23-generic/build


可以看到arch、block、...、usr这些众多的内核源码目录,与/work/kernel/linux-2.6.22.6路径下的内核源码目录是一样的格式,只是他们内核版本号不同罢了


我们保存好Makefile后,Makefile中最终的内容为:


KERN_VER = $(shell uname -r)

KERN_DIR = /lib/modules/$(KERN_VER)/build


#KERN_DIR = /work/kernel/linux-2.6.22.6


all:

    make -C $(KERN_DIR) M=`pwd` modules 


obj-m += first_drv.o


在/work/my_drivers/first_drv/1th/路径下执行make,就可以运行Makefile中的命令了,命令行中会打印出如下编译信息:


make -C /lib/modules/2.6.31-23-generic/build M=`pwd` modules 

make[1]: Entering directory `/usr/src/linux-headers-2.6.31-23-generic‘

  CC [M]  /work/my_drivers/first_drv/1th/first_drv.o

  Building modules, stage 2.

  MODPOST 1 modules

  CC      /work/my_drivers/first_drv/1th/first_drv.mod.o

  LD [M]  /work/my_drivers/first_drv/1th/first_drv.ko

make[1]: Leaving directory `/usr/src/linux-headers-2.6.31-23-generic‘


执行ls,显示出当前/work/my_drivers/first_drv/1th/目录下的所有文件:


first_drv.c  first_drv.ko  first_drv.mod.c  first_drv.mod.o  first_drv.o  

Makefile  Module.markers  modules.order  Module.symvers


可以看到在执行make之前该路径下只有first_drv.c和Makefile两个文件,现在多出了一大堆文件,这就是我们Makefile执行过程中生成的产物,我们只关心first_drv.ko,因为这就是我们的驱动模块文件,


其实我们可以把Makefile修改成下面这样:


KERN_VER = $(shell uname -r)

KERN_DIR = /lib/modules/$(KERN_VER)/build


#KERN_DIR = /work/kernel/linux-2.6.22.6


all:

    make -C $(KERN_DIR) M=`pwd` modules 


clean:

    make -C $(KERN_DIR) M=`pwd` modules clean

    rm -rf modules.order


obj-m += first_drv.o


这样,我们增加了clean目标,用于删除make命令所生成的文件,要执行删除命令,在该路径下执行输入make clean即可,clean不可以省略,因为clean目标不是我们Makefile中的第一个目标,all才是,执行make就相当于执行了make all。


执行make clean后ls,发现该目录下又只剩下了first_drv.c和Makefile两个文件。





本文出自 “12253782” 博客,请务必保留此出处http://12263782.blog.51cto.com/12253782/1872875

以上是关于第1个linux驱动___编译空壳驱动程序的主要内容,如果未能解决你的问题,请参考以下文章

第1个linux驱动___给驱动模块上户口

第1个linux驱动___投靠NFS网络文件系统

第1个linux驱动___安装驱动模块之内核再爱我一次

第1个linux驱动___应用程序才是大Boss

第1个linux驱动___给驱动模块上户口

第1个linux驱动___搭建环境,蓄势待发