Linxu设备驱动学习之构造和运行模块(菜鸟篇)~~~

来源:互联网 发布:ubuntu中文语言包下载 编辑:程序博客网 时间:2024/06/15 13:00

构造和运行模块


(1):内核模块和应用程序之间的对比



    在学习模块这一内核相关知识之前我们有必要搞清楚内核模块和应用程序之间的不同:

    程序是从头到尾执行的单个任务,应用程序运行在用户空间,并不是所有的应用程序都是事件驱动。

     模块(module)是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充内核的功能。模块最主要的用处就是用来实现设备驱动程序。


(2)内核模块的基本概念


          内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(LoadableKernelModule,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithickernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。

    模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。

   总之:模块是为内核或其它模块提供功能的代码块(在某种意义上内核也是模块)。


(3):模块的运行方式:


    在开始时调用模块初始化函数然后它的初始化函数会立即结束即:模块初始化函数的作用是为以后调用模块函数作准备,就像是模块在说“我在这里,并且我能作这些工作”。在工作结束是会调用模块退出函数,模块退出函数在模块被卸载之前调用,它告诉内核“我要离开了,不要再让我作任何事情”。


(4):使用模块的优缺点:


    1,将来修改内核时,不必全部重新编译整个内核,可节省不少时间
    2,系统中如果需要使用新模块,不必重新编译内核,只要插入相应的模块即可


内核模块的引入也带来了一些问题:

1、这种动态加载的特性不利于系统的性能和内存的利用,会带来负面的影响。

2、装入内核的模块和其他内核部分一样具有最高的权限,使用不当就可能引起系统的崩溃。

3、内核版本和模块版本的不兼容也会导致系统的崩溃,因此必须进行严格的版本检查,这样就使模块的编写变得更加复杂了。

4、有些模块要使用其他模块(例如VFAT就要使用FAT)的内容,模块之间存在一定的依赖关系,这样模块的实用就复杂化了。

由于模块的这种动态装载/卸载的特性,在Linux中大部分设备驱动程序都是使用模块来编写的,例如文件系统(minix、msdos、isofs、smbms、nfs、proc等等)、SCSI设备驱动程序、以太网驱动程序、CD-ROM驱动程序等等。



(5):简单模块的实现:

eg:

helloworld.c:

      #include<linux/init.h>             //  头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。
           #include<linux/module.h>     //所有模块都要使用头文件module.h,此文件必须包含进来。头文件kernel.h包含                                                           了常用的内核函数
           MODULE_LICENSE("Dual BSD/GPL");//MODULE_LICENSE是一个特殊宏,他用来告诉内核该模块在采用自由许可证

                    static int hello_init(void)
                             {
                                           printk(KERN_ALERT "hello, world\n");
                                           return 0;
                                  }

                    static void hello_exit(void)
                            {
                                         printk(KERN_ALERT "Goodbye, cruel world\n ");
                                 }

                     module_init(hello_init);
                     module_exit(hello_exit);


Makefile文件:

              obj-m += helloworld.o
                  all:
                           make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
                  clean:
                            make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


加载模块和卸载模块:
1>module_init(hello_init)
       a. 告诉内核你编写模块程序从那里开始执行。
       b.module_init() 函数中的参数就是注册函数的函数名。

2>module_exit(hello_exit)
       a. 告诉内核你编写模块程序从那里离开。

       b.module_exit() 中的参数名就是卸载函数的函数名。

编译运行方法:

首先我们保存代码到helloworld.c,一般使用vim helloworld.c命令。

第二步是写一个Makefile文件,即:vim Makefile ,该文件所做的工作是编译生成helloworld.o,helloworld.ko 等文 件!

第三步是    insmod helloworld.ko   //加载hello模块(在root下执行)

第四步是    rmmod helloworld.ko   //卸载hello模块

第五步是    dmesg                          //dmesg 是一个显示内核缓冲区系统控制信息的工具
/var/log/boot* 文件中;



(6):模块解释


 对于头文件:
      所有模块都要使用头文件module.h,此文件必须包含进来。
      头文件kernel.h包含了常用的内核函数。
      头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。

编写内核模块时必须要有的两个函数 :
1> 初始化 函数:
static int init_fun(void)                                   |   static int __init init_fun(void)
{                                                                     |   {
// 初始化代码                                                 |    //初始化代码
}                                                                     |   }
2> 清除函数( 无返回值 )
static void cleaup_fun(void)                          |   static void __exit cleaup_fun(void)
{                                                                    |   {
// 释放代码                                                    |    //释放代码
}                                                                    |   }
_init 和 __exit 是 Linux 内核的一个宏定义,使系统在初始化完成后释放该函数,并释放其所占内存。因此它的优点是显而易见的。所以建议大家啊在编写入口函数和出口函数时采用后面的方法。
1> 在 linux 内核中,所有标示为 __init 的函数在连接的时候都放在 .init.text 这个区段内,此外,所有的 __init 函数在区段 .initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 __init 函数,并在初始化完成后释放 init 区段(包括 .init.text,.initcall.init 等)。
2> 和 __init 一样, __exit 也可以使对应函数在运行完成后自动回收内存。

还有,我们在内核 编程时所用的库函数和在用户态下的是不一样的。如程序中使用的printk函数,对应于用户态下的printf函数,printk 是内核态信息打印函数,功能和printf类似但 printk还有信息打印级别。


    Makefile,make简介:

1>Makefile 是一种脚本,这种脚本主要是用于多文件的编译

2> make 程序可以维护具有相互依赖性的源文件,但某些文件发生改变时,它能自动识别出,并只对相应文件进行自动编译。
       上述简单例子中的Makefile文件的内容为:

obj-m += helloworld.o
                  all:
                           make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
                  clean:
                            make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


        注:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules  生成helloworld.o,helloworld.ko等文件

               make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean    删除编译时生成的中间文件和目标文件

              如在编译模块文件时执行make命令则相当于执行:

              all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

              如执行:make clean 命令则把编译过程中生成的中间文件和目标文件删除

           

 对于上述.ko文件等,在这里补充一下 Linux下后缀名为ko、o、a、so、la的文件简述:

1).ko 是kernel object 的缩写,是Linux 2.6内核使用的动态连接文件,在Linux系统启动时加载内核模块。

2).o 是相当于windows中的.obj文件
     注意:.ko与.o的区别在于,.ko是linux 2.6内核编译之后生成的,多了一些module信息,如author,license之类    的。.o文件则是linux 2.4内核编译生成的。
     3).a 是静态库,由多个.o组成在一起(assemble 集合),用于静态连接
     4).so 是shared object的缩写,用于动态连接,和windows的dll差不多
     5).la 为libtool自动生成的一些共享库。


 学习还在进行,未完待续。。。。。。。。。。





原创粉丝点击