内核模块基础

来源:互联网 发布:xp自动修复软件 编辑:程序博客网 时间:2024/05/18 06:24

Linux内核是个很庞大的东西,要全部了解清楚是很难的,所以我们所做的内核上的开发也是为驱动做一些基本的准备。

 

一、内核模块的特点

    a)        不会被编译进内核之中,这样的话可以尽可能的控制Linux内核的大小。

    b)        在内核运行期间,可以动态的进行加载或卸载。

 

二、相关函数的介绍

   a)        module_init宏表示在加载模块的时候,自动调用这个宏所指示的函数。

   b)        module_exit宏表示在卸载模块的时候,自动调用这个宏所指示的函数。

 

例如:

         代码中:

                  module_init(funa);

                  module_exit(funb);

         当加载该模块时,自动调用funa函数,同理当卸载该模块时,自动调用funb函数。

 

三、模块的相关命令

    a)        加载模块:insmod  modprobe

    b)        卸载模块:rmmod

    c)        查看模块:lsmod

 

insmod和modprobe的区别:

         当a模块与b模块有依赖关系时,假设安装b模块需先安装a模块。

         如果用insmod命令那么需要先insmod a.ko 然后再insmod b.ko。

         如果用modprobe命令那么直接可以modprobeb.ko。

         其中/lib/modules/“内核版本号”/modules.dep中记录了模块之间的依赖关系。

 

         rmmod命令:只需要调用模块名即可,比如安装的时候是inmod a.ko 安装完毕后该模块存在于内核中的名字为a,那么卸载该模块只需要rmmod a即可。

前面讲了内核模块怎么安装和卸载,那么现在就正式讲解一下内核模块怎么编写。

以一个在内核中打印出”helloworld”到终端的例子讲解。

1、 首先要写模块加载时的入口函数和模块卸载时的入口函数

static  int  hello_init()

{

          printk(“<0>hello  world\n”);

          return 0;

}

 

static  void  hello_exit()

{

          printk(“<0>exit\n”);

}

 

module_init(hello_init);

module_exit(hello_exit);

 

这几段代码就是一个模块函数代码了,其中有几个需要注意:

    a)        static是为了防止函数命名污染。

    b)        在内核中的打印函数是printk,其中<0>表示该信息的等级,数字越小,级别越高。

    c)        用module_init和module_exit宏来指定入口函数。

 

当在命令行安装该模块时,会自动调用hello_init这个函数,当卸载该函数时,对自动调用hello_exit函数。

 

2、 模块可选信息

模块有一些用来表示相关信息的宏:

           MODULE_LICENSE                     用来告诉内核遵循什么协议  GPL GPLv2等

           MODULE_AUTHOR                    作者

           MODULE_DESCRIPTION            描述

           MODULE_VERSION                    版本

           MODULE_ALIAS                         别名

 

 

例如:

         MODULE_LICENSE("GPL");

         MODULE_AUTHOR("yangboyuan");

         MODULE_DESCRIPTION("KernelLink Module");

          MODULE_ALIAS("asimplestmodule"); 

 

内核模块的初始化函数,撤消函数通常是这样的形式:

        static int __init xxx-init(.....);        //函数没有参数就写void,init前是两个_

        staticvoid __exit xxx-exit(....);

        其中__init属性标志, 表明该段代码位于代码段的子段初始化段,初始化段的程序只执行一次,执行完后它所占用的内存空间将被回收。当模块加载时,__init属性的函数就被执行;

          __exit修饰词标记函数只在模块卸载时使用,如果模块被直接编进内核则该函数就不会被调用。如果内核编译时没有包含该模块,则此标记的函数将被简单地丢弃。

 

3、 Makefile文件的编写

大家都知道make命令,所以Makefile文件的作用在这里就没必要做过多介绍,我们先看看内核模块的Makefile怎么写的:

 

ifneq ($(KERNELRELEASE),)

obj-m := hello.o

else

KDIR := /lib/modules/2.6.35.6-45.fc14.i686/build

all:

         make-C $(KDIR)M=$(PWD) modules

clean:

         rm-f *.ko *.o*.mod.o *.mod.c *.symvers

endif

 

其中:

   a)        KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量。

               ifneq($(KERNELRELEASE),)  判断该变量是否为空。

                在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,
                所以make将读取执行else之后的内容。

 

  b)        KDIR := /lib/modules/2.6.35.6-45.fc14.i686/build 是给KDIR这个变量赋值,值为当前linux运行的内核源码。2.6.35.6-45.fc14.i686是我的Linux下的,可以直接

               KDIR :=/lib/modules/ $(shell  uname  -r) /build 这样写。

 

   c)        当make的目标为all时,-C$(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD)表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。

   d)        我们可以把上述的Makefile文件作为一个模板,只需要改动obj-m :=hello.o这条语句就可以了:obj-m=XXX.o。

 

 编写ARM上的Makefile只需要对上面的Makefile作简单的修改即可

ifneq($(KERNELRELEASE),)

obj-m := hello.o

else

KDIR := /home/liye/forlinux/linux-2.6.36

all:

         make-C $(KDIR)M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-

clean:

         rm-f *.ko *.o*.mod.o *.mod.c *.symvers

endif

 

4、Makefile文件编写好了之后,在内核模块代码的当前目录下敲命令:”make”