devfs
devfs是一种特殊的文件系统,用于在Linux内核中管理设备文件。在早期版本的Linux内核中,设备文件是通过手动创建和管理的,但是随着系统设备数量的增加,这变得越来越困难。
devfs解决了这个问题,它是一种动态的文件系统,可以自动创建和删除设备文件。当一个设备被添加到系统中时,devfs会自动为其创建一个设备文件,并在设备被删除时删除该文件。
devfs的优点包括:
- 简化设备文件的管理
- 允许设备文件的动态创建和删除
- 提高系统的可靠性和稳定性
但是,devfs在现代Linux内核中已经被udev取代,因为udev提供了更多的功能和灵活性。因此,如果您正在使用最新版本的Linux内核,您不需要了解devfs的详细信息。
udev
udev是Linux内核中的一个子系统,它负责管理系统中的设备。与devfs不同,udev是一个用户空间程序,它允许在设备被添加或删除时执行用户定义的操作。
udev的主要功能包括:
- 动态地创建和删除设备文件
- 分配设备名称
- 检测设备属性
- 运行用户定义的脚本
当一个设备被添加到系统中时,udev会自动为其创建一个设备文件,并为其分配一个唯一的名称。然后,它会检测设备的属性(如设备类型、厂商ID、产品ID等),并可以根据这些属性运行用户定义的脚本。
例如,当您插入一个USB存储设备时,udev会检测到该设备并为其创建一个设备文件(如/dev/sdb)。然后,它可以运行一个脚本,以便自动挂载该设备并在桌面上显示一个图标。
udev的优点包括:
- 允许用户定义设备管理操作
- 提供更多的灵活性和功能
- 支持热插拔设备
sysfs
sysfs是Linux内核中的一个特殊文件系统,它用于向用户空间公开内核中的设备和驱动程序信息。它提供了一种以层次结构方式组织设备信息的方式,可以轻松访问设备的各种属性和状态。
在sysfs中,每个设备都表示为一个目录,其中包含有关该设备的信息和属性。例如,如果您在系统中连接了一个USB存储设备,可以在/sys/bus/usb/devices目录下找到该设备的目录。该目录中包含有关设备的信息,如设备ID、供应商ID、产品ID等。
sysfs的优点包括:
- 提供了一种以层次结构方式组织设备信息的方式
- 允许用户轻松访问设备的各种属性和状态
- 可以方便地与其他系统工具(如udev)集成使用
sysfs把连接在系统上的设备和总线组织成为一个个分级的文件,它们可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性。sysfs的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括block、bus、dev、devices、class、fs、kernel、power和firmware等。block目录包含所有的块设备;devices目录包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构;bus目录包含系统中所有的总线类型;class目录包含系统中的设备类型(如网卡设备、声卡设备、输入设备等)。
Linux驱动模型中设备、总线和类之间的关系
在Linux驱动模型中,设备、总线和类之间存在一定的关系。这些概念是为了更好地管理和组织系统中的设备而引入的。
设备是最基本的概念,它代表系统中的一个物理设备,如硬盘、网卡等。每个设备都有一个唯一的设备号,用于标识该设备。设备通常与设备驱动程序配对使用,驱动程序负责将设备与系统其他部分进行交互。
总线是一组相关设备的集合,例如PCI总线、USB总线等。每个总线都有一个唯一的标识符,用于标识该总线上的所有设备。总线通常由总线驱动程序管理,驱动程序负责检测总线上的设备并加载相应的设备驱动程序。
类是一组具有相似功能的设备的集合,例如输入设备、网络设备等。每个类都有一个唯一的标识符,用于标识该类中的所有设备。类通常由类驱动程序管理,驱动程序负责提供类相关的功能和服务。
总之,在Linux驱动模型中,设备、总线和类之间存在一定的关系。设备是最基本的概念,总线是设备的集合,类是具有相似功能的设备的集合。这些概念有助于更好地管理和组织系统中的设备。
在Linux内核中,分别使用bus_type、device_driver和device来描述总线、驱动和设备,这3个结构体定义于include/linux/device.h头文件中,其定义如下:
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
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 (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
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;
const struct acpi_device_id *acpi_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;
};
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 */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#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. */
unsigned long dma_pfn_offset;
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 */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
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 iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
device_driver和device分别表示驱动和设备,而这两者都必须依附于一种总线,因此都包含struct bus_type指针。在Linux内核中,设备和驱动是分开注册的,注册1个设备的时候,并不需要驱动已经存在,而1个驱动被注册的时候,也不需要对应的设备已经被注册。设备和驱动各自涌向内核,而每个设备和驱动涌入内核的时候,都会去寻找自己的另一半,而正是bus_type的match()成员函数将两者捆绑在一起。简单地说,设备和驱动就是红尘中漂浮的男女,而bus_type的match()则是牵引红线的月老,它可以识别什么设备与什么驱动是可配对的。一旦配对成功,xxx_driver的probe()就被执行(xxx是总线名,如platform、pci、i2c、spi、usb等)。
udev的组成
udev由以下几个组成部分组成:
-
内核事件子系统(kernel event subsystem):当设备连接或断开时,内核会发出事件。udev通过内核事件子系统监听这些事件,并根据事件发生的类型执行相应的操作。
-
规则引擎(rule engine):udev规则引擎基于设备属性和事件类型来匹配规则,并执行与规则相关的操作。规则可以是系统范围的,也可以是特定设备的。
-
设备信息数据库(device information database):udev维护一个设备信息数据库,其中包含有关设备的属性和状态信息。当udev检测到一个新设备时,它会更新设备信息数据库中的相关信息。
-
设备文件系统(device file system):udev使用设备文件系统来管理设备文件。当udev检测到一个新设备时,它会自动为其创建一个设备文件,并在设备被删除时删除该文件。
-
用户空间工具(user-space tools):udev提供了一些用户空间工具,用于管理设备。例如,udevadm工具用于管理udev规则和设备信息数据库。
udev规则文件
udev规则文件是用于匹配udev事件和执行相应操作的脚本文件。每个规则文件包含一个或多个规则,每个规则都由一个或多个条件和一个或多个操作组成。
规则文件的位置通常是在/etc/udev/rules.d/目录下,文件名以数字和描述性名称的组合命名,数字表示规则文件的优先级,数字越小,优先级越高。
规则文件中的每个规则由以下几个部分组成:
-
规则名称:规则的名称,通常是一个描述性的名称。
-
条件:规则所匹配的条件,通常是设备的属性、设备类型、内核事件类型等。可以使用各种测试函数来编写条件。
-
操作:规则所执行的操作,通常是创建、删除、移动或更改设备文件,运行脚本等。
下面是一个示例udev规则:
# This is a comment
# Rule to match USB storage devices
SUBSYSTEM=="block", ATTRS{removable}=="1", KERNEL=="sd*", ACTION=="add", RUN+="/usr/local/bin/mount_usb.sh"
# Rule to match USB storage devices when removed
SUBSYSTEM=="block", KERNEL=="sd*", ACTION=="remove", RUN+="/usr/local/bin/unmount_usb.sh"
以上规则中,第一个规则匹配可移动的USB存储设备,当设备连接时,运行脚本/usr/local/bin/mount_usb.sh
,第二个规则匹配USB存储设备断开连接时,运行脚本/usr/local/bin/unmount_usb.sh
。
其它
在嵌入式系统中,也可以用udev的轻量级版本mdev,mdev集成于busybox中。在编译busybox的时候,选中mdev相关项目即可。Android也没有采用udev,它采用的是vold。vold的机制和udev是一样的,理解了udev,也就理解了vold。Android的源代码NetlinkManager.cpp同样是监听基于netlink的套接字,并解析收到的消息。