本文共 11875 字,大约阅读时间需要 39 分钟。
Linux内核驱动模块
Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。
4.1~4.2节讲解了Linux内核模块的概念和结构,4.3~4.8节对Linux内核模块的各个组成部分进行了展现,4.1~4.2与4.3~4.8节是整体与部分的关系。
4.9节说明了独立存在的Linux内核模块的Makefile文件编写方法和模块的编译方法。
4.1 Linux内核模块简介Linux内核的整体结构已经非常庞大,而其包含的组件也非常多。我们怎样把需要的部分都包含在内核中呢?
一种方法是把所有需要的功能都编译到Linux内核。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。
有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?
答案是肯定的,Linux提供了这样的一种机制,这种机制被称为模块(Module)。模块具有这样的特点:
·
模块本身不被编译入内核映像,这控制了内核的大小。 · 模块一旦被加载,它就和内核中的其它部分完全一样。 为了建立读者对模块的初步感性认识,我们先来看一个最简单的内核模块“Hello World”,如代码清单4.1。 代码清单4.1 一个最简单的Linux内核模块 1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 static int hello_init(void) 5 { 6 printk(KERN_INFO " Hello World enter\n"); 7 return 0; 8 } 9 static void hello_exit(void) 10 { 11 printk(KERN_INFO " Hello World exit\n "); 12 } 13 module_init(hello_init); 14 module_exit(hello_exit); 15 16 MODULE_AUTHOR("Song Baohua"); 17 MODULE_DESCRIPTION("A simple Hello World Module"); 18 MODULE_ALIAS("a simplest module"); 这个最简单的内核模块只包含内核模块加载函数、卸载函数和对Dual BSD/GPL许可权限的声明以及一些描述信息。编译它会产生hello.ko目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello World enter”,卸载时输出“Hello World exit”。 内核模块中用于输出的函数是内核空间的printk()而非用户空间的printf(),printk()的用法和printf()基本相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段,在Linux驱动的调试章节中将详细讲解这个函数。 在Linux中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系,例如: [root@localhost driver_study]# lsmod Module Size Used by hello 1568 0 ohci1394 32716 0 ide_scsi 16708 0 ide_cd 39392 0 cdrom 36960 1 ide_cd lsmod命令实际上读取并分析“/proc/modules”文件,与上述lsmod命令结果对应的“/proc/modules”文件如下: [root@localhost driver_study]# cat /proc/modules hello 1568 0 - Live 0xc8859000 ohci1394 32716 0 - Live 0xc88c8000 ieee1394 94420 1 ohci1394, Live 0xc8840000 ide_scsi 16708 0 - Live 0xc883a000 ide_cd 39392 0 - Live 0xc882f000 cdrom 36960 1 ide_cd, Live 0xc8876000 内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行tree –a得到如下目录树: [root@localhost hello]# tree -a . |-- refcnt `-- sections |-- .bss |-- .data |-- .gnu.linkonce.this_module |-- .rodata |-- .rodata.str1.1 |-- .strtab |-- .symtab |-- .text `-- __versions modprobe命令比insmod命令要强大,它在加载某模块时,会同时加载该模块所依赖的其它模块。使用modprobe命令加载的模块若以“modprobe -r filename”的方式卸载将同时卸载其依赖的模块。 使用modinfo <模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic: [root@localhost driver_study]# modinfo hello.ko filename: hello.ko license: Dual BSD/GPL author: Song Baohua description: A simple Hello World Module alias: a simplest module vermagic: 2.6.15.5 686 gcc-3.2 depends: 4.2 Linux内核模块程序结构一个Linux内核模块主要由如下几个部分组成: · 模块加载函数(一般需要) 当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。 · 模块卸载函数(一般需要) 当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。 · 模块许可证声明(必须) 许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。 在Linux 2.6内核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。 大多数情况下,内核模块应遵循GPL兼容许可权。Linux 2.6内核模块最常见的是以MODULE_LICENSE( "Dual BSD/GPL" )语句声明模块采用BSD/GPL双LICENSE。 · 模块参数(可选) 模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。 · 模块导出符号(可选) 内核模块可以导出符号(symbol,对应于函数或变量),这样其它模块可以使用本模块中的变量或函数。 · 模块作者等信息声明(可选) 4.3模块加载函数Linux内核模块加载函数宜被以__init标识声明,典型的模块加载函数的形式如代码清单4.2所示。 代码清单4.2 内核模块加载函数 1 static int __init initialization_function(void) 2 { 3 4 } 5 module_init(initialization_function);