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的更多相关文章

  1. 《Linux Device Drivers》第十四章 Linux 设备型号

    基本介绍 2.6内核设备模型来提供的抽象叙述性描述的一般系统的结构,为了支持各种不同的任务 电源管理和系统关机 用户空间与通信 热插拔设备 设备类型 kobject.kset和子系统 kobject是 ...

  2. 《Linux Device Drivers》第十二章 PCI司机——note

    一个简短的引论 它给这一章总线架构的高级概述 集中访问讨论Peripheral Component Interconnect(PCI,外围组件互连)外设内核函数 PCI公交车是最好的支持的内核总线 本 ...

  3. 《Linux Device Drivers》 第十七章 网络驱动程序——note

    基本介绍 第三类是标准的网络接口Linux设备,本章介绍的内核,其余的交互网络接口描述 网络接口,必须使用特定的内核数据结构本身注册,与外部分组交换数据线打电话时准备 经常使用的文件上的网络接口操作是 ...

  4. 《Linux Device Drivers》第十五章 内存映射和DMA——note

    简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现 ...

  5. 《Linux Device Drivers》第十六章 块设备驱动程序——note

    基本介绍 块设备驱动程序通过主传动固定大小数据的随机访问设备 Linux核心Visual块设备作为基本设备和不同的字符设备类型 Linux块设备驱动程序接口,使块设备最大限度地发挥其效用.一个问题 一 ...

  6. 《Linux Device Drivers》第十八章 TTY驱动程序——note

    简单介绍 tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或虚拟终端 Linux tty驱动程序的核心紧挨在标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端 ...

  7. linux device drivers ch03

    ch03.字符设备驱动程序 编写驱动程序的第一步就是定义驱动程序为用户程序提供的能力(机制).接下来以scull(“Simple Character Utility for Loading Local ...

  8. 《Linux Device Drivers》第十章 中断处理——note

    概述:系统要及时的感知硬件的状态,通常有两种方式:一种是轮询.一种是通过响应硬件中断.前者会浪费处理器的时间,而后者不会. 准备并口 在没有节设定产生中断之前,并口是不会产生中断的 并口的标准规定设置 ...

  9. linux device drivers ch01

    ch01. 设备驱动程序简介 设备驱动程序的作用在于提供机制(需要提供什么功能),而不是提供策略(如何使用这些功能). 内核功能划分: 进程管理:进程创建.销毁.进程间通信.共享cpu调度器. 内存管 ...

随机推荐

  1. SQL语句(理论)

    1.SQL已经成为关系数据库的标准语言 2.SQL是一个非过程化的语言,因为他一次处理一个记录 3.SQL命令比较简单,最高级的命令几天之内便可掌握. 有属下类型的命令: 查询数据. 在表中插入.修改 ...

  2. iOS 防止UIButton重复点击

    使用UIButton的enabled或userInteractionEnabled 使用UIButton的enabled属性, 在点击后, 禁止UIButton的交互, 直到完成指定任务之后再将其en ...

  3. 闭包函数&回调函数

    闭包函数&回调函数 谈到回调函数,不得不提匿名函数;匿名函数,也叫闭包函数,也就是没有名字的函数,它可以单独存在,也可以将其赋值给某一个变量.so,先来看一下闭包函数. 闭包函数 php文档: ...

  4. Python操作db2

    官方文档:https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.5.0/com.ibm.db2.luw.apdv.python.doc/doc ...

  5. 【转】Android调用Sqlite数据库时自动生成db-journal文件的原因

    数据库为了更好实现数据的安全性,一半都会有一个Log文件方便数据库出现意外时进行恢复操作等.Sqlite虽然是一个单文件数据库,但麻雀虽小五脏俱全,它也会有相应的安全机制存在 这个journal文件便 ...

  6. Spring Security(三十二):10. Core Services

    Now that we have a high-level overview of the Spring Security architecture and its core classes, let ...

  7. 【Codeforces 1000F】One Occurrence

    题意:给一个序列,每次查询某个区间内一个只出现一次的数. 思路:线段树. 首先我们看只出现一次的本质是什么. 如果一个数\(x​\)在\((l,r)​\)中只出现了一次,那么它在其中第一次出现位置为\ ...

  8. sigsuspend()阻塞:异步信号SIGIO为什么会被截胡?

    关键词:fcntl.fasync.signal.sigsuspend.pthread_sigmask.trace events. 此文主要是解决问题过程中的记录,内容有较多冗余.但也反映解决问题中用到 ...

  9. 云端安装MQTT服务器

    如果自己下载的3.1版本的MQTT, 安装步骤参考 https://developer.emqx.io/docs/emq/v3/cn/install.html 配置用户名和密码第一种是用http ht ...

  10. 用python实现的一个自动聊天的机器人

    因为之前想过 如果每天早上微信能够发送天气预报给我,给我老婆多好,然后就动手看网上的教程做了一个可以定时发送天气预报的程序, 最近又想到折腾,做了一个更加详细的版本.但是需要主动操作 具体操作看图. ...