模块

  • 1 构建模块
    • 放在内核源代码树中
    • 放在内核代码外
  • 2 安装模块
  • 3 产生模块依赖性
  • 4 载入模块
  • 5 管理配置选项
  • 6 模块参数
  • 7 导出符号表

Linux内核是模块化组成的,它允许内核在运行时动态地向其中插入或从中删除代码。

与开发的内核核心子系统不同,模块开发更接近编写新的应用程序,因为至少要在模块文件中具有入口点和出口点。

下面是hello_world内核模块

#include  <linux/init.h>
#include  <linux/module.h>
#include <linux/kernel.h>static int hello_init(void)
{printk(KERN_ALERT "I bear a charmed life.\n");return 0;
}static void hello_exit(void)
{printk(KERN_ALERT "Out,out,brief candle !\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shakespeare");

hello_init()函数是模块的入口点,它通过module_init()注册到系统中,在模块装载时被调用。调用module_init()实际上不是真正的函数调用,而是一个宏调用,它唯一的参数便是模块的初始化函数。模块的所有初始化函数必须符合下面的形式:

int my_init(void);

因为init函数通常不会被外部函数直接调用,所以不必导出该函数,故它可被标记为static类型。init函数会返回一个int型数据,如果初始化顺利完成,那么它的返回值为0,失败的话,返回一个非0值。

hello_exit()函数是模块的出口函数,它由module_exit()注册到系统,在模块从内存卸载时,内核便会调用hello_exit()。在退出函数返回后,模块就被卸载了。

退出函数必须符号以下形式:

void my_exit(void);

MODULE_LICENSE()宏用于指定模块的版权,如果载入非GPL模块到系统内存,则会在内核中设置被污染标志。MODULE_AUTHOR()宏指定代码作者,完全是用作信息记录目的。

1 构建模块

在2.6内核中,由于采用了新的kbuild构建系统,现在构建模块相比从前更加容易,构建过程中的第一步是决定在哪里管理模块代码,你可以把模块源码加入到内核源代码树中,也可以在内核源码树外维护构建模块源码。

放在内核源代码树中

最理想的情况莫过于模块正式成为Linux内核的一部分,这样就会被存放在内核源代码树中。

首先你要清楚你的模块应该在内核源代码树中何处。设备驱动程序存放在内核源码树根目录drivers/的子目录下,在drivers内部,设备驱动文件被进一步细分。如字符设备存在于drivers/char/目录下,而块设备存放在drivers/block/目录下,USB设备则存放在drivers/usb/目录下。

假设你有一个字符设备,而且希望放在drivers/char/目录下,那么你要注意,在该目录下同时会存在大量的C源代码文件和其他目录。所以对于仅仅只有一两个源文件的设备驱动程序,可以直接存放在该目录下。如果驱动程序包含许多源文件和其他辅助文件,那么可以创建一个新子目录。

假设你想创建自己代码的子目录,你的驱动程序是一个钓鱼杆和计算机的接口,名为Fish Master XL 2000 Titanium,那么你应在drivers/char/目录下建立一个名为fishing的子目录。现在你需要项drivers/char/下的Makefile文件中添加一行。编辑drivers/char/Makefile加入:

obj-m += fishing/

这行编译指令告诉模块构建系统在编译模块时需要进入fishing/子目录中。更可能发生的情况是,你的驱动程序的编译取决于一个特殊配置选项,比如,可能的CONFIG_FISHING_POLE。如果这样,你需要用下面的指针替代刚才那条指令:

obj-$(CONFIG_FISHING_POLE) += fishing/

最后,在drivers/char/fishing/下,需要添加一个新的Makefile文件,其中需要有下面这样:

obj-m += fishing.o

此刻构建系统运行就将会进入fishing/目录下,并且将fishing.c编译为fishing.ko模块,虽然拓展名是.o,但是模块被编译后的拓展名是.ko。
要是你的钓鱼杆驱动程序编译时有编译选项,那么你可能需要这么来做:

obj-$(CONFIG_FISHING_POLE) += fishing.o

如果喜欢把源文件置于drivers/char/目录下,并且不建立新目录。那么你要做的便是将前面提到的行(也就是原来处于drivers/char/fishing/下你自己的Makefile中的)都加入到drivers/char/Makefile中。

开始编译吧,运行内核构建过程,如果模块编译取决于配置选项,比如有CONFIG_FISHING_POLE约束,那么在编译前首先要确保选项被允许。

放在内核代码外

如果你喜欢脱离内核源代码树来维护和构建你的模块,那么你要做的就是在你自己的源代码树目录建立一个Makefile文件,它只需要一行指令:

obj-m := fishing.o

就可以把fishing.c编译成fishing.ko。

模块在内核内或内核外构建的最大区别 在于构建过程。当你的模块在内核源代码树外时,你必须告诉make如何找到内核源代码文件和基础的Makefile文件。

make -C /kernel/sourece/location SUBDIRS=$PWD modules

/kernel/source/location是你以配置的内核源码树。

2 安装模块

编译后的模块将被装入到目录/lib/modules/version/kernel/下。比如,如果使用的是2.6.10内核,而且你将你的模块源代码直接放在drivers/char/下,那么编译后的钓鱼杆驱动程序的存放路径是:/lib/modules/2.6.10/kernel/drivers/char/fishing.ko

下面的构建命令用来安装编译的模块到合适的目录下:

make modules_install

3 产生模块依赖性

Linux模块之间存在依赖性,也就是说钓鱼模块依赖鱼饵模块,那么当载入钓鱼模块时,鱼饵模块会被自动载入,这里需要的依赖信息必须事先生成。若想产生内核依赖关系的信息,root用户可运行命令:

depmod

为了执行更快的更新操作,可以只为新模块生成依赖信息,而不是生成所有的依赖关系,这时root用户可运行命令:

depmod -A

模块依赖关系信息存放在/lib/modules/version/modules.dep文件中

4 载入模块

载入模块最简单的方法是通过insmod命令,这是个功能很有限的命令,它的作用就是请求内核载入你指定的模块。insmod程序不执行任何依赖性分析或进一步的错误检查,它用法简单,以root运行命令:

insmod module

需要载入的模块名称由参数module指定,比如装载钓鱼杆模块,那你就执行命令:

insmod fishing

卸载一个模块,你可使用rmmod命令,同样用root身份执行:

rmmod module

比如,rmmod fishing将卸载钓鱼杆模块。

系统为我们提供了一个更先进的工具modprobe,它提供了模块依赖性分析,错误智能检查,错误报告以及许多其他功能和选项,推荐用这个命令:

modprobe module [module parameters]

module指定需要载入的模块名称,后面的参数将在模块加载时传入内核。modprobe命令不但会加载指定的模块,而且会自动加载任何它所依赖的有关模块。所以说它是加载模块的最佳技术。

modprobe命令也可用来从内核中卸载模块,当然这也需要root身份运行。

modprobe -r modules

参数modules指定一个或多个需要卸载的模块,与rmmod命令不同,modprobe也会卸载给定模块所依赖的相关模块,前提是这些相关模块没有被使用。

5 管理配置选项

2.6内核中新引入了kbuid系统,加入一个新配置选项是很容易的。你所需做的全部就是向Kconfig文件中添加一项,用于对应内核源码树。对驱动程序而言,Kconfig通常和源码处于同一目录。如果钓鱼杆驱动程序子drivers/char/下,那么你便会发现drivers/char/Konfig同时存在。
如果你建立了一个新子目录,而且也希望Kconfig文件存在于该目录中的话,那么必须在一个已存在的Kconfig文件中将它引入

source "drivers/char/fishing/Kconfig"

可以很方便地在Kconfig文件中加入一个配置选项,请看钓鱼杆模块的选项如下所示:

配置选项第一行定义了该选项所代表的配置文件,注意CONFIG_前缀不需要写上。这个就是构建模块时,在Makefile里加入的特殊配置选项CONFIG_FISHING_POLE。
第二行声明选项类型为tristate,也就是说被编译进内核(Y),也可作为模块编译(M),或干脆不编译它(N),选Y,CONFIG_FISHING_POLE的值就是Y,依次类推。如果编译选项代表的是一个系统功能,而不是一个模块,那么编译选项将用bool代替tristate,这说明它不允许被编译成模块。处于指令之后的引号内文件为该选项指定了名称。
第三行指定了该选项的默认选择,这里默认操作是不编译它。

help指令是为该选项提供帮助文档。

除了上述选项外,还存在其他选项。

6 模块参数

Linux允许驱动程序声明参数,从而用户可以在系统启动或者模块装载时再指定参数值,这些参数对于你的驱动程序属于全局变量,模块参数同时也将出现在sysfs文件系统中。
定义一个模块参数可通过宏module_param()完成:

module_param(name,type,perm);

参数name既是用户可见的参数名,也是模块中存放模块参数的变量名。参数type则存放参数的类型,最后一个参数perm指定了模块在sysfs文件系统下对应文件的权限,该值可以是八进制格式,比如0666,或是S_Ifoo的定义形式,比如S_IRUGO|S_IWUSR,如果该值为0,则表示禁止所有的sysfs项。

上面的宏并没有定义变量,你必须在使用该宏前进行变量定义。通常使用类似下面的语句完成定义。

static int allow_live_bait = 1;
module_param(allow_live_bait,bool,0666);

有可能模块的外部参数名称不同于它对应的内部变量名称,这是就该使用宏module_param_named()定义了:

module_param_named(name,variable,type,perm);

参数name是外部可见的参数名称,参数variable是参数对应的内部全局变量名称。比如:

static unsigned int max_text = DEFAULT_MAX_LINE_TEST;
module_param_named(maximum_line_test,max_test,int ,0)

其他宏:

module_param_string(name,string,len,name);   /* 拷贝字符串到指定的字符数组 */
module_param_array(name,type,nump,perm);    /* 接受逗号分割的参数序列 */
module_param_array_named(name,array,type,nump,perm);    /* 将内部参数数组命名区别于外部参数 */

上述所有宏被定义在linux/moduleparam.h文件中。

7 导出符号表

模块被载入后,就会动态连接到内核。注意,它与用户空间中的动态连接库类型,只有当被显式导出后的外部函数,才可以被动态库调用。在内核中,导出内核内核函数需要使用特殊的指令:
EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()。

导出的内核函数可以被模块调用,而未导出的函数模块则无法使用。导出的内核符号表被看做是导出的内核接口,甚至称为内核API。

导出符号相当简单,在声明函数后紧跟上EXPORT_SYMBOL()指令就搞定了,比如:

int get_priate_beard_color(void)
{return pirate->beard->color;
}
EXPORT_SYMBOL(get_priate_beard_color);

假定get_priate_beard_color同时也定义在一个可访问的文件中,那么任何模块现在都可以访问它。

有一些开发者希望自己的接口仅仅对GPL兼容的模块可见,内核连接器使用MODULE_LICENSE()宏可满足这个要求,如果你希望先前的函数仅仅对标记为GPL协议的模块可见,那么你就需要用:

EXPORT_SYMBOL_GPL(get_priate_beard_color);

如果你的代码被设置为模块,那么就必须确保它被编译为模块时所用的全部接口已被导出,否则就会产生连接错误(而且模块不能成功编译)。

Linux内核设计与实现---模块相关推荐

  1. 《Linux内核设计与实现》读书笔记(十七)- 设备与模块

    本章主要讨论与linux的设备驱动和设备管理的相关的4个内核成分,设备类型,模块,内核对象,sysfs. 主要内容: 设备类型 内核模块 内核对象 sysfs 总结 1. 设备类型 linux中主要由 ...

  2. 《Linux内核设计与实现》读书笔记 - 目录 (完结)

    读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个<Linux内核设计与实现>读书笔记 的目录: < ...

  3. Linux内核设计与实现学习笔记目录

    **注:**这是别人的笔记,我只是把目录抄过来 <Linux内核设计与实现学习笔记> 1.<Linux内核设计与实现>读书笔记(一)-内核简介 2.<Linux内核设计与 ...

  4. 《linux内核设计与实现》读书笔记第一、二章

    第一章 Linux内核简介 1.1 Unix的历史 1971年,Unix被移植到PDP-11型机中.  1973年,Unix操作系统用C语言改写--为Unix系统的广泛移植铺平了道路.  1977年, ...

  5. Linux内核设计的艺术

    Linux内核设计的艺术这本书是我认为对Linux内核描述非常优秀的书籍.书籍中描述了内核启动的流程,内核运行的机理,内存管理,进程管理等等. #书籍目录 第1章 从开机加电到执行 main函数之前的 ...

  6. 初探内核之《Linux内核设计与实现》笔记下

    定时器和时间管理 系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要. 主要内容: 系统时间 定时器 定时器相关 ...

  7. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单. 2. 高 ...

  8. 《Linux内核设计与实现》读书笔记 - 目录 (完结)【转】

    转自:http://www.cnblogs.com/wang_yb/p/3514730.html 读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!! ...

  9. Linux内核设计与实现

    <Linux内核设计与实现>读书笔记 目录: <Linux内核设计与实现>读书笔记(一)-内核简介 <Linux内核设计与实现>读书笔记(二)- 内核开发的准备 & ...

最新文章

  1. 今日头条成功的核心技术秘诀是什么?深度解密个性化资讯推荐技术 本文作者:AI研习社 2017-07-05 12:24 导语:从“内行”的角度解密个性化资讯推荐技术。 雷锋网按:本文系知名 IT 技术资
  2. 苹果双系统运行oracle失败,oracle 11gR2 RAC for linux x86_64 grid运行root.sh 失败问题处理...
  3. MyBatis映射文件1(增删改、insert获取自增主键值)
  4. 从资源管理器中,获取被选择的文件的路径(及文件夹)的API
  5. Python3系列__01Python安装
  6. UI设计干货模板|首页设计技巧
  7. Spring中利用java注解声明切面
  8. C#调用第三方ocx控件 (winform /aspx)
  9. 在使用Navicat for MySQL 出现 异常“You have an error in your SQL syntax......“
  10. MIT App Inventor使用与入门教程
  11. Process Explorer
  12. Oracle 19c 安装教程
  13. 开源又好用的录屏软件
  14. 铁威马NAS使用docker安装全网音乐教程
  15. KchmViewer linux kde下出色一个chm阅读器
  16. pvs-stdio ue4_PlatformIO中的PVS-Studio集成
  17. 解决IE浏览器打印iframe页面时字体等样式缩小的问题
  18. FxFactory 7 Pro for Mac(fcpx/ae/pr视觉特效插件包)v7.2.5
  19. linux常用命令语句(全)
  20. linux文件分隔符

热门文章

  1. python的总结与心得词云设计理念_1 Python文本分析——词云分析篇
  2. 关于 iOS开发者账号提示必须更新绑定手机号的解决方法
  3. 国际智能制造联盟筹备会成功召开
  4. Ubuntu 安装配置
  5. 工作5年,我的互联网工具箱(30个提升办公效率的神器)
  6. 计算机辅助设计没交作业,计算机辅助设计作业要求
  7. invalid token XXX
  8. unable to qualify my own domain name
  9. Win7任务栏右下角图标空白、点击无反应问题的解决
  10. Blender着色器:使用手绘遮罩进行材质叠加