udev user space device management

1 The difference between udev and devfs

Despite the advantages of devfs in one way or another, in the Linux 2.6 kernel, devfs was considered an obsolete method and was eventually abandoned, and udev replaced it. Linux VFS kernel maintainer Al Viro pointed out several reasons why udev replaced devfs:

  • 1) The work done by devfs is believed to be done in user mode.
  • 2) When devfs was added to the kernel, everyone expected its quality to catch up.
  • 3) Found that devfs has some fixable and unfixable bugs.
  • 4) For bugs that can be fixed, they have been fixed a few months ago, and their maintainers think everything is good.
  • 5) For the latter, nothing has changed in quite some time.
  • 6) The maintainer and author of devfs has been disappointed with it and has stopped maintaining the code.

In the debate between Richard Gooch and Greg Kroah-Hartman, the theoretical basis used by Greg Kroah-Hartman is that the policy (strategy) cannot be located in the kernel space. A fundamental point emphasized in the design of Linux is the separation of mechanisms and policies. A mechanism is a fixed step, a method of doing something, and a strategy is the different ways each step is taken.

The mechanism is relatively fixed, but the strategy adopted by each step is not fixed . Mechanisms are stable, but policies are flexible, so in the Linux kernel, policies should not be implemented.

For example, Linux provides an API that allows people to increase or decrease the priority of threads, or adjust the scheduling policy to SCHED_FIFO, but the Linux kernel itself does not care who is higher or lower. Providing an API is a mechanism, and whoever is higher and who is lower is a strategy, so it should be the application that tells the kernel to be higher or lower, and the kernel doesn't care about these chores. In the same way, Greg Kroah-Hartman believes that things that belong to the policy should be moved to the user space, whoever wants to create a name for which device or wants to do more processing, whoever sets it by himself. The kernel just needs to tell the user this information. This is why devfs in kernel space should be replaced by udev in user space, because devfs manages some unreliable things.

Let's give a popular example to understand the starting point of udev design. Taking falling in love as an example, Greg Kroah-Hartman believes that the kernel can provide a mechanism for falling in love, but it cannot restrict who to fall in love with in the kernel space, and the strategy of falling in love cannot be placed in the kernel space. Because love is free, users should be able to realize the ideal of "radish and cabbage, each has his own love" in the user space, and can freely choose according to the other party's appearance, hometown, personality, etc. Corresponding to devfs, the first blind date girl is named /dev/girl0, the second blind date girl is named /dev/girl1, and so on. The udev implemented in user space can enable users to realize such freedom: no matter which girl you like, as long as it conforms to the rules you defined, it will be named /dev/mygirl!

udev works entirely in user mode, using the hotplug event (Hotplug Event) sent by the kernel when the device is added or removed. When hot plugging, the detailed information of the device will be sent by the kernel through the netlink socket, and the sent thing is called uevent.

The device naming strategy, permission control and event processing of udev are all completed in the user mode , and it uses the information received from the kernel to create device file nodes and other work . Code Listing 5.6 gives an example of receiving and flushing hot plug events from the kernel through netlink, and udev adopts a similar approach.

 1 #include <linux/netlink.h>
 2
 3 static void die(char *s)
 4 {
 5  write(2, s, strlen(s));
 6  exit(1);
 7 }
 8
 9 int main(int argc, char *argv[])
10 {
11  struct sockaddr_nl nls;
12  struct pollfd pfd;
13  char buf[512];
14
15  // Open hotplug event netlink socket
16
17  memset(&nls, 0, sizeof(struct sockaddr_nl));
18  nls.nl_family = AF_NETLINK;
19  nls.nl_pid = getpid();
20  nls.nl_groups = -1;
21
22  pfd.events = POLLIN;
23  pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
24  if (pfd.fd == -1)
25      die("Not root\n");
26
27  // Listen to netlink socket
28  if (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl)))
29      die("Bind failed\n");
30  while (-1 != poll(&pfd, 1, -1)) {
31      int i, len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);
32      if (len == -1)
33          die("recv\n");
34
35      // Print the data to stdout.
36      i = 0;
37      while (i < len) {
38          printf("%s\n", buf + i);
39          i += strlen(buf + i) + 1;
40      }
41  }
42  die("poll\n");
43
44  // Dear gcc: shut up.
45  return 0;
46 }

Compile and run the above program, plug the Apple Facetime HD Camera USB camera into Ubuntu, the program will dump information similar to the following:

ACTION=add
DEVLINKS=/dev/input/by-id/usb-Apple_Inc._FaceTime_HD_Camera__Built-in__
CC2B2F0TLSDG6LL0-event-if00 /dev/input/by-path/pci-0000:00:0b.0-usb-0:1:1.0-event
DEVNAME=/dev/input/event6
DEVPATH=/devices/pci0000:00/0000:00:0b.0/usb1/1-1/1-1:1.0/input/input6/event6
ID_BUS=usb
ID_INPUT=1
ID_INPUT_KEY=1
ID_MODEL=FaceTime_HD_Camera__Built-in_
ID_MODEL_ENC=FaceTime\x20HD\x20Camera\x20\x28Built-in\x29
ID_MODEL_ID=8509
ID_PATH=pci-0000:00:0b.0-usb-0:1:1.0
ID_PATH_TAG=pci-

Udev receives netlink messages in this way, and works according to its content and the rules set by the user for udev. There is a problem here, that is, what to do with cold-swapped devices?

Cold-plugged devices exist at boot time and have been plugged in before udev starts. For cold-swappable devices, the Linux kernel provides a uevent node under sysfs. You can write an "add" to this node, causing the kernel to resend the netlink, and then udev can receive the cold-swappable netlink message . We still run the program in Listing 5.6, and manually write an "add" to /sys/module/psmouse/uevent, the above program will dump out the following information:

ACTION=add
DEVPATH=/module/psmouse
SEQNUM=1682
SUBSYSTEM=module
UDEV_LOG=3
USEC_INITIALIZED=220903546792

Another significant difference between devfs and udev is: using devfs, when a non-existent /dev node is opened, devfs can automatically load the corresponding driver, while udev does not.

This is because the designers of udev thought that Linux should load the driver module when the device is discovered, not when it is accessed . The designers of udev decided that the automatic loading of drivers when opening /dev nodes provided by devfs is superfluous on a properly configured computer. All devices in the system should generate hotplug events and load the appropriate drivers, and udev will notice this and create corresponding device nodes for it.

2 sysfs file system and Linux device model

The kernel after Linux 2.6 introduces the sysfs file system. sysfs is regarded as a file system of the same category as proc, devfs, and devpty. This file system is a virtual file system that can generate a hierarchical view that includes all system hardware . Very similar to the proc filesystem that provides process and state information.

sysfs organizes the devices and buses connected to the system into a hierarchical file , which can be accessed by user space, and exports kernel data structures and their attributes to user space. One purpose of sysfs is to display the hierarchical relationship of components in the device driver model . Its top-level directories include block, bus, dev, devices, class, fs, kernel, power, and firmware.

  • The block directory contains all block devices;
  • The devices directory contains all the devices of the system and is organized into a hierarchy according to the bus type to which the devices are attached;
  • The bus directory contains all bus types in the system;
  • The class directory contains the device types in the system (such as network card devices, sound card devices, input devices, etc.).

Running tree in the /sys directory will get a rather long tree directory, part of which is extracted below:

.
├── block
│ ├── loop0 -> ../devices/virtual/block/loop0
│ ├── loop1 -> ../devices/virtual/block/loop1
│ ├── loop2 -> ../devices/virtual/block/loop2
│ ├── loop3 -> ../devices/virtual/block/loop3
...
├── bus
│ ├── ac97
│ │ ├── devices
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent│ ├── acpi
...
│ ├── i2c
│ │ ├── devices
│ │ ├── drivers
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
...
├── class
│ ├── ata_device
│ │ ├── dev1.0 -> ../../devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0
│ │ ├── dev1.1 -> ../../devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1
│ │ ├── dev2.0 -> ../../devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0
│ │ ├── dev2.1 -> ../../devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1
...
├── dev
│ ├── block
│ │ ├── 1:0 -> ../../devices/virtual/block/ram0
│ │ ├── 1:1 -> ../../devices/virtual/block/ram1
│ │ ├── 1:10 -> ../../devices/virtual/block/ram10
│ │ ├── 11:0 -> ../../devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0
...
│ └── char
│     ├── 10:1 -> ../../devices/virtual/misc/psaux
│     ├── 10:184 -> ../../devices/virtual/misc/microcode
│     ├── 10:200 -> ../../devices/virtual/misc/tun
│     ├── 10:223 -> ../../devices/virtual/misc/uinput
...
├── devices
│ ├── breakpoint
│ │ ├── power
│ │ ├── subsystem -> ../../bus/event_source
│ │ ├── type
│ │ └── uevent
│ ├── isa
│ │ ├── power
│ │ └── uevent
...
├── firmware
...
├── fs
│ ├── cgroup
│ ├── ecryptfs
│ │ └── version
│ ├── ext4
│ │ ├── features
│ │ ├── sda1
│ │ └── sdb1
│ └── fuse
│     └── connections
├── hypervisor
├── kernel
│ ├── debug
│ │ ├── acpi
│ │ ├── bdi
...
├── module
│ ├── 8250
│ │ ├── parameters
│ │ └── uevent
│ ├── 8250_core
│ │ ├── parameters
│ │ └── uevent
...
└── power
  ├── disk
  ├── image_size
  ├── pm_async
  ├── reserved_size
  ├── resume
  ├── state
  ├── wake_lock
  ├── wake_unlock
  └── wakeup_count

Under the pci and other subdirectories of /sys/bus, the drivers and devices directories will be separated again, and the files in the devices directory are symbolic links to the files in the /sys/devices directory.

Likewise, the /sys/class directory also contains many links to the files in /sys/devices. As shown in Figure 5.3, the Linux device model directly corresponds to the actual situation of devices, drivers, buses, and classes, and it also conforms to the device model of the Linux kernel after 2.6.

insert image description here

With the continuous advancement of technology, the topology of the system is becoming more and more complex, and the requirements for intelligent power management, hot swap, and plug-and-play support are also getting higher and higher. The Linux 2.4 kernel has been difficult to meet these requirements. In order to meet the needs of this situation, the kernel after Linux 2.6 has developed the above-mentioned brand-new device model in which devices, buses, classes and drivers are interlocking. Figure 5.4 graphically represents the relationship between devices, buses, and classes in the Linux driver model.

insert image description here
In most cases, Linux 2.6 and later

  • The device driver core layer code in the kernel, as the "big man behind the scenes", can handle these relationships well ,
  • The bus and other kernel subsystems in the kernel complete the interaction with the device model,

This makes driver engineers hardly need to care about the device model when writing the underlying driver. They only need to "cramp" the various callback functions in xxx_driver according to the requirements of each framework, where xxx is the name of the bus.

In the Linux kernel, bus_type, device_driver, and device are used to describe the bus, driver, and device . These three structures are defined in the include/linux/device.h header file, and their definitions are shown in Listing 5.7.

  1 struct bus_type {
  2         const char              *name;
  3         const char              *dev_name;
  4         struct device           *dev_root;
  5         struct device_attribute *dev_attrs;     /* use dev_groups instead */
  6         const struct attribute_group **bus_groups;
  7         const struct attribute_group **dev_groups;
  8         const struct attribute_group **drv_groups;
  9
 10         int (*match)(struct device *dev, struct device_driver *drv);
 11         int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 12         int (*probe)(struct device *dev);
 13         int (*remove)(struct device *dev);
 14         void (*shutdown)(struct device *dev);
 15
 16         int (*online)(struct device *dev);
 17         int (*offline)(struct device *dev);
 18
 19         int (*suspend)(struct device *dev, pm_message_t state);
 20         int (*resume)(struct device *dev);
 21
 22         const struct dev_pm_ops *pm;
 23
 24         struct iommu_ops *iommu_ops;
 25
 26         struct subsys_private *p;
 27         struct lock_class_key lock_key;
 28 };
 29
 30 struct device_driver {
 31         const char              *name;
 32         struct bus_type         *bus;
 33
 34         struct module           *owner;
 35         const char             *mod_name;      /* used for built-in modules */
 36
 37         bool suppress_bind_attrs;              /* disables bind/unbind via sysfs */
 38
 39         const struct of_device_id       *of_match_table;
 40         const struct acpi_device_id     *acpi_match_table;
 41
 42         int (*probe) (struct device *dev);
 43         int (*remove) (struct device *dev);
 44         void (*shutdown) (struct device *dev);
 45         int (*suspend) (struct device *dev, pm_message_t state);
 46         int (*resume) (struct device *dev);
 47         const struct attribute_group **groups;
 48
 49         const struct dev_pm_ops *pm;
 50
 51         struct driver_private *p;
 52 };
 53
 54 struct device {
 55 struct device        *parent;
 56
 57 struct device_private        *p;
 58
 59 struct kobject kobj;
 60 const char           *init_name;              /* initial name of the device */
 61 const struct device_type *type;
 62
 63 struct mutex         mutex;                   /* mutex to synchronize calls to
 64                       * its driver.
 65                       */
 66
 67 struct bus_type      *bus;                    /* type of bus device is on */
 68 struct device_driver  *driver;                /* which driver has allocated this
 69    device */
 70 void         *platform_data;                  /* Platform specific data, device
 71                                     core doesn't touch it */
 72 struct dev_pm_infopower;
 73 struct dev_pm_domain*pm_domain;
 74
 75 #ifdef CONfiG_PINCTRL
 76 struct dev_pin_info*pins;
 77 #endif
 78
 79 #ifdef CONfiG_NUMA
 80 int          numa_node;                       /* NUMA node this device is close to */
 81 #endif
 82 u64          *dma_mask;                       /* dma mask (if dma'able device) */
 83 u64          coherent_dma_mask;               /* Like dma_mask, but for
 84                          alloc_coherent mappings as
 85                          not all hardware supports
 86                          64 bit addresses for consistent
 87                          allocations such descriptors. */
 88
 89 struct device_dma_parameters *dma_parms;
 90
 91 struct list_head dma_pools;                    /* dma pools (if dma'ble) */
 92
 93 struct dma_coherent_mem     *dma_mem;          /* internal for coherent mem
 94                                         override */
 95 #ifdef CONfiG_DMA_CMA
 96 struct cma *cma_area;                          /* contiguous memory area for dma
 97                                allocations */
 98 #endif
 99 /* arch specific additions */
100 struct dev_archdataarchdata;
101
102 struct device_node   *of_node;                 /* associated device tree node */
103 struct acpi_dev_node acpi_node;                /* associated ACPI device node */
104
105 dev_t                devt;                     /* dev_t, creates the sysfs "dev" */
106 u32                  id;                       /* device instance */
107
108 spinlock_t           devres_lock;
109 struct list_head     devres_head;
110
111 struct klist_node    knode_class;
112 struct class         *class;
113 const struct attribute_group **groups;         /* optional groups */
114
115 void(*release)(struct device *dev);
116 struct iommu_group   *iommu_group;
117
118 boo          offline_disabled:1;
119 boo          offline:1;
120 };

device_driver and device represent drive and device respectively , and both of them must be attached to a bus, so they both contain struct bus_type pointers.

In the Linux kernel, devices and drivers are registered separately. When a device is registered, the driver does not need to exist, and when a driver is registered, the corresponding device does not need to be registered. Devices and drivers flood into the kernel separately, and when each device and driver floods into the kernel, they will look for their other half, and it is the match() member function of bus_type that binds the two together.

Simply put, devices and drivers are men and women floating in the world of mortals, and the match() of bus_type is the old man who pulls the red line. It can identify which devices and which drivers can be paired.

Once the pairing is successful, the probe() of xxx_driver is executed (xxx is the bus name, such as platform, pci, i2c, spi, usb, etc.).

Note: The bus, driver and device will eventually be implemented as a directory in sysfs, because further tracking code will find that they can actually be considered as derived classes of kobject, and kobject can be regarded as the abstraction of all buses, devices and drivers Base class, one kobject corresponds to one directory in sysfs.

Each attribute in the bus, device, and driver is directly implemented as a file in sysfs. The attribute will be accompanied by two functions, show() and store(), which are used to read and write the sysfs file corresponding to the attribute. Code list 5.8 gives the definitions of the structures of attribute, bus_attribute, driver_attribute and device_attribute.

 1 struct attribute {
 2         const char              *name;
 3         umode_t                 mode;
 4 #ifdef CONfiG_DEBUG_LOCK_ALLOC
 5         bool                    ignore_lockdep:1;
 6         struct lock_class_key   *key;
 7         struct lock_class_key   skey;
 8 #endif
 9 };
10
11 struct bus_attribute {
12        struct attribute        attr;
13        ssize_t (*show)(struct bus_type *bus, char *buf);
14        ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
15 };
16
17 struct driver_attribute {
18        struct attribute attr;
19        ssize_t (*show)(struct device_driver *driver, char *buf);
20        ssize_t (*store)(struct device_driver *driver, const char *buf,
21                          size_t count);
22 };
23
24 struct device_attribute {
25        struct attribute        attr;
26        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
27                        char *buf);
28        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
29                         const char *buf, size_t count);
30 };

In fact, the directory in sysfs comes from bus_type, device_driver, device , and the files in the directory come from attribute . Some shortcuts are also defined in the Linux kernel to facilitate the creation of attributes.

#define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DRIVER_ATTR_RW(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)
#define DRIVER_ATTR(_name, _mode, _show, _store) \
        struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DRIVER_ATTR_RW(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)
#define BUS_ATTR(_name, _mode, _show, _store)   \
        struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define BUS_ATTR_RW(_name) \
        struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define BUS_ATTR_RO(_name) \
        struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)

For example, we can find such code in the drivers/base/bus.c file:

static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
             show_drivers_autoprobe, store_drivers_autoprobe);
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);

The corresponding files can be found in /sys/bus/platform, etc.:

barry@barry-VirtualBox:/sys/bus/platform$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent

The script in Listing 5.9 can traverse the entire sysfs and dump bus, device and driver information.

 1 #!/bin/bash
 2
 3   # Populate block devices
 4
 5   for i in /sys/block/*/dev /sys/block/*/*/dev
 6   do
 7     if [ -f $i ]
 8     then
 9       MAJOR=$(sed 's/:.*//' < $i)
10       MINOR=$(sed 's/.*://' < $i)
11       DEVNAME=$(echo $i | sed -e 's@/dev@@' -e 's@.*/@@')
12       echo /dev/$DEVNAME b $MAJOR $MINOR
13       #mknod /dev/$DEVNAME b $MAJOR $MINOR
14     fi
15   done
16
17   # Populate char devices
18
19   for i in /sys/bus/*/devices/*/dev /sys/class/*/*/dev
20   do
21     if [ -f $i ]
22     then
23       MAJOR=$(sed 's/:.*//' < $i)
24       MINOR=$(sed 's/.*://' < $i)
25       DEVNAME=$(echo $i | sed -e 's@/dev@@' -e 's@.*/@@')
26       echo /dev/$DEVNAME c $MAJOR $MINOR
27       #mknod /dev/$DEVNAME c $MAJOR $MINOR
28     fi
29   done

The above script traverses sysfs, finds out all devices, and analyzes the device name and major and minor device numbers. If we remove the "#" before line 27, the script can actually create nodes under /dev/ for the devices in the entire system.

3 Composition of udev

udev is currently merged with the systemd project, see the document "Udev and systemd to merge" at https://lwn.net/Articles/490413/, available from http://cgit.freedesktop.org/systemd/, https ://github.com/systemd/systemd and other locations to download the latest code. udev executes in user space, dynamically creates/deletes device files, allows everyone to provide LSB (Linux Standard Specification, Linux Standard Base) names without caring about major/minor device numbers, and can fix names as needed. The working process of udev is as follows.

  • 1) When the kernel detects that a new device has appeared in the system, the kernel will send uevent through the netlink socket.

  • 2) udev obtains the information sent by the kernel and matches the rules. Things that match include SUBSYSTEM, ACTION, atttribute, names provided by the kernel (via KERNEL=), and other environment variables.

Assuming that a Kingston U disk is inserted into the Linux system, we can use the udev tool "udevadm monitor–kernel–property–udev" to capture the information contained in the uevent:

UDEV  [6328.797974] add
/devices/pci0000:00/0000:00:0b.0/usb1/1-1/1-1:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
ACTION=add
DEVLINKS=/dev/disk/by-id/usb-Kingston_DataTraveler_2.0_5B8212000047-0:0 /dev/
        disk/by-path/pci-0000:00:0b.0-usb-0:1:1.0-scsi-0:0:0:0
DEVNAME=/dev/sdc
DEVPATH=/devices/pci0000:00/0000:00:0b.0/usb1/1-1/1-1:1.0/host7/target7:0:0/7:0:0:0/
         block/sdc
DEVTYPE=disk
ID_BUS=usb
ID_INSTANCE=0:0
ID_MODEL=DataTraveler_2.0
ID_MODEL_ENC=DataTraveler\x202.0
ID_MODEL_ID=6545
ID_PART_TABLE_TYPE=dos
ID_PATH=pci-0000:00:0b.0-usb-0:1:1.0-scsi-0:0:0:0
ID_PATH_TAG=pci-0000_00_0b_0-usb-0_1_1_0-scsi-0_0_0_0
ID_REVISION=PMAP
ID_SERIAL=Kingston_DataTraveler_2.0_5B8212000047-0:0
ID_SERIAL_SHORT=5B8212000047
ID_TYPE=disk
ID_USB_DRIVER=usb-storage
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_VENDOR=Kingston
ID_VENDOR_ENC=Kingston
ID_VENDOR_ID=0930
MAJOR=8
MINOR=32
SEQNUM=2335
SUBSYSTEM=block

Based on this information, we can create a rule to create a /dev/kingstonUD symbolic link for the disk every time it is inserted. This rule can be written as the code in Listing 5.10.

# Kingston USB mass storage
SUBSYSTEM=="block", ACTION=="add", KERNEL=="*sd ", ENV{ID_TYPE}=="disk",
     ENV{ID_VENDOR}=="Kingston", ENV{ID_USB_DRIVER}=="usb-storage", SYMLINK+="kingstonUD"

After inserting the kingston U disk, /dev/ will automatically create a symbolic link:

root@barry-VirtualBox:/dev# ls -l kingstonUD
lrwxrwxrwx 1 root root 3 Jun 30 19:31 kingstonUD -> sdc

4 udev rule file

The rule file of udev is in units of lines, and lines beginning with "#" represent comment lines.

Each remaining line represents a rule. Each rule is divided into one or more matching parts and assignment parts. The matching part is represented by a matching-specific keyword, and the corresponding assignment part is represented by an assignment-specific keyword.

Matching keywords include: ACTION (behavior), KERNEL (matching kernel device name), BUS (matching bus type), SUBSYSTEM (matching subsystem name), ATTR (attribute), etc.,

Assignment keywords include: NAME (created device file name), SYMLINK (symbolic creation link name), OWNER (set device owner), GROUP (set device group), IMPORT (call external program), MODE (node ​​access permissions), etc.

For example, the following rules:

SUBSYSTEM=="net", ACTION=="add", DRIVERS==" *", ATTR{address}=="08:00:27:35:be:ff",
        ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"

The "matching" part includes SUBSYSTEM, ACTION, ATTR, KERNEL, etc., and the "assignment" part has one item, which is NAME.

This rule means:

  • When the new hardware appearing in the system belongs to the category of net subsystem,
  • The action taken by the system to this hardware is "add" this hardware,
  • And the "address" attribute information of this hardware is equal to "08:00:27:35:be:ff",
  • The "dev_id" attribute is equal to "0x0",
  • The "type" attribute is 1 etc.,
  • At this point, the action performed on this hardware at the udev level is to create /dev/eth1.

A simple example shows the difference in naming between udev and devfs.

If there are two USB printers on the system, one might be called /dev/usb/lp0 and the other /dev/usb/lp1.

But it is impossible to determine which file corresponds to which printer. There is no one-to-one correspondence between lp0, lp1 and the actual device. The mapping relationship will be uncertain due to the order in which the device is discovered and the shutdown of the printer itself.

Therefore, the ideal way is that two printers should be mapped based on their serial numbers or other identification information. devfs cannot do this, but udev can. Use the following rules:

SUBSYSTEM="usb",ATTR{serial}="HXOLL0012202323480",NAME="lp_epson",SYMLINK+="printers/
         epson_stylus"

The matching items in this rule are SUBSYSTEM and ATTR, and the assignment items are NAME and SYMLINK, which means that when the serial number of a USB printer is "HXOLL0012202323480", the /dev/lp_epson file is created and a symbolic link /dev is created at the same time /printers/epson_styles. No matter when the USB printer with the serial number "HXOLL0012202323480" is plugged in, the corresponding device name is /dev/lp_epson, and devfs obviously cannot achieve this fixed naming of the device.

The writing method of udev rules is very flexible. In the matching part, shell wildcards such as "*", "?", [a c], and [1 9] can be used to flexibly match multiple items. Similar to wildcards in the shell , instead of any string of any length, ? replaces a character. In addition, %k is the KERNEL, and %n is the KERNEL serial number of the device (such as the partition number of the storage device).

You can use the udevadm info tool in udev to find the kernel information and sysfs attribute information that the rule file can use. For example, run the "udevadm info-ap/sys/devices/platform/serial8250/tty/ttyS0" command to get:

udevadm info -a -p  /sys/devices/platform/serial8250/tty/ttyS0
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
  looking at device '/devices/platform/serial8250/tty/ttyS0':
    KERNEL=="ttyS0"
    SUBSYSTEM=="tty"
    DRIVER==""
    ATTR{irq}=="4"
    ATTR{line}=="0"
    ATTR{port}=="0x3F8"
    ATTR{type}=="0"
    ATTR{flags}=="0x10000040"
    ATTR{iomem_base}=="0x0"
    ATTR{custom_divisor}=="0"
    ATTR{iomem_reg_shift}=="0"
    ATTR{uartclk}=="1843200"
    ATTR{xmit_fifo_size}=="0"
    ATTR{close_delay}=="50"
    ATTR{closing_wait}=="3000"
    ATTR{io_type}=="0"
  looking at parent device '/devices/platform/serial8250':
    KERNELS=="serial8250"
    SUBSYSTEMS=="platform"
    DRIVERS=="serial8250"
  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""

If the node under /dev/ has been created, but you do not know its corresponding /sys specific node path, you can use the "udevadm info-ap$(udevadm info-q path-n/dev/<node name>)" command to reverse the analysis, such as:

udevadm info -a -p  $(udevadm info -q path -n /dev/sdb)
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
  looking at device '/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb':
    KERNEL=="sdb"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{ro}=="0"
    ATTR{size}=="71692288"
    ATTR{stat}=="     584     1698     4669     7564        0        0        0
        0        0     7564     7564"
    ATTR{range}=="16"
    ATTR{discard_alignment}=="0"
    ATTR{events}==""
    ATTR{ext_range}=="256"
    ATTR{events_poll_msecs}=="-1"
    ATTR{alignment_offset}=="0"
    ATTR{inflight}=="       0        0"
    ATTR{removable}=="0"
    ATTR{capability}=="50"
    ATTR{events_async}==""
  looking at parent device '/devices/pci0000:00/0000:00:0d.0/ata4/host3/
           target3:0:0/3:0:0:0':
    KERNELS=="3:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{rev}=="1.0 "
    ATTRS{type}=="0"
    ATTRS{scsi_level}=="6"
    ATTRS{model}=="VBOX HARDDISK   "
    ATTRS{state}=="running"
    ATTRS{queue_type}=="simple"
    ATTRS{iodone_cnt}=="0x299"
    ATTRS{iorequest_cnt}=="0x29a"
    ATTRS{queue_ramp_up_period}=="120000"
    ATTRS{timeout}=="30"
    ATTRS{evt_media_change}=="0"
    ATTRS{ioerr_cnt}=="0x7"
    ATTRS{queue_depth}=="31"
    ATTRS{vendor}=="ATA     "
    ATTRS{device_blocked}=="0"
    ATTRS{iocounterbits}=="32"
  looking at parent device '/devices/pci0000:00/0000:00:0d.0/ata4/host3/
           target3:0:0':
    KERNELS=="target3:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS==""
  looking at parent device '/devices/pci0000:00/0000:00:0d.0/ata4/host3':
    KERNELS=="host3"
    SUBSYSTEMS=="scsi"
    DRIVERS==""
  looking at parent device '/devices/pci0000:00/0000:00:0d.0/ata4':
    KERNELS=="ata4"
    SUBSYSTEMS==""
    DRIVERS==""
  looking at parent device '/devices/pci0000:00/0000:00:0d.0':
    KERNELS=="0000:00:0d.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="ahci"
    ATTRS{irq}=="21"
    ATTRS{subsystem_vendor}=="0x0000"
    ATTRS{broken_parity_status}=="0"
    ATTRS{class}=="0x010601"
    ATTRS{consistent_dma_mask_bits}=="64"
    ATTRS{dma_mask_bits}=="64"
    ATTRS{local_cpus}=="ff"
    ATTRS{device}=="0x2829"
    ATTRS{enable}=="1"
    ATTRS{msi_bus}==""
    ATTRS{local_cpulist}=="0-7"
    ATTRS{vendor}=="0x8086"
    ATTRS{subsystem_device}=="0x0000"
    ATTRS{d3cold_allowed}=="1"
  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

In embedded systems, you can also use mdev, a lightweight version of udev, which is integrated in busybox .

When compiling busybox, select mdev related items. Android also does not use udev, it uses vold. The mechanism of vold is the same as that of udev. If you understand udev, you will also understand vold.

Android's source code NetlinkManager.cpp also listens to netlink-based sockets and parses received messages.

5 summary

There are two methods for file programming in Linux user space, namely accessing files through Linux API and through C library functions . User space cannot see the device driver, but only the files corresponding to the device, so file programming is also device programming in user space.

Linux has a good plan for the directory structure of the file system according to the function.

  • /dev is the storage directory for device files.
  • devfs and udev are the methods for generating device file nodes by the kernel after Linux 2.4 and Linux 2.6 respectively, the former runs in the kernel space, and the latter runs in the user space.

The kernel after Linux 2.6 defines the device model through a series of data structures, and there is a corresponding relationship between the device model and the directories and files in the sysfs file system. Devices and drivers are separated and matched via the bus.

Udev can use the uevent information sent by the kernel through netlink to dynamically create device file nodes.

The content comes from: Detailed Explanation of Linux Device Driver Development

Guess you like

Origin blog.csdn.net/weixin_45264425/article/details/130714408