`

内核模块开发基础

 
阅读更多

说明:只供学习交流

一,什么是内核模块

Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用需要的组件呢:

方法一:把所有的组件都编译进内核文件,即:zImagebzImage,但这样会导致两个问题:一是生成的内核文件过大;二是如果要添加或删除某个组件,需要重新编译整个内核。

有没有一种机制能让内核文件(zImagebzImage)本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?

有,Linux提供了一种叫做“内核模块”的机制,就可以实现以上效果。

内核模块具有如下特点:

1)可以根据需求,在内核运行期间动态的安装或卸载。

2)内核模块是具有独立功能的程序。它可以被单独编译,但是不能单独运行,它的运行必须被链接到内核作为内核的一部分在内核空间中运行。

3):模块编程和内核版本密切相连,因为不同的内核版本中某些函数的函数名会有变化。因此模块编程也可以说是内核编程。

4):模块本身不被编译进内核映像,从而控制了内核的大小;模块一旦加载,就和内核中其他部分完全一样。

一个简单的范例:

#include <linux/init.h>
#include <linux/module.h>
static int hello_init(void)
{
printk(KERN_WARNING"Hello, world !\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "Goodbye,  world\n");
}
module_init(hello_init);
module_exit(hello_exit);


说明:代码中__init__exitLinux内核的宏定义,使系统在初始化或卸载完成后释放掉该函数,并释放其占用的内存。

1、模块加载函数(必需)

安装模块时被系统自动调用的函数,通过module_init宏来指定。

2、模块卸载函数(必需)

卸载模块时被系统自动调用的函数,通过module_exit宏来指定。

二,模块的安装与卸载

加载:insmod(insmodhello.ko)

卸载:rmmod(rmmodhello)

查看:lsmod

加载:modprobemodprobe hello

modprobe如同insmod, 也是加载一个模块到内核。它的不同之处在于它会根据文件

/lib/modules/<$version>/modules.dep

来查看要加载的模块,看它是否还依赖于其他模块,如

果是,modprobe会首先找到这些模块, 把它们先加载到内核。

三,内核打印函数printk

Printkprintf最大的区别是printk可以通过附加不同日志级别(loglevel),或者说消息优先级,可让printk根据这些级别所表示的严重程度对消息进行分类。在头文件<linux/kernel.h>中定义了8种可用的日志级别字符串,对应的文件内容如下:

#define KERN_EMERG "<0>" /*紧急事件消息,系统崩溃之前提示,表示系统不可用 */

#define KERN_ALERT "<1>" /*报告消息,表示必须立即采取措施 */

#define KERN_CRIT "<2>" /*临界条件,通常涉及严重的硬件或软件操作失败*/

#define KERN_ERR "<3>" /*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/

#define KERN_WARNING "<4>" /*警告条件,对可能出现问题的情况进行警告*/

#define KERN_NOTICE "<5>" /*正常但又重要的条件,用于提醒。常用于与安全相关的*/

#define KERN_INFO "<6>" /*提示信息,如驱动程序启动时,打印硬件信息*/

#define KERN_DEBUG "<7>" /*调试级别信息*/

数值越小优先级越高。在/proc/sys/kernel/printk中记录了与printk打印相关的几个数值,如在RHEL5中这个文件内容是:

可见该文件包含了4个整数值,从左到右分别是

1):当前控制台日记级别:优先级小于此值时,printk才能打印到终端(纯字符界面)。

2):未明确指定日记级别的默认消息日志级别。

3):最小充许设置的控制台日志级别数值。

4)系统引导时默认的日志级别:限定系统启动时控制台打印情况。

注意:在Xwindows图形环境下,虚拟终端不会打印printk信息(除非声明为最高优先级KERN_EMERG),这些信息的输出进到了内核回环缓冲区或日志文件中。

如果系统同时运行klogdsyslogd这两个日志服务程序,printk的信息会追加到/var/log/messages日志文件中。如果klogd没有运行,则这些消息不会传递到用户空间,这时只能查看/proc/kmesg文件(里面是内核回环缓冲区的消息),该文件不能像普通文件一样的打开,需使用dmesg命令查看。

四,模块可选信息

1):许可证申明(MODULE_LICENSE)。

MODULE_LICENSE用来告知内核,该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨。有效的许可证有"GPL“、"GPL v2""GPL and additional rights""Dual

BSD/GPL""Dual MPL/GPL""Proprietary"

2)作者申明(可选)

MODULE_AUTHOR(“Simon Li");

3)模块描述(可选)

MODULE_DESCRIPTION("Hello WorldModule");

4)模块版本(可选)

MODULE_VERSION("V1.0");

5)模块别名(可选)

MODULE_ALIAS("a simple module");

(五)模块参数

模块参数传递的概念:

对于如何向模块传递参数,LinuxKernel提供了一个简单的框架,其充许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,在驱动程序里,参数的用法如同全局变量。

1module_param

通过宏module_param指定模块参数,模块参数用于在加载模块时传递参数给模块。

module_param(name,type,perm)

name是模块参数的名称,type是这个参数的类型,

perm是模块参数的访问权限。

type常见值:

bool:布尔型 int:整型 charp:字符串型

perm 常见值:

S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限

S_IWUSR:允许root用户修改/sys/module中出现的该参数

例如:

int a = 3;

char *st;

module_param(a,int, S_IRUGO)

module_param(st,charp, S_IRUGO)

如果模块内部的变量名和外部参数的变量名不同,则使用

module_param_named(name, variable, type,perm);

其中name为外部可见的参数名。

Variable为源文件内部的全局变量名。其他参数同module_param

注意:这类宏函数并不会声明变量,因此在使用宏之前,必须声明变量,另外参数变量还必须是全局的,放在模块源文件开头。

(二):module_param_array

module_param_array(name, type, nump, perm)宏定义一个模块参数数组,并让内核在模块插入时把命令行的以逗号分开的参数序列传入该模块参数数组中。如果内部的数组名与外部的数组名不同可以使用:

module_param_array_named(name, array, type,nump,perm)

参数说明:name:模块参数数组,即是外部模块的参数名又是程序内部的变量名,该数组必须静态分配。

type:表示参数的数据类型。

nump:一个整形数,其值表示有多少个参数存放在数组name中,NULL表示不关心用户提供的个数。

permsysfs的访问权限。

注意:如果name表示字符串数组时,其中的字符串不能包含逗号,否则一个字符串会被解析成两个。

范例:

Static int fish[MAX_FISH];

Static int nr_fish;

//最终传递数组的个数存在nr_fish

Module_param_array(fish, int, &nr_fish,0444);

3module_param_string

module_param_stringname string len perm)定义一个模块字符串参数,并让内核在模块插入时把命令行的字符串参数直接复制到程序中的字符数组内。

参数说明:name:外部可见的参数名。

string:内部的变量名。

len:以string命名的buffer大小(可以小于buffer的大小,但是没有意义)。

permsysfs的访问权限(或者perm0,表示完全关闭相对应的sysfs项)。

范例:

static char species[BUF_LEN];

module_param_string(specifies, species,BUF_LEN, 0);

注意:以上宏函数的perm表示的权限值不能包含让普通用户也用写权限,否则编译报错。这点可参考linux/moduleparam.h__module_param_call()宏的定义。

例子:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("param test");
MODULE_AUTHOR("tanghui");

static char *str_var = "Hello Linux";
static int a = 1;
static int int_array[6];
static char string[100];
int narr;

module_param(a, int , 0644); //权限不能为0666,一般用户不能有写权限,否则编译出错
module_param(str_var, charp, 0644);
module_param_array(int_array, int, &narr, 0644);
module_param_string(str, string, 100, 0644);

static int __init Module_param_init(void)
{
	printk(KERN_WARNING "Param Test!\n");

	printk("a = %d\n", a);

	printk("%s\n", str_var);

	int i = 0;
	for (i = 0; i < narr; i++)
	{
		printk("%d\t", int_array[i]);
	}

	printk("\n%s\n", string);

	printk("\n");

	return (0);
}

static void __exit Module_param_exit(void)
{
	printk(KERN_WARNING "Bye!\n");
}

module_init(Module_param_init);
module_exit(Module_param_exit);


运行结果:

提示:这里我们还需要掌握一个知识:前面提到过,函数module_paramname, type, perm)的第3个参数perm表示在sysfs中建立的对应文件的权限,事实上当插入模块param.ko后,内核会在/sys/module/中建立param/目录。而由于模块param中通过param中通过module_param等方法声明了4个参数a ,int_array, str, ptr,所以内核还会在/sys/module/param/目录下产生parameters/目录,并生成对应的文件a, int_array, str, ptr,而module_param函数的第三个参数perm正是指定这3个文件的访问权限,通过写这3个文件可以修改对应的参数,而通过读它们则可以获得其当前值。如果perm值填0,内核将不会产生对应参数文件。

六,内核符号导出

1)内核符号的概念:

在编程中,一个符号(symbol)是一个程序的创建块:它是一个变量名或一个函数名。正如你自己编制的程序一样,内核具有各种符号也是不应该感到惊奇的。当然,区别在于内核是一个非常复杂的代码块,并且含有许多许多的全局符号。

2)存放内核符号的文件

两个文件是用作符号表的:

/proc/kallsyms和内核顶层目录下的System.map文件。内核符号表的格式形式如命令:nm –n vmlinux的输出,其中vmlinux是内核镜像文件。

/proc/kallsyms是一个“proc文件”,是在内核启动时创建的。实际上它并不是一个真实的文件;它只是内核数据的简单表示形式。

System.map是文件系统上的一个真实文件。随着每次内核的编译,就会产生一个新的System.map文件,并且需要用该文件取代原来的文件。另外,System.map存放的内核符号不会因为插入模块而更新,只会因为重新编译内核而改变,/proc/kallsyms则包含了模块的符号列表。

(三)内核符号导出的方法

内核符号的导出使用:

EXPORT_SYMBOL(符号名)

EXPORT_SYMBOL_GPL(符号名)

其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块。

范例:

Calculate.c

1 #include <linux/module.h>
  2 #include <linux/init.h>
  3 #include <linux/kernel.h>
  4 
  5 MODULE_LICENSE("GPL");
  6 MODULE_AUTHOR("tang hui");
  7 MODULE_DESCRIPTION("Module_export Test");
  8 
  9 int add_integar(int a, int b)
 10 {
 11         return (a + b);
 12 }
 13 
 14 int sub_integar(int a, int b)
 15 {
 16         return (a - b);
 17 }
 18 
 19 static int __init sym_init(void)
 20 {
 21         return (0);
 22 }
 23 
 24 static void __exit sym_exit(void)
 25 {
 26 
 27 }
28 
 29 module_init(sym_init);
 30 module_exit(sym_exit);
 31 EXPORT_SYMBOL(add_integar);
 32 EXPORT_SYMBOL(sub_integar);
 33


Hello.c

1 #include <linux/kernel.h>
  2 #include <linux/module.h>
  3 #include <linux/init.h>
  4 
  5 MODULE_LICENSE("GPL");
  6 MODULE_AUTHOR("tang hui");
  7 MODULE_DESCRIPTION("Module_export Test");
  8 
  9 extern int add_integar(int a, int b);
 10 extern int sub_integar(int a, int b);
 11 
 12 static int __init hello_init(void)
 13 {
 14         int ret = add_integar(1, 2);
 15 
 16    printk(KERN_WARNING "hello init, ret = %d\n", ret);
 17 
 18         return (0);
 19 }
 20 
 21 static void __exit hello_exit(void)
 22 {
 23         int res = sub_integar(2, 1);
 24 
 25   printk(KERN_WARNING "hello exit, res = %d\n", res);
 26 }
28 module_init(hello_init);
 29 module_exit(hello_exit);


Makefile:

Makefile:
  1 ifneq ($(KERNELRELEASE),)
  2 
  3 obj-m := calculate.o hello.o
  4 
  5 else
  6 
  7 KDIR := /lib/modules/2.6.29/build
  8 
  9 all:
 10         make -C $(KDIR) M=$(PWD) modules
 11 
 12 clean:
 13      rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers
 14 
 15 endif


运行结果:

注意:模块加载的顺序,hello.ko依赖于calculate.ko,所有要先加载calculate.ko,在加载hello.ko

()内核模块开发常见的问题

1)版本不匹配

解决方法:

1、使用modprobe --force-modversion强行插入

2、确保编译内核模块时,所依赖的内核代码版本等同于当前正在运行的内核。

内核模块版本信息可以通过命令modinfo查看:modinfo *.ko

好了,到此为止,模块的基础知识我已经介绍完了,之所以把这些知识整理成文档,一则为了以后好复习,二则想把自己所学与大家分享。我会持续更新我的博客,从Linux命令,到Linux应用开发,Linux驱动,Linux系统移植,Uboot开发与移植,项目的制作等等。希望大家多多关注与支持。

分享到:
评论

相关推荐

    Linux内核API完全参考手册(第2版).邱铁(详细书签)

    对Linux内核系统知识进行精心策划,以内核模块方式对内核API进行系统分析; 在系统功能模块内部,内核API以函数名称排序,能够快速检索; 立足于基础,高效率的学习理论配合内核API经典实例,深入Linux编程实践; ...

    [14本经典Android开发教程]-8-Linux内核阅读心得体会

    读核感悟 kbuild系统 内核模块的编译 22 读核感悟 kbuild系统 编译到内核和编译成模块的区别 24 读核感悟 kbuild系统 make bzImage的过程 26 读核感悟 kbuild系统 make menuconfig 31 读核感悟 文件系统 用C来实现...

    《嵌入式 驱动开发基础1》之 内核模块

    本课程介绍了内核模块的基础知识,为后续课程打基础

    apache模块开发指南(中文版)

    Apache的开发人员认识到Apache 最初的架构具有局限性,比较粗糙,于是在2000年开始建立新的代码仓库(codebase)主分支,并在此代码仓库的基础上于2002年4月创建了Apache2.0的第一个发布版本。Apache 2包括以下优良...

    嵌入式内核开发

    交叉工具链的安装,嵌入式系统开发操作系统基础知识总结;内核模块知识

    论文研究-基于内核跟踪的动态函数调用图生成方法.pdf

    针对目前大多数的函数调用关系分析工具无法分析函数指针、系统启动过程以及可加载模块的函数调用关系的现象,在CG-...实验结果表明,DCG-RTL能全面和准确地跟踪包括函数指针引用和可加载内核模块在内的函数调用关系。

    深入分析Linux内核源码

    10.5.2 内核模块的Makefiles文件 10.5.3 内核模块的多个文件 第十一章 设备驱动程序 11.1 概述 11.1.1 I/O软件 11.1.2 设备驱动程序 11.2 设备驱动基础 11.2.1 I/O端口 11.2.2 I/O接口及设备控制器 ...

    Windows内核安全与驱动开发光盘源码

    1.3 调试内核模块 9 1.3.1 下载和安装WinDbg 9 1.3.2 设置Windows XP调试执行 9 1.3.3 设置Vista调试执行 10 1.3.4 设置VMware的管道虚拟串口 11 1.3.5 设置Windows内核符号表 12 1.3.6 实战调试first 13 第2...

    深入分析Linux内核源码.chm

    10.5 编写内核模块 第十一章 设备驱动程序 11.1 概述 11.2 设备驱动基础 11.3 块设备驱动程序 11.4 字符设备驱动程序 第十二章 网络 12.1 概述 12.2 网络协议 12.3 套接字(socket) 12.4 套接字缓冲区(sk_buff) 12.5 ...

    Linux 2.6内核标准教程(部分)

    然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析;在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这...

    Linux2.6内核标准教程(共计8-- 第1个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    寒江独钓-Windows内核安全编程 中文版

    本书从Windows内核编程出发,全面系统地介绍了串口、键盘、磁盘、文件系统、网络等相关的Windows内核模块的编程技术,以及基于这些技术实现的输入密码保护、防毒引擎、文件加密、网络嗅探、网络防火墙等信息安全软件...

    linux内核中的各种锁.pdf

    讲解了linux、内核中的各种锁的基础知识及使用,针对不同的使用场景,linux内核使用不同的方式来控制对共享资源的访问,掌握这方面的知识有助于内核模块的设计及驱动开发,可作为相关行业人士参考

    Linux2.6内核标准教程(共计8--第6个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux设备驱动程序开发基础(PPT)

    Linux设备驱动程序开发简介 Linux设备驱动程序结构 Linux设备驱动程序加载方式 实验:编写一个字符设备驱动程序(LED或蜂鸣器) 分别用静态编译,模块动态加载方法实现加入内核

    Linux2.6内核标准教程(共计8--第8个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    深入分析Linux内核源代码

    对Linux 内核2.4版的源代码进行了较全面的分析,既包括对中断机制、进程调度、内存管理、进程间通信、虚拟文件系统、设备驱动程序及网络子系统的分析,也包括对Linux 整体结构的把握、Linux的启动过程的分析及Linux...

    Linux2.6内核标准教程(共计8--第3个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux2.6内核标准教程(共计8--第7个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    寒江独钓——Windows 内核编程与信息安全(免费试读版)

    内容简介: 这本书是一本专门介绍实时扫描的防毒软件、虚拟磁盘、硬盘还原、...文件透明加密、防火墙、反外挂、反窃取密码等软件的Windows 内核模块开发的编程技 术书。这本书的读者需要有C 语言的基础。 作者: 楚狂人

Global site tag (gtag.js) - Google Analytics