In the Linux system, all hardware devices are managed as files. These special files corresponding to devices are called device nodes, and they are all stored in the /dev directory. Their function is like a bridge: on one side, they connect to the hardware devices recognized by the system internally, and on the other side, they allow applications to read and write device data just like operating on regular files. This way, programs can indirectly control hardware operations by accessing these special files.
1. SYSFS File System
sysfs is a virtual file system provided by the Linux kernel, usually mounted at the /sys directory. It presents various devices, kernel modules, kernel parameters and other information in the form of directories and attribute files.
Like a window, it allows you to intuitively see all hardware and driver status without delving into kernel code. When a driver registers using interfaces like class_create, it automatically generates related class directories under /sys/class/ (such as /sys/class/myclass/), making it easy to view and automatically generate /dev/ device nodes.
Input:
ls /sys/class/The output will be similar to:
android_usb/ gpio/ net/ rtc/ tty/
block/ hidraw/ power_supply/ sound/ usbmon/
drm/ input/ spidev/ thermal/ video4linux/
...2
3
4
2. Automatic Device File Creation Tools
In newer Linux kernels, device node creation primarily relies on udev (User Device), which is a user-space device management tool. udev can automatically monitor hardware change events emitted by the kernel (such as inserting or removing devices), and intelligently create device node files for all types of devices in the /dev/ directory.
udev advantages:
- Automatically identify devices and create device files;
- Support custom rule settings for device file naming and permissions;
- No need to manually use
mknodto create nodes, convenient and safe.
3. Manually Creating Device Nodes
Although in most cases udev will automatically help us create device file nodes, sometimes for testing or special kernel configurations, we may need to manually create nodes.
You can use the command:
sudo mknod /dev/device_name [c|b] major_number minor_number- device_name: User-defined, such as mydev
- c: Indicates character device (b indicates block device)
- major_number, minor_number: Must match what was registered in the driver
Example: Create mydev with major number 240 and minor number 0:
sudo mknod /dev/mydev c 240 0You also need to set file permissions, for example:
sudo chmod 666 /dev/mydevThis allows all users to read and write the device.
Dangerous operation: Delete device node using
sudo rm /dev/mydev
4. Automatically Creating Device Nodes
The recommended method is to use automatic device node creation. Simply call the class_create and device_create functions provided by the kernel in the driver code, which allows the kernel to automatically generate device files under /dev/, and with udev for proper recognition.
Example (working with driver registration section):
struct class *cls;
cls = class_create(THIS_MODULE, "myclass");
device_create(cls, NULL, devno, NULL, "mydev");2
3
This will automatically generate /dev/mydev.
Remember to clean up when unloading the driver:
device_destroy(cls, devno);
class_destroy(cls);2
5. Device Node Creation Experiment
1. Source Code Writing
Write a simple character device driver that automatically creates the device node /dev/mydev when loaded.
First, create a 04_mk_device_node/ directory and create a source file mydev_mk.c in it, then write the following driver:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
static dev_t devno;
static struct cdev my_cdev;
static struct class *my_class;
// Device operation functions
static int mydev_open(struct inode *inode, struct file *file) { return 0; }
static int mydev_release(struct inode *inode, struct file *file) { return 0; }
static struct file_operations mydev_fops = {
.owner = THIS_MODULE,
.open = mydev_open,
.release = mydev_release,
};
static int __init mydev_init(void)
{
int ret;
// Automatically allocate device number
ret = alloc_chrdev_region(&devno, 0, 1, "mydev");
if (ret < 0) return ret;
// Register cdev
cdev_init(&my_cdev, &mydev_fops);
cdev_add(&my_cdev, devno, 1);
// Create device class and device node
my_class = class_create(THIS_MODULE, "myclass");
device_create(my_class, NULL, devno, NULL, "mydev");
printk("mydev driver installed!\n");
return 0;
}
static void __exit mydev_exit(void)
{
device_destroy(my_class, devno);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(devno, 1);
printk("mydev driver removed!\n");
}
module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2. Source Code Analysis
mydev_open and mydev_release are required parameters for file_operations:
openis called automatically by the kernel when you access the device file using a system call likeopen("/dev/mydev"). You can usually do some initialization work here, such as parameter checking, buffer allocation, etc. In this example, since there is no real physical device, it's just a demonstration, so you can simplyreturn 0;.releaseis called automatically by the kernel when closing the device file or when the program exits, to perform cleanup and release work. Here we also just return 0, indicating no special operations.
alloc_chrdev_region(&devno, 0, 1, "mydev");: Automatically allocate device number
devno: Used to save the major and minor device numbers (actually a combined dev_t type value).0: Represents that the starting minor device number is 0.1: Represents how many consecutive device numbers are needed. Here it is 1, only one number is allocated."mydev": Gives the device number a name label for easy viewing in/proc/devicesand other kernel logs and debugging information.
Example explanation:
- When creating only one
/dev/mydevnode, you can write1. - If you want to create multiple (such as
/dev/mydev0,/dev/mydev1, etc.), you can write0, 4, which will allocate four minor device numbers in sequence, allowing you to implement a "multi-device instance" driver.
cdev_init and cdev_add: Register and associate cdev object
**cdev_init(&my_cdev, &mydev_fops);**Creates and initializes a character device objectmy_cdev, and associates it with our defined device operation setmydev_fops. This way the kernel knows how to call your driver code when opening this device file.**cdev_add(&my_cdev, devno, 1);**Tells the kernel: "I want to register this character devicemy_cdev, with device numberdevno(generally the major+minor number allocated by alloc), and the device quantity is 1." After success, the kernel will forward user requests (such asopen/read/write) for this major/minor device number range to this driver for processing.
class_create and device_create: Automatically generate device file nodes
**class_create(THIS_MODULE, "myclass")**Creates a class namedmyclassunder the/sys/class/directory. It not only provides identification information for the user-spaceudevmanagement mechanism but also allows users to see related nodes under/sys/class/myclass/, serving as a bridge between the driver and system automation management.**device_create(my_class, NULL, devno, NULL, "mydev")**Through the previously allocated device numberdevno, automatically creates a device file node named/dev/mydevunder/dev/. This way, ordinary users and applications can access hardware devices using standard file operations, without needing to manually mknod to create nodes.
Unload process:
device_destroy: Delete/dev/mydevnode.class_destroy: Unregister device class, delete/sys/class/myclass/directory.cdev_del: Unregister character device object.unregister_chrdev_region: Unregister the previously allocated device number.
Proper resource release is a basic skill in driver development; otherwise, kernel reloads or long-term system operation can easily produce "dirty" environments or even crashes.
3. Makefile Writing
Create a Makefile file in the 04_mk_device_node/ folder and write the following:
export ARCH=arm64
# Cross compiler absolute path prefix
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# Consistent with source file name
obj-m += mydev_mk.o
# Kernel source directory
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
4. Makefile Explanation
Looking at this, you should be quite familiar with it - it's almost exactly the same as the Makefile in the chapter on writing your first driver!
The only difference is that it became mydev_mk.o
CROSS_COMPILE: Still the compiler path prefix from the SDK.KDIR: Still the kernel source directory.
5. Compilation
INFO
Special Note: Before compiling the kernel driver module, make sure the kernel has already been compiled, because some dependency files for compiling kernel modules need to be generated after compiling the kernel.
How to compile the kernel: Reference Debian12 Kernel Compilation
Enter 04_mk_device_node/ and run:
makeWhen you see mydev_mk.ko generated in the directory, the compilation was successful:
6. Driver Testing
Copy mydev_mk.ko to our development board (either USB drive or SSH, we discussed how to transfer files earlier), and use the following command to mount the driver:
sudo insmod mydev_mk.koThen we use dmesg | grep -E 'mydev' to view logs and look for all logs related to the mydev keyword. We see relevant logs, which is the mydev driver installed! statement we wrote in the driver ourselves, so the mount was successful.
Next, we use the ls -l /dev/mydev command to view the automatically generated device file:
At this point we see
/dev/mydevhas appeared, with the automatically allocated device number511, 0.
Next, we use the following command to unload the mounted mydev_mk driver and see what happens:
sudo rmmod mydev_mkThe logs show we have successfully unloaded. Next, let's check if the previous /dev/mydev file still exists:
ls -l /dev/mydevWe found that this file no longer exists. In other words, when the driver is unloaded, the device node is also removed. We performed a series of operations in the driver unload function:
- Delete device node
- Delete device class
- Unregister cdev
- Release device number
So after our driver exits, the resources are completely reclaimed.