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. centos7查看可登陆用户

    一.命令 cat /etc/passwd | grep -v /sbin/nologin | cut -d : -f 1 cat /etc/passwd | grep   /bin/bash | cu ...

  2. asp.net网页上获取其中表格中的数据(爬数据)

    下面的方法获取页面中表格数据,每个页面不相同,获取的方式(主要是正则表达式)不一样,只是提供方法参考.大神勿喷,刚使用了,就记下来了. 其中数据怎么存,主要就看着怎么使用了.只是方便记录就都放在lis ...

  3. 【PAT】B1014 福尔摩斯的约会

    因为前面两字符串中第 1 对相同的大写英文字母(大小写有区分)是第 4 个字母D,代表星期四: 第 2 对相同的字符是 E ,那是第 5 个英文字母,代表一天里的第 14 个钟头(于是一天的 0 点到 ...

  4. centos后台运行Python

    在服务器上,为了退出终端,程序依然能够运行,需要设置程序在后台运行. 关键的命令:nohup *基本用法:进入要运行的py文件目录前 nohup python  -u test.py > tes ...

  5. LeetCode算法题-Find Mode in Binary Search Tree(Java实现)

    这是悦乐书的第246次更新,第259篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第113题(顺位题号是501).给定具有重复项的二叉搜索树(BST),找到给定BST中的 ...

  6. LeetCode算法题-Max Consecutive Ones(Java实现)

    这是悦乐书的第242次更新,第255篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第109题(顺位题号是485).给定二进制数组,找到此数组中连续1的最大数量.例如: 输 ...

  7. 个人对JS原型链的一些理解(prototype、__proto__)

    前言 在我一开始学习java web的时候,对JS就一直抱着一种只是简单用用的心态,于是并没有一步一步地去学习,当时认为用法与java类似,但是在实际web项目中使用时却比较麻烦,便直接粗略了解后开始 ...

  8. 数据可视化的开源方案: Superset vs Redash vs Metabase (一)

    人是视觉动物,要用数据把一个故事讲活,图表是必不可少的.如果你经常看到做数据分析同事,在SQL客户端里执行完查询,把结果复制/粘贴到Excel里再做成图表,那说明你的公司缺少一个可靠的数据可视化平台. ...

  9. Oracle 查询表对应的索引

    select col.table_owner "table_owner", idx.table_name "table_name", col.index_own ...

  10. 一文搞懂Raft算法

      raft是工程上使用较为广泛的强一致性.去中心化.高可用的分布式协议.在这里强调了是在工程上,因为在学术理论界,最耀眼的还是大名鼎鼎的Paxos.但Paxos是:少数真正理解的人觉得简单,尚未理解 ...