Introduction
A child character device is a node in the Linux kernel’s device model that represents a character device whose logical or physical existence is tied to a parent device. While a character device exposes a file‑system interface for byte‑stream I/O, the “child” designation refers to its placement within the hierarchical structure of sysfs. Such devices are created by drivers when the device they represent is a subordinate component of a larger hardware or virtual subsystem. Typical examples include USB serial ports, pseudo‑terminals, input devices, and the FUSE file‑system interface. The concept of child character devices is central to the modular design of the kernel, allowing drivers to expose functionality without needing to manage the entire device tree themselves.
Definition
In the Linux kernel, a device is an abstract representation of a hardware or virtual component. The kernel organizes devices in a tree, with root devices (e.g., bus controllers) at the top and individual components as leaves. A character device is a type of device that provides unstructured, stream‑based I/O. A child character device is one whose sysfs parent entry is another device node, meaning that it exists only in the context of its parent. The child status does not alter the device’s I/O semantics; it merely dictates how the kernel presents the device to user space and how the driver registers and manages it.
Historical Context
The character device abstraction dates back to early Unix systems, where devices such as /dev/tty and /dev/ttyS0 were exposed as simple file nodes. With the advent of the Linux kernel in the early 1990s, the device model evolved to include a more robust sysfs hierarchy. The 2.6 kernel series introduced the struct device and struct cdev APIs, which formalized the creation of device nodes and their placement in sysfs. The concept of parent‑child relationships in sysfs emerged to reflect the physical topology of buses (PCI, USB, I²C) and to facilitate coherent driver interaction. As drivers grew in complexity, the need to expose multiple logical functions under a single physical device led to the widespread use of child character devices.
Linux Device Model
Device Hierarchy
The kernel’s device model presents a tree of struct device objects. Each object can have zero or more children and a single parent. The root of the tree is the root device, and bus devices such as PCI controllers or USB hubs are typically added as intermediate nodes. Devices attached to a bus are registered as children of the corresponding bus device. This hierarchical organization is reflected in the /sys/class and /sys/bus directories, where each device has a symlinked representation. For example, a USB serial port’s device node resides under /sys/bus/usb/devices/ and appears as a child of the USB hub node.
Character Devices
A character device provides sequential or random access to a device through a file descriptor. The kernel’s register_chrdev_region function reserves a range of major and minor numbers, while cdev_add associates a struct cdev with the device’s file operations. Once registered, the kernel creates a device node in /dev using device_create or class_create. If the device’s struct device has a parent, the device node is considered a child character device.
Child Character Device Registration
Major/Minor Number Allocation
Drivers allocate a major number (identifying the driver) and a minor number (identifying a specific device). The API alloc_chrdev_region can allocate a contiguous range, while register_chrdev_region assigns a specific range. For child devices, drivers often use alloc_chrdev_region once and then offset the minor number for each child.
Creating the cdev Object
- Initialize a
struct cdevwithcdev_init, passing the driver’sfile_operations. - Set the device’s owner using
cdev.owner = THIS_MODULE. - Add the character device to the system with
cdev_add, supplying the device’s dev_t.
Sysfs and Device Creation
To expose the child device to user space, drivers use the following steps:
- Instantiate a
struct classfor the device type, typically viaclass_create. - For each child device, call
device_create(ordevicecreatewith_groups) passing the parentstruct device *obtained from the bus driver. - The
device_createfunction automatically creates the device node in/devand populates the sysfs entry under/sys/class.
Example code for a USB serial driver:
dev_t devt = MKDEV(major, minor);
struct cdev *cdev = &usb_serial_cdev[dev_index];
cdev_init(cdev, &usb_serial_fops);
cdev_add(cdev, devt, 1);
struct device *dev = device_create(usb_serial_class,
usb_device,
devt,
NULL,
"ttyUSB%d",
dev_index);
Permissions and Security
Udev Rules
When the device node is created, udev processes the node’s major/minor pair and applies rules from /etc/udev/rules.d or /lib/udev/rules.d. Rules can set ownership, permissions, and even symlink names. For child devices, the parent’s device type can influence the rule set. Example rule: SUBSYSTEM=="tty", ACTION=="add", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", MODE="0666" sets all Prolific USB serial ports to be world‑readable.
SELinux Contexts
SELinux or AppArmor can restrict access to child devices. The kernel automatically tags the device node with a security context based on the device’s class and the parent’s context. For example, /dev/ttyUSB0 inherits the “tty” type from its parent, allowing services to interact with the device according to policy.
Examples
/dev/ttyUSBx (USB Serial Ports)
USB serial adapters appear as child devices of the USB bus. The driver registers a major number for serial devices (typically 4) and assigns minor numbers 64–79 for each USB serial port. Each port is a child character device with a sysfs entry under /sys/bus/usb/devices/. The device node’s permissions can be set by udev rules that match the vendor/product IDs.
/dev/input/eventX (Input Devices)
The input subsystem creates a separate child character device for each logical input device. For instance, a USB keyboard creates /dev/input/event3 as a child of the USB keyboard’s device node. The driver provides input event reporting through the input_report_key API.
/dev/ptmx and /dev/pts/* (Pseudo‑Terminals)
The PTY subsystem uses a master device (/dev/ptmx) and creates a pool of slave devices (/dev/pts/0, /dev/pts/1, …). Each slave is a child character device whose parent is the PTY master. The kernel dynamically creates the slave node when a process opens /dev/ptmx and attaches it to the appropriate master. Sysfs entries appear under /sys/dev/pts with symlinks named by the slave number.
/dev/fuse (Filesystem in Userspace)
The FUSE kernel module provides a single character device (/dev/fuse) that serves as the gateway for all FUSE file‑system mounts. Internally, the module creates child devices for each user‑space mount point, which appear as entries under /sys/dev/fuse. These child devices expose the same file operations but are isolated per mount.
/dev/tty0 (Console)
The console driver creates /dev/tty0 and /dev/tty1, etc. Each console is a child of the console subsystem. While not a hierarchical bus, the console devices are logically grouped and share the same major number (4).
Use Cases
Multiplexed Interfaces
Drivers for multiplexed hardware often expose multiple logical devices as children. For example, a network adapter with multiple virtual interfaces registers a separate child character device for each interface. This design allows user space to interact with each interface independently while sharing the same physical device.
Virtualization and Hypervisors
Virtual machines expose guest devices as child character devices of the hypervisor’s bus. A virtual GPU driver may create a child /dev/tty for each virtual display. The hypervisor manages the bus hierarchy, and the guest sees each device as a standard character device.
Device Grouping and Attribute Exposure
Child devices can expose additional attributes via sysfs files. For instance, a USB storage driver creates a serial attribute for each child device that lists the device’s serial number. Applications can read these attributes without parsing the device node directly.
Troubleshooting
Examining Device Hierarchy
- Use
ls -l /sys/class/ttyto view the symlinks to/sys/devicesand identify parent devices. - Navigate to the parent’s directory and inspect child symlinks:
cd /sys/devices/usb//tty.
Checking udev Rules
Run udevadm info -a -n /dev/ttyUSB0 to see which rules applied and the resulting attributes. If the node is missing or has incorrect permissions, adjust the rule or re‑load udev: udevadm control --reload-rules && udevadm trigger.
Kernel Logs
The kernel’s dmesg buffer contains messages about device registration and errors. Use dmesg | grep ttyUSB to filter for serial driver messages. If a child device fails to register, the log may indicate “Cannot allocate minor number” or “Device busy.”
Advanced Topics
Kernel Configuration Options
To enable child character device support, the following kernel configuration options are typically required:
CONFIG_SYSFS– Provides the sysfs interface.CONFIGDEVICECLASS– Enables the class interface for device creation.CONFIGSYSFSDEPRECATED– For backward compatibility.
Device Groups and Attribute Files
Drivers can define attribute_group structures that group multiple sysfs attributes. When creating a child device, the driver can pass a pointer to the attribute group, and udev can use these attributes to generate naming conventions or apply rules. Example: the usb_serial_driver exposes a manufacturer and product attribute for each child serial port.
Interacting with Device Hierarchy Programmatically
Applications can traverse the sysfs hierarchy using the libudev library. The udev_device_get_parent function retrieves the parent device of a given child, allowing programs to discover device relationships and gather metadata.
Comparison with Block Devices
While character devices provide byte‑stream I/O, block devices expose block‑based I/O and are represented by a separate struct block_device type. Child block devices are common as well, e.g., a RAID array has individual disks as child block devices. The registration process is similar, but block devices require additional support for buffering and caching. Unlike character devices, block devices often create partitions that appear as further child devices (e.g., /dev/sda1). The sysfs representation for block devices appears under /sys/block rather than /sys/class/tty.
Conclusion
Child character devices are a fundamental mechanism in Linux for representing logical sub‑devices of a larger hardware bus or subsystem. Their integration with sysfs, udev, and security frameworks allows flexible, dynamic, and secure device exposure to user space. Understanding the registration workflow, permissions management, and troubleshooting methods is essential for both driver developers and system administrators dealing with complex device hierarchies.
No comments yet. Be the first to comment!