第二章 构造和运行模块(笔记)

1. 设置测试系统

如果读者正在编写一个只适用于某特定发行版的驱动程序,则应该针对相关内核创建和测试自己的驱动程序。
2.6.x内核构造模块,必须在自己的系统中配置并构造好内核树(因为2.6内核的模块要和内核源代码树中的目标文件连接)。先前的内核只需要一套内核头文件就够了。

2. Hello World模块

/*
 * MODULE_LICENSE用来告诉内核,该模块采用自由许可证;
 * moudule.h包含有可装载模块需要的大量符号和函数定义。
 * init.h包含初始化和清楚函数。
 */
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
 
static int hello_init(void)
{
    /*
     * 代码中的字符串KERN_ALERT定义了这条消息的优先级;
     * printk与printf函数功能类似,最大的不同在于对浮点数的支持。
     */
    printk(KERN_ALERT "Hello, World\n");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, cruel world\n");
}
 
/*
 * moudule_init和module_exit行使用了内核的特殊宏来表示上述两个函数所扮演的角色。
 */
 module_init(hello_init);
 module_exit(hello_exit);

3. 核心模块与应用程序的对比

a. 大多数小规模及中规模应用程序是从头到尾执行单个任务,而模块却只是预先注册自己以便服务于将来的某个请求,然后它的初始化函数就立即结束(事件驱动程序的应用程序和内核代码之间的另一个主要不同是:应用程序在退出时,可以不管资源的释放和其他的清除工作,但模块的退出函数却必须仔细撤销初始化函数所做的一切,否则,在系统重新引导之前某些东西就会残留在系统中)。
b. 应用程序可以调用它并未定义的函数,这是因为连接过程能够解析外部引用从而使用适当的函数库;而模块仅仅被链接到内核,因此它能调用的函数仅仅是由内核导出的那些函数,而不存在任何可链接的函数库。
c. 内核编程和应用程序编程的另外一点重要不同之处在于各环境下处理错误的方式不同:应用程序开发过程中的段错误是无害的,并且总是可以使用调试器跟踪到源代码的问题所在,而一个内核错误即使不影响整个系统,也至少会杀死当前进程。

内核空间和用户空间

模块运行在所谓的内核空间,而应用程序运行在所谓的用户空间中。
操作系统的作用是为应用程序提供一个对计算机硬件的一致视图。
在Unix当中,内核运行在最高级别(也称作超级用户态),这个级别中可以进行所有的操作。而应用程序运行在最低级别(及所谓的用户态)。

内核中的并发

有几方面的原因促使内核编程必须考虑并发问题。首先,Linux系统中通常正在运行多个并发进程,并且可能有多个进程同时使用我们的驱动程序。其次,大多数设备能够中断处理器,而中断处理程序异步运行,而且可能在驱动程序正试图处理其他任务时被调用。还有,Linux还可以运行在对称多处理器系统上,因此可能同时有不止一个CPU运行我们的驱动程序。最后,2.6内核代码已经是可抢占的,这意味着即使在单处理器系统上也存在许多类似多处理器系统的并发问题。
结果,Linux内核代码(包括驱动程序代码)必须是可重入的,他必须能够同时运行在多个上下文中。

当前进程

current指针指向当前正在运行的进程。

其他一些细节

应用程序在虚拟内存中布局,并且有一块很大的栈空间。
而相反的是,内核具有非常小的栈,他可能只和一个4096字节大小的页那样小。如果我们需要大的结构,则应该在调用时动态分配该结构。
内核API中具有两个下划线前缀(_ _)的函数名称通常是接口的底层组件。
内核代码不能实现浮点数运算。

4. 编译和装载

编译模块

# 如果已定义KERNELRELEASE,则说明是从内核构造系统调用的,
# 因此可以利用其内建语句。

ifneq ($(KERNELRELEASE),)
obj-m := hello.o

# 否则,是直接从命令行调用,
# 这是要调用内核构造系统。
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    pwd := $(shell pwd)
 
# 首先改变目录到-C选项指定的位置(即内核源代码目录),
# M=选项让该makefile在构造modules目标之前返回到模块源代码目录,
# modules目标指向obj-m变量中设定的模块。
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
    

转载和卸载模块

insmod将模块的代码和数据装入内核,然后使用内核的符号表解析模块中任何未解析的符号(与连接器不同,内核不会修改模块的磁盘文件,而仅仅修改内存中的副本)。
内核如何支持insmod工作的?实际上它依赖于定义在kernel/module.c中系统调用。函数sys_init_module给模块分配内核内存(函数vmalloc负责分配内存)以便装载模块,然后,该系统调用将模块正文复制到内存区域,并通过内核符号解析表解析模块中的内核引用,最后调用模块的初始化函数。
modprobe也用来将模块装载到内核中。它和insmod的区别在于,他会考虑要装载的模块是否引用了一些当前内核不存在的符号。
我们可以用rmmod工具从内核中移除模块。

版本依赖

平台依赖


5. 内核符号表

公共内核符号表中包含了所有的全局内核项(即函数和变量)的地址,这是实现模块化驱动程序所必须的。当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。
模块层叠技术(并口驱动程序的模块层叠技术)
模块层叠技术在复杂的项目中非常有用。新模块可以使用由我们自己的模块导出的符号,这样我们可以在其他模块上层叠新的模块。
Linux内核头文件提供了一个方便的方法来管理符号对模块外部的可见性,从而减少了可能造成的名字空间污染(名字空间的名称可能会和内核其他地方定义的名称发生冲突),并且适当隐藏信息。如果一个模块需要向其他模块导出符号,则应该使用下面的宏。
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
__GPL版本使得导出的模块只能被GPL许可证下的模块使用。符号必须在模块文件的全局部分导出,不能在函数中导出,这是因为上面这两个宏将被扩展为一个特殊变量的声明,而该变量必须是全局的。改变量将在模块可执行文件的特殊部分(即一个“ELF”段)中保存,在装载时,内核通过这个段来找模块导出的变量。    

6. 预备知识

MODULE_LICENSE
MODULE_AUTHOR
MODULE_DESCRIPTION
MODULE_VERSION
MODULE_ALIAS
MODULE_DEVICE_TABLE

7. 初始化和关闭

初始化函数应该被声明为static,因为这种函数在特定文件之外没有其他意义。
_ _init标记对内核来讲是一种暗示,表明该函数仅在初始化期间使用。
module_init的使用是强制性的。这个宏会在模块的目标代码中增加一个特殊的段,用以说明内核初始化函数所在的位置没有个定义初始化函数永远不会被调用。
能够注册的设施类型包括串口、杂项设备、sysfs入口、/proc文件、可执行域以及线路规程(line discipline)等。很多可注册的设施所支持的功能属于”软件抽象“范畴,而不与任何硬件直接相关。

清除函数

_ _exit修饰词标记该代码仅用于模块卸载(编译器将把该函数放在特殊的ELF段中)。module_exit声明对于帮助内核找到该模块的清楚函数是必须的。

初始化过程中错误处理

如果在发生了某个特定类型的错误后无法继续装载模块,则要将出错之前的任何工作撤销。如果由于某种原因我们未能撤销已注册的设施,则该内核会处于一种不稳定状态,这是因为内核包含了一些指向并不存在的代码的内部指针。
处理错误时我们可以使用goto语句。

模块装载竞争

在首次注册完之后,代码就应该准备好被内核的其他部分调用;在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施。
当初始化失败而内核的某些部分已经使用了模块所注册的某个设施时,则应该仔细处理内核其他部分正在进行的操作,并且要等待这些操作的完成。

8. 模块参数

可以使用下面的命令行来装载该模块:
insmod hellop howmany=10 whom=”Mom“

static char* whom = “world”;
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);

要生命数组参数,需要使用下面的宏:
module_param_array(name, type, num, perm);
name是数组的名称,type是数组元素的类型,num是一个整数变量,而perm是常见的访问许可值。

内核支持的模块参数类型如下:bool、invbool、charp、int、long、short、uint、ulong、ushort

9. 在用户空间编写驱动程序

用户空间编写驱动程序的有点和缺点归纳:

10. 快速参考


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值