如果你走入我们的内核之旅网站http://www.kerneltravel.net/ ,静下心阅读并动手实践,或许,流逝的时间,让你手捧沉甸甸的果实。 欢迎在讨论区http://www.lupaworld.com/bbs/forum-255-1.html提问。 如果你希望加入Linux内核学习圈,请到http://blog.chinaunix.net/group/group_1475.html

内核模块编程之入门(二)—必备知识

上一篇 / 下一篇  2008-02-20 05:18:43 / 个人分类:释义Linux内核

查看( 2311 ) / 评论( 1 )

模块编程属于内核编程,因此,除了对内核相关知识有所了解外,还需要了解与模块相关的知识。
1.应用程序与内核模块的比较

为了加深对内核模块的了解,表一给出应用程序与内核模块程序的比较。
表一 应用程序与内核模块程序的比较


C语言应用程序
  内核模块程序
使用函数
  Libc
   内核函数
运行空间
  用户空间
   内核空间
运行权限
  普通用户
   超级用户
入口函数
   main()
  module_init()
出口函数
   exit()
  module_exit()
编译
  Gcc –c
  Makefile
连接
  Gcc
  insmod
运行
  直接运行
    insmod
调试
  Gdb
kdbug, kdb,kgdb
从表一我们可以看出,内核模块程序不能调用libc库中的函数,它运行在内核空间,且只有超级用户可以对其运行。另外,模块程序必须通过module_init()module-exit()函数来告诉内核“我来了”和“我走了”。

2内核符号表(如果对以下第2~4点理解上有困难,可以越过

如前所述,Linux内核是一个整体结构,像一个圆球,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个“母”模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。到低哪些符号可以被共享? Linux内核有自己的规定。对于内核这个特殊的母模块,在kernel/ksyms.c中定义了从中可以“移出”的符号,例如进程管理子系统可以“移出”的符号定义如下:

/* 进程管理 */

EXPORT_SYMBOL(do_mmap_pgoff);

EXPORT_SYMBOL(do_munmap);

EXPORT_SYMBOL(do_brk);

EXPORT_SYMBOL(exit_mm);

    EXPORT_SYMBOL(schedule);

    EXPORT_SYMBOL(jiffies);

    EXPORT_SYMBOL(xtime);

   

你可能对这些变量和函数已经很熟悉。其中宏定义EXPORT_SYMBOL()本身的含义是“移出符号”。为什么说是“移出”呢?因为这些符号本来是内核内部的符号,通过这个宏放在一个公开的地方,使得装入到内核中的其他模块可以引用它们。

实际上,仅仅知道这些符号的名字是不够的,还得知道它们在内核地址空间中的地址才有意义。因此,内核中定义了如下结构来描述模块的符号:

struct module_symbol

{

        unsigned long value; /*符号在内核地址空间中的地址*/

        const char *name;   /*符号名*/

};

我们可以从/proc/ksyms文件中读取所有内核模块“移出”的符号,这所有符号就形成内核符号表,其格式如下:

内存地址      符号名       [所属模块]

在模块编程中,可以根据符号名从这个文件中检索出其对应的地址,然后直接访问该地址从而获得内核数据。第三列“所属模块”指符号所在的模块名,对于从内核这一母模块移出的符号,这一列为空。

  模块加载后,2.4内核下可通过 /proc/ksyms、 2.6 内核下可通过/proc/kallsyms查看模块输出的内核符号

3模块依赖

   如前所述,内核符号表记录了所有模块可以访问的符号及相应的地址。当一个新的模块被装入内核后,它所申明的某些符号就会被登记到这个表中,而这些符号可能被其他模块所引用,这就引出了模块依赖这个问题。

   一个模块A引用另一个模块B所移出的符号,我们就说模块B被模块A引用,或者说模块A依赖模块B。如果要链接模块A,必须先链接模块B。这种模块间相互依赖的关系就叫模块依赖。

4.模块引用计数器

   为了确保模块安全地卸载,每个模块都有一个引用计数器。当执行模块所涉及的操作时就递增计数器,在操作结束时就递减这个计数器;另外,当模块B被模块A引用时,模块B的引用计数就递增,引用结束,计数器递减。什么时候可以卸载这个模块?当然只有这个计数器值为0的时候,例如,当一个文件系统还被安装在系统上时就不能将其卸载,当这个文件系统不再被使用时,引用计数器就为0,于是可以卸载。

四.模块编译

   Linux 中最重要的软件开发工具是 GCCGCC GNU C C++ 编译器。但是,在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。

1.编译工具make

实际上,make 工具通过一个称为 Makefile 的文件来完成并自动维护编译工作。Makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。下面给出2.6 内核模块的Makefile模板(有点复杂,实在看不懂,可以仅作参考)

# Makefile2.6
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
mymodule-objs := file1.o file2.o
   obj-m := mymodule.o
else
  PWD  := $(shell pwd)
  KVER ?= $(shell uname -r)
  KDIR := /lib/modules/$(KVER)/build
all:      
    $(MAKE) -C $(KDIR) M=$(PWD)
clean:
   rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义, 所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。mymodule-objs := file1.o file2.o表示mymoudule.o 由file1.o与file2.o 连接生成。obj-m := mymodule.o表示编译连接后将生成mymodule.o模块。
补充一点,"$(MAKE) -C $(KDIR) M=$(PWD)"与"$(MAKE) -C $(KDIR) SUBDIRS =$(PWD)"的作用是等效的,后者是较老的使用方法。推荐使用M而不是SUBDIRS,前者更明确。
通过以上比较可以看到,从Makefile编写来看,在2.6内核下,内核模块编译不必定义复杂的CFLAGS,而且模块中各文件依赖关系的表示简洁清晰。


  例如,要把第一部分中所提到的hellomod.c编译成一个模块,简单的使用GCC无法完成。最好写一个Makefile文件,内容如下(以后写其他模块的Makefile可以如法炮制,改变一下模块名字即可)
  
obj-m += hellomod.o
all:
    make -C /usr/src/linux  M=$(PWD) modules
clean:
    make -C /usr/src/linux  M=$(PWD) clean


   在这里要特别说明的是,/usr/src/linux 中的Linux目录,因发布版不同而不同,比如在我安装的Ubuntu下为linux-headers-2.6.20.16-generic,因此,只需要建立一个针对Linux的符号链接就可以。
有了Makefile,执行make命令,会自动形成相关的后缀为.o和.ko文件。
  到此,模块编译好了,该把它插入到内核了:

  如:insmod hellomod.ko

  是否插入成功可以通过dmesg命令查看,屏幕最后几行的输出就是你程序中输出的内容。
  当不再模块需要时,可以通过rmmod命令移去。







TAG:

仰空冥思 陈莉君 发布于2008-02-22 08:55:47
Nancy在留言中问:模块如果被静态编译进入内核,那么的那个入口函数通过什么机制被执行?代码中try_module_get等针对于模块的函数是在执行时自动被跳过还是?
其实我是想问同样的source code,只是编译方式不同,一个是动态以模块的方式编译,一个是静态编译。具体到运行代码上会有什么差异呢?

回答如下:一般来说,如果驱动程序以模块的形式来写,然后编译之后插入到内核,就是动态模块,也就是可以随时插入或去掉。所谓静态编译,指对修改过的内核进行编译,就不指模块的编译了,也就不需要module_init这个模块入口函数了。

try_module_get对应2.4版中的MOD_INC_USE_COUNT ,只要模块被引用,这个函数就执行.

[ 本帖最后由 陈莉君 于 2008-2-22 20:57 编辑 ]
我来说两句

(可选)

日历

« 2008-08-30  
     12
3456789
10111213141516
17181920212223
24252627282930
31      

数据统计

  • 访问量: 43540
  • 日志数: 105
  • 图片数: 2
  • 书签数: 1
  • 建立时间: 2006-11-09
  • 更新时间: 2008-08-28

RSS订阅

Open Toolbar