2014年(5)
分类: LINUX
2014-01-28 14:18:48
原文地址:Linux内核设计与实现(17)--设备与模块 作者:leon_yu
关于设备驱动和设备管理,Linux主要有四种内核成分
设备类型:在所有Unix系统中为了统一普通设备的操作所采用的分类。
模块:Linux内核中用于按需加载和卸载目标码的机制。
内核对象:内核数据结构中支持面向对象的简单操作,还支持维护对象之间的父子关系。
sysfs: 表示系统中设备树的一个文件系统。
1.设备类型
在Linux以及所有Unix系统中,设备分为三种类型:块设备,字符设备,网络设备。
块设备通常以块为单位寻址,可以随机访问。块设备通过成为“块设备节点”的特殊文件来访问,并且通常被挂载为文件系统。
字符设备是不可寻址的,进提供数据的流式访问,通过“字符设备节点”的特殊文件访问,与块设备不同的是,应用程序通过直接访问设备节点与字符设备交互。
网络设备,提供对网络的访问,这是通过一个物理适配器和一种特定协议进行的。网络设备打破了Unix的“所有东西都是文件”的设计原则,它不是通过设备节点访问的,而是通过套接字API这样的特殊接口访问。
Linux还提供了不少其他设备类型,比如杂项设备,是为简化字符设备架构而创建的设备类型。
并不是所有设备驱动都表示物理设备,有些设备驱动是虚拟的,仅提供访问内核的功能,成为“伪设备”,常见的有内核随机数发生器(通过/dev/random/和/dev/urandom访问),空设备(/dev/null),满设备(/dev/full),内存设备(/dev/mem)。
2.模块
尽管Linux是单块内核的操作系统—整个系统运行于一个单独的保护域中,但是Linux内核是由模块组成的。Linux允许内核在运行时动态地向其中插入或从中删除代码。
支持模块的好处是:基本内核镜像可以尽可能的小,因为可选的功能和驱动程序可以利用模块形式提供,模块允许我们方便地删除和载入内核,也方面了调试工作。
2.1hello world模块
点击(此处)折叠或打开
hello_init()函数是模块的入口点,通过module_init()宏注册到系统中,在内核装载时被调用。
init函数返回一个整型的数值,如果初始化顺利完成,那么返回0,否则返回非零值。
在实际模块中,init函数一般会注册资源,初始化硬件,分配数据结构等。
Hello_exit()函数是模块的出口函数,由module_exit()注册到系统,在模块从内核中卸载时调用。
MODULE_LICENSE()宏用于指定模块的版权,如果载入非GPL模块系统内存,则会在内核中设置被污染标识。
MODULE_AUTHOR()和 MODULE_DESCRIPTION(),分别指定了代码作者信息和模块的简述,它们完全是用作信息记录目的。
2.2 构建模块
2.6内核中,采用了新的“kbuild”构建系统,构建模块有两种方式:
(1)放在内核源码树中
首先要清楚你的模块应放在内核源码树中的什么地方,设备驱动代码一般都在/drivers子目录下,在其内部,设备驱动文件被进一步按照类别,类型等更有序地组织起来。如字符设备存于drivers/char/目录下,块设备放在drivers/block/目录下,USB设备放在drivers/usb/目录下。文件的具体组织并不须绝对墨守成规,不容打破,比如很多USB设备也属于字符设备。
但无论如何,一个准则是,组织关系应该更容易理解.
实例1:在drivers/char/下添加源码为test-lec.c文件
先将test-led.c文件拷贝到drivers/char/目录下
然后在Kconfig文件中添加LED的配置选项
点击(此处)折叠或打开
然后在Makefile文件中添加
点击(此处)折叠或打开
实例2:在drivers/char目录下新增加一个fishing目录
①在fishing目录下,应该包含如下Kconfig文件
点击(此处)折叠或打开
②这个Kconfig文件要起作用,必须在drivers/char/Kconfig里添加以下内容
点击(此处)折叠或打开
③在fishing目录下包含以下Makefile文件
点击(此处)折叠或打开
如果有多个文件依赖,可以写成
点击(此处)折叠或打开
④fishing父目录的Makefile文件爱你需要增加
点击(此处)折叠或打开
(2)放在内核代码外
把代码放在内核树外,作为一个独立的模块,只需要加你一个Makefile文件即可
点击(此处)折叠或打开
如果多个文件,则
点击(此处)折叠或打开
编译是还必须告诉make如何找到内核树
make –C /kernel/source M=$(PWD) modules
2.3 安装模块
编译后的模块将被装入到目录/lib/modules/version/kernel下,在kernel/目录下的每一个目录都与内核源码树中的模块位置对应。
make modules_install
构建命令,安装编译的模块到合适的目录下
2.4加/卸载模块
insmod fishing.ko //加载fishing.ko模块到内核
rmmod fishing //卸载fishing模块
modeprobe module [module parameters] //加载模块的同时,传入param参数, modeprobe命令还可以分析模块依赖性和错误检查
modprobe –r module //卸载module,并且卸载所有module依赖的模块
2.5到处符号表
模块被加载后,就会被动态地连接到内核,它与用户空间中的动态链接库类似,只有当被显式导出后的外部函数,才可以被动态库调用。
核心代码在内核中可以调用任意非静态接口,因为所有的核心源代码文件被链接成了同一个镜像。而模块代码,必须显示导出,到处的内核符号表被看做导出的内核接口,甚至成为内核API。
点击(此处)折叠或打开
如果你的代码被配置为模块,那么就必须确保当它被编译为模块时,它所有的接口都要已被导出,否则就会产生链接错误。
3.设备模型
2.6内核引入新的统一设备模型,设备模型提供了一个独立的机制专门表示设备及其在系统中的拓扑结构,具有以下优点:
1)代码重复最小化;
2)提供诸如引用计数这样的统一机制;
3)可以枚举系统中所有设备,观察它们的状态,并且查看它们的链接总线;
4)可以将系统中全部设备结构以树的形式完整、有效地展现出来(包括所有总线和内部链接)
5)可以是设备和其对应的驱动联系起来,反之亦然;
6)可以将设备按照类型加以归类,比如分类为输入设备等,而无需理解物理设备的拓扑结构;
7)可以沿设备树的叶子向其根的方向依次遍历,以确保能以正确顺序关闭各设备电源;
最后一点,其实就是设备模型设计的动机,若想在内核中实现智能的电源管理,就必须建立系统中设备拓扑关系的树结构。
3.1 kobject
kobject是设备模型的核心部分,由struct kobject结构体表示,可以理解为类似C++中的基类,它本身很少单独使用,一般是嵌套如其他的数据结构中
点击(此处)折叠或打开
3.2ktype
kobject被关联到一种特殊的类型,ktype.由kobj_type结构体表示
点击(此处)折叠或打开
3.3 kset
kset是kobject对象的集合体,ktype描述相关类型kobject所共有的特性,具有相同ktype的kobject可以被分组到不同的kset.
点击(此处)折叠或打开
3.4 kobject, ktype和kset的相互关系
kobject本身意义不大,通常它是需要被嵌入到其他数据结构中,让哪些包含它的结构具有kobject的特性。
kobject与一个特别的ktype对象关联,ktype定义了一些kobject相关的默认特性:西沟行为,sysfs行为以及别的一些默认属性。
kobject又归入了kset的集合,kset提供了两个功能:第一,其中嵌入的kobject作为kobject组的基类;第二,kset将相关的kobject集合在一起。在sysfs中,这些相关的kobject将以独立的目录出现在文件系统中。
关于kobject,ktye, kset, 驱动,总线,设备的关系详细参考另外的博文:
4.sysfs文件系统
sysfs文件系统是一个处于内存中的虚拟文件系统,它为我们提供恶劣Kobject对象层次结构的视图。借助属性对象,kobject可以用导出文件的方式,将内核变量提供给用户读写。sysfs代替了先前的处于/proc下的设备相关文件.
sysfs的诀窍是把kobject对象与目录项加密联系起来,这是通过kobject对象中的dentry字段实现的。通过连接kobject到指定的目录项上,方便地将kobject映射到目录上,从而把kobject到处形成文件系统如同在内存中构建目录项一样简单。
4.1 sysfs中添加和删除kobject
添加一个kobject目录
点击(此处)折叠或打开
或者
点击(此处)折叠或打开
删除一个kobject目录
点击(此处)折叠或打开
4.2 向syffs中添加文件
(1)默认属性: kobject被映射为文件目录,那里面的文件是什么?
默认的文件集合是通过kobject和kset的ktype字段提供的,所有相同类型的kobject在他们对应的sysfs目录下都有相同的默认文件集合, kobj_type含有一个字段—default_attrs,它是一个atrribute结构体数组,这些属性负责将内核数据映射成sysfs中的文件。
点击(此处)折叠或打开
name字段就是该属性的名字,也就是出现在sysfs中的文件名;
default_attrs列出了默认的属性,sysfs_ops字段描述了如何使用它们,
点击(此处)折叠或打开
当从用户空间读取sysfs的项时调用show()方法,它会拷贝attr提供的属性值到buffer缓冲区,缓冲区大小PAGE_SIZE;
而store()方法在写操作时调用,它会从buffer中读取size大小的字节,并将其存放入attr表示的属性结构体变量中。
(2)创建新属性
有时在一些特殊情况下,会碰到特殊的object实例,需要有自己的属性,因此内核在默认集合之上,再添加新属性而提供了sysfs_create_file()接口
点击(此处)折叠或打开
这个接口通过attr参数指向相应的attribute结构体,kobj指定了属性所在的kobject对象。Kobject对应的sysfs_ops操作将负责处理新属性,现有的show()和store()方法必须能够处理新属性。
还可以创建符号链接
点击(此处)折叠或打开
(3)删除新属性
点击(此处)折叠或打开
(4)sysfs约定
当前的sysfs文件系统可以替换一些设备节点上实现ioctl的功能,但必须遵从一下约定:
①sysfs属性应该保证每个文件只导出一个值,或者同一类型多个值,而且应该映射为简单的C类型,避免数据的国度结构化或太凌乱;
②sysfs要以一个清晰的层次组织数据,kobject相关属性统一需要正确;
③sysfs提供内核到用户空间的服务,用户程序可以检测和获得其存在性,位置,取值以及sysfs目录和文件的行为。任何情况下都不应改变现有的文件,另外更改给定的属性;
这些简单的约定保证sysfs可为用户空间提供丰富和直观的接口。
4.3 内核事件层
2.6版本以后的系统,需要一种机制来将事件传出内核输送到用户空间,特别是对桌面系统而言,内核事件层实现了内核到用户的消息通知系统- - 建立在kobject基础上的。
内核事件层是把事件模拟为信号---从明确的kobject对象发出,所以每个事件源都是一个sysfs路径,每个事件都被赋予了一个动作或动作字符串表示信号。
在用户空间,实现一个系统后台服务用语监听套接字,处理任何读到的信息,并将时间传送到系统栈里,内核事由内核空间传递到用户空间需要经过netlink, netlink是一个用于传送网络信息的多点传送套接字。
在内核代码中向用户发送信号使用函数kobject_uevent()
int kobject_uevent(struct kobject *kobj, enum kobject_action action) ;
使用kobject和属性不但有利于很好的实现基于sysfs的事件,同时也有利于创建新kobjects对象和属性来表示新对象和数据(尚未出现在sysfs中)。