Linux内核模块    Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备驱动的先决条件。

1.1linux内核模块简介

Linux内核的整体结构非常庞大,其包含的组件非常多。我们如何把需要的部分都包含在内核中呢? ●把需要的功能都编译到linux内核。 ●以模块方式扩展内核功能。

为了使学生对模块建立初步的感性认识,我们先来看一个最简单的内核模块”hello world”,代码如下: #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) {     printk("hello world\n”);     return 0; } static void hello_exit(void) {     printk(1  "hello module exit\n "); } module_init(hello_init); module_exit(hello_exit);

MODULE_AUTHOR("zky"); MODULE_DESCRIPTION("A simple  hello Module "); MODULE_VERSION("V1.0");

这个最简单的内核模块只包含内核加载函数、卸载函数和对Dual BSD/GPL许可权限的声明以及一些描述信息。编译会产生hello.ko目标文件,通过”insmod ./hello.ko”命令可以加载它,通过”rmmod hello”命令可以卸载它,加载时输出”hello world”, 卸载时输出”hello module exit”,查看输出信息可通过dmesg命令。

内核模块中用于输出的函数式内核空间的printk()而非用户空间的printf(),printk()的用法和printf()相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段。

printk有8个loglevel,定义在<linux/kernel.h>中: #define KERN_EMERG          "<0>"    /* system is unusable */ #define KERN_ALERT          "<1>"    /* action must be taken immediately */ #define KERN_CRIT             "<2>"    /* critical conditions */ #define KERN_ERR               "<3>"    /* error conditions */ #define KERN_WARNING      "<4>"    /* warning conditions */ #define KERN_NOTICE         "<5>"    /* normal but significant condition */ #define KERN_INFO             "<6>"    /* informational */ #define KERN_DEBUG          "<7>"    /* debug-level messages */

未指定优先级的默认级别定义在/kernel/printk.c中: #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */

当优先级的值小于console_loglevel这个整数变量的值,信息才能显示出来。而console_loglevel的初始值DEFAULT_CONSOLE_LOGLEVEL也定义在/kernel/printk.c中: #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */

在linux系统中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系,例如:

lsmod命令实际上读取并分析/proc/modules文件,与上述lsmod命令结果对应的/proc/modules文件如下:

内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行”tree -a”得到如下目录树:

Modprobe命令比insmod命令要强大,它在加载某模块时会同时加载该模块所依赖的其他模块。使用modprobe命令加载的模块若以”modprobe –r  filename”的方式卸载将同时其他依赖的模块。

使用modinfo <模块名>命令可以获得模块的信息,包括模块的作者、模块的说明、模块所支持的参数以及vermagic,如下所示:

1.2模块的编译

接下来我们来简单看看模块是如何构造的,模块的构造和用户空间应用程序的构造过程有很大的不同。实际上对本章先前给出的”hello world”示例来说,下面一行就足以了: Obj-m  := hello.o

如果大家熟悉make但对2.6内核构造系统还不熟悉的话,则可能会对此makefile的工作方式感到疑惑。毕竟上面这行并不是makefile文件的常见格式。问题的答案就是内核的构造系统处理了其余的问题。上面的赋值语句说明了又一个模块需要从目标文件hello.o中构造,而从目标文件中构造的模块名称为hello.ko。

如果我们要构造的模块名称为module.ko,并有两个源文件生成(比如file1.c和file2.c),则正确的makefile可如下编写: Obj-m  := module.o Module-objs :=file1.o file2.o

为了让上面这种类型的makefile文件正常工作,必须在大的内核构造系统环境中调用它们。如果内核源码保存在~/kernel-2.6目录中,则用来构造模块的make命令应该为(在包含模块源代码和makefile的目录中键入): Make –C  ~/kernel-2.6 M=`pwd` modules

上述命令首先改变目录到-C选项指定的位置(既内核源代码目录),其中保存有内核的顶层makefile文件,M=选项让该makefile在构造modules目标之前返回到模块源代码目录。然后modules目标指向obj-m变量中设定的模块;在上面的例子中,我们将该变量设置成了module.o。

上面这样的make命令还是有些烦人,因此内核开发者又开发了一种makefile方法,这种方法将使得内核树之外的模块构造变得更加容易,其技巧就是用下面的方法来编写makefile: #如果已定义KERNELRELEASE,则说明是从内核构造系统调用的,因此可利用其内建语句。 Ifneq($(KERNELRELEASE),) obj-m :=hello.o #否则,是直接从命令行调用的,这时要调用内核构造系统 Else KERNELDIR  ?=~/KERNEL-2.6 PWD  := $(shell pwd) Default: $(MAKE) –C $(KERNELDIR) M=$(PWD) modules endif

需要注意的是上面的makefile文件并不完整;一个真正的makefile文件应该包含通常用来清除无用文件的目标,安装模块的目标等等。

1.3linux内核模块的结构

一个linux内核模块主要由以下几个部分组成: ●模块加载函数(必须)     当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块相关初始化工作。 ●模块卸载函数(必须)     当通过rmmod命令卸载模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。 ●模块许可证声明(必须)     模块许可证(LICENCE)声明描述内核模块的许可权限,如果不声明LICENCE,模块被加载时将收到内核被污染的警告。

在2.6内核中,可接受得LICENSE包括“GPL”、“GPL v2”、“GPL and additional right”、“Dual BSD/GPL”、“Dual MPL/GPL和“Proprietary”。

大多数情况下,内核模块应遵循GPL兼容许可权。Linux2.6内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”)语句声明模块采用BSD/GPL双LICENSE。 ●模块参数(可选) 模块参数是模块被加载的时候可以被传递给他的值,它本身对应模块内部的全局变量。 ●模块导出符号(可选) 内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。 ●模块作者等信息声明(可选)。

1.4模块加载函数

Linux内核模块加载函数一般以__init标识声明,典型的模块加载函数的形式如下: Static  int  __init  initialization_function(void) {      //初始化代码 } Module_init(initialization_function);

模块加载函数必须以“module_init(函数名)”的形式指定。它返回整形值,若初始化成功,应返回0。而在初始化失败时。应该返回错误编码。在linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。

在linux2.6内核中,所有标识为__init的函数在连接的时候都会放在.init.text(这是module_init宏在目标代码中增加的一个特殊区段,用于说明内核初始化函数的所在位置)这个区段中,此外,所有的__init函数在区段.initcall.init中还保存着一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后释放init区段(包括.init.text和.initcall.init等)。所以大家应注意不要在结束初始化后仍要使用的函数上使用这个标记。

1.5模块卸载函数

Linux内核卸载模块函数一般以__exit标识声明,典型的模块卸载函数的形式如下: Satic void __exit cleanup_function(void) {    //释放代码 } Module_exit(cleanup_function);

模块卸载函数在模块卸载时被调用,不返回任何值,必须以”module_exit(函数名)”的形式来指定。

一般来说,模块卸载函数完成与模块加载函数相反的功能: ●如果模块加载函数注册了 XXX模块,则模块卸载函数应注销XXX。 ●若模块加载函数动体申请了内存,则模块卸载函数应释放该内存。 ●若模块加载函数申请了硬件资源,则模块卸载函数应释放这些硬件资源。 ●若模块加载函数开启了硬件,则模块卸载函数应关闭硬件。

和__init一样__exit也可以使对应函数在运行完成后自动回收内存。

1.6模块参数

我们可以用”module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了一个整形参数和一个字符指针参数: Static  char *book_name = “linux 模块”; Static   int  num = 4000; Module_param(num, int, S_IRUGO); Module_param(book_name, charp, S_IRUGO);

在装载内核模块时,用户可以向模块传递参数,形式为”insmod (或 modprobe) 模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的默认值。

参数类型可以是byte、short、ushort、int、uint、long、ulong、charp、bool、或invbool(布尔的反),在模块被编译时会将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。

模块被加载后,在/sys/module/目录下将出现以此模块命名的目录。当“参数读/写权限”为0时,表示此参数不存在sysfs文件系统下对应的文件节点,如果此模块存在“参数读/写权限”不为0的命令行参数,在此模块的目录下还将出现parameters目录,包含一系列以参数名命名的文件节点,这些文件的权限值就是传入module_param()的“参数读/写权限”,而文件的内容为参数的值。

现在我们定义一个包含两个参数的模块,并观察模块加载时被传递参数和不传递参数时的输出。 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");

static char *book_name = "dissecting Linux Device Driver"; static int num = 4000;

static int book_init(void)
{ printk(KERN_INFO " book name:%s\n",book_name); printk(KERN_INFO " book num:%d\n",num); return 0; } static void book_exit(void) { printk(KERN_INFO " Book module exit\n "); } module_init(book_init); module_exit(book_exit); module_param(num, int, S_IRUGO); module_param(book_name, charp, S_IRUGO);

MODULE_AUTHOR("zky"); MODULE_DESCRIPTION("A simple Module for testing module params"); MODULE_VERSION("V1.0");

对上述模块运新“insmod  book.ko”命令加载,相应输出都为模块内的默认值,通过查看“/var/log/messages”日志文件可以看到内核的输出,如下所示:

当用户运行“insmod book.ko book_name=’mybook’ num=3000”命令时,输出的是用户传递的参数,如下所示:

1.7导出符号

Linux2.6的/proc/kallsyms文件对应着内核符号表,它记录了符号以及符号所在的内存地址。模块可使用如下宏导出符号到内核符号表: EXPORT_SYMBOL(符号名); EXPORT_SYMBOL_GPL(符号名);

导出的符号将可以被其他模块使用,使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块。如下代码给出了一个导出整数加、减运算函数符号的内核模块的例子。 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");

int add_integar(int a,int b) { return a+b;
}

int sub_integar(int a,int b) { return a-b; }

EXPORT_SYMBOL(add_integar); EXPORT_SYMBOL(sub_integar);

从/proc/kallsyms文件中找出add_integar、sub_integar相关信息:

1.8模块声明与描述

在linux模块中,我们可以使用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明模块的作者、描述、版本、设备表和别名,例如: MODULE_AUTHOR(author); MODULE_DESCRIPTION(description); MODULE_VERSION(version); MODULE_DEVICE_TABLE(device table); MODULE_ALIAS(alternate_name);

对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,如下所示:

1.9模块与GPL

对于自己编写的驱动等内核代码,如果不编译为模块则无法绕开GPL,编译为模块后企业在产品中使用模块,则公司对外不需要提供对应的源代码,为了使国内公司产品所使用的Linux操作系统支持模块,需要完成如下工作。 ●在内核编译时应该选上”Enable loadble module support”,嵌入式产品一般不需要动态卸载模块,所以“可以卸载模块”不用选,当然选了也没有关系,如图:

如果有项目被选择”M”,则编译时除了make bzImage以外,也要make modules. ●将我们编译的内核模块.ko文件放置在目标文件系统的相关目录中。 ●产品的文件系统中应该包含了新内核的insmod、lsmod、rmmod等工具。 ●在使用中用户可使用insmod 命令手动加载模块,如insmod xxx.ko

转载于:https://www.cnblogs.com/sn-dnv-aps/archive/2012/11/04/2754276.html

Linux内核模块学习笔记(转载)相关推荐

  1. linux内核模块开发(笔记),Linux内核模块学习笔记

    ########## Makefile ########## obj-m := modname.o 编译完会有modname.ko 当前目录下必须有modname.[c/s/S]文件 obj-m的值有 ...

  2. linux系统管理学习笔记之三----软件的安装

    linux系统管理学习笔记之三----软件的安装 2009-12-29 19:10:02 标签:linux 系统管理 [推送到技术圈] 版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 ...

  3. linux系统管理学习笔记之一-------linux解压缩命令

    linux系统管理学习笔记之一-------linux解压缩命令 2009-12-29 11:52:55 标签:linux tar [推送到技术圈] 版权声明:原创作品,允许转载,转载时请务必以超链接 ...

  4. linux系统管理学习笔记之八---进程与作业的管理

    linux系统管理学习笔记之八---进程与作业的管理 2010-01-05 13:00:42 标签:linux 进程 [推送到技术圈] 版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 ...

  5. linux系统管理学习笔记之八---linux文件与目录的管理及权限

    linux系统管理学习笔记之八---linux文件与目录的管理及权限 2010-01-05 09:00:49 标签:权限 管理 文件目录 linx [推送到技术圈] 版权声明:原创作品,允许转载,转载 ...

  6. 操作系统进程学习(Linux 内核学习笔记)

    操作系统进程学习(Linux 内核学习笔记) 进程优先级 并非所有进程都具有相同的重要性.除了大多数我们所熟悉的进程优先级之外,进程还有不同的关键度类别,以满足不同需求.首先进程比较粗糙的划分,进程可 ...

  7. linux 内核模块开发,Linux内核模块开发(笔记)

    Linux内核模块开发(笔记) 作者:扶凯 来源: 扶凯 时间:2011-11-21 00:21:11 人气:249 评论:0 标签: 内核 个人笔记..在不放过来都快找不到了.有空还得好好整理一下了 ...

  8. 韩顺平Linux教程学习笔记

    Linux系统学习笔记   新装了deepin v23系统,结果磁盘没设置好,玩崩了,百度半天修复不了,看看韩顺平老师的Linux操作课程,做做笔记(只记录对自己有用的).   B站网址 基础篇·Li ...

  9. Linux操作系统学习笔记【入门必备】

    Linux操作系统学习笔记[入门必备] 文章目录 Linux操作系统学习笔记[入门必备] 1.Linux入门 2.Linux目录结构 3.远程登录 3.1 远程登录Linux-Xshell5 3.2 ...

最新文章

  1. 修复 Windows XP/2003 双系统无法启动
  2. 5G 在轨道运输网络中的需求
  3. not optimal php,php环境配置 配置
  4. 【解决方法】Panda read_csv()把第一行的数据变成了列名,怎么处理
  5. 类成员指针和0x0地址转换
  6. Request_继承体系
  7. html中怎么写jq,用jQuery替换HTML页面中的文本
  8. python运算符中用来计算整商的是什么_零基础学python,看完这篇文章,你的python基础就差不多了...
  9. java context.xml_java-context.xml的解释
  10. JavaScript中常用变量介绍
  11. 通用算法(ML,DL)分类判定指标:召回率 Recall、精确度Precision、准确率Accuracy等
  12. linux下离线安装gcc详细教程
  13. matlab下载ar人脸库,AR ar人脸数据库,经典的 用于 检测与识别。 Graph Recognize 图形/文字 274万源代码下载- www.pudn.com...
  14. 瀑布流插件vue-masonry(使用和踩坑心得)适合Vue脚手架开发(适用于Vue2)
  15. C++ 完全不完全资源导引
  16. 通俗易懂理解几何光学(六)光学系统的像质评价
  17. 物联卡需要实名认证吗?物联网卡实名认证有什么用?
  18. python中print函数的输出问题(空格,制表符)
  19. unity3D学习10 AR/MR技术
  20. 摄影中的微距镜头是什么?

热门文章

  1. MATLAB 中怎么求图像在水平方向和垂直方向的像素和,用图表示
  2. Robert算子的运用
  3. oracle12c多个pdb,Oracle 12c 多租户专题|12cR2中PDB内存资源管理
  4. Java中static的作用详解_java中static作用详解
  5. hourglass论文_DSSD(1)_论文_arxiv2017
  6. git push 推送大文件失败的处理办法
  7. ACM_变形课(并查集)
  8. Linux不停往外发包
  9. 敏捷转型历程 - Sprint3 回顾会
  10. MySQL知识点链接