我的第一个驱动程序

说明:
开发环境:ubuntu14.04
硬件:EasyArm-i.mx283.A

为什么要引入内核模块

 Linux内核属于宏内核,简单来说,就是把很多东西都集成进内核,除了最基本的进程、线程管理、内存管理外,文件系统,驱动,网络协议等等都在内核里面,形成单一的内核映像,其优点在于运行效率高,内核中各模块之间的交互通过直接函数调用来实现。这也是和微内核最大的区别,windows采用的内核架构是微内核,微内核只实现关键和最核心的一部分,其余模块被单独编译,模块之间的交互需要通过微内核提供的通信机制来建立。这种通信机制称为消息传递,微内核相当于一个信息交换中心。而宏内核相当于一个中央集权控制中心。

 微内核中只有最基本的调度、内存管理。驱动、文件系统等都是用户态的守护进程去实现的。其优点是稳定性高,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃,做驱动开发时,发现错误,只需要kill掉进程,修正后重启进程就行了,比较方便。缺点是运行效率低。

 相比于微内核,宏内核虽然具有较高的运行效率,但是其稳定低,维护难度高。在进行驱动开发时,一个小小的bug会倒是整个系统死掉。当开发人员重新修复bug后,必须重新编译内核和重新启动系统,时间消耗巨大。为了弥补这一缺点,Linux引进了内核模块的概念,内核模块就是单个编译的一段驱动代码,在需要的时候可以动态加载到内核,增加内核的功能,在不需要的时候动态地卸载模块,而不需要重新编译整个系统,增加了系统的可剪裁性。根据这一特点可定制属于自己的操作系统,在一定程度上减小了内核的体积,自然节约了内存空间。

驱动模块的一般格式

#include 
#include 
#include 



static int __init vser_init(void)
{
    printk("vser_init\n");
    return 0;
}

static void __exit vser_exit(void)
{
    printk("vser_exit\n");
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YangZhengQing<[email protected]>");
MODULE_DESCRIPTION("A test module");

 Linux下编写驱动程序,至少需要包含include、include、include这三个基本的文件,这个三个文件位于内核目录树下的顶层目录include下,所以编译驱动程序时,必须指定内核源码路径。

  • include包含init_module和cleanup_module两个函数的声明。
  • include包含一些内核中调用的一些函数的声明,比如prink。
  • include包含标注模块信息的一下宏。

 大致了解了驱动模块的格式后,很奇怪,驱动文件的最后为什么增加一些必要的说明函数呢?如果不加将会出现以下的一些的警告信息:
我的第一个驱动程序_第1张图片
 通过查阅资料后,其警告的大致意思是加载的驱动模块使内核被污染了,禁止了锁的调试功能。原因是,Linux是一个开源的项目,为了使Linux在发展的过程中不成为一个闭源的项目,这就要求任何使用Linux内核源码的个人或组织在免费获得源码并针对源码做任意的修改和在发布的同时,必须将修改后的源码发布。这就是所谓的GPL许可证协议,所以在模块文件的最后需要注明,表示接受这个协议。也可以不标注,但是会被限制使用一些内核的API函数。

  • MODULE_LISENSE:许可证协议,可以是GPL、GPL v2、GPL and additional rights等。
  • MODULE_AUTHOR:模块的作者信息,通常包括作者的姓名和邮箱地址。
  • MODULE_DESCRIPTION :模块的详细信息说明,通常是该模块的功能说明。
  • MODULE_ALIAS:驱动模块一个别名,此别名被用户空间所使用,可有可无。

我的第一个驱动程序_第2张图片

 init_module和cleanup_module模块初始化函数和清除函数的名字是固定的,为了更好地表达函数的功能意图,开发人员可以借助内核提供的函数别名机制灵活地定义初始化函数和清除函数。

 module_init和module_exit,分别用于指定init_module和cleanup_module的别名vser_init和vser_exit,如果不适用别名机制,初始化函数和清除函数的名只能是init_module和cleanup_module。另外c语言没有命名空间的概念,使用别名机制可能会导致名字和内核中其他的函数名重复定义,为了避免这种风险,使用static修饰,限制其作用域。

 Linux对内存的使用可谓是相当勤俭持家了,任何无意义被占用的内存都将被回收。比如,在以上模块中初始化函数在整个生命周期中,仅仅只会被调用一次,以后不会再被调用,所以调用结束之后应该释放掉此内存,为了实现这个功能,应该使用宏_init来达到目的。_init是把标记的函数放到可执行文件的特定代码段,再加载这些段时将会单独分配内存,这些函数调用成功后,模块的加载程序会释放这部分内存空间。_exit用于修饰清除函数,用于模块的卸载,如果模块不允许卸载,那么这段代码完全不用加载。

驱动模块的Makefile文件

 在以下脚本文件中以注释的形式对Makefile进行解释。


#中间目标文件
obj-m:=test.o 

#当前工作目录,驱动模块的安装路径,从内核源码路径退回的目录
PWD:=$(shell pwd)

#内核源码的路径
KERNEL_PATH:=/home/yzq/EasyArm-imx28xx/kernel/linux-2.6.35.3

#将其赋值给另一个环境变量
KDIR:=$(KERNEL_PATH)

#总目标
all:
    $(MAKE) -C $(KDIR) M=$(PWD)

#清理中间文件
.PHONY:clean
clean:
    rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions

运行:
在这里插入图片描述

你可能感兴趣的:(linux,嵌入式,驱动程序)