关于设备驱动和设备管理,内核中存在四种成分:
设备类型 | 描述 |
---|---|
块设备 | 缩写为blkdev,可寻址,以块为单位,块大小随设备不同而不同; 支持重定位操作(seeking,随机访问); 通过称为“块设备节点”的特殊文件进行访问,通常被挂载为文件系统。 |
字符设备 | 缩写为cdev,不可寻址,数据流式访问。 通过称为“字符设备节点”的特殊文件进行访问。 应用程序通过直接访问设备节点与字符设备交互。 |
网络设备 | 有时以以太网(ethernet devices)来称呼,提供了对网络的访问, 通过一个物理适配器(网卡)和一种特定的协议(ip协议)进行。 网络设备打破了Unix的“所有文件都是文件”的设计原则,不是通过设备节点来访问, 而是通过套接字API的特殊接口进行访问。 |
杂项设备 (miscellaneous device) |
简写为miscdev,实际上是个简化的字符设备;杂项设备使驱动程序开发很容易表示一个简单设备–实际上使对通用基本架构的一种折中。 |
伪设备 (pseudo device) |
虚拟的设备驱动,仅提供访问内核功能,如内核随机数发生器(/dev/random和/dev/urandom)、空设备(/dev/null)、零设备(/dev/zero)、满设备(/dev/full)、内存设备(/dev/mem)。 |
Q:何为模块?
A:Linux是模块化的,它允许内核在运行时动态地向其中插入或从中删除代码(相关的子例程、数据、函数入口和函数出口)被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中,简称模块。
支持模块的好处是基本内核镜像可以仅可能的小,因为可选的功能和驱动程序可以利用模块形式再提供。
如果驱动文件被静态地编译到内核映像中,那么退出函数将不被包含,而且永远都不会被调用。
MODULE_AUTHOR()宏和MODULE_DESCRIPTION()宏指定了代码作者和模块的简要描述,用作信息记录目的,无他目的。
在2.6内核中,采用了新的”kbuild“构建系统,构建过程的第一步是决定在哪里管理模块源码,方式如下:
Linux提供了这样一个简单框架–它可允许驱动程序声明参数,从而用户可以在系统启动或者模块装载时再指定参数值,这些参数对于驱动而言属于全局变量。
格式如下:
module_param(name, type, perm);
参数name是用户可见的参数名,也是模块中存放模块参数的变量名。
参数type存放参数的类型。(byte、short、int、ushort、uint、long、ulong、bool或invbool)。
参数perm指定了模块在sysfs文件系统下对于文件的权限,该值可以是八进制格式(0644),也可以是S_Ifoo的定义形式(S_IRUGO | S_IWUSR),如果该值是零,则表示禁止所有的sysfs项。
ps:模块参数同时也将会出现在sysfs文件系统种。
模块被载入后,就会动态地链接到内核,只有被显示导出后的外部函数,才可以被外部调用。
导出的内核函数可以被模块调用,而未导出的函数模块则无法被调用。
被导出的符号表所含的函数必然也要是非静态的。
导出的内核符号表被看作导出的内核接口(也可称为内核API),使用如下:
int getvalue()
{
return vaule;
}
EXPORT_SYMBOL(getvalue);
ps:
有些接口仅仅希望对GPL兼容的模块可见,内核连接器使用MODULE_LICENSE()宏可满足该要求 ;如果希望函数仅仅对标记为GPL协议的模块可见,EXPORT_SYMBOL_GPL()可满足该要求。
统一设备模型(device model)是2.6内核增加的一个引人注目的新特性。
设备模型提供了一个独立的机制专门来表示设备,并描述其在系统中的拓扑结构,从而使系统具有以下优点:
设备模型的核心部分就是kobject(kernel object),由struct kobject结构体表示,定义于头文件
kobject类似于C#或java这些面向对象语言中的对象(object)类,提供了诸如引用计数、名称和父指针等字段,可以创建对象的层次结构。
struct kobject {
const char *name; /* 指向此kobject的名称 */
struct list_head entry;
struct kobject *parent; /* 指向kobject的父对象,sysfs的真实面目:一个用户空间的文件系统,用来表示内核中kobject对象的层次结构 */
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd; /* 指向sysfs_dirent结构体,该结构体在sysfs中表示的就是这个kobject */
struct kref kref; /* 提供引用计数 */
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
kobject通常是嵌入其他结构中,如定义于
/*cdev struct 表示该对象代表一个字符设备*/
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
当kobject被嵌入到其他结构中时,该结构拥有了kobject提供的标准功能。
嵌入kobject的结构体可以成为对象层次架构中的一部分。
ktype(kernel object type)是kobject对象被关联到一种特殊的类型。
ktype由kobj_type结构体表示,定义于头文件
struct kobj_type {
void (*release)(struct kobject *kobj); /* release 指针指向在kobject引用计数减至零时要被调用的析构函数,该函数负责释放所有kobject使用的内存和其他相关清理工作 */
const struct sysfs_ops *sysfs_ops; /* sysfs_ops变量指向sysfs_ops结构体,该结构体描述了sysfs文件读写时的特性 */
struct attribute **default_attrs; /* 指向一个attribute结构体数组,定义了该kobject相关的默认属性,属性描述了给定对象的特征,该数组的最后一项必须为NULL */
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
Q:ktype存在的意义?
A:ktype的存在是为了描述一族kobject所具有的普遍特性。这样不再需要每个kobject都分别定义自己的特性,而是将这些普遍的特性在ktype结构体中一次定义,然后所有“同类”的kobject都能共享一样的特性。
kset可把kobject集中到一个集合中,而ktype描述相关类型kobject所共有的特性,它们之间的重要区别在于:具有相同ktype的kobject可以被分组到不同的kset。
kobject的kset指针指向相应的kset集合。
kset集合由kset结构体表示,定义于头文件
struct kset {
struct list_head list; /* list连接该集合(kset)中所有的kobject对象 */
spinlock_t list_lock; /* list_lock是保护这个链表中元素的自旋锁 */
struct kobject kobj; /* kobj指向的kobject对象代表了该集合的基类 */
const struct kset_uevent_ops *uevent_ops; /* 指向一个结构体,用于处理集合中kobject对象的热插拔操作,uevent就是用户事件(user event),提供了与用户空间热插拔信息进行通信的机制 */
};
kobject:由struct kobject表示,引入诸如引用计数、父子关系和对象名称等基本对象,以一个统一的方式提供这些功能。通常需要被嵌入到其他数据结构中,给与那些包含该它的结构具有kobject的特性。
ktype:kobject对象被关联到一种特殊类型即是ktype(实际也是kobject)。ktype由struct kobj_type结构体表示,在kobject中ktype字段指向该对象。ktype定义了一些kobject相关的默认特性:析构行为(反构造功能)、sysfs行为(sysfs的操作表)以及别的一些默认属性。
kset:kset集合可以将kobject归入,kset集合由struct kset结构体表示。提供如下两个功能:
通常多数时候,驱动程序并不必直接处理kobject,因为kobject是被潜入到一些特殊类型结构体中,而且会由相关的设备驱动程序在“幕后”管理。
Q:如何管理和操作kobject?
A:
声明和初始化kobject,通过函数kobject_init进行初始化,定义在文件
void kobject_init(struct kobject *obj, struct kobj_type *ktype);
kobj:需要初始化的kobject对象,再调用初始化函数前,该对象必须清空,使用memset()可以完成清空工作;
ps:大多数清空下,通常使用 kobject_create()完成上述多步操作(初始化,清空等)。
引用计数系统被初始化后,kobject的引用计数设置为1。
只要引用计数不为零,那么该对象就会继续保留在内存中。
任何包含对象引用的代码首先要增加该对象的引用计数,当代码结束后则减少它的引用计数。
增加引用计数称为获得(getting)对象的引用,减少引用计数称为释放(putting)对象的引用。
当引用计数跌到零时,对象便可以被撤销,同时相关内存也都被释放。
Q:如何操作引用计数?
A:
递增引用计数:
struct kobject * kobject_get(struct kobject *kobj);
递减引用计数:
void kobject_put(struct kobject *kobj); /* 如果对于的kobject的引用计数减少到零,则于该kobject管理的ktype中的析构函数将被调用 */
kobject的引用计数是通过kref结构体实现的,该结构体定义在文件
struct kref {
atomic_t refcount; /* 存放引用计数的原子变量 */
};
Q:kref结构体里就一个唯一的成员变量,为啥采用结构体?
A:因为在使用kref前,必须先对kref进行类型检测,检测的函数为kref_init(struct kref *kref);
kref的相关操作函数如下所示,声明在
函数 | 描述 |
---|---|
void kref_get(struct kref *kref) | 获得对kref的引用 |
int kref_put(struct kref *kref, void(*release)(struct kref *kref)) | 减少对kref的引用,每次调用引用计数减少1,如果减少至零,则要调用作为参数提供的release()函数 |
sysfs文件系统是一个处于内存中的虚拟文件系统,提供了kobject对象层次结构的视图。
sysfs代替了先前处于/proc下的设备相关文件,并且为系统对象提供了一个很有效的视图。
sysfs的诀窍是把kobject对象与目录项(directory entries)紧密联系起来,通过kobject对象中的dentry字段实现的。
sysfs文件系统挂载在sys目录下,sysfs的根目录包含了至少十个目录:block、bus、class、dev、devices、firmware、fs、kernel、module和power。
block目录下的每个子目录都对应着系统中的一个已注册的块设备;
bus目录提供了一个系统的总线视图;
class目录包含了以高层功能逻辑组织起来的系统设备视图;
dev目录是已注册设备节点的视图;
devices目录是系统中设备拓扑结构视图,直接映射出内核中设备结构体的组织层次;
firmware目录包含了一些如ACPI、EDD、EFI等低层子系统的特殊树;
fs目录是已注册文件系统的视图;
kernel目录包含内核配置项和状态信息;
module目录则包含系统已加载模块的信息;
power目录包含系统范围的电源管理数据;
ps:上述目录中最重要的是devices,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。其他目录中的很多数据将devices目录下的数据加以转换加工而得。
在初始化kobject之后是不能自动将其导出到sysfs中,想要把kobject导入sysfs,需要用到kobject_add()
int kobject_add(struct kobject *obj, struct kobject *parent, const char *fmt, ...);
kobject在sysfs中的位置取决于kobject在对象层次结构中的位置。
如果kobject的父指针被设置,那么在sysfs中kobject将被映射为其父目录下的子目录;
如果parent没有设置,那么kobject将被映射为kset->kobj中的子目录;
如果给定的kobject中的parent或kset字段都没有被设置,那么就认为kobject没有父对象,所以就会被映射成sysfs下的根级目录。
kobject的目录名字是由fmt指定的,它也接收printf()样式的格式化字符串。
ps:辅助函数kobject_create_and_add()把kobject_create()和kobject_add()所做的工作放在一个函数中:
struct kobject *kobject_create_and_add(const char *name,struct kobject *parent);
/*kobject_create_and_add函数接受直接的指针name作为kobject所对应的目录名称,而kobject_add函数使用printf()风格的格式化字符串*/
Q:如何从sysfs中删除一个kobject对应文件目录?
A:使用函数kobject_del()
void kobject_del(struct kobject *kobj);
kobject被映射成文件目录,所有的对象层次结构都映射成sys下的目录结构。
ps:
sysfs仅仅是一个树,没有提供实际数据的文件。
Q:文件从何而来?
A:
1.默认属性:
默认的文件集合是通过kobject和kset中的ktype字段提供的。
kobj_type字段含有一个字段–default_attrs,它是一个attribute结构体数组,这些属性负责将内核数据映射成sysfs中的文件。
attribute结构体定义在文件
struct attribute {
const char *name; /* 属性名称,出现在sysfs的文件名 */
umode_t mode; /* 权限:表示了sysfs中该文件的权限,如S_IRUGO等 */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
ps:
sysfs中的所有文件和目录的uid与gid标志均为零。
kobj_type字段中的sysfs_ops字段描述了如何使用default_attrs字段列出的默认属性,sysfs_ops字段指向了一个定义域文件
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *); /* 在读sysfs文件时该方法被调用 */
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); /* 在写sysfs文件时该方法被调用 */
const void *(*namespace)(struct kobject *, const struct attribute *);
};
2.创建新属性
通常由kobject相关ktype所提供的默认属性是充足的,但是有时候在一些特别清空下会碰到特殊的kobject实例需要有自己的属性,sysfs_create_file()接口满足了该需求。
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr); /* 成功返回零,失败返回错误码 */
attr:attr参数指向相应的attribute结构体;
kobj:kobj参数指定了属性所在的kobject对象。
Q:如何在sysfs中创建一个符号连接?
A:int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);
,该函数创建的符号连接名由name指定,连接则由kobj对应的目录映射到target指定的目录。如果成功函数返回零,如果失败返回负的错误码。
3.删除新属性
void sysfs_remove_file(struct kobject *kobj, const struct atrtribute *attr); /* 删除给定的kobj对应目录中的属性 */
void sysfs_remove_link(struct kobject *kobj, char *name); /* 删除kobj对应目录中的名为name的符号连接 */
4.sysfs约定
sysfs文件系统代替了以往需要由ioctl()(作用于设备节点)和procfs文件系统完成的功能。
这种代替避免了在调用ioctl()时使用类型不正确的参数和弄乱/proc目录结构。
因此为了保持sysfs干净和直观,需遵循以下约定:
内核事件层把事件模拟为信号–从明确的kobject对象发出,所以每个事件源都是要给sysfs路径。
每个事件都被赋予一个动词或者字符串表示信号。
每个事件都有一个可选的负载(payload),内核事件层使用sysfs属性代表负载。
从内部实现上来讲,内核事件层由内核空间传递到用户空间需要经过netlink。
netlink是一个用于传送网络信息的多点传送套接字。使用方法是用户空间实现一个系统后台服务用于监听套接字,处理任何读到的信息,并将事件传送到系统栈里。
Q:如何在内核代码中向用户空间发送信号?
A:
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
kobj:参数kobj指定发送该信号的kobject对象,实际的内核事件将包含该kobject映射到sysfs的路径;
action:参数action指定了描述该信号的“动作”或者“动词”。实际的内核事件将包含一个映射成枚举类型kobject_action的字符串,而且也消除了打字错误或其他错误。该枚举变量定义于文件
/*
* The actions here must match the index to the string array
* in lib/kobject_uevent.c
*
* Do not add new actions here without checking with the driver-core
* maintainers. Action strings are not meant to express subsystem
* or device specific properties. In most cases you want to send a
* kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
* specific variables added to the event environment.
*/
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};