从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响(一)

简介:

 2.4  2.6Linux 内核可装载模

  块机制的改变对设备驱动的影响

   

( 此文章非常精彩,强烈推荐 )

 

 2.4  2.6Linux 内核在可装载模块机制、设备模型、一些核心 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 内核的设备驱动程序中,经常可以看到以下代码段:

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)

#define LINUX26

#endif

#ifdef LINUX26

/*code in 2.6 kernel*/

#else

/*code in 2.4 kernel */

#endif

 

 

 

2 .内核模块机制的改变

2.1 模块编译

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

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

2.6 内核中,模块的编译需要配置过的内核源码;编译、连接后生成的内核模块后缀为.ko;编译过程首先会到内核源码目录下,读取顶层的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中定义。

# 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) 指明跳转到内核源码目录下读取那里的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,而且模块中各文件依赖关系的表示简洁清晰。

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.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段。 在内核源码目录下scripts\mod\modpost.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'

 

2.3 模块的初始化与退出

2.6内核中,内核模块必须调用宏module_init module_exit() 去注册初始化与退出函数。在2.4 内核中,如果初始化函数命名为init_module()、退出函数命名为cleanup_module(),可以不必使用module_init module_exit 宏。推荐使用module_init module_exit宏,使代码在2.42.6内核中都能工作。

#include <linux/module.h>  /* Needed by all modules */

#include <linux/init.h>    /* Needed for init&exit macros */

static int mod_init_func(void)

{

/*code here*/

return 0;

}

static void mod_exit_func(void)

{

/*code here*/

}

module_init(mod_init_func);

module_exit(mod_exit_func);

 

需要注意的是初始化与退出函数必须在宏module_initmodule_exit使用前定义,否则会出现编译错误。

 


本文转自 曾永刚 51CTO博客,原文链接:http://blog.51cto.com/zyg0227/270373


相关文章
|
7天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
32 4
|
8天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
28 6
|
9天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
28 5
|
9天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
9天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
27 2
|
2月前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】