linux驱动目录结构,Linux内核之设备驱动-driver model结构

本文深入解析Linux设备模型,包括设备、驱动、类和总线的基本结构。阐述了这些组件如何组织,以及它们之间的交互方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux内核之设备驱动-driver

model结构

如表1-1,Linux设备模型包含以下四个基本结构:

类型

所包含的内容

内核数据结构

对应/sys项

设备(Devices)

设备是此模型中最基本的类型,以设备本身的连接按层次组织

struct device

/sys/devices.../

驱动(Drivers)

在一个系统中安装多个相同设备,只需要一份驱动程序的支持

struct device_driver

/sys/bus/pci/drivers

类别(Classes)

这是按照功能进行分类组织的设备层次树;如USB接口和PS/2接口的鼠标都是输入设备,都会出现在/sys/class/input/下

struct class

/sys/class

设备

具有特定功能的硬件,比如键盘、鼠标等。

device 数据结构

struct device {

struct

klistklist_children;

struct

klist_nodeknode_parent;

struct

klist_nodeknode_driver;

struct

klist_nodeknode_bus;

struct

device*parent;

struct

kobjectkobj;

charbus_id[BUS_ID_SIZE];

unsigneduevent_suppress:1;

const

char*init_name;

struct

bus_type*bus;

struct

device_driver *driver;

void*driver_data;

dev_tdevt;

struct

klist_nodeknode_class;

struct

class*class;

struct

attribute_group

**groups;

void(*release)(struct

device *dev);

};

klist_children

子设备双向链表头,用于指向该设备所有子设备链表的表头。通过此表头可以查找所有子设备。操作函数如下:

int

device_for_each_child(struct device *parent, void

*data,

int (*fn)(struct

device *dev, void *data));

struct device *device_find_child(struct device

*parent, void *data,

int (*match)(struct device *dev, void

*data));

这两个函数的区别是:前者遍历子设备,并作fn()处理,如果fn()返回非0值,则停止遍历,返回错误码;后者是遍历子设备,并作match()处理,如果match()返回非0,则调用get_device()获取对应的子设备(将对应的子设备参考值曾1),然后停止遍历,返回子设备。注意后者获取的子设备用完后,需要用put_device()去除子设备。

knode_parent

父设备的子设备集节点,用于链入父设备的klist_children链表中。

knode_driver

设备所属驱动的设备集节点,用于链入具有相同驱动的设备链表中。该链的表头在device_driver->p

->klist_devices。

knode_bus

设备所属总线的设备集节点,用于链入具有相同总线的设备链表中。该链的表头为bus_type->p

-> klist_devices。

*parent

设备的父设备,即该设备所属的设备。在大多数情况下,一个父设备通常是某种总线或者是宿主控制器。

kobj

表示该设备并把该它连接到sysfs体系中的kobject。

bus_id[BUS_ID_SIZE]

在总线上唯一标识该设备的字符串。

uevent_suppress

过滤该设备的uevent事件标志位。为1,则过滤。

*init_name

设备初始化名称,device_add()里将该值拷贝给bus_id,因此也表示总线上该设备标识。注意,文件系统中显示的设备名称并非此处指定,而是由该设备对应的kobj中指定。

*bus

标识了该设备连接在何种类型的总线上。

*driver

管理该设备的驱动程序。在下一节中将介绍device_driver结构。

*driver_data

由设备驱动程序使用的私有数据成员。

devt

32位主从设备号,高12位为主设备号,低20位为从设备号。

knode_class

设备所属类的设备集节点,用于链入具有相同类的设备链表中。该链的表头为class->p

-> class_devices。

*class

标识设备所属类。

**groups

设备属性组,会体现到sysfs系统中,用户可操作。

*release

当指向设备的最后一个引用被删除时,内核调用该方法;它将从内嵌的kobject的release方法中调用。所有指向核心注册的device结构都必须有一release方法,否则内核将打印出错误信息。

图1-2给出了设备与总线、驱动等连接关系。文件系统子框表明设备是通过kobj将自己与sysfs中的文件夹连接到一起的。子设备、总线、驱动、类等子系统给设备进行相应分类,并使用klist和klist_node将它们链到一起。每个子系统同时提供***_for_each_***

和 ***_find_***两个遍历函数来查找并操作指定设备。

a4c26d1e5885305701be709a3d33442f.png

图2-2:设备结构图

设备架构初始化:

int __init

devices_init(void);

1 在sysfs目录下创建设备子集(kset)和目录/sys/devices;并将设备通用事件处理函数device_uevent_ops注册给该设备子集。之后所有设备都会添加到devices目录下。

2另外,kset在初始化时,会将公共的kobj_type一组函数注册到kset中内嵌的kobj中,其中包括释放kset函数kset_release和一组属性show/store函数kobj_attr_show/kobj_attr_store。

3 kset注册成功后,向用户空间发送添加对象的uevent事件。

4 创建目录/sys/dev,并在该目录下创建两个目录/sys/dev/block和/sys/dev/char。这两个目录下维护一个按字符设备/块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件。

设备注册/去注册函数:

int

device_register(struct device *dev);

void

device_unregister(struct device *dev);

注册函数先后执行的操作有:获取设备所在子集,初始化设备数据,将设备加入到sysfs系统中,增加设备默认属性及操作方法,加设备到类设备中,加设备到总线,向用户空间发送添加设备uevent事件,尝试为设备获取驱动。

采用此方法注册的设备都会获取释放对象(device_release)及处理所属属性(dev_attr_show/dev_attr_store)的方法(也就是kobj_type结构)。

如果用户空间程序用sysfs来读取设备属性的值,sysfs的read函数先调用的是对象属性处理函数dev_attr_show,因为sysfs能处理或者说能看得见的只有对象级的东西,我们从图3的图中也可表明这一点。

去注册是注册的逆向操作。去注册后,用户不可再操作设备。

遍历子设备:

int

device_for_each_child(struct device *parent, void

*data,

int (*fn)(struct

device *dev, void *data));

struct device

*device_find_child(struct device *parent, void

*data,

int (*match)(struct device *dev, void

*data));

函数说明请参考前一节数据结构分析中klist_children项。

设备通用事件(uevent)处理函数:

static int

dev_uevent_filter(struct kset *kset, struct kobject

*kobj);

static const

char *dev_uevent_name(struct kset *kset, struct kobject

*kobj);

static int

dev_uevent(struct kset *kset, struct kobject *kobj, struct

kobj_uevent_env *env);

dev_uevent_filter设备级事件过滤函数,发送uevent事件前会先调用此函数检查是否发送该事件,返回0则不发送,为1则调用总线或类级过滤函数再次检查。

dev_uevent_name获取设备所属总线或类的名称。

dev_uevent设备级事件处理函数,向事件中添加指定设备的环境变量,如:设备主次编号,设备类型,设备使用的驱动。如果设备有所属总线或类,本函数还会调用总线或类级事件处理函数。

详细事件处理流程将在uevent章节中描述。

设备属性文件添加删除:

int

device_create_file(struct device *dev, struct device_attribute

*attr);

void

device_remove_file(struct device *dev, struct device_attribute

*attr);

创建/删除设备属性文件。

设备的对象(kobj)属性文件操作函数:

static ssize_t

dev_attr_show(struct kobject *kobj, struct attribute *attr, char

*buf);

static ssize_t

dev_attr_store(struct kobject *kobj, struct attribute

*attr,

const char *buf, size_t

count);

从sysfs层面看,只能识别对象级的属性文件处理函数(如上面这两个函数的格式)。设备使用这两个函数实现对象到设备属性文件处理的转换。它们使用container_of方法实现内核对象(kobj)到对应设备的转换。通过设备找到属性处理函数,并调用找到的函数(格式如下)来读取/存储指定设备的属性值。

struct

device_attribute {

struct attribute

attr;

ssize_t (*show)(struct device

*dev, struct device_attribute *attr,

char

*buf);

ssize_t (*store)(struct device

*dev, struct device_attribute *attr,

const char *buf, size_t

count);

};

让设备运行起来的一组函数。

struct

device_driver {

const char *name;

struct bus_type *bus;

int (*probe) (struct device

*dev);

int (*remove) (struct device

*dev);

void (*shutdown) (struct device

*dev);

struct attribute_group

**groups;

struct driver_private

*p;

};

*name

驱动程序的名称。与kobj->name相同。

*bus

该驱动使用的总线。

*probe

用来查询特定设备是否存在,以及该驱动程序能否操作它。

*remove

删除设备时使用remove函数告知驱动不可再操作此设备。

*shutdown

关机时调用shutdown函数关闭设备。

**groups

驱动属性组,会体现到sysfs系统中,用户可操作。

*p

该成员用于处理与设备、总线以及sysfs之间的连接关系,如下:

struct

driver_private {

struct kobject

kobj;

struct klist

klist_devices;

struct klist_node

knode_bus;

struct module_kobject

*mkobj;

struct device_driver

*driver;

};

操作device_driver结构的函数有注册/去注册函数,增删属性及属性组文件函数,遍历设备函数。

注册函数:

int

driver_register(struct device_driver *drv);

该函数实现向相应总线的驱动子集(kset)中注册驱动对象,并将总线上可以使用该驱动的设备加入到该驱动的设备链表中。

注意,每个驱动对象均赋予driver_ktype类型,用来释放对象和处理属性文件。

可以用下面函数去注册一个驱动:

void

driver_unregister(struct device_driver *drv);

驱动对象释放函数:

static void

driver_release(struct kobject *kobj);

该函数同过driver_ktype注册给驱动对象,用来释放对象。

驱动对象属性文件处理函数:

static ssize_t

drv_attr_show(struct kobject *kobj, struct attribute *attr, char

*buf);

static ssize_t

drv_attr_store(struct kobject *kobj, struct attribute

*attr,

const char *buf, size_t

count);

这两个函数也是通过driver_ktype注册给驱动对象的,用于操作对象的属性。如同驱动一节对应函数描述的那样。这两个函数也只是个适配函数,每个驱动有自己的属相处理函数。sysfs只是通过这两个适配函数去访问驱动属性处理函数。实现方法仍然是通过container_of行为由对象获取外层包含该对象的驱动结构,从而获取驱动属性处理函数。注意,这两个函数定义在bus.c中。

增删属性文件函数:

int

driver_create_file(struct device_driver *drv, struct

driver_attribute *attr);

void

driver_remove_file(struct device_driver *drv, struct

driver_attribute *attr);

用户可以通过操作增加到sysfs中驱动属性文件来修改驱动属性值。

增删属性组文件函数:

static int

driver_add_groups(struct device_driver *drv, struct attribute_group

**groups);

static void

driver_remove_groups(struct device_driver *drv, struct

attribute_group **groups);

这两个函数可以一次向系统增删一组驱动属性。

遍历设备函数:

int

driver_for_each_device(struct device_driver *drv, struct device

*start,

void *data, int (*fn)(struct device *, void

*));

struct device

*driver_find_device(struct device_driver *drv, struct device

*start, void *data,

int (*match)(struct device *dev, void

*data));

这两函数均用于遍历驱动上的设备,并对设备执行fn或match操作。具体差异请读代码。

总线是处理器与一个或者多个设备之间的通道。在设备模型中,所有的设备都通过总线相连。

设备模型用bus_type结构表示总线,如下:

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);

struct bus_type_private

*p;

};

*name

总线名称。

*bus_attrs

总线属性集。

*dev_attrs

总线设备属性集。

*drv_attrs

总线驱动属性集。

*match

当一个总线上的新设备或者新驱动程序被添加时,会调用此函数。如果指定的驱动程序可以处理指定的设备,该函数返回1。

*uevent

总线级别的uevent处理。向用户空间发送uevent时间前,使用该方法给总线增加环境变量。

*probe

探测指定设备是否在总线上。

*p

struct

bus_type_private {

struct kset subsys;

struct kset

*drivers_kset;

struct kset

*devices_kset;

struct klist

klist_devices;

struct klist

klist_drivers;

};

subsys 表示总线集;*drivers_kset和*devices_kset是从sysfs角度描述总线支持的驱动集和设备集,而klist_devices和klist_drivers是总线支持的设备及驱动链表,用于遍历总线上的驱动和设备。

总线初始化函数:

int __init

buses_init(void);

系统初始化时调用。在sysfs根目录下增加bus目录,并创建bus子集(kset),注册子集uevent事件处理函数。完成后向用户空间发送增加对象uevent事件。

另外,kset在初始化时,会将公共的kobj_type一组函数注册到kset中内嵌的kobj中,其中包括释放kset函数kset_release和一组属性show/store函数kobj_attr_show/kobj_attr_store。注意,所有系统子集都会这么做,且使用一组同样的函数,包括设备子集和类子集。

总线注册函数:

int

bus_register(struct bus_type *bus);

void

bus_unregister(struct bus_type *bus);

向系统注册/去注册总线时,使用此两函数。这里要注意的是注册的每一个总线在sysfs中不仅仅是一个目录(kobject),它还是一个子集。即总线是以子集(kset)方式注册的,而非对象(kobject)。更重要的是每个总线子集下同时注册了设备(devices)和驱动(drivers)两个子集(kset)。前面讲到设备初始化时也会注册devices子集。事实上,这儿的设备子集只是保存设备的链接。而驱动没有初始化函数,因此驱动对象都保存在这儿各总线的驱动子集里。

uevent属性文件操作函数:

static ssize_t

bus_uevent_store(struct bus_type *bus, const char *buf, size_t

count);

用户可以通过修改总线文件夹下的uevent文件内容,触发内核向用户空间发送uevent事件,事件类型由用户写入的内容决定。

总线属性文件操作函数:

static ssize_t

bus_attr_show(struct kobject *kobj, struct attribute *attr, char

*buf);

static ssize_t

bus_attr_store(struct kobject *kobj, struct attribute

*attr,

const char *buf, size_t

count);

与设备和驱动对应的对象属性操作函数功能近似,这里不再赘述。记住,这只是个适配函数。为sysfs服务。

增删属性函数:

int

bus_create_file(struct bus_type *bus, struct bus_attribute

*attr);

void

bus_remove_file(struct bus_type *bus, struct bus_attribute

*attr);

增加删除总线属性文件,一次一个。

static int

bus_add_attrs(struct bus_type *bus);

static void

bus_remove_attrs(struct bus_type *bus);

增加删除总线属性文件,一次多个,其实就是将总线提供的属性列表一次性增加到系统中。我们从数据结构中可知bus_type不仅为自己提供了属性列表。也为设备和驱动各提供了一份。因此,总线也提供了相应的添加/删除方法,类似于上面两个函数,不再列出。

总线遍历设备函数:

int

bus_for_each_dev(struct bus_type *bus, struct device

*start,

void *data, int (*fn)(struct device *, void

*));

struct device

*bus_find_device(struct bus_type *bus, struct device *start, void

*data,

int (*match)(struct device *dev, void

*data));

前者对指定总线上所有设备进行fn()操作。若fn()返回非零值,则视为出错,退出遍历。而后者只是用match()对总线上每个设备进行匹配,找到则返回对应的设备。

总线遍历驱动函数:

int

bus_for_each_drv(struct bus_type *bus, struct device_driver

*start,

void *data, int (*fn)(struct device_driver *,

void *));

对指定总线上的所有驱动进行fn()操作。若fn()返回非零值,则视为出错,退出遍历。

向总线增加驱动:

int

bus_add_driver(struct device_driver *drv);

该函数为驱动注册函数的一部分,将驱动对象注册到对应总线的驱动(drivers)子集下。

向总线增加设备:

int

bus_add_device(struct device *dev);

该函数为设备注册函数的一部分,但并不像驱动那样,它只将设备子集的指定设备链接到总线的设备(devices)子集下。

设备绑定驱动函数:

void

bus_attach_device(struct device *dev);

为设备获取合适的驱动。

int

device_reprobe(struct device *dev);

将设备与其驱动解绑,然后为设备获取新的驱动。这通常为热插拔服务。

int

bus_rescan_devices(struct bus_type *bus);

为指定的bus上的每个设备重新绑定驱动,如果设备还没有驱动,则为其重新探测驱动并绑定。

类是一个设备的高层视图,它抽象出了底层的实现细节。比如驱动程序看到的是SCSI磁盘和ATA磁盘,但是在类的层次上,它们都是磁盘而已。类允许用户空间使用设备所能提供的功能,而不关心设备是如何连接的,以及它们是如何工作的。

struct class

{

const char *name;

struct module *owner;

struct class_attribute

*class_attrs;

struct device_attribute

*dev_attrs;

struct kobject

*dev_kobj;

int (*dev_uevent)(struct device *dev, struct

kobj_uevent_env *env);

void (*class_release)(struct class

*class);

void (*dev_release)(struct device

*dev);

struct class_private

*p;

};

*name

类名称。*owner

类所属模块。

*class_attrs

类属性集。

*dev_attrs

类给其所有设备定义的公共属性集。

*dev_kobj

在devices_init函数中创建了/sys/dev/char和/sys/dev/block两个对象,dev_kobj指向二者之一,添加对象时(device_add),会将对象链接到这个目录下。按照我的理解,内核设计者是想在/sys/dev/char或/sys/dev/block目录下将各设备按类分目录存放,此处的dev_kobj即是为了创建这个分目录。遗憾的是该功能并未使用,我们看到char和block下没有分目录,全是以major:minor格式命名的链接文件。这样我们就可以通过主次设备号快速找到对应的设备了。

*dev_uevent

类级uevent事件构造函数。

*class_release

用于释放指定的类。

*dev_release

用于释放指定的设备。

*p

struct class_private {

struct kset

class_subsys;

struct klist

class_devices;

struct list_head

class_interfaces;

struct class *class;

};

class_subsys是该类在sysfs中的代表,它表明每个类都是一个子集。class_devices将所有归属于该类的设备串成一个链表,便于遍历查询或对设备进行操作。class_interfaces用于链接所有类接口。class指向所属的class对象。

类初始化函数:

int __init

classes_init(void);

系统初始化时调用。在sysfs根目录下增加class(/sys/class)目录,并创建class子集(kset),注册子集uevent事件处理函数。完成后向用户空间发送增加对象uevent事件。

另外,kset在初始化时,会将公共的kobj_type一组函数注册到kset中内嵌的kobj中,其中包括释放kset函数kset_release和一组属性show/store函数kobj_attr_show/kobj_attr_store。注意,所有系统子集都会这么做,且使用一组同样的函数,包括设备子集和总线子集。

类注册/去注册函数:

int

__class_register(struct class *cls, struct lock_class_key

*key);

void

class_unregister(struct class *cls);

向系统增加/去除类子集和相应路径。

类创建/删除函数:

struct class

*__class_create(struct module *owner, const char *name, struct

lock_class_key *key);

void

class_destroy(struct class *cls);

与类注册/去注册函数功能一致,只是对以上两个函数的简单封装。

遍历类设备函数:

int

class_for_each_device(struct class *class, struct device

*start,

void *data, int (*fn)(struct device *, void

*));

struct device

*class_find_device(struct class *class, struct device *start, void

*data,

int (*match)(struct device *, void

*));

与总线或设备中的遍历功能类似,不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值