Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1944880
  • 博文数量: 383
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 4061
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-24 18:53
文章分类

全部博文(383)

文章存档

2011年(1)

2010年(9)

2009年(276)

2008年(97)

我的朋友

分类: LINUX

2009-04-19 17:27:16

2.4 2.6 内核在可装载模块机制、设备模型、一些核心 API 等方面发生较大改变,设备驱动开发人员面临着将驱动从 2.4 移植到 2.6 内核,或是使驱动同时支持2.4 2.6 内核的任务。站在设备驱动开发人员的角度,驱动由一个或几个外部可加载内核模块组成,本文针对 2.6 内核里模块机制的改变对编写设备驱动程序的影响,从内核模块的编译、装载时的版本检查、初始化与退出、模块使用计数、输出内核符号、命令行输入参数、许可证声明等方面对2.4 2.6 内核做了比较;并总结了使设备驱动同时支持 2.4 2.6 内核的一系列模板。
  1.获取内核版本信息
当设备驱动需要同时支持不同版本内核时,在编译阶段,内核模块需要知道当前使用的内核源码的版本,从而使用相应的内核 API2.4 2.6 内核下,源码头文件 linux/version.h 定义有:

  LINUX_VERSION_CODE ― 内核版本的二进制表示,主、从、修订版本号各对应一个字节;

  KERNEL_VERSION(major, minor, release)
由主、从、修订版本号构造二进制版本号。

  在同时支持2.42.6 内核的设备驱动程序中,经常可以看到以下代码段:

  清单1:判断并限制编译时内核版本的代码段。

代码:

 
#ifndef LINUX_VERSION_CODE  /* 避免重复引用 */
#  include 
#endif
  
#ifndef  KERNEL_VERSION  
#  define KERNEL_VERSION(vers,rel,seq) ( ((vers)<<16) | ((rel)<<8) | (seq) ) /*
由主、从、修订版本号构造二进制版本号的宏
*/
#endif
 
/* if only allow 2.4.x and 2.6.x */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)   
#  error 'This kernel is too old: not supported by this file' /*
内核版本小于2.4则编译报错,编译终止
*/
#endif
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,20)  /*
内核版本大于2.6.20,表明版本太新(根据自己的要求修改),则编译报错,编译终止
*/
#  error 'This kernel is too recent: not supported by this file'
#endif



清单2:打印编译时内核的版本号。
使用字符串宏UTS_RELEASE打印如下:

代码:

 printk(KERN_NOTICE'---- use Linux version %s ----\n', UTS_RELEASE) ;


宏定义UTS_RELEASE在头文件#include 中,但2.6.20及其后续版本将宏定义UTS_RELEASE在头文件#include

  2.内核模块机制的改变

  2.1模块编译

  从2.42.6,外部可装载内核模块的编译、连接过程以及Makefile的书写都发生了改变。

  2.4内核中,模块的编译只需内核源码头文件;需要在包含linux/modules.h之前定义MODULE;编译、连接后生成的内核模块后缀为.o

  2.6内核中,模块的编译需要配置过的内核源码;编译、连接后生成的内核模块后缀为.ko;编译过程首先会到内核源码目录下,读取顶层的Makefile文件,然后再返回模块源码所在目录。

  清单22.4 内核模块的Makefile模板

代码:

 
  #Makefile2.4

       KVER=$(shell uname -r)
      KDIR=/lib/modules/$(KVER)/build
    OBJS=mymodule.o
     CFLAGS=-DKERNEL -I$(KDIR)/include -DMODULE -DKERNEL_SYSCALLS -DEXPORT_SYMTAB -O2 -fomit--pointer -Wall -DMODVERSIONS -include $(KDIR)/include/linux/modversions.h

  all:
            $(OBJS) mymodule.o: file1.o file2.o ld -r -o $@ $^
       clean:
             rm -f *.o

  在2.4 内核下,内核模块的Makefile与普通用户程序的Makefile在结构和语法上都相同,但是必须在CFLAGS中定义-DKERNEL- DMODULE,指定内核头文件目录-I$(KDIR)/include。有一点需注意,之所以在CFLAGS中定义变量,而不是在模块源码文件中定义,一方面这些预定义变量可以被模块中所有源码文件可见,另一方面等价于将这些预定义变量定义在源码文件的起始位置。在模块编译中,对于这些全局的预定义变量,一般在CFLAGS中定义。

  清单32.6 内核模块的Makefile模板

代码:

 
  # Makefile2.6

       ifneq ($(KERNELRELEASE),)
       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 *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

  KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时, KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。如果make的目标是clean(控制台输入:make clean),直接执行clean操作,然后结束。当make的目标为all(控制台输入:make )-C $(KDIR) 指明跳转到内核源码目录下读取那里的MakefileM=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。mymodule-objs := file1.o file2.o表示mymoudule.o file1.ofile2.o 连接生成。obj-m := mymodule.o表示编译连接后将生成mymodule.o模块。

  补充一点,”$(MAKE) -C $(KDIR) M=$(PWD)””$(MAKE) -C $(KDIR) SUBDIRS =$(PWD)”的作用是等效的,后者是较老的使用方法。推荐使用M而不是SUBDIRS,前者更明确。

  通过以上比较可以看到,从Makefile编写来看,在2.6内核下,内核模块编译不必定义复杂的CFLAGS,而且模块中各文件依赖关系的表示简洁清晰。

  清单4
可同时在2.4 2.6 内核下工作的Makefile

代码:

 
  Makefile for 2.4 & 2.6

       VERS26=$(findstring 2.6,$(shell uname -r))
       MAKEDIR?=$(shell pwd)
        ifeq ($(VERS26),2.6)
        include $(MAKEDIR)/Makefile2.6
        else
        include $(MAKEDIR)/Makefile2.4
        endif

        其中uname -r的作用是 --kernel-release     print the kernel release

  
2.2模块装载时的版本检查

  Linux内核一直在更新、完善,在a版本内核源码下编译的模块在b版本内核下通常不能运行,所以必须有一种机制,限制在a版本内核下编译生成的模块在b版本内核下被加载。

  2.42.6内核在可装载内核模块的版本检查机制方面发生了根本性的改变,不过这些改变对设备驱动开发人员而言基本是透明的。为了使模块装载时的版本检查机制生效,2.4 内核下,只需在CFLAGS中定义

  -DMODVERSIONS -include $(KDIR)/include/linux/modversions.h

  2.6内核下,开发人员无须采用任何操作。

  不过,在此仍有必要阐明2.42.6内核对可加载模块的版本检查机制。

  2.4内核下,
执行`cat /proc/ksyms`可看到内核符号在名字后还跟随着一串校验字符串,此校验字符串与内核版本有关。在内核源码头文件linux/modules 目录下存在许多*.ver文件,这些文件起着为内核符号添加校验后缀的作用,如ksyms.ver 文件里有一行 #define printk _set_ver(printk)linux/modversions.h 文件会包含全部的 ver文件。所以当模块包含linux/modversions.h文件后,编译时,模块里使用的内核符号实质是带有校验后缀的内核符号。在加载模块时,如果模块中所使用内核符号的校验字符串与当前运行内核所导出的相应的内核符号的校验字符串不一致,即当前内核空间并不存在模块所使用的内核符号,就会出现 “Invalid module format “的错误。

  为内核符号添加校验字符串来验证模块的版本与内核的版本是否匹配是繁杂和浪费内核空间的;而且随着SMP(对称多处理器)、PREEMPT(可抢占内核)等机制在2.6内核的引入和完善,模块运行时对内核的依赖不仅取决于内核版本,还取决于内核的配置,此时内核符号的校验码是否一致不能成为判断模块可否被加载的充分条件。2.6 内核下,在linux/vermagic.h中定义有VERMAGIC_STRINGVERMAGIC_STRING不仅包含内核版本号,还包含有内核使用的gcc版本,SMPPREEMPT等配置信息。模块在编译时,我们可以看到屏幕上会显示“MODPOST”。在此阶段, VERMAGIC_STRING会添加到模块的modinfo段。在内核源码目录下smodmodpost.c文件中可以看到模块后续处理部分的代码。模块编译生成后,通过`modinfo mymodule.ko`命令可以查看此模块的vermagic等信息2.6 内核下的模块装载器里保存有内核的版本信息,在装载模块时,装载器会比较所保存的内核vermagic与此模块的modinfo段里保存的 vermagic信息是否一致,两者一致时,模块才能被装载。譬如Fedora core 4 core 2 使用的都是2.6 版本内核,在Fedore Core 2下去加载Fedora Core4下编译生成的hello.ko,会出现“invalid module format” 错误。

代码:

 
  insmod hello.ko Invalid module format hello: version magic ‘2.6.11-1.1369_FC4 686 REGPARM 4KSTACKS gcc-4.0’ should be ‘2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3’

  未完.

 

 

阅读(3025) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~