日期:2014-05-16  浏览次数:23343 次

『分享』从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响!!! (转载)
从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响!!! 收藏
从 2.4 到 2.6,Linux 内核在可装载模块机制、设备模型、一些核心 API 等方面发生较大改变,设备驱动开发人员面临着将驱动从 2.4 移植到 2.6 内核,或是使驱动同时支持2.4 与 2.6 内核的任务。站在设备驱动开发人员的角度,驱动由一个或几个外部可加载内核模块组成,本文针对 2.6 内核里模块机制的改变对编写设备驱动程序的影响,从内核模块的编译、装载时的版本检查、初始化与退出、模块使用计数、输出内核符号、命令行输入参数、许可证声明等方面比较了 2.4 与 2.6 内核的区别;并总结了使设备驱动同时支持 2.4 与 2.6 内核的一系列模板。
1. 获取内核版本

当设备驱动需要同时支持不同版本内核时,在编译阶段,内核模块需要知道当前使用的内核源码的版本,从而使用相应的内核 API。2.4 与 2.6 内核下,源码头文件 linux/version.h 定义有:

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

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

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


清单1:判断内核版本的代码段。

#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
#define LINUX26
#endif
#ifdef LINUX26

#else

#endif
 

 

 

 


 回页首
 

 

2.内核模块机制的改变

2.1模块编译

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

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

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


清单2:2.4 内核模块的Makefile模板

 
#Makefile2.4
KVER=$(shell uname -r)
KDIR=/lib/modules/$(KVER)/build
OBJS=mymodule.o
CFLAGS=-D__KERNEL__ -I$(KDIR)/include -DMODULE -D__KERNEL_SYSCALLS__ -DEXPORT_SYMTAB
  -O2 -fomit-frame-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中定义-D__KERNEL__-DMODULE,指定内核头文件目录-I$(KDIR)/include。有一点需注意,之所以在CFLAGS中定义变量,而不是在模块源码文件中定义,一方面这些预定义变量可以被模块中所有源码文件可见,另一方面等价于将这些预定义变量定义在源码文件的起始位置。在模块编译中,对于这些全局的预定义变量,一般在CFLAGS中定义。


清单3: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,而且模块中各文件依赖关系的表示简洁清晰。


清单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
 
 

2.2模块装载时的版本检查

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

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

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

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

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

2.4内核下, 执行`cat /proc/ksyms`可看到内核符