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调度器. 内存管 ...
随机推荐
- 【English】二、It作为代词,可以代指什么
it的用法 一.用作人称代词: 1.指代事物: — What’s this? — It’s a cat.2.指代人:常用于不知对方性别时,比如:询问敲门人或打电话时询问对方是谁,或者用来指代婴儿. ( ...
- PJSUA2开发文档--第四章 端点ENDPOINT
4.端点ENDPOINT Endpoint类是一个单例类,应用程序必须在此类实例之前创建一个并且最多只能创建一个,然后才能执行任何操作.同样,一旦这个类被销毁,应用程序就不能调用该库的任何API.这个 ...
- Oracle的ORA-02292报错:违反完整性约束,已找到子记录
第一种方法: 第一步就是找到子表的记录: select a.constraint_name, a.table_name, b.constraint_name from user_constraints ...
- Linux系统下virtuoso数据库安装与使用
最近在调研关联数据的一些东西,需要用到rdf数据库,所以接触了virtuoso数据库.安装的坑其实并不多,之前在windows 10上安过一次.这次在ubuntu 18.04上安装一下,其他的linu ...
- Leaflet实现动态线路
一.引用Leaflet脚本样式,和Leaflet Ant Path 插件 下载地址: Leaflet:https://leafletjs.com/download.html Leaflet Ant P ...
- LVS+Keepalived实现mysql的负载均衡
1 初识LVS:Linux Virtual Server 1.1 LVS是什么 LVS是Linux Virtual Server的简称,也就是Linux虚拟服务器, 是一个由章文嵩博士发起 ...
- Python简单多进程demo
''' 多线程使用场景: 怎样用Python的多线程提高效率? io操作不占用CPU 计算操作占用CPU Python多线程不适合CPU操作密集型的任务,适合io操作密集型的任务 如果有CPU操作密集 ...
- 【English】20190312
tokens记号 [ˈtoʊkən] delimiter characters分隔符字符 [dɪ'lɪmɪtə] [ˈkærɪktɚs] argument论据主题[ˈɑ:rgjumənt] ...
- 在 Xshell 中 使用 hbase shell 进入后 无法删除
在 Xshell 中 使用 hbase shell 进入后 无法删除 问题: 在hbase shell下,误输入的指令不能使用backspace和delete删除,使用过的人都知道,这是有多坑,有多苦 ...
- kernel笔记——内核编译与进程管理
内核与操作系统 由于一些商业操作系统设计上的缺陷以及日益庞杂,“操作系统”的概念对很多人而言变得含糊不清.在进一步讨论Linux内核的话题前,我们先区分“内核”与“操作系统”这两个概念. 操作系统:指 ...