第9章 linux设备驱动模型
9.1 sysfs文件系统
sysfs文件系统可以取代ioctl的功能.
sysfs文件系统的初始化发生在linux系统的启动阶段:
函数将向系统沆一个类型为sysfs_fs_type的文件系统, sysfs_fs_type的定义为:
时向用户空间提供接口及展示系统中各种设备的拓展视图等.
sysfs_get_sb函数用来产生sysfs文件系统的超级块, 其内部的主要函数是sysfs_fill_super,后者经过一系列
的函数调用链进入到sysfs_init_inode, 在后者里边, 函数将为sysfs文件系统中每个文件或者目录所对应的
inode赋予一个新的操作对象:
sysfs文件系统是基于RAM实现的文件系统, 编译内核时指定了CONFIG_SYSFS选项, 这个文件系统才会包含到内
核中. 对用户程序来说sysfs的标准挂载点是"/sys"目录, 挂载命令为:
mount -t sysfs sysfs /sys
9.2 kobject和kset
9.2.1 kobject
为kobject内核对象定义的属性成员.
对象创建属性文件使用的函数为sysfs_create_file:
使用这个函数时, 必须确保kobj对象之前已经加入了sysfs, sysfs_add_file将在kobj->sd对应的目录下生成
一个属性文件.
当用户空间open这件创建的文件时, 最终会调用到sysfs_open_file:
如果要删除一个属性文件, 应用sysfs_remove_file.
9.2.3 kset
kset可以认为是一组kobject的集合, 是kobject的容器. kset本身也是一个内核对象, 所以需要内嵌一个kob
ject对象.
spinlock_t list_lock: 对kset上的list链表进行访问操作时用来作为互斥保护使用的自旋锁.
struct kobject kobj: 代表当前kset内核对象的kobject变量.
const struct kset_uevent_ops *uevent_ops: 当kset中的某此kobject对象发生了状态变化需要通知用户空
间时, 调用其中的函数来完成.
. kset_register: 用来初始化并向系统注册一个kset对象. kset对象本身就是一个由kobject代表的内核对象,
所以kobject_add_internal函数会为代表该kset对象的k->kobj在sysfs文件树中生成一个新目录.
kset与单个kobject不一样的地方在于, 将一个kset向系统注册时, 如果linux内核编译时启用了CONFIG_HOTP
LUG, 那么需要将这一事件通知用户空间, 这个过程由kobject_uevent完成. 如果kobject对象不属于任一kset
, 那么这个孤立的kobject将无法通过uevent向用户空间发送消息.
. kset_create_and_add: 动态产生一个kset对象然后将其加入到sysfs文件系统中.
. kset_unregister: 用来将k指向的kset对象从系统中注销, 完成的是kset_register的反向操作.
9.2.4 热插拨中的uevent和call_usermodehelper
热插拨指当一个设备动态加入到系统时, 驱动可以检查到这种设备状态的变化, 然后通过某种机制使用在用户
空间找到该设备对应的驱动程序模块并加载.
linux中有两种机制可以实现: 1, udev, 2, /sbin/hotplug 之前一直是hotplug, 它的幕后推手是call_user
modehelper, 能从内核空间启动一个用户空间的应用程序. 随着内核的发展, udev逐渐取代了hotplug.
热插拨在内核中通过kobject_uevent函数来实现. 它通过发送一个uevent消息和调用call_usermodehelper来
与用户空间进行沟通. kobject_uevent所实现的功能和linux系统中用以实现热插拨的特性息息相关.
第1部分用到kset->uevent_ops, 调用其中的filter函数,以决定kset对象当前状态的改变是否要通知到用户层
,不同的kset有不同的uevent_ops对象, 意味着不同的kset有自己独特的uevent_ops操作集. 总之,kset对象状
态的变化, 将会首先调用该kset对象的uevent_ops操作集中的filter函数, 决定是否向用户报告.
第2部分主要完成环境变量的设置, 一开始分配一个存储环境变量的窨env, 接下来把用户空间程序需要的环境
变量通过add_uevent_var加入到env中. 处理完环境变量后, 调用kset对象的uevent_ops操作集中的uevent函
数, 这是内核赋予kset通过该函数完成自己特定功能的最后一次机会.
第3部分是kobject_uevent_env的亮点, 主要用来和用户空间进程进行交互. 在linux内核中, 有两种方式完成
这项任务, 一个是由CONFIG_NET宏包含的部分,这部分代码通过netlink的方式向用户空间广播kset对象中的u
event消息. 另一种方式是在内核空间启动一个用户空间的进程, 通过给该进程传递内核设定的环境变量的方
式来通知用户空间kset对象中的uevent事件.
uevent_helper方法通过调用call_usermodehelper来达到从内核空间运行一个用用户空间进程的目的, 用户
空间进程的二进制文件路径由uevent_helper提供.
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
CONFIG_UEVENT_HELPER_PATH是一内核编译阶段的配置宏, 依赖于CONFIG_HOTPLUG, 意味着如果系统需要支持
设备的执插拨等特性, 则需要给出用户空间进程的文件路径信息. 通常CONFIG_UEVENT_HELPER_PATH会指向/s
bin/hotplug, 后者用来处理系统中出现的热插拨事件, 不过现在多半没有这个文件了.
call_usermodehleper的实现
INIT_WORK(&sub_info->work, __call_usermodehelper);
其中sub_info是struct subprocess_info类型的变量, 工作队列节点是它的一个内嵌对象,sub_info用来存储
运行用户态进程的一些相关信息, 主要是相关的环境变量.
__call_usermodehelper是该工作节点上的延迟执行的函数.
将call_usermodehelper_setup中建立的工作节点提交到工作队列的行为发生在call_usermoehelper_exec函
数中.
函数内首先是khelper_wq, 这是一个工作队列, 其创建发生在linux系统初始化阶段:
延迟函数__call_usermodehelper的同步, 函数通过queue_work将工作节点提交到khelper_wq之后, 将等待在
wait_for_completion(&done)上, 当__call_usermodehelper执行完毕后, 会通过complete来唤醒睡眠的exec
函数.
__call_usermodehelper中的kernel_thread来生成一个新的进程, kernel_thread的调用将会导致__call_use
rmodehelper中出现两条执行路径, 一是父进程,二是子进程, 父进程在调用kernel_thread后直接返回,而子
进程则需要等到首次被调度的机会才会从kernel_thread返回,因此函数接下来出现了三个case来处理父子进程
间同步问题.
根据传参我们的wait是UMH_WAIT_EXEC, 所以注意这个函数最后的complete, 它将唤醒睡眠的call_usermodeh
elper_exec函数.
__call_usermodehelper执行完毕后, 所在进程将会因为do_exit的调用而从系统中消失掉. kernel_execve
用来在内核空间运行一个用户空间的进程, 路径存放在sub_info->path中, 环境变量由sub_info->argv和sub
_info->envp来提供.
重要的参数是系统调用号, linux用eax寄存哭保存系统调用号, 在嵌入的汇编代码中, __NR_execve的值将写
入eax寄存器, 那就是系统调用号, 这个系统调用号对应的sys_execve. 然后进入do_execve, 将执行filena
me所对应的进程文件, 这个过程和在shell里执行一个可执行文件是一样的.
.......这个意思是说在内核空间去执行一个用户空间程序的方法是通过系统调用来执行的.
9.3 总线, 设备与驱动
9.3.1 总线及其注册
struct bus_attribute *bus_attrs: 总线的属性, 包括操作这些属性的一组函数.
struct device_attribute *dev_attrs: 挂到该总线上的设备的属性, 功能逻辑与总线属性一样.
struct driver_attribute *drv_attrs: 挂到该总线上的驱动的属性, 功能逻辑与总线属性一样.
int (*match)(struct device *dev, struct device_driver *drv): 总线用来对试图挂到其上的设备和驱动
执行的匹配操作. 除了这个函数, 还定义了其它一些操作函数.
const struct dev_pm_ops *pm: 总线上一组跟电源管理相关的操作集, 用来对总线上的设备进行电源管理.
struct subsys_private *p: 一个用来管理其上设备与驱动的数据结构.
指向bus_kset, 也就是说bus_kset是系统中所有bus内核对象的容器,而新注册的bus本身也是一个kset型对象.
而新注册的bus本身也是一个kset型对象. drivers_kset表示该bus上所有驱分的一个集合,devices_kset则表
示该bus上所有设备的一个集合. klist_device和klist_drivers分别表示该bus上所有设备与驱动的链表.
drivers_autoprobe表示当向系统中注册某一设备或驱动时, 是否进行设备与驱动的绑定操作. *bus指向与st
ruct bus_type_private对象相关联的bus.
linux内核中针对总线的一些主要操作有:
.buses_init 揭示了总线在系统中的起源, 在系统初始化阶段, 通过buses_init函数为系统中后续的bus操作
奠定了基础.
kset中有状态变化时, 用来通知用户空间uevent消息的操作集. 这个操作集只定义了一个filter操作, 意味
着当"bus" kset中发生状态变化时, 会通过bus_uevent_ops中的filter函数先执行, 以决定是否通知用户.
文件系统的根目录下建立一个bus目录, 这就是系统中所有后续注册总线的祖先.
. bus_register: 用来向系统中注册一个bus.
bus_sysfs_ops中的操作主要是用来显示或设置当前注册的bus在sysfs文件系统中的属性.
kset_register用来将当前操作的bus所对应的kset加入到sysfs文件系统树中, 因为priv->subsys.kobj.pare
nt = NULL且priv->subsys.kobj.kset = bus_kset, 所以当前注册的bus对应的kset的目录将建立在/sys/bus
当中.
. bus_create_file将为该bus创建一属性文件.
9.3.2 总线的属性
总线属性代表着该总线特有的信息与配置, 如果通过sysfs文件系统为总线生成属性文件, 用户空间的程序可
以通过该文件接口的方式很容易地显示或更改总线的属性. 可以为总线创建不止一个属性文件, 每个文件代
表该总线的一个或一组属性.
ATTR用来方便为总线定义一个属性对象.
绑定是指将一个设备与能控制它的驱动程序结合到一起的行为. 总线在设备与驱动绑定的过程中发挥着核心
作用:总线相关的代码屏蔽了大量底层琐碎的技术细节, 为驱动提供了一组友好的接口.
总线上发生的两类事件将导致设备与驱动绑定行为发生:
1. 通过device_register函数向某一BUS上注册一设备, 这种情况下内核除了将设备加入到bus上的设备链表
的尾端, 同时会试图将此设备与总线上的所有驱动对象进行绑定操作.
2. 通过driver_register将某一驱动注册到其所属的bus上, 内核除了将该驱动加入到bus所所有驱动对象构
成的链表尾部, 也将试图与其上所有设备进行绑定的操作.
device_bind_driver函数会被调用来将设备与它的驱动程序绑定起来:
中关于绑定的核心代码为:
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
用来将private结构中的knode_driver节点加入到与该设备绑定的驱动private结构中的klist_devices链表中
. 所以所谓的设备与驱动绑定, 从代码角度看, 其实是在两者之间通过某种数据结构的使用建立一种关联的
渠道.
9.3.4 设备
struct device *parent: 当前设备的父设备.
struct device_private *p: 该设备的驱动相关的数据.
struct kobject kobj: 代表struct device的内核对象.
const char *init_name: 设备对象的名称.
struct bus_type *bus: 设备所在的总线对象指针.
struct device_driver *driver: 用来表示当前设备是否与它的driver进行了绑定.
系统中的每个设备都是一个struct device对象, 内核为容纳所有这些设备定义了一个ket--devices_kset,作
为系统中所有struct device类型内核对象的容器. 内核将系统中的设备分成两大类: block 和 char. 每类对
应一个内核对象, 分别为sysfs_dev_block_kobj和sysfs_dev_char_kobj, block和char内核对象的上线为dev
_kobj, 设备相关的这些事在linux系统初始化期间由devices_init来完成.
devices_init将会在/sys目录下生成/sys/devices, /sys/dev, /sys/dev/block, /sys/dev/char.
内核针对设备的主要操作有:
. device_inititalize: 用于设备的初始化.
. device_register: 用来向系统注册一个设备.
device_add函数一些比较重要的功能分成下面几个部分:
. 在sysfs文件系统中建立系统硬件拓朴关系结构图.
1. dev->class和dev->parent都为空. 内核根据devices_kset进行赋值, 最后在/sys/devices下生成目录.
2. dev->class为空, dev->parent不为空. 这种情况下dev对象的新目录将建立在parent->kobj目录之下.
3. dev->class不为空, dev->parent为空. class不为空意味着该dev属于某一class, 系统将为dev->kobj
.parent建立一个虚拟上层对象virtual, 如此dev对象加入系统将会在/sys/devices/virtual中产生一个
新的目录/sys/devices/virtual/dev->init_name.
4. dev->class和dev->parent都不为空. 这时如果parent->class不为空, 则dev的parent kobject为dev
->parent->kobj, 即父设备的内嵌kobject. 如果dev->parent->class为空, 则内核需要在dev->class->
p->class_dirs.list中寻找是否有满足条件的kobject对象k, 使k->parent=&parent->kobj, 如果找到那
么dev->kobj的parent kobj就是dev的父设备内嵌的kboject, 否则重新生成一个kobject对像作为父kobj.
高用device_create_sys_dev_entry建立一个新的链接, 链接的目的和源取决于dev->class.
调用bus_add_device在/sys/bus/devices目录下创建一个链接文件, 指向/sys/devices/dev->init_name.
.在sysfs文件树中创建与该dev对象对应的属性文件
uevent_attr是dev对象的一个属性.
如果主设备号不为0, 内核还会调用devtmpfs_create_node在/dev目录下动态生成一个设备节点. 关于devtmp
fs文件系统, 它是内核建立的另一棵独立的VFS树, 最终挂载到用户的/dev目录之上, 在内核中主要用来动
态生成设备节点.
bus_probe_device是总线,设备与驱动相互沟通的重要函数.
在device_attach中, 如果dev->driver不为空, 表明当前设备已经和它的驱动进行了绑定, 这时只需要调用
device_bind_driver在sysfs文件树中建立dev与其驱动程序之间的互联关系.
如果dev->driver为空, 表明当前设备对象dev还没有和它的驱动绑定, 此时需要遍历dev所在总线dev->bus上
挂载的所有驱动程序对象:
然后对遍历过程中的每个驱动对象drv, 调用__device_attch进行绑定.根据总是是否定义match分两种情况,
如果没有定义match, 那么直接返回1, 表明成功, device_attach函数继续对dev->bus上的下一个驱动进行
匹配.
如匹配成功, 将调用driver_probe_device将drv和dev进行绑定, 这个工作实际上是由really_probe来完成的
此时将调用dev所在总线的probe方法, 如果没有, 否则如果drv定义了该方法, 就调用drv->probe, 如果drv
函数认为探测成功, 应该返回0.
. device_unregister
9.3.5 驱动
struct bus_type *bus: 驱动所属的总线.
struct module *owner: 驱动所在的内核模块.
int (*probe)(struct device *dev): 驱动定义的探测函数.
int (*remove)(struct device *dev): 驱动定义的卸载函数.
驱动上的主要操作方法有:
. driver_find: 在一个bus的driver_kset集合中查找指定的驱动.
??????????根据名称来查找, 难道也是说在同一个bus上不能有重名的驱动.
. driver_register: 向系统注册一个驱动.
drv与该bus上所属的设备进行绑定, 绑定的过程将遍历bus上的所有设备, 对于其中的每个设备dev, 将调用
really_probe进行实际的绑定操作.
. driver_unregister: 将某一指定的驱动从系统中注销掉.
9.4 class
相对于设备device, class是一种更高层次的抽象, 用于对设备进行功能上的划分, 有时也被称为设备类.
const char *name: 类的名称
struct module *owner: 拥有该类的模块的指针
struct class_attribute *class_attrs: 类的属性.
struct device_attribute *dev_attrs: 设备的属性.
struct kobject *dev_kboj: 代表当前类中设备的内核对象.
struct class_private *p: 类的私有数据区, 用于处理类的子系统及所包含的设备链表.
内核针对类对象定义的主要操作有:
. classes_init: 系统中类的起源函数, 在系统初始化期间调用, 主要作用是产生类对象的顶层kset --
class_kset;
. class_create: 宏class_create用来生成一个类对象, 主要是将同类型的设备添加其中.
. class_destroy: 用于从系统中注销一个class对象.
. device_create: 属于设备相关的操作范畴.
. device_destroy:用于从系统中移除通过device_create增加的设备device.
9.5 本章小结
统设计的灵活性, 伴随而来的代价就是增另了复杂度.
本文欢迎转载, 请标明出处
本文出处: http://blog.csdn.net/dyron
9.1 sysfs文件系统
sysfs文件系统可以取代ioctl的功能.
sysfs文件系统的初始化发生在linux系统的启动阶段:
int __init sysfs_init(void)
{
int err = -ENOMEM;
sysfs_dir_cachep = kmem_cache_create("sysfs_dir_cache",
sizeof(struct sysfs_dirent),
0, 0, NULL);
if (!sysfs_dir_cachep)
goto out;
err = sysfs_inode_init();
if (err)
goto out_err;
err = register_filesystem(&sysfs_fs_type);
if (!err) {
sysfs_mnt = kern_mount(&sysfs_fs_type);
if (IS_ERR(sysfs_mnt)) {
printk(KERN_ERR "sysfs: could not mount!\n");
err = PTR_ERR(sysfs_mnt);
sysfs_mnt = NULL;
unregister_filesystem(&sysfs_fs_type);
goto out_err;
}
} else
goto out_err;
out:
return err;
out_err:
kmem_cache_destroy(sysfs_dir_cachep);
sysfs_dir_cachep = NULL;
goto out;
}
函数将向系统沆一个类型为sysfs_fs_type的文件系统, sysfs_fs_type的定义为:
static struct file_system_type sysfs_fs_type = {
.name = "sysfs",
.get_sb = sysfs_get_sb,
.kill_sb = sysfs_kill_sb,
}
sysfs_get_sb在内核空间创造了一棵独立的VFS树, 内核创建这棵树主要用来沟通系统中总线,设备与驱动,同
时向用户空间提供接口及展示系统中各种设备的拓展视图等.
sysfs_get_sb函数用来产生sysfs文件系统的超级块, 其内部的主要函数是sysfs_fill_super,后者经过一系列
的函数调用链进入到sysfs_init_inode, 在后者里边, 函数将为sysfs文件系统中每个文件或者目录所对应的
inode赋予一个新的操作对象:
sysfs文件系统是基于RAM实现的文件系统, 编译内核时指定了CONFIG_SYSFS选项, 这个文件系统才会包含到内
核中. 对用户程序来说sysfs的标准挂载点是"/sys"目录, 挂载命令为:
mount -t sysfs sysfs /sys
9.2 kobject和kset
9.2.1 kobject
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
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;
}
const char *name: 表示该内核对象的名称. 如果该对象加入系统, 它的name将会出现在sysfs文件系统中.
struct list_head entry: 用来将一系列的内核对象构成链表.
struct kobject *parent: 指向该内核对象的上层节点.
struct kset *kset: 当前内核对象所属的kset对象的指针. kset对象代表一个subsystem, 其中容纳了一系
列同类型的kobject对象.
struct kref kref: 其核心数据是一原子型变量, 用来表示内核对象的引用计数.
struct kobj_type *ktype: 定义了该内核对象的一组sysfs文件系统相关的操作函数和属性. 不同类型的内
核对象会有不同的ktype, 用以体现kobject所代表的内核对象的特质. 通过该成员,C中
的struct 据类型具备了c++中class类型的某些特点. 同时,内核通过ktype将kobject对
象的sysfs文件操作与其属性文件关联起来.
struct sysfs_dirent *sd: 用来表示该内核对象在sysfs文件系统中对应的目录项的实例.
unsigned int state_initialized: 表示该kobject代表的对象的初始化状态.1已初始化.0尚未初始化.
unsigned int state_in_sysfs: 表示该kobject代表的内核对象有没有在sysfs文件中建立一个入口点.
unsigned int uevent_suppress: 如果kobject对象属于某一kset, 那么它的状态变化可以导致其所在的kset
对象向用户空间发送event消息. 该标志表示当该kobject发生变化时, 是否让其所在的k
set向用户空间发送event消息. 1表示不让kset发送这种event消息.
kboject数据结构最通的的用法是嵌在某一对象的数据结构中.
. kobject_set_name 用来设定kobject中的name成员.
. kobject_init 用来初始化一个内核对象的kobject结构.
. kobject_add 主要功能有两个:1是建立kobject对象间的层次关系.2是在sysfs文件系统中建立一个目录.在
将一个kobject对象通过kobject_add加入系统前, kobject对象必须被初始化.
. kobject_init_and_add 实际的工作是将kobject_init和kobject_add两个函数的功能合并到一起.
. kobject_create 用来分配并初始化一个kobject对象.
. kobject_create_and_add 实际的工作是将kobject_create和kobject_add函数的功能合并到一起.
. kobject_del 删除一个kobject对象
9.2.2 kobject的类型属性
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
release 是一个函数指针, sysfs_ops是一struct sysfs_ops类型的指针
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
};
sysfs_ops实际上定义了一组针对struct attribute对象的操作函数的集合, struct attribute数据结构则是
为kobject内核对象定义的属性成员.
struct attribute {
const char *name;
mode_t mode;
}
kobject_init初始化一个kobject的时候, 会赋予它一个具体的struct kobj_type对象成员, 为一个kobject
对象创建属性文件使用的函数为sysfs_create_file:
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
{
BUG_ON(!kobj || !kobj->sd || !attr);
return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR);
}
使用这个函数时, 必须确保kobj对象之前已经加入了sysfs, sysfs_add_file将在kobj->sd对应的目录下生成
一个属性文件.
当用户空间open这件创建的文件时, 最终会调用到sysfs_open_file:
如果要删除一个属性文件, 应用sysfs_remove_file.
9.2.3 kset
kset可以认为是一组kobject的集合, 是kobject的容器. kset本身也是一个内核对象, 所以需要内嵌一个kob
ject对象.
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
struct list_head list: 用来将其中的kobject对象构成链表.
spinlock_t list_lock: 对kset上的list链表进行访问操作时用来作为互斥保护使用的自旋锁.
struct kobject kobj: 代表当前kset内核对象的kobject变量.
const struct kset_uevent_ops *uevent_ops: 当kset中的某此kobject对象发生了状态变化需要通知用户空
间时, 调用其中的函数来完成.
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);
const char *(* const name)(struct kset *kset, struct kobject *kobj);
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
. kset_init: 用来初始化一个kset对象.
. kset_register: 用来初始化并向系统注册一个kset对象. kset对象本身就是一个由kobject代表的内核对象,
所以kobject_add_internal函数会为代表该kset对象的k->kobj在sysfs文件树中生成一个新目录.
kset与单个kobject不一样的地方在于, 将一个kset向系统注册时, 如果linux内核编译时启用了CONFIG_HOTP
LUG, 那么需要将这一事件通知用户空间, 这个过程由kobject_uevent完成. 如果kobject对象不属于任一kset
, 那么这个孤立的kobject将无法通过uevent向用户空间发送消息.
. kset_create_and_add: 动态产生一个kset对象然后将其加入到sysfs文件系统中.
. kset_unregister: 用来将k指向的kset对象从系统中注销, 完成的是kset_register的反向操作.
9.2.4 热插拨中的uevent和call_usermodehelper
热插拨指当一个设备动态加入到系统时, 驱动可以检查到这种设备状态的变化, 然后通过某种机制使用在用户
空间找到该设备对应的驱动程序模块并加载.
linux中有两种机制可以实现: 1, udev, 2, /sbin/hotplug 之前一直是hotplug, 它的幕后推手是call_user
modehelper, 能从内核空间启动一个用户空间的应用程序. 随着内核的发展, udev逐渐取代了hotplug.
热插拨在内核中通过kobject_uevent函数来实现. 它通过发送一个uevent消息和调用call_usermodehelper来
与用户空间进行沟通. kobject_uevent所实现的功能和linux系统中用以实现热插拨的特性息息相关.
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
参数action是个枚举型变量, 定义为:
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};
kobject_uevent_env总体上可分成三个功能部分:
第1部分用到kset->uevent_ops, 调用其中的filter函数,以决定kset对象当前状态的改变是否要通知到用户层
,不同的kset有不同的uevent_ops对象, 意味着不同的kset有自己独特的uevent_ops操作集. 总之,kset对象状
态的变化, 将会首先调用该kset对象的uevent_ops操作集中的filter函数, 决定是否向用户报告.
第2部分主要完成环境变量的设置, 一开始分配一个存储环境变量的窨env, 接下来把用户空间程序需要的环境
变量通过add_uevent_var加入到env中. 处理完环境变量后, 调用kset对象的uevent_ops操作集中的uevent函
数, 这是内核赋予kset通过该函数完成自己特定功能的最后一次机会.
第3部分是kobject_uevent_env的亮点, 主要用来和用户空间进程进行交互. 在linux内核中, 有两种方式完成
这项任务, 一个是由CONFIG_NET宏包含的部分,这部分代码通过netlink的方式向用户空间广播kset对象中的u
event消息. 另一种方式是在内核空间启动一个用户空间的进程, 通过给该进程传递内核设定的环境变量的方
式来通知用户空间kset对象中的uevent事件.
uevent_helper方法通过调用call_usermodehelper来达到从内核空间运行一个用用户空间进程的目的, 用户
空间进程的二进制文件路径由uevent_helper提供.
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
CONFIG_UEVENT_HELPER_PATH是一内核编译阶段的配置宏, 依赖于CONFIG_HOTPLUG, 意味着如果系统需要支持
设备的执插拨等特性, 则需要给出用户空间进程的文件路径信息. 通常CONFIG_UEVENT_HELPER_PATH会指向/s
bin/hotplug, 后者用来处理系统中出现的热插拨事件, 不过现在多半没有这个文件了.
call_usermodehleper的实现
static inline int
call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
{
return call_usermodehelper_fns(path, argv, envp, wait,
NULL, NULL, NULL);
}
跳过非核心的代码, 直接到核心部分.
static inline int
call_usermodehelper_fns(char *path, char **argv, char **envp,
enum umh_wait wait,
int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *), void *data)
{
struct subprocess_info *info;
gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
info = call_usermodehelper_setup(path, argv, envp, gfp_mask);
if (info == NULL)
return -ENOMEM;
call_usermodehelper_setfns(info, init, cleanup, data);
return call_usermodehelper_exec(info, wait);
}
这个函数的设计思想是采用工作队列的方式, 在call_usermodehelper_setup内部分实始化一个工作队列节点
INIT_WORK(&sub_info->work, __call_usermodehelper);
其中sub_info是struct subprocess_info类型的变量, 工作队列节点是它的一个内嵌对象,sub_info用来存储
运行用户态进程的一些相关信息, 主要是相关的环境变量.
__call_usermodehelper是该工作节点上的延迟执行的函数.
将call_usermodehelper_setup中建立的工作节点提交到工作队列的行为发生在call_usermoehelper_exec函
数中.
函数内首先是khelper_wq, 这是一个工作队列, 其创建发生在linux系统初始化阶段:
void __init usermodehelper_init(void)
{
khelper_wq = create_singlethread_workqueue("khelper");
}
其次, call_usermodehelper_exec通过引入一个completion变量done来实现和工作节点sub_info->work上的
延迟函数__call_usermodehelper的同步, 函数通过queue_work将工作节点提交到khelper_wq之后, 将等待在
wait_for_completion(&done)上, 当__call_usermodehelper执行完毕后, 会通过complete来唤醒睡眠的exec
函数.
__call_usermodehelper中的kernel_thread来生成一个新的进程, kernel_thread的调用将会导致__call_use
rmodehelper中出现两条执行路径, 一是父进程,二是子进程, 父进程在调用kernel_thread后直接返回,而子
进程则需要等到首次被调度的机会才会从kernel_thread返回,因此函数接下来出现了三个case来处理父子进程
间同步问题.
根据传参我们的wait是UMH_WAIT_EXEC, 所以注意这个函数最后的complete, 它将唤醒睡眠的call_usermodeh
elper_exec函数.
__call_usermodehelper执行完毕后, 所在进程将会因为do_exit的调用而从系统中消失掉. kernel_execve
用来在内核空间运行一个用户空间的进程, 路径存放在sub_info->path中, 环境变量由sub_info->argv和sub
_info->envp来提供.
int kernel_execve(const char *filename,
const char *const argv[],
const char *const envp[])
{
long __res;
asm volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_execve), "b" (filename), "c" (argv), "d" (envp) : "memory");
return __res;
}
kernel_execve是个体系架构相关的函数, 它做的首先是int $0x80陷入系统调用, 进入系统调用后, 一个很
重要的参数是系统调用号, linux用eax寄存哭保存系统调用号, 在嵌入的汇编代码中, __NR_execve的值将写
入eax寄存器, 那就是系统调用号, 这个系统调用号对应的sys_execve. 然后进入do_execve, 将执行filena
me所对应的进程文件, 这个过程和在shell里执行一个可执行文件是一样的.
.......这个意思是说在内核空间去执行一个用户空间程序的方法是通过系统调用来执行的.
9.3 总线, 设备与驱动
9.3.1 总线及其注册
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
const char *name: 总线的名称.
struct bus_attribute *bus_attrs: 总线的属性, 包括操作这些属性的一组函数.
struct device_attribute *dev_attrs: 挂到该总线上的设备的属性, 功能逻辑与总线属性一样.
struct driver_attribute *drv_attrs: 挂到该总线上的驱动的属性, 功能逻辑与总线属性一样.
int (*match)(struct device *dev, struct device_driver *drv): 总线用来对试图挂到其上的设备和驱动
执行的匹配操作. 除了这个函数, 还定义了其它一些操作函数.
const struct dev_pm_ops *pm: 总线上一组跟电源管理相关的操作集, 用来对总线上的设备进行电源管理.
struct subsys_private *p: 一个用来管理其上设备与驱动的数据结构.
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct list_head class_interfaces;
struct kset glue_dirs;
struct mutex class_mutex;
struct class *class;
};
struct kset subsys表示该bus所在的子系统, 内核中所有通过bus_register注册进系统的bus所在的kset都将
指向bus_kset, 也就是说bus_kset是系统中所有bus内核对象的容器,而新注册的bus本身也是一个kset型对象.
而新注册的bus本身也是一个kset型对象. drivers_kset表示该bus上所有驱分的一个集合,devices_kset则表
示该bus上所有设备的一个集合. klist_device和klist_drivers分别表示该bus上所有设备与驱动的链表.
drivers_autoprobe表示当向系统中注册某一设备或驱动时, 是否进行设备与驱动的绑定操作. *bus指向与st
ruct bus_type_private对象相关联的bus.
linux内核中针对总线的一些主要操作有:
.buses_init 揭示了总线在系统中的起源, 在系统初始化阶段, 通过buses_init函数为系统中后续的bus操作
奠定了基础.
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
return 0;
}
这里创建一个名为"bus"的kset并将其加入到sysfs文件系统树中, 这里的bus_event_ops定义了当"bus"这个
kset中有状态变化时, 用来通知用户空间uevent消息的操作集. 这个操作集只定义了一个filter操作, 意味
着当"bus" kset中发生状态变化时, 会通过bus_uevent_ops中的filter函数先执行, 以决定是否通知用户.
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &bus_ktype)
return 1;
return 0;
}
static const struct kset_uevent_ops bus_uevent_ops = {
.filter = bus_uevent_filter,
};
如果要求发送的uevent消息类型不是总线类型, 那么函数返回0, 不发送到用户空间. buses_init将在sysfs
文件系统的根目录下建立一个bus目录, 这就是系统中所有后续注册总线的祖先.
. bus_register: 用来向系统中注册一个bus.
bus_sysfs_ops中的操作主要是用来显示或设置当前注册的bus在sysfs文件系统中的属性.
kset_register用来将当前操作的bus所对应的kset加入到sysfs文件系统树中, 因为priv->subsys.kobj.pare
nt = NULL且priv->subsys.kobj.kset = bus_kset, 所以当前注册的bus对应的kset的目录将建立在/sys/bus
当中.
. bus_create_file将为该bus创建一属性文件.
9.3.2 总线的属性
总线属性代表着该总线特有的信息与配置, 如果通过sysfs文件系统为总线生成属性文件, 用户空间的程序可
以通过该文件接口的方式很容易地显示或更改总线的属性. 可以为总线创建不止一个属性文件, 每个文件代
表该总线的一个或一组属性.
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
成员变量attr表示总线的属性信息, 其类型为struct attribute.
struct attribute {
const char *name;
mode_t mode;
}
struct bus_attribute的另外两个成员show和store分别用来显示和更改总线的属性. 内核定义了一个宏BUS_
ATTR用来方便为总线定义一个属性对象.
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
BUS_ATTR宏将定义一个以bus_attr_开头的总线属性对象, 生成属性文件则需要bus_create_file函数.
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
{
int error;
if (bus_get(bus)) {
error = sysfs_create_file(&bus->p->subsys.kobj, &attr->attr);
bus_put(bus);
} else
error = -EINVAL;
return error;
}
9.3.3 设备与驱动的绑定
绑定是指将一个设备与能控制它的驱动程序结合到一起的行为. 总线在设备与驱动绑定的过程中发挥着核心
作用:总线相关的代码屏蔽了大量底层琐碎的技术细节, 为驱动提供了一组友好的接口.
总线上发生的两类事件将导致设备与驱动绑定行为发生:
1. 通过device_register函数向某一BUS上注册一设备, 这种情况下内核除了将设备加入到bus上的设备链表
的尾端, 同时会试图将此设备与总线上的所有驱动对象进行绑定操作.
2. 通过driver_register将某一驱动注册到其所属的bus上, 内核除了将该驱动加入到bus所所有驱动对象构
成的链表尾部, 也将试图与其上所有设备进行绑定的操作.
device_bind_driver函数会被调用来将设备与它的驱动程序绑定起来:
int device_bind_driver(struct device *dev)
{
int ret;
ret = driver_sysfs_add(dev);
if (!ret)
driver_bound(dev);
return ret;
}
driver_sysfs_add用来在sysfs文件系统中建立绑定的设备与驱动程序间的链接符号文件, driver_bound函数
中关于绑定的核心代码为:
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
用来将private结构中的knode_driver节点加入到与该设备绑定的驱动private结构中的klist_devices链表中
. 所以所谓的设备与驱动绑定, 从代码角度看, 其实是在两者之间通过某种数据结构的使用建立一种关联的
渠道.
9.3.4 设备
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_power_domain *pwr_domain;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
struct device *parent: 当前设备的父设备.
struct device_private *p: 该设备的驱动相关的数据.
struct kobject kobj: 代表struct device的内核对象.
const char *init_name: 设备对象的名称.
struct bus_type *bus: 设备所在的总线对象指针.
struct device_driver *driver: 用来表示当前设备是否与它的driver进行了绑定.
系统中的每个设备都是一个struct device对象, 内核为容纳所有这些设备定义了一个ket--devices_kset,作
为系统中所有struct device类型内核对象的容器. 内核将系统中的设备分成两大类: block 和 char. 每类对
应一个内核对象, 分别为sysfs_dev_block_kobj和sysfs_dev_char_kobj, block和char内核对象的上线为dev
_kobj, 设备相关的这些事在linux系统初始化期间由devices_init来完成.
devices_init将会在/sys目录下生成/sys/devices, /sys/dev, /sys/dev/block, /sys/dev/char.
内核针对设备的主要操作有:
. device_inititalize: 用于设备的初始化.
. device_register: 用来向系统注册一个设备.
device_add函数一些比较重要的功能分成下面几个部分:
. 在sysfs文件系统中建立系统硬件拓朴关系结构图.
1. dev->class和dev->parent都为空. 内核根据devices_kset进行赋值, 最后在/sys/devices下生成目录.
2. dev->class为空, dev->parent不为空. 这种情况下dev对象的新目录将建立在parent->kobj目录之下.
3. dev->class不为空, dev->parent为空. class不为空意味着该dev属于某一class, 系统将为dev->kobj
.parent建立一个虚拟上层对象virtual, 如此dev对象加入系统将会在/sys/devices/virtual中产生一个
新的目录/sys/devices/virtual/dev->init_name.
4. dev->class和dev->parent都不为空. 这时如果parent->class不为空, 则dev的parent kobject为dev
->parent->kobj, 即父设备的内嵌kobject. 如果dev->parent->class为空, 则内核需要在dev->class->
p->class_dirs.list中寻找是否有满足条件的kobject对象k, 使k->parent=&parent->kobj, 如果找到那
么dev->kobj的parent kobj就是dev的父设备内嵌的kboject, 否则重新生成一个kobject对像作为父kobj.
高用device_create_sys_dev_entry建立一个新的链接, 链接的目的和源取决于dev->class.
调用bus_add_device在/sys/bus/devices目录下创建一个链接文件, 指向/sys/devices/dev->init_name.
.在sysfs文件树中创建与该dev对象对应的属性文件
uevent_attr是dev对象的一个属性.
如果主设备号不为0, 内核还会调用devtmpfs_create_node在/dev目录下动态生成一个设备节点. 关于devtmp
fs文件系统, 它是内核建立的另一棵独立的VFS树, 最终挂载到用户的/dev目录之上, 在内核中主要用来动
态生成设备节点.
bus_probe_device是总线,设备与驱动相互沟通的重要函数.
在device_attach中, 如果dev->driver不为空, 表明当前设备已经和它的驱动进行了绑定, 这时只需要调用
device_bind_driver在sysfs文件树中建立dev与其驱动程序之间的互联关系.
如果dev->driver为空, 表明当前设备对象dev还没有和它的驱动绑定, 此时需要遍历dev所在总线dev->bus上
挂载的所有驱动程序对象:
然后对遍历过程中的每个驱动对象drv, 调用__device_attch进行绑定.根据总是是否定义match分两种情况,
如果没有定义match, 那么直接返回1, 表明成功, device_attach函数继续对dev->bus上的下一个驱动进行
匹配.
如匹配成功, 将调用driver_probe_device将drv和dev进行绑定, 这个工作实际上是由really_probe来完成的
此时将调用dev所在总线的probe方法, 如果没有, 否则如果drv定义了该方法, 就调用drv->probe, 如果drv
函数认为探测成功, 应该返回0.
. device_unregister
9.3.5 驱动
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
const char *name : 驱动的名称.
struct bus_type *bus: 驱动所属的总线.
struct module *owner: 驱动所在的内核模块.
int (*probe)(struct device *dev): 驱动定义的探测函数.
int (*remove)(struct device *dev): 驱动定义的卸载函数.
驱动上的主要操作方法有:
. driver_find: 在一个bus的driver_kset集合中查找指定的驱动.
??????????根据名称来查找, 难道也是说在同一个bus上不能有重名的驱动.
. driver_register: 向系统注册一个驱动.
drv与该bus上所属的设备进行绑定, 绑定的过程将遍历bus上的所有设备, 对于其中的每个设备dev, 将调用
really_probe进行实际的绑定操作.
. driver_unregister: 将某一指定的驱动从系统中注销掉.
9.4 class
相对于设备device, class是一种更高层次的抽象, 用于对设备进行功能上的划分, 有时也被称为设备类.
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs;
struct bin_attribute *dev_bin_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
const char *name: 类的名称
struct module *owner: 拥有该类的模块的指针
struct class_attribute *class_attrs: 类的属性.
struct device_attribute *dev_attrs: 设备的属性.
struct kobject *dev_kboj: 代表当前类中设备的内核对象.
struct class_private *p: 类的私有数据区, 用于处理类的子系统及所包含的设备链表.
内核针对类对象定义的主要操作有:
. classes_init: 系统中类的起源函数, 在系统初始化期间调用, 主要作用是产生类对象的顶层kset --
class_kset;
. class_create: 宏class_create用来生成一个类对象, 主要是将同类型的设备添加其中.
. class_destroy: 用于从系统中注销一个class对象.
. device_create: 属于设备相关的操作范畴.
. device_destroy:用于从系统中移除通过device_create增加的设备device.
9.5 本章小结
本文欢迎转载, 请标明出处
本文出处: http://blog.csdn.net/dyron
统设计的灵活性, 伴随而来的代价就是增另了复杂度.