4 Device Management

This chapter describes how the system uses device files and how the udev device manager dynamically creates or removes device node files.

About Device Files

The /dev directory contains device files (also sometimes known as device special files and device nodes) that provide access to peripheral devices such as hard disks, to resources on peripheral devices such as disk partitions, and pseudo devices such as a random number generator.

The /dev directory has several subdirectory hierarchies, each of which holds device files that relate to a certain type of device. For example, the /dev/disk/id-by-uuid directory contains device files for hard disks named according to the universally unique identifier (UUID) for the disk. The device files in subdirectories such as these are actually implemented as symbolic links to device files in /dev. You can access the same device using the file in /dev or the corresponding link to the file listed in /dev/disk/id-by-uuid.

If you use the ls -l command to list the files under /dev, you see that some device files are shown as being either type b for block or type c for character. These devices have a pair of numbers associated with them instead of a file size. These major and minor numbers identify the device to the system.

ls -l /dev
total 0
crw-rw----. 1 root    root     10,  56 Mar 17 08:17 autofs
drwxr-xr-x. 2 root    root         640 Mar 17 08:17 block
drwxr-xr-x. 2 root    root          80 Mar 17 08:16 bsg
drwxr-xr-x. 3 root    root          60 Mar 17 08:16 bus
lrwxrwxrwx. 1 root    root           3 Mar 17 08:17 cdrom -> sr0
drwxr-xr-x. 2 root    root        2880 Mar 17 08:17 char
crw-------. 1 root    root      5,   1 Mar 17 08:17 console
lrwxrwxrwx. 1 root    root          11 Mar 17 08:17 core -> /proc/kcore
drwxr-xr-x. 4 root    root         100 Mar 17 08:17 cpu
crw-rw----. 1 root    root     10,  61 Mar 17 08:17 cpu_dma_latency
drwxr-xr-x. 6 root    root         120 Mar 17 08:16 disk
brw-rw----. 1 root    disk    253,   0 Mar 17 08:17 dm-0
brw-rw----. 1 root    disk    253,   1 Mar 17 08:17 dm-1
...
crw-rw-rw-. 1 root    root      1,   3 Mar 17 08:17 /dev/null
...
drwxr-xr-x. 2 root     root          0 Mar 17 08:16 pts
...
crw-rw-rw-. 1 root     root     1,   8 Mar 17 08:17 random
...
brw-rw----. 1 root     disk     8,   0 Mar 17 08:17 sda
brw-rw----. 1 root     disk     8,   1 Mar 17 08:17 sda1
brw-rw----. 1 root     disk     8,   2 Mar 17 08:17 sda2
...
lrwxrwxrwx. 1 root     root         15 Mar 17 08:17 stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root     root         15 Mar 17 08:17 stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root     root         15 Mar 17 08:17 stdout -> /proc/self/fd/1
...
crw--w----. 1 root     tty      4,   0 Mar 17 08:17 tty0
crw--w----. 1 root     tty      4,   1 Mar 17 08:17 tty1
...
crw-rw-rw-. 1 root     root     1,   9 Mar 17 08:17 urandom
...
crw-rw-rw-. 1 root     root     1,   5 Mar 17 08:17 zero

Block devices support random access to data, seeking media for data, and usually allow data to be buffered while it is being written or read. Examples of block devices include hard disks, CD-ROM drives, flash memory, and other addressable memory devices. The kernel writes data to or reads data from a block device in blocks of a certain number of bytes. In the sample output, sda is the block device file that corresponds to the hard disk, and it has a major number of 8 and a minor number of 0. sda1 and sda2 are partitions of this disk, and they have the same major number as sda (8), but their minor numbers are 1 and 2.

Character devices support streaming of data to or from a device, and data is not usually buffered nor is random access permitted to data on a device. The kernel writes data to or reads data from a character device one byte at a time. Examples of character devices include keyboards, mice, terminals, pseudo-terminals, and tape drives. tty0 and tty1 are character device files that correspond to terminal devices that allow users to log in from serial terminals or terminal emulators. These files have major number 4 and minor numbers 0 and 1.

Pseudo-terminals worker or secondary (slave) devices emulate real terminal devices to interact with software. For example, a user might log in on a terminal device such as /dev/tty1, which then uses the pseudo-terminal primary (master) device /dev/pts/ptmx to interact with an underlying pseudo-terminal device. The character device files for worker and primary pseudo-terminals are located in the /dev/pts directory:

ls -l /dev/pts
total 0
crw--w----. 1 guest tty  136, 0 Mar 17 10:11 0
crw--w----. 1 guest tty  136, 1 Mar 17 10:53 1
crw--w----. 1 guest tty  136, 2 Mar 17 10:11 2
c---------. 1 root  root   5, 2 Mar 17 08:16 ptmx
Some device entries, such as stdin for the standard input, are symbolically linked via the self subdirectory of the proc file system. The pseudo-terminal device file to which they actually point depends on the context of the process.
ls -l /proc/self/fd/[012]
total 0
lrwx------. 1 root root 64 Mar 17 10:02 0 -> /dev/pts/1
lrwx------. 1 root root 64 Mar 17 10:02 1 -> /dev/pts/1
lrwx------. 1 root root 64 Mar 17 10:02 2 -> /dev/pts/1

Character devices such as null, random, urandom, and zero are examples of pseudo-devices that provide access to virtual functionality implemented in software rather than to physical hardware.

/dev/null is a data sink. Data that you write to /dev/null effectively disappears but the write operation succeeds. Reading from /dev/null returns EOF (end-of-file).

/dev/zero is a data source of an unlimited number of zero-value bytes.

/dev/random and /dev/urandom are data sources of streams of pseudo-random bytes. To maintain high-entropy output, /dev/random blocks if its entropy pool does not contains sufficient bits of noise. /dev/urandom does not block and, as a result, the entropy of its output might not be as consistently high as that of /dev/random. However, neither /dev/random nor /dev/urandom are considered to be truly random enough for the purposes of secure cryptography such as military-grade encryption.

You can find out the size of the entropy pool and the entropy value for /dev/random from virtual files under /proc/sys/kernel/random:

cat /proc/sys/kernel/random/poolsize
4096
cat /proc/sys/kernel/random/entropy_avail
3467

For more information, see the null(4), pts(4), and random(4) manual pages.

About the Udev Device Manager

The udev device manager dynamically creates or removes device node files at boot time or if you add a device to or remove a device from the system with a 2.6 version kernel or later. When creating a device node, udev reads the device’s /sys directory for attributes such as the label, serial number, and bus device number.

Udev can use persistent device names to guarantee consistent naming of devices across reboots, regardless of their order of discovery. Persistent device names are especially important when using external storage devices.

The configuration file for udev is /etc/udev/udev.conf. The file contains the variable udev_log which indicates the logging priority. The variable can be set to err, info and debug. The default value is err.

For more information, see the udev(7) manual page.

About Udev Rules

Udev uses rules files that determine how it identifies devices and creates device names. The udev service (systemd-udevd) reads the rules files at system startup and stores the rules in memory. If the kernel discovers a new device or an existing device goes offline, the kernel sends an event action (uevent) notification to udev, which matches the in-memory rules against the device attributes in /sys to identify the device. As part of device event handling, rules can specify additional programs that should run to configure a device. Rules files, which have the file extension .rules, are located in the following directories:

/lib/udev/rules.d

Contains default rules files. Do not edit these files.

/etc/udev/rules.d/*.rules

Contains customized rules files. You can modify these files.

/dev/.udev/rules.d/*.rules

Contains temporary rules files. Do not edit these files.

Udev processes the rules files in lexical order, regardless of which directory they are located. Rules files in /etc/udev/rules.d override files of the same name in /lib/udev/rules.d.

The following rules are extracted from the file /lib/udev/rules.d/50-udev- default.rules and illustrate the syntax of udev rules.

# do not edit this file, it will be overwritten on update

SUBSYSTEM=="block", SYMLINK{unique}+="block/%M:%m"
SUBSYSTEM!="block", SYMLINK{unique}+="char/%M:%m"

KERNEL=="pty[pqrstuvwxyzabcdef][0123456789abcdef]", GROUP="tty", MODE="0660"
KERNEL=="tty[pqrstuvwxyzabcdef][0123456789abcdef]", GROUP="tty", MODE="0660"
...
# mem
KERNEL=="null|zero|full|random|urandom", MODE="0666"
KERNEL=="mem|kmem|port|nvram",  GROUP="kmem", MODE="0640"
...
# block
SUBSYSTEM=="block", GROUP="disk"
...
# network
KERNEL=="tun",                  MODE="0666"
KERNEL=="rfkill",               MODE="0644"

# CPU
KERNEL=="cpu[0-9]*",            MODE="0444"
...
# do not delete static device nodes
ACTION=="remove", NAME=="", TEST=="/lib/udev/devices/%k", \
    OPTIONS+="ignore_remove"
ACTION=="remove", NAME=="?*", TEST=="/lib/udev/devices/$name", \
    OPTIONS+="ignore_remove"

Comment lines begin with a # character. All other non-blank lines define a rule, which is a list of one or more comma-separated key-value pairs. A rule either assigns a value to a key or it tries to find a match for a key by comparing its current value with the specified value. The following list shows the assignment and comparison operators that you can use:

  • =: Assign a value to a key, overwriting any previous value.

  • +=: Assign a value by appending it to the key's current list of values.

  • :=: Assign a value to a key. This value cannot be changed by any further rules.

  • ==: Match the key's current value against the specified value for equality.

  • !=: Match the key's current value against the specified value for equality.

You can use the following shell-style pattern matching characters in values:

  • ?: Matches a single character.

  • *: Matches any number of characters, including zero.

  • []: Matches any single character or character from a range of characters specified within the brackets. For example, tty[sS][0-9] would match ttys7 or ttyS7.

The following list shows commonly used match keys in rules.

  • ACTION

    Matches the name of the action that led to an event. For example, ACTION="add" or ACTION="remove".

  • ENV{key}

    Matches a value for the device property key. For example, ENV{DEVTYPE}=="disk".

  • KERNEL

    Matches the name of the device that is affected by an event. For example, KERNEL=="dm-*" for disk media.

  • NAME

    Matches the name of a device file or network interface. For example, NAME="?*" for any name that consists of one or more characters.

  • SUBSYSTEM

    Matches the subsystem of the device that is affected by an event. For example, SUBSYSTEM=="tty".

  • TEST

    Tests if the specified file or path exists. For example, TEST=="/lib/udev/devices/$name", where $name is the name of the currently matched device file.

Other match keys include ATTR{filename}, ATTRS{filename}, DEVPATH, DRIVER, DRIVERS, KERNELS, PROGRAM, RESULT, SUBSYSTEMS, and SYMLINK.

The following list shows commonly used assignment keys in rules.

  • ENV{key}

    Specifies a value for the device property key. For example, GROUP="disk".

  • GROUP

    Specifies the group for a device file. For example, GROUP="disk".

  • IMPORT{type}:

    Specifies a set of variables for the device property, depending on type:

    • cmdline: Import a single property from the boot kernel command line. For simple flags, udev sets the value of the property to 1. For example, IMPORT{cmdline}="nodmraid".

    • db: Interpret the specified value as an index into the device database and import a single property, which must have already been set by an earlier event. For example, IMPORT{db}="DM_UDEV_LOW_PRIORITY_FLAG".

    • file: Interpret the specified value as the name of a text file and import its contents, which must be in environmental key format. For example, IMPORT{file}="keyfile".

    • parent: Interpret the specified value as a key-name filter and import the stored keys from the database entry for the parent device. For example IMPORT{parent}="ID_*".

    • program: Run the specified value as an external program and imports its result, which must be in environmental key format. For example IMPORT{program}="usb_id --export %p".

  • MODE

    Specifies the permissions for a device file. For example, MODE="0640".

  • NAME

    Specifies the name of a device file. For example, NAME="em1".

  • OPTIONS

    Specifies rule and device options. For example, OPTIONS+="ignore_remove", which means that the device file is not removed if the device is removed.

  • OWNER

    Specifies the owner for a device file. For example, GROUP="root".

  • RUN

    Specifies a command to be run after the device file has been created. For example, RUN+="/usr/bin/eject $kernel", where $kernel is the kernel name of the device.

  • SYMLINK

    Specifies the name of a symbolic link to a device file. For example, SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}", where $env{} is substituted with the specified device property.

Other assignment keys include ATTR{key}, GOTO, LABEL, RUN, and WAIT_FOR.

The following list shows string substitutions that are commonly used with the GROUP, MODE, NAME, OWNER, PROGRAM, RUN, and SYMLINK keys:

  • $attr{file} or %s{file}

    Specifies the value of a device attribute from a file under /sys. For example, ENV{MATCHADDR}="$attr{address}".

  • $devpath or %p

    The device path of the device in the sysfs file system under /sys. For example, RUN+="keyboard-force-release.sh $devpath common-volume-keys".

  • $env{key} or %E{key}

    Specifies the value of a device property. For example, SYMLINK+="disk/by-id/md-name-$env{MD_NAME}-part%n".

  • $kernel or %k

    The kernel name for the device.

  • $major or %M

    Specifies the major number of a device. For example, IMPORT{program}="udisks-dm-export %M %m".

  • $minor or %m

    Specifies the minor number of a device. For example, RUN+="$env{LVM_SBIN_PATH}/lvm pvscan --cache --major $major --minor $minor".

  • $name

    Specifies the device file of the current device. For example, TEST=="/lib/udev/devices/$name".

Udev expands the strings specified for RUN immediately before its program is executed, which is after udev has finished processing all other rules for the device. For the other keys, udev expands the strings while it is processing the rules.

For more information, see the udev(7) manual page.

Querying Udev and Sysfs

You can use the udevadm command to query the udev database and sysfs.

For example, to query the sysfs device path relative to /sys that corresponds to the device file /dev/sda:

sudo udevadm info --query=path --name=/dev/sda
/devices/pci0000:00/0000:00:0d.0/host0/target0:0:0/0:0:0:0/block/sda

To query the symbolic links that point to /dev/sda:

sudo udevadm info --query=symlink --name=/dev/sda
block/8:0
disk/by-id/ata-VBOX_HARDDISK_VB6ad0115d-356e4c09
disk/by-id/scsi-SATA_VBOX_HARDDISK_VB6ad0115d-356e4c09
disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0

To query the properties of /dev/sda:

sudo udevadm info --query=property --name=/dev/sda
UDEV_LOG=3
DEVPATH=/devices/pci0000:00/0000:00:0d.0/host0/target0:0:0/0:0:0:0/block/sda
MAJOR=8
MINOR=0
DEVNAME=/dev/sda
DEVTYPE=disk
SUBSYSTEM=block
ID_ATA=1
ID_TYPE=disk
ID_BUS=ata
ID_MODEL=VBOX_HARDDISK
ID_MODEL_ENC=VBOX\x20HARDDISK\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20...
ID_REVISION=1.0
ID_SERIAL=VBOX_HARDDISK_VB579a85b0-bf6debae
ID_SERIAL_SHORT=VB579a85b0-bf6debae
ID_ATA_WRITE_CACHE=1
ID_ATA_WRITE_CACHE_ENABLED=1
ID_ATA_FEATURE_SET_PM=1
ID_ATA_FEATURE_SET_PM_ENABLED=1
ID_ATA_SATA=1
ID_ATA_SATA_SIGNAL_RATE_GEN2=1
ID_SCSI_COMPAT=SATA_VBOX_HARDDISK_VB579a85b0-bf6debae
ID_PATH=pci-0000:00:0d.0-scsi-0:0:0:0
ID_PART_TABLE_TYPE=dos
LVM_SBIN_PATH=/sbin
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION_TABLE=1
UDISKS_PARTITION_TABLE_SCHEME=mbr
UDISKS_PARTITION_TABLE_COUNT=2
UDISKS_ATA_SMART_IS_AVAILABLE=0
DEVLINKS=/dev/block/8:0 /dev/disk/by-id/ata-VBOX_HARDDISK_VB579a85b0-bf6debae ...

To query all information for /dev/sda:

sudo udevadm info --query=all --name=/dev/sda
P: /devices/pci0000:00/0000:00:0d.0/host0/target0:0:0/0:0:0:0/block/sda
N: sda
W: 37
S: block/8:0
S: disk/by-id/ata-VBOX_HARDDISK_VB579a85b0-bf6debae
S: disk/by-id/scsi-SATA_VBOX_HARDDISK_VB579a85b0-bf6debae
S: disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0
E: UDEV_LOG=3
E: DEVPATH=/devices/pci0000:00/0000:00:0d.0/host0/target0:0:0/0:0:0:0/block/sda
E: MAJOR=8
E: MINOR=0
E: DEVNAME=/dev/sda
E: DEVTYPE=disk
E: SUBSYSTEM=block
E: ID_ATA=1
E: ID_TYPE=disk
E: ID_BUS=ata
E: ID_MODEL=VBOX_HARDDISK
E: ID_MODEL_ENC=VBOX\x20HARDDISK\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20...
E: ID_SERIAL=VBOX_HARDDISK_VB579a85b0-bf6debae
E: ID_SERIAL_SHORT=VB579a85b0-bf6debae
E: ID_ATA_WRITE_CACHE=1
E: ID_ATA_WRITE_CACHE_ENABLED=1
E: ID_ATA_FEATURE_SET_PM=1
E: ID_ATA_FEATURE_SET_PM_ENABLED=1
E: ID_ATA_SATA=1
E: ID_ATA_SATA_SIGNAL_RATE_GEN2=1
E: ID_SCSI_COMPAT=SATA_VBOX_HARDDISK_VB579a85b0-bf6debae
E: ID_PATH=pci-0000:00:0d.0-scsi-0:0:0:0
E: ID_PART_TABLE_TYPE=dos
E: LVM_SBIN_PATH=/sbin
E: UDISKS_PRESENTATION_NOPOLICY=0
E: UDISKS_PARTITION_TABLE=1
E: UDISKS_PARTITION_TABLE_SCHEME=mbr
E: UDISKS_PARTITION_TABLE_COUNT=2
E: UDISKS_ATA_SMART_IS_AVAILABLE=0
E: DEVLINKS=/dev/block/8:0 /dev/disk/by-id/ata-VBOX_HARDDISK_VB579a85b0-bf6debae ...

To display all properties of /dev/sda and its parent devices that udev has found in /sys:

sudo udevadm info --attribute-walk --name=/dev/sda
...
  looking at device '/devices/pci0000:00/0000:00:0d.0/host0/target0:0:0/0:0:0:0/block/sda':
    KERNEL=="sda"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{range}=="16"
    ATTR{ext_range}=="256"
    ATTR{removable}=="0"
    ATTR{ro}=="0"
    ATTR{size}=="83886080"
    ATTR{alignment_offset}=="0"
    ATTR{capability}=="52"
    ATTR{stat}=="   20884    15437  1254282   338919     5743     8644   103994   109005 ...
    ATTR{inflight}=="       0        0"

  looking at parent device '/devices/pci0000:00/0000:00:0d.0/host0/target0:0:0/0:0:0:0':
    KERNELS=="0:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{device_blocked}=="0"
    ATTRS{type}=="0"
    ATTRS{scsi_level}=="6"
    ATTRS{vendor}=="ATA     "
    ATTRS{model}=="VBOX HARDDISK   "
    ATTRS{rev}=="1.0 "
    ATTRS{state}=="running"
    ATTRS{timeout}=="30"
    ATTRS{iocounterbits}=="32"
    ATTRS{iorequest_cnt}=="0x6830"
    ATTRS{iodone_cnt}=="0x6826"
    ATTRS{ioerr_cnt}=="0x3"
    ATTRS{modalias}=="scsi:t-0x00"
    ATTRS{evt_media_change}=="0"
    ATTRS{dh_state}=="detached"
    ATTRS{queue_depth}=="31"
    ATTRS{queue_ramp_up_period}=="120000"
    ATTRS{queue_type}=="simple"

  looking at parent device '/devices/pci0000:00/0000:00:0d.0/host0/target0:0:0':
    KERNELS=="target0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS==""

  looking at parent device '/devices/pci0000:00/0000:00:0d.0/host0':
    KERNELS=="host0"
    SUBSYSTEMS=="scsi"
    DRIVERS==""

  looking at parent device '/devices/pci0000:00/0000:00:0d.0':
    KERNELS=="0000:00:0d.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="ahci"
    ATTRS{vendor}=="0x8086"
    ATTRS{device}=="0x2829"
    ATTRS{subsystem_vendor}=="0x0000"
    ATTRS{subsystem_device}=="0x0000"
    ATTRS{class}=="0x010601"
    ATTRS{irq}=="21"
    ATTRS{local_cpus}=="00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000003"
    ATTRS{local_cpulist}=="0-1"
    ATTRS{modalias}=="pci:v00008086d00002829sv00000000sd00000000bc01sc06i01"
    ATTRS{numa_node}=="-1"
    ATTRS{enable}=="1"
    ATTRS{broken_parity_status}=="0"
    ATTRS{msi_bus}==""
    ATTRS{msi_irqs}==""

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

The command starts at the device specified by its device path and walks up the chain of parent devices. For every device that it finds, it displays all possible attributes for the device and its parent devices in the match key format for udev rules.

For more information, see the udevadm(8) manual page.

Modifying Udev Rules

The order in which rules are evaluated is important. Udev processes rules in lexical order. If you want to add your own rules, you need udev to find and evaluate these rules before the default rules.

The following example illustrates how to implement a udev rules file that adds a symbolic link to the disk device /dev/sdb.

  1. Create a rule file under /etc/udev/rules.d with a file name such as 10-local.rules that udev will read before any other rules file.

    For example, the following rule in 10-local.rules creates the symbolic link /dev/my_disk, which points to /dev/sdb:

    KERNEL=="sdb", ACTION=="add", SYMLINK="my_disk"

    Listing the device files in /dev shows that udev has not yet applied the rule:

    sudo ls /dev/sd* /dev/my_disk
    ls: cannot access /dev/my_disk: No such file or directory
    /dev/sda  /dev/sda1  /dev/sda2  /dev/sdb
  2. To simulate how udev applies its rules to create a device, you can use the udevadm test command with the device path of sdb listed under the /sys/class/block hierarchy, for example:

    sudo udevadm test /sys/class/block/sdb
    calling: test
    version ...
    This program is for debugging only, it does not run any program
    specified by a RUN key. It may show incorrect results, because
    some values may be different, or not available at a simulation run.
    ...
    LINK 'my_disk' /etc/udev/rules.d/10-local.rules:1
    ...
    creating link '/dev/my_disk' to '/dev/sdb'
    creating symlink '/dev/my_disk' to 'sdb
    ...
    ACTION=add
    DEVLINKS=/dev/disk/by-id/ata-VBOX_HARDDISK_VB186e4ce2-f80f170d 
      /dev/disk/by-uuid/a7dc508d-5bcc-4112-b96e-f40b19e369fe 
      /dev/my_disk
    ...
  3. Restart the systemd-udevd service:

    sudo systemctl restart systemd-udevd

    After udev processes the rules files, the symbolic link /dev/my_disk has been added:

    sudo ls -F /dev/sd* /dev/my_disk
    /dev/my_disk@  /dev/sda  /dev/sda1  /dev/sda2  /dev/sdb

To undo the changes, remove /etc/udev/rules.d/10-local.rules and /dev/my_disk and run systemctl restart systemd-udevd again.