linux device drivers ch02
ch02.构造和运行模块
模块的构造:
#include <linux/init.h>
#include <linux/module.h> MODULE_LICENSE("GPL"); static int hello_init(void)
{
printk("Hello,world\n");
return ;
} static void hello_exit(void)
{
printk("Goodbye,cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
Module Code
对于自己为模块创建的makefile文件:
obj-m :=hello.o表示有一个模块需要从目标文件hello.o中构造,而从该目标文件中构造的模块名称是hello.ko。
如果我们要构造的模块名称为module.ko,并由两个源文件生成(比如file1.c和file2.c),则正确的makefile可如下编写:
obj-m :=module.o
module-objs :=file1.o file2.o
构造模块的make命令应该是参照内核顶层目录的Makefiel文件指定的编译方法:
make -C $(KERNELDIR) M=$(PWD) modules
上述命令首先改变目录到-C选项指定的位置(即内核源码顶层目录),其中保存有内核的顶层makefile文件。M=选项让改makefile在构造modules目标之前返回到模块源代码目录。染回,modules目标指向obj-m变量中设定的模块。
完整的makefile:
ifeq ($(KERNELRELEASE),) //ifeq和括号之间需要有一个空格
//防止-C选项切回到内核后M=选项切回模
块所在目录重复执行相同语句
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd) modules:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
else
obj-m :=hello.o
endif
makefiel Code
注:在一个典型的构造过程中,上述makefile将被读取两次。
当从命令行执行make时,KERNERRELEASE变量尚未设置,通过-C切回到内核顶层目录,调用modules目标,由M=选项指定模块所在目录。当第二次读取makefile文件时,KERNELRELEASE在内核sourcetree目录已经有了定义,所以执行else语句部分,调用obj-m :=hello.o生成对应的hello.ko文件。
装载和卸载模块
加载模块:insmod
卸载模块:rmmod
insmod和ld有些类似,将模块的代码和数据装入内核,然后使用内核的符号表解析模块中任何未解析的符号。insmod可以接受一些命令行选项,并且可以在模块链接到内核之前给模块中的整型和字符串型变量赋值。
注;insmod如何工作?实际上它依赖于定义在kernel/module.c中的一个系统调用。函数sys_init_module给模块分配内核内存(函数vmalloc负责分配内存)以便装载模块,然后,该系统调用将模块正文复制到内存区域,并通过内核符号表解析模块中的内核引用,最后调用模块的初始化函数。(内核源码中只有系统调用的名字前带有sys_前缀)
lsmod程序列出当前装载到内核中的所有模块,还提供了一些其他信息,比如其他模块是不是在使用某个特定模块等。lsmod通过读取/proc/modules虚拟文件来获得这些信息(也可以在sysfs虚拟文件系统中/sys/module下找到)
如果模块装载失败,可以查看系统日志文件(/var/log/messages或者系统配置使用的文件),将看到导致模块装载失败的具体原因。
内核符号表
当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。通常情况下,模块只需要实现自己的功能,而无需导出任何符号。但是,如果其他模块需要从某个模块中获得“好处”时,我们可以导出符号供其他模块使用。(模块层叠技术)
如果一个模块需要向其他模块导出符号,使用:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name); 要导出的模块只能被GPL许可证下的模块使用。
同时,也需要在调用该符号的模块中使用export外部引用。
初始化和关闭
static int __init initialization_function(void)
{
/*初始化代码*/
}
module_init(initialization_function);
static:防止和内核中其他模块重名
__init:表明该函数仅在初始化期间使用,在模块装载完后,模块装载器将会把初始化函数扔掉,这样可将该函数占用的内存释放出来。
static void __exit cleanup_function(void)
{
/*清除代码*/
}
module_exit(cleanup_function);
清除函数没有返回值。
__exit:标记该代码仅用于模块卸载,如果不想只用于卸载函数,可以不加__exit。
初始化过程中的错误处理
当我们在内核中注册设施时,要时刻铭记注册可能会失败,即使是最简单的动作,都需要分配内存,而所需要的内存可能无法获得。因此模块代码必须始终检查返回值,并确保所请求的操作已真正成功。
如果模块的初始化出现错误之后,模块必须自行撤销已注册的设施。如果由于某种原因我们未能撤销已注册的设施,则内核会处于一种不稳定状态,这是因为内核中包含了一些指向并不存在的代码和内部指针。这种情况下,唯一有效的解决方法就是重新引导系统。因此,必须在初始化过程中出现错误时认真完成正确的工作。
goto语句进行错误处理:
不管初始化过程在什么时候失败,下面的例子(使用了虚构的注册和撤销注册函数)都能正确工作:
int __init my_init_function(void)
{
int err;
/*使用指针和名称注册*/
err = register_this(ptr1,"skul1");
if(err) goto fail_this;
err = register_that(ptr2,"skul2");
if(err) goto fail_that;
err = register_those(ptr3,"skul3");
if(err) goto fail_those;
return ; /*成功(写在标号前)*/ fail_those: unregister_that(ptr2,"skul2");
fail_that: unregister_this(ptr1,"skul1");
fail_this: return err; /*返回错误*/
/*标号倒序*/
}
Goto Code
这段代码准备注册三个(虚构的)设施。在出错的时候使用goto语句,它将只撤销出错时刻以前所成功注册的那些设施!(需要包含<linux/errno.h>)
列一种观点不支持goto的使用,而是记录任何成功注册的设施,然后在出错的时候调用模块的清除函数。清除函数将仅仅回滚以成功完成的步骤。然而这种替代方法需要更多的代码和cpu时间,因此在追求效率的代码中使用goto语句仍然是最好的错误恢复机制。
模块的清除函数需要撤销初始化函数所注册的所有设施,并且习惯上(但不是必须的)以相反于注册的顺序撤销设施:
void __exit my-cleanup_function(void)
{
unregister_those(ptr3,"skul3"); /*倒序注销*/
unregister_that(ptr2,"skul2");
unregister_this(ptr1,"skul1");
return;
}
cleanup Code
如果初始化和清除工作涉及到很多设施,则goto方法可能变得难以管理,因为所有用于清除设施的代码在初始化函数中重复,同时一些标号交织在一起。因此,有时候我们需要考虑重新构思代码结构。----每当发生错误时从初始化函数中调用清除函数,
这种方法将减少代码的重复并且使代码更清晰有条理。下面是这种方法的简单示例:
struct something *item1;
struct somethingelse *item2;
int stuff_ok; void my_cleanup(void) /*此时卸载函数不能加__exit*/
{
if(item1)
release_thing(item1);
if(item2)
release_thing2(item2);
if(stuff_ok)
unregister_stuff();
return;
} int __init my_init(void)
{
int err = -ENOMEM; /*err是一个负数*/ item1 = allocate_thing(arguments); /*分配函数*/
item2 = allocate_thing2(arguments2);
if(!item1 || !item2)
goto fail;
err = register_stuff(item1,item2); /*注册函数*/
if(!err)
stuff_ok = ;
else
goto fail;
return ; /*成功*/ fail:
my_cleanup();
return err;
}
goto引用清除函数 Code
这种方式的初始化能够扩展到对大量设施的支持,因此比前面的技术更具优越性。需要注意的是,因为清除函数被非退出代码调用,因此不能将清除函数标记为__exit。
注:在注册完成之后,内核的某些部分可能会立即使用我们刚刚注册的任何设施。话句话说,在初始化函数还在运行的时候,内核就完全可能会调用我们的模块,因此,在首次注册完成之后,代码就应该准备好被内核的其他部分调用==》在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施!
模块参数
模块的传参可在运行insmod或modprobe命令装载模块时传递。在insmod改变模块参数之前,模块必须让这些参数对insmod命令可见。参数必须使用module_param宏来声明(moduleparam.h)。
如:
static char *whom = "world";
static int howmany = 1;
module_param(howmany,int,S_IRUGO);
module_param(whom,char*,S_IRUGO);
参数1:变量的名称 变量2:类型 变量三:用于sysfs入口项的访问许可掩码 一般设置为0444
linux device drivers ch02的更多相关文章
- 《Linux Device Drivers》第十四章 Linux 设备型号
基本介绍 2.6内核设备模型来提供的抽象叙述性描述的一般系统的结构,为了支持各种不同的任务 电源管理和系统关机 用户空间与通信 热插拔设备 设备类型 kobject.kset和子系统 kobject是 ...
- 《Linux Device Drivers》第十二章 PCI司机——note
一个简短的引论 它给这一章总线架构的高级概述 集中访问讨论Peripheral Component Interconnect(PCI,外围组件互连)外设内核函数 PCI公交车是最好的支持的内核总线 本 ...
- 《Linux Device Drivers》 第十七章 网络驱动程序——note
基本介绍 第三类是标准的网络接口Linux设备,本章介绍的内核,其余的交互网络接口描述 网络接口,必须使用特定的内核数据结构本身注册,与外部分组交换数据线打电话时准备 经常使用的文件上的网络接口操作是 ...
- 《Linux Device Drivers》第十五章 内存映射和DMA——note
简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现 ...
- 《Linux Device Drivers》第十六章 块设备驱动程序——note
基本介绍 块设备驱动程序通过主传动固定大小数据的随机访问设备 Linux核心Visual块设备作为基本设备和不同的字符设备类型 Linux块设备驱动程序接口,使块设备最大限度地发挥其效用.一个问题 一 ...
- 《Linux Device Drivers》第十八章 TTY驱动程序——note
简单介绍 tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或虚拟终端 Linux tty驱动程序的核心紧挨在标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端 ...
- linux device drivers ch03
ch03.字符设备驱动程序 编写驱动程序的第一步就是定义驱动程序为用户程序提供的能力(机制).接下来以scull(“Simple Character Utility for Loading Local ...
- 《Linux Device Drivers》第十章 中断处理——note
概述:系统要及时的感知硬件的状态,通常有两种方式:一种是轮询.一种是通过响应硬件中断.前者会浪费处理器的时间,而后者不会. 准备并口 在没有节设定产生中断之前,并口是不会产生中断的 并口的标准规定设置 ...
- linux device drivers ch01
ch01. 设备驱动程序简介 设备驱动程序的作用在于提供机制(需要提供什么功能),而不是提供策略(如何使用这些功能). 内核功能划分: 进程管理:进程创建.销毁.进程间通信.共享cpu调度器. 内存管 ...
随机推荐
- 06-Nodejs介绍
06-Nodejs介绍 打开Nodejs英文网:https://nodejs.org/en/ 中文网:http://nodejs.cn/ 我们会发现这样一句话: 翻译成中文如下: Node.js 是一 ...
- shell脚本批量ssh登陆主机并执行命令
shell脚本批量ssh登陆主机并执行命令 今天在客户现场遇到了这个问题,客户没有管理工具,无法批量登陆主机下发命令,几个个C段啊,让我一个一个登陆,.................. 所以写了个s ...
- t-sql语句创建表(基础)
create table ta1 ( id int identity(1,2) not null, name nvarchar(20) not null, identify v ...
- SQLServer之修改索引
使用SSMS数据库管理工具修改索引 使用表设计器修改索引 表设计器可以修改任何类型的索引,修改索引的步骤相同,本示例为修改唯一非聚集索引. 1.连接数据库,选择数据库,选择数据表->右键点击表- ...
- Linux中删除特殊名称文件的多种方式
今日分享:我们在肉体的疾病方面花了不少钱,精神的病害方面却没有花什么,现在已经到了时候,我们应该有不平凡的学校.--<瓦尔登湖> 前言 我们都知道,在linux删除一个文件可以使用rm命令 ...
- 爬虫系列二(数据清洗--->正则表达式)
一 正则常识 模式 描述 \w 匹配字母数字及下划线 \W 匹配非字母数字及下划线 \s 匹配任意空白字符,等价于 [\t\n\r\f]. \S 匹配任意非空字符 \d 匹配任意数字,等价于 [0-9 ...
- Flink 的Window 操作(基于flink 1.3描述)
Window是无限数据流处理的核心,Window将一个无限的stream拆分成有限大小的”buckets”桶,我们可以在这些桶上做计算操作.本文主要聚焦于在Flink中如何进行窗口操作,以及程序员如何 ...
- poj 2255 Tree Recovery(求后序遍历,二叉树)
版权声明:本文为博主原创文章,未经博主同意不得转载.vasttian https://blog.csdn.net/u012860063/article/details/37699219 转载请注明出处 ...
- .NET CORE微服务中CONSUL的相关使用
.NET CORE微服务中CONSUL的相关使用 1.consul在微服务中的作用 consul主要做三件事:1.提供服务到ip的注册 2.提供ip到服务地址的列表查询 3.对提供服务方做健康检查(定 ...
- Linux下时钟框架实践---一款芯片的时钟树配置
关键词:时钟.PLL.Mux.Divider.Gate.clk_summary等. 时钟和电源是各种设备的基础设施,整个时钟框架可以抽象为几种基本的元器件:负责提供晶振 Linux内核提供了良好的CC ...