Linux驱动开发之设备文件

 

devfs

devfs是一种特殊的文件系统,用于在Linux内核中管理设备文件。在早期版本的Linux内核中,设备文件是通过手动创建和管理的,但是随着系统设备数量的增加,这变得越来越困难。

devfs解决了这个问题,它是一种动态的文件系统,可以自动创建和删除设备文件。当一个设备被添加到系统中时,devfs会自动为其创建一个设备文件,并在设备被删除时删除该文件。

devfs的优点包括:

  1. 简化设备文件的管理
  2. 允许设备文件的动态创建和删除
  3. 提高系统的可靠性和稳定性

但是,devfs在现代Linux内核中已经被udev取代,因为udev提供了更多的功能和灵活性。因此,如果您正在使用最新版本的Linux内核,您不需要了解devfs的详细信息。

udev

udev是Linux内核中的一个子系统,它负责管理系统中的设备。与devfs不同,udev是一个用户空间程序,它允许在设备被添加或删除时执行用户定义的操作。

udev的主要功能包括:

  1. 动态地创建和删除设备文件
  2. 分配设备名称
  3. 检测设备属性
  4. 运行用户定义的脚本

当一个设备被添加到系统中时,udev会自动为其创建一个设备文件,并为其分配一个唯一的名称。然后,它会检测设备的属性(如设备类型、厂商ID、产品ID等),并可以根据这些属性运行用户定义的脚本。

例如,当您插入一个USB存储设备时,udev会检测到该设备并为其创建一个设备文件(如/dev/sdb)。然后,它可以运行一个脚本,以便自动挂载该设备并在桌面上显示一个图标。

udev的优点包括:

  1. 允许用户定义设备管理操作
  2. 提供更多的灵活性和功能
  3. 支持热插拔设备

sysfs

sysfs是Linux内核中的一个特殊文件系统,它用于向用户空间公开内核中的设备和驱动程序信息。它提供了一种以层次结构方式组织设备信息的方式,可以轻松访问设备的各种属性和状态。

在sysfs中,每个设备都表示为一个目录,其中包含有关该设备的信息和属性。例如,如果您在系统中连接了一个USB存储设备,可以在/sys/bus/usb/devices目录下找到该设备的目录。该目录中包含有关设备的信息,如设备ID、供应商ID、产品ID等。

sysfs的优点包括:

  1. 提供了一种以层次结构方式组织设备信息的方式
  2. 允许用户轻松访问设备的各种属性和状态
  3. 可以方便地与其他系统工具(如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由以下几个组成部分组成:

  1. 内核事件子系统(kernel event subsystem):当设备连接或断开时,内核会发出事件。udev通过内核事件子系统监听这些事件,并根据事件发生的类型执行相应的操作。

  2. 规则引擎(rule engine):udev规则引擎基于设备属性和事件类型来匹配规则,并执行与规则相关的操作。规则可以是系统范围的,也可以是特定设备的。

  3. 设备信息数据库(device information database):udev维护一个设备信息数据库,其中包含有关设备的属性和状态信息。当udev检测到一个新设备时,它会更新设备信息数据库中的相关信息。

  4. 设备文件系统(device file system):udev使用设备文件系统来管理设备文件。当udev检测到一个新设备时,它会自动为其创建一个设备文件,并在设备被删除时删除该文件。

  5. 用户空间工具(user-space tools):udev提供了一些用户空间工具,用于管理设备。例如,udevadm工具用于管理udev规则和设备信息数据库。

udev规则文件

udev规则文件是用于匹配udev事件和执行相应操作的脚本文件。每个规则文件包含一个或多个规则,每个规则都由一个或多个条件和一个或多个操作组成。

规则文件的位置通常是在/etc/udev/rules.d/目录下,文件名以数字和描述性名称的组合命名,数字表示规则文件的优先级,数字越小,优先级越高。

规则文件中的每个规则由以下几个部分组成:

  1. 规则名称:规则的名称,通常是一个描述性的名称。

  2. 条件:规则所匹配的条件,通常是设备的属性、设备类型、内核事件类型等。可以使用各种测试函数来编写条件。

  3. 操作:规则所执行的操作,通常是创建、删除、移动或更改设备文件,运行脚本等。

下面是一个示例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的套接字,并解析收到的消息。