Timeline
Timeline
2025-12-02
init
This article introduces the hotplug mechanism in the Linux system. It explains that hotplug is a technology that allows hardware devices to be safely inserted or removed while the device is running, without restarting the system. Its purpose is to improve the convenience and flexibility of devices, and it is widely used in scenarios such as USB devices, hard disk drives, and expansion cards. The article details the implementation mechanism of hotplug, in which the kernel interacts by calling user-space programs (such as mdev and udev). mdev is suitable for embedded Linux systems, while udev is widely used on PCs. It focuses on analyzing the role of the kernel function kobject_uevent(), which encapsulates actions such as device addition, removal, and modification into uevent events through the netlink mechanism and sends them to user space. After receiving them, udev performs operations such as creating device nodes or loading drivers. In addition, the article also mentions the udevadm command-line tool, which is used to query and manage devices and trigger uevent events. The overall content covers the underlying principles of hotplug, the collaboration process between the kernel and user space, and common management tools.
Linux Driver Notes
| Table of Contents | Links |
|---|---|
| 1. Linux Driver Framework | |
| 2. Linux Driver Loading Logic | |
| 3. Character Device Basics | |
| 4. Concurrency and Race Conditions | |
| 5. Advanced Character Device Topics | |
| 6. Interrupts | |
| 7. Platform Bus | |
| 8. Device Tree | |
| 9. Device Model | |
| 10. Hotplug | |
| 11. pinctrl Subsystem | |
| 12. GPIO subsystem | |
| 13. Input subsystem | |
| 14. 1-Wire | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. Network devices | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
Hotplug
**Hotplug refers to the ability to safely insert or remove hardware devices while the device is running, without shutting down or restarting the system.**This means you can insert or remove hardware components (such as USB devices, expansion cards, hard drives, etc.) on a computer or other electronic device without shutting down or interrupting ongoing operations.
The main purpose of hotplug is to provide convenience and flexibility. With hotplug, you can quickly replace or add hardware devices without stopping ongoing tasks. This is very useful in many scenarios, such as:
- USB devices: You can insert or remove USB devices at any time, such as mice, keyboards, printers, storage devices, etc., without restarting the system.
- Hard disk drives: In some servers or storage systems, you can add or replace hard disk drives at runtime to expand storage capacity or replace failed drives.
- Expansion cards: You can insert or remove expansion cards such as graphics cards, network cards, or sound cards on a computer to meet different needs or upgrade hardware performance.
To support hotplug functionality, both hardware devices and the system must have corresponding support. On the hardware side, device interfaces must be designed to allow insertion and removal without damaging the device or system. The system needs to provide corresponding drivers and management functions to correctly configure and identify devices when they are inserted or removed.
Hotplug Mechanism
Hotplug is the interaction between the kernel and user space by calling user-space programs (such ashotplug、udevandmdev) interaction. When the kernel needs to notify user space that a hotplug event has occurred, it calls this user-space program to implement the interaction.
In the Linux kernel, the hotplug mechanism supports dynamic insertion and removal of components such as USB devices, PCI devices, and even CPUs. This mechanism implements the connection between underlying hardware, kernel space, and user-space programs, and has been continuously evolving and improving. The device file system is a mechanism for managing device files. There are three common device file systems in Linux: devfs, mdev, and udev.
devfs: devfs is a kernel-based dynamic device file system that first appeared in the Linux 2.3.46 kernel. It handles device files by dynamically creating and managing device nodes. However, devfs has some limitations and performance issues, and was removed starting from Linux version 2.6.13.mdev: mdev is alightweight hotplug device user-space program, usually used in embedded Linux systems. It is a simplified version of udev, usinguevent_helpermechanism to handle device insertion and removal events. When a device is inserted, mdev calls the corresponding user program to create device nodes.udev: udev is currentlya hotplug device user-space program widely used on PCs. It is based onnetlinkmechanism, listening to uevents sent by the kernel to handle device insertion and removal. udev can dynamically create and manage device nodes, and load appropriate drivers when devices are inserted. It provides rich configuration options, allowing users to flexibly manage device files.
Kernel Sends Events to User Space
kobject_uevent()
kobject_ueventIt is a function in the Linux kernel used to generate and send uevent events. It is a way for udev and other device management tools to communicate with the kernel.
12345678910111213141516 | // lib/kobject_uevent.c/** * kobject_uevent - notify userspace by sending an uevent * * @kobj: struct kobject that the action is happening to * @action: action that is happening * * Returns 0 if kobject_uevent() is completed with success or the * corresponding error when it fails. */int kobject_uevent(struct kobject *kobj, enum kobject_action action){ return kobject_uevent_env(kobj, action, NULL);}EXPORT_SYMBOL_GPL(kobject_uevent); |
Parameter description:
kobj: The kernel object (kobject) for which the uevent event is to be sent.action: Indicates the action that triggers the uevent, which can be device insertion, removal, attribute changes, etc. The following are some common action parameter values. These action types are used to describe different events that occur on a device, by passing the corresponding action type as the action parameter tokobject_ueventThe function can trigger the corresponding uevent event, notifying user-space udev to perform the corresponding operation.KOBJ_ADD: indicatesDevice addition or insertion operation, indicating adding an object to the kernel object system.KOBJ_REMOVE: indicatesDevice removal or unplugging operation, indicating deleting an object from the kernel object system.KOBJ_CHANGE: indicatesDevice attribute modification operation, indicating changes to the kernel object, such as attribute modification, etc.KOBJ_MOVE: indicatesDevice movement operation, that is, the device moves from one location to another.KOBJ_ONLINE: indicatesDevice online operation, that is, the device changes from offline state to online state, making it accessible.KOBJ_OFFLINE: indicatesDevice offline operation, that is, the device changes from online state to offline state, making it inaccessible.KOBJ_BIND: indicatesconnecting a device to the kernel objectKOBJ_UNBIND: indicatesunbinding a device from the kernel objectKOBJ_MAX: indicates the maximum value of the enumeration type, usually used to indicate that there is no operation behavior.
kobject_ueventThe main role of the function is to generate uevent events in the kernel and send the events to user-space udev via the netlink mechanism. When the function is called, the kernel encapsulates the relevant device information and event type into a uevent message and sends the message to user space through a netlink socket.
The udev in user space will receive these uevent messages and perform corresponding operations based on the device information and event type in the messages, such as creating or deleting device nodes, loading or unloading drivers, etc.
example
123456789101112131415161718192021222324252627282930313233343536373839404142 | struct kobject *mykobject01;struct kset *mykset;struct kobj_type mytype;static int __init mykobj_uevent_init(void){ int ret; // Create and add a kset mykset = kset_create_and_add("mykset", NULL, NULL); // Initialize and add a kobject to the kset mykobject01 = kzalloc(sizeof(*mykobject01), GFP_KERNEL); mykobject01->kset = mykset; ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01"); // Trigger a uevent event ret = kobject_uevent(mykobject01, KOBJ_CHANGE); return 0;}static void __exit mykobj_uevent_exit(void){ kobject_put(mykobject01); kset_unregister(mykset);}module_init(mykobj_uevent_init);module_exit(mykobj_uevent_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for kobject uevent"); |
udevadm command
udevadmis a command-line tool used to interact with the udev device manager. It provides a series of subcommands for querying and managing devices, triggering uevent events, and performing other udev-related operations. Some commonudevadmsubcommands and their functions are as follows:
udevadm info: used to obtain detailed information about a device, including device path, attributes, drivers, etc.udevadm monitor: used to monitor and display uevent events in the current system. It displays device insertion, removal, and other related events in real time.udevadm trigger: used to manually trigger uevent events for devices. This command can be used to simulate device insertion, removal, and other operations to trigger corresponding event handling.udevadm settle: used to wait for udev to process all queued uevent events. It blocks until udev completes all current device processing operations.udevadm control: used to interact with the udev daemon and control its behavior. For example, this command can be used to reload udev rules, set log levels, etc.udevadm test: used to test the matching and execution process of udev rules. This command can be used to test whether a specific device correctly triggers the corresponding rules.
Test:
12345678910111213 | root@topeet:/root# udevadm monitor &[1] 1561root@topeet:/root# monitor will print the received events for:UDEV - the event which udev sends out after rule processingKERNEL - the kernel ueventroot@topeet:/root# insmod uevent_test.koKERNEL[1812.051745] change /mykng out-of-tree module taints kernel. set/mykobject01 (mykset)KERNEL[1812.056798] add /module/uevent_test (module)root@topeet:/root# UDEV [1812.061846] change /mykset/mykobject01 (mykset)UDEV [1812.068604] add /module/uevent_test (module) |
uevent is implemented based on kset
In the driver code above, ifthe step of creating a kset is removed, user space cannot receive uevent events, the analysis is as follows:
kobject_uevent_env()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 | /** * kobject_uevent_env - send an uevent with environmental data * * @kobj: struct kobject that the action is happening to * @action: action that is happening * @envp_ext: pointer to environmental data * * Returns 0 if kobject_uevent_env() is completed with success or the * corresponding error when it fails. */int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]){ struct kobj_uevent_env *env;//points to kobj_uevent_pointer to the env structure, used to store the sent event and environment variables const char *action_string = kobject_actions[action];//type of event const char *devpath = NULL;//stores the path of the kobject const char *subsystem;//stores the name of the subsystem to which it belongs struct kobject *top_kobj;//kobject pointer pointing to the top-level top_kobj struct kset *kset;//pointer to kset, indicating the kset to which the kobject belongs const struct kset_uevent_ops *uevent_ops;//Pointer to struct kset_uevent_Pointer to the ops structure int i = 0;//Counter i, used to build the environment variable array int retval = 0;//Represents the execution result of the function, i.e., the return value /* * Mark "remove" event done regardless of result, for some subsystems * do not want to re-trigger "remove" event via automatic cleanup. */ if (action == KOBJ_REMOVE)//Check whether action is KOBJ_REMOVE, if so, set kobj->state_remove_uevent_sent is set to 1, indicating that the 'remove' event has been sent. kobj->state_remove_uevent_sent = 1; pr_debug("kobject: '%s' (%p): %s\n", kobject_name(kobj), kobj, __func__); /* search the kset we belong to */ top_kobj = kobj; while (!top_kobj->kset && top_kobj->parent)// Loop to find the kset to which kobj belongs, until finding the top-level kobj of a concrete valid kset, i.e., the root node of the kset. top_kobj = top_kobj->parent; if (!top_kobj->kset) {// If kobj has no kset, return directly. pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " "without kset!\n", kobject_name(kobj), kobj, __func__); return -EINVAL; } kset = top_kobj->kset; uevent_ops = kset->uevent_ops;// The uevent_ops structure of the top-level kset. /* skip the event, if uevent_suppress is set*/ if (kobj->uevent_suppress) {// Check kobj->uevent_whether suppress is 1, if kobj->uevent_suppress, then output debug information indicating that the event is skipped. pr_debug("kobject: '%s' (%p): %s: uevent_suppress " "caused the event to drop!\n", kobject_name(kobj), kobj, __func__); return 0; } /* skip the event, if the filter returns zero. */ if (uevent_ops && uevent_ops->filter) if (!uevent_ops->filter(kset, kobj)) { pr_debug("kobject: '%s' (%p): %s: filter function " "caused the event to drop!\n", kobject_name(kobj), kobj, __func__); return 0; } /* originating subsystem */ // According to uevent_the name field in ops obtains the name of the original subsystem. If uevent_ops->name exists, then call the uevent_ops->name(kset, kobj) function to get the subsystem name; otherwise, use the kset's name as the subsystem name. if (uevent_ops && uevent_ops->name) subsystem = uevent_ops->name(kset, kobj); else subsystem = kobject_name(&kset->kobj); if (!subsystem) { pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the " "event to drop!\n", kobject_name(kobj), kobj, __func__); return 0; } /* environment buffer */ env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); if (!env) return -ENOMEM; /* complete object path */ devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; } /* default keys */ retval = add_uevent_var(env, "ACTION=%s", action_string); if (retval) goto exit; retval = add_uevent_var(env, "DEVPATH=%s", devpath); if (retval) goto exit; retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); if (retval) goto exit; /* keys passed in from the caller */ if (envp_ext) { for (i = 0; envp_ext[i]; i++) { retval = add_uevent_var(env, "%s", envp_ext[i]); if (retval) goto exit; } } /* let the kset specific function add its stuff */ if (uevent_ops && uevent_ops->uevent) { retval = uevent_ops->uevent(kset, kobj, env); if (retval) { pr_debug("kobject: '%s' (%p): %s: uevent() returned " "%d\n", kobject_name(kobj), kobj, __func__, retval); goto exit; } } switch (action) { case KOBJ_ADD: /* * Mark "add" event so we can make sure we deliver "remove" * event to userspace during automatic cleanup. If * the object did send an "add" event, "remove" will * automatically generated by the core, if not already done * by the caller. */ kobj->state_add_uevent_sent = 1; break; case KOBJ_UNBIND: zap_modalias_env(env); break; default: break; } mutex_lock(&uevent_sock_mutex); /* we will send an event, so request a new sequence number */ // Add an environment variable named "SEQNUM" to the uevent environment variable list, and its value //set to uevent_the value of seqnum plus 1. Here, add_uevent_var is an internal function used to add a key-value //pair to the uevent environment variable list. If adding fails, the function returns a non-zero value and releases //uevent_sock_the mutex lock and jumps to the exit label for cleanup. The main purpose of this function is to //add a unique sequence number to uevent events, so that their order can be identified when processing uevent events. In plain //terms, every time an event is sent, it must have its event number, which cannot be repeated, and it is also added to the environment variables. retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum); if (retval) { mutex_unlock(&uevent_sock_mutex); goto exit; } retval = kobject_uevent_net_broadcast(kobj, env, action_string, devpath);// The kernel broadcasts a uevent event mutex_unlock(&uevent_sock_mutex); /* call uevent_helper, usually only enabled during early boot */ if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { struct subprocess_info *info; retval = add_uevent_var(env, "HOME=/"); if (retval) goto exit; retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); if (retval) goto exit; retval = init_uevent_argv(env, subsystem); if (retval) goto exit; retval = -ENOMEM; info = call_usermodehelper_setup(env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env); if (info) { retval = call_usermodehelper_exec(info, UMH_NO_WAIT); env = NULL; /* freed by cleanup_uevent_env */ } }exit: kfree(devpath); kfree(env); return retval;}EXPORT_SYMBOL_GPL(kobject_uevent_env); |
Because uevent is sent vianetlink socketto user-space applications, andkobject_uevent_env()requires that the kobject must belong to a kset — the kset is needed to obtainuevent_ops(filter/name/uevent callbacks) and the subsystem name (SUBSYSTEM), otherwise it will directly return-EINVALand fail to send the event.
kobject_uevent_net_broadcastis a kernel function used to send a uevent event to all network namespaces in the system. Its parameters include:
kobjis the kernel object associated with the uevent eventenvis a list containing uevent environment variablesaction_stringis a string representing the type of the uevent eventdevpathis a string representing the device path associated with the uevent event.
This function traverses all network namespaces in the system and sends the uevent event to each network namespace. The main purpose of this function is to broadcast a uevent event in the kernel so that user-space applications can receive and process these events.
In the kernel, the user-spaceuevent_helperprogram is called to handle uevent events.uevent_helperis a user-space program that can be called when a uevent event is generated in kernel space. IfCONFIG_UEVENT_HELPERmacro is defined, the kernel will call theuevent_helperprogram, so as to process these events in user space. In the above code, ifuevent_helpervariable is not empty and thekobj_usermode_filterfunction returns false, then thecall_usermodehelper_setupfunction is called to start a user-space process and pass the parameters in env to the process. In this process, the parameters in env will be converted into environment variables and passed to the user-space process.
And in the function that creates a kset and adds it to the system, the second parameter is the passed-in uevent-related ops
123 extern struct kset * __must_check kset_create_and_add(const char *name, const struct kset_uevent_ops *u, struct kobject *parent_kobj);
struct kset_uevent_ops
Andstruct kset_uevent_opsdefined as follows
123456 | struct kset_uevent_ops { int (* const filter)(struct kset *kset, struct kobject *kobj); const char *(* const name)(struct kset *kset, struct kobject *kobj); int (* const uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);}; |
1.filterevent filter
Function: determines whether toblocksend this to user space
kobjectthe uevent.Return value:
0:filter out(do not send uevent)- non-
0:allowed to send
Typical Uses:
- Some internal devices do not require user space intervention;
- Avoid triggering uevents for virtual devices (such as debugfs entries).
example:
12345 | static int my_filter(struct kset *kset, struct kobject *kobj){ /* If the device name starts with "internal_", do not notify user space */ return strncmp(kobject_name(kobj), "internal_", 9) != 0;} |
2.namesubsystem name provider
- Function: return the
kobjectto which it belongs “subsystem” name, as the uevent’sSUBSYSTEM=field. - Return value: a string (such as
"usb","block","mybus")。 - importance: user space (e.g., udev rules) often based on
SUBSYSTEMmatching rules. - example:
1234 | static const char *my_name(struct kset *kset, struct kobject *kobj){ return "mybus"; // All device uevents under this kset carry SUBSYSTEM=mybus} |
The corresponding uevent message will contain
1234 | ACTION=addDEVPATH=/devices/mydeviceSUBSYSTEM=mybus ← 来自此回调... |
Note: if not providednamecallback, the kernel defaults to usingkset->uevent_opsthe location’sksetname (usually the parent directory name).
3.ueventCustom Environment Variable Injector
Function: to the environment variable buffer of uevent (
env) inAdd custom key-value pairs。Parameters:
env: type isstruct kobj_uevent_env, internally it is a string array (maximum 32 items, each 2048 bytes).
Calling Method: Use
add_uevent_var(env, "KEY=%s", value)。Typical Uses:
- Pass device attributes (such as vendor ID, serial number);
- Trigger specific user-space logic.
example
1234567 | static int my_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env){ add_uevent_var(env, "MYBUS_VERSION=1.0"); add_uevent_var(env, "DEVICE_TYPE=sensor"); return 0;} |
The corresponding uevent will contain
12 | MYBUS_VERSION=1.0DEVICE_TYPE=sensor |
Example: Improve kset_uevent_ops struct
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 | struct kobject *mykobject01;struct kobject *mykobject02;struct kset *mykset;struct kobj_type myktype;int myfilter(struct kset *kset, struct kobject *kobj){ if (strcmp(kobj->name, "mykobject01") == 0) { return 0; // Returning 0 means filter it out and do not send uevent. } return 1;}const char *myname(struct kset *kset, struct kobject *kobj){ return "my_kset";}int myuevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env){ add_uevent_var(env, "MYDEVICE=%s", "RK3568"); return 0;}static struct kset_uevent_ops my_uevent_ops = { .filter = myfilter, .name = myname, .uevent = myuevent,};static int __init kset_uevent_ops_init(void){ int ret = 0; // Create and add a kset mykset = kset_create_and_add("mykset", &my_uevent_ops, NULL); // Create kboject01 mykobject01 = kzalloc(sizeof(*mykobject01), GFP_KERNEL); mykobject01->kset = mykset; ret = kobject_init_and_add(mykobject01, &myktype, NULL, "mykobject01"); // Create kboject02 mykobject02 = kzalloc(sizeof(*mykobject02), GFP_KERNEL); mykobject02->kset = mykset; ret = kobject_init_and_add(mykobject02, &myktype, NULL, "mykobject02"); // Trigger uevent ret = kobject_uevent(mykobject01, KOBJ_ADD); ret = kobject_uevent(mykobject02, KOBJ_ADD); return ret;}static void __exit kset_uevent_ops_exit(void){ kobject_put(mykobject01); kobject_put(mykobject02); kset_unregister(mykset);}module_init(kset_uevent_ops_init);module_exit(kset_uevent_ops_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for kset_uevent_ops"); |
Test:
1234567891011 | root@topeet:/root# udevadm monitor &[1] 1341root@topeet:/root# monitor will print the received events for:UDEV - the event which udev sends out after rule processingKERNEL - the kernel ueventroot@topeet:/root# insmod kset_uevent_ops_test.ko[ 170.300220] kset_uevent_ops_test: loading out-of-tree module taintsKERNEL[167.589323] add /mykset/mykobject02 (my_ksekt)eKERNEL[167.594449] add /module/kset_uevent_ops_test (module)root@topneet:/root# UDEV [167.600297] add /myeksetl/mykobject02 (my_kset)e)EV [167.60.7308] add /module/kset_uevent_ops_test (modul |
netlink
netlink listens to broadcast information
Netlink is in the Linux kernelA mechanism for duplex communication between the kernel and user space. ItBased on socket communication mechanism, and provides a reliable, asynchronous, multicast, ordered communication method.
The main features of the Netlink mechanism include:
- Full-duplex communication: Netlink allows bidirectional communication between the kernel and user space, enabling the kernel to send messages to user space and also receive messages from user space.
- Reliability: Netlink provides a reliable message delivery mechanism, ensuring message integrity and reliability. It uses acknowledgment and retransmission mechanisms to ensure reliable transmission of messages.
- Asynchronous communication: Netlink supports asynchronous communication, meaning the kernel and user space can send and receive messages independently without synchronously waiting for the other party’s response.
- Multicast support: Netlink allows broadcasting messages to multiple processes or sockets to achieve one-to-many communication.
- Ordered transmission: Netlink guarantees ordered transmission of messages, meaning that sent messages are received in the order they were sent.
Netlink has a wide range of applications. Common applications include:
- System administration tools: Tools such as ifconfig and ip use Netlink to communicate with the kernel to obtain and configure network interface information.
- Inter-process communication: Processes can use Netlink for cross-process communication to achieve data exchange and coordination between processes.
- Communication between kernel modules and user-space applications: Kernel modules can send notifications to user-space applications or receive instructions from user-space applications via Netlink.
Using netlink
Creating a socket
In Linux socket programming, creating a socket is the first step in building a network application. A socket can be understood as a bridge between the application and the network, used for sending, receiving, and processing data over the network. The prototype of this system call and the required header files are as follows:
| Header file | Function prototype |
|---|---|
#include<sys/types.h>#include<sys/socket.h> | int socket(int domain, int type, int protocol); |
domain The parameter specifies the protocol family of the socket.
The protocol family specifies the type of protocol used by the socket. Common protocol families includeAF_INET、AF_INET6、AF_UNIXetc. Among them:
AF_INETrepresents the IPv4 protocol familyAF_INET6represents the IPv6 protocol familyAF_UNIXrepresents the Unix domain protocol familyAF_NETLINKrepresents the Netlink domain protocol family.
type The parameter specifies the type of socket.
The socket type specifies the data transmission method of the socket. Commonly used socket types includeSOCK_STREAM、SOCK_DGRAM、SOCK_RAWetc. Among them:
SOCK_STREAMIndicates a connection-oriented stream socket, mainly used for reliable data transmission, such as the TCP protocol.SOCK_DGRAMIndicates a connectionless datagram socket, mainly used for unreliable data transmission, such as the UDP protocol.SOCK_RAWIndicates a raw socket, which can directly access the underlying network protocol.
protocol The parameter specifies the specific protocol used by the socket. The meanings of these three parameters are described below respectively:
The protocol type specifies the specific protocol type used by the socket. Commonly used protocol types includeIPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMPetc. Among them:
IPPROTO_TCPIndicates the TCP protocolIPPROTO_UDPIndicates the UDP protocolIPPROTO_ICMPIndicates the ICMP protocolNETLINK_KOBJECT_UEVENTIndicates the NETLINK_KOBJECT protocol
The following code will be used to create a new socket:
1 | int socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); |
AF_NETLINK: Specifies the use of the Netlink protocol family. The Netlink protocol family is a Linux-specific protocol family used for communication between the kernel and user space.SOCK_RAW: Specifies the creation of a raw socket. This socket type can directly access the underlying protocol without protocol stack processing. In this case, we can directly use the Netlink protocol for communication.NETLINK_KOBJECT_UEVENT: Specifies a type of Netlink protocol, namely the kobject uevent type. kobject uevent is used for event notifications related to kernel objects. When a kobject object in the kernel changes, user space is notified through this type of Netlink message.
Binding a socket
| Required header files | Function prototype |
|---|---|
#include<sys/types.h>#include<sys/socket.h> | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
sockfd Parameter: specifies the socket descriptor to be bound,
addr Parameter: specifies the address information to be bound. Here, the
struct sockaddr_nlstructure,struct sockaddr_nlThe definition of the structure is as follows:123456
struct sockaddr_nl { sa_family_t nl_family; // AF_NETLINK unsigned short nl_pad; // zero uint32_t nl_pid; // port ID uint32_t nl_groups;// multicast groups mask};
nl_family: Indicates the address family, which is fixed toAF_NETLINK, indicating the use of the Netlink protocol family.nl_pad: Padding field, set to 0. Used for byte alignment in structures.nl_pid: Port ID, set to 0 to indicate allocation by the kernel.nl_groups: Multicast group mask, used to specify the multicast groups of interest. When set to 1, it indicates that the user-space process will only receive kernel events from the base group. This means that the user-space process will only receive kernel events belonging to the base group and will not receive events from other multicast groups.
addrlen Parameters: The addrlen parameter is an integer that specifies the byte length of the structure pointed to by addr. It is used to ensure correct parsing of the size of the structure passed to the addr parameter.
example
1234567891011121314151617 | { ... struct sockaddr_nl nl; // Define a pointer nl to the struct sockaddr_nl structure. bzero(&nl, sizeof(struct sockaddr_nl));// Clear the memory area pointed to by nl to ensure that the structure's fields are initialized to 0. nl.nl_family = AF_NETLINK;// Set the nl field of the nl structure_family field to AF_NETLINK, specifying the address family as Netlink. nl.nl_pid = 0;// Set the nl_pid field of the nl structure to 0, indicating that the target process ID is 0, i.e., broadcast to all processes. nl.nl_groups = 1;// Set the nl_groups field of the nl structure to 1, indicating that only kernel events of the base group are received. // Use the bind function to bind the socket_fd socket to the nl address structure. ret = bind(socket_fd, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)); if (ret < 0) { printf("bind error\n"); return -1; } ...} |
Receive data
A Netlink socket does not need to call the listen function when receiving data; instead, it can directly use the recv function to receive data.
| Required header files | Function prototype |
|---|---|
#include<sys/types.h>#include<sys/socket.h> | ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
Function parameters:
sockfd: Specifies the socket descriptor, i.e., the Netlink socket that will receive data.buf: Pointer to the data receiving buffer, used to store the received data.len: Specifies the byte size of the data to be read.flags: Specifies some flags to control the way data is received. Usually, it can be set to 0.
Return value:
- On success, returns the number of bytes actually read.
- If the return value is 0, it indicates that the peer has closed the connection.
- If the return value is -1, it indicates an error occurred; you can check the errno variable to get the specific error code.
Using the recv function, data can be received from the specified Netlink socket and stored in the provided buffer. The return value of the function indicates the number of bytes actually read, and you can determine whether data was successfully received based on the return value.
Example code:
12345678910 | while (1) { bzero(buf, 4096); // Clear the buffer buf to zero to ensure initialization before data reception. len = recv(socket_fd, buf, 4096, 0);// Receive data from the socket_fd socket, store it into the buffer buf, with a maximum of 4096 bytes received. for (i = 0; i < len; i++) { if (*(buf + i) == '\0') {// If the received data contains a '\0' character, replace it with '\n' so that it displays as a newline when printed. buf[i] = '\n'; } } printf("%s\n", buf);// Print the received data.} |
Example code
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 | int main(int argc, char *argv[]){ int ret; int len = 0, i = 0; int socket_fd; char buf[UEVENT_BUF_SIZE] = { 0 }; struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_pid = 0, // Port ID allocated by the kernel (usually set to 0 in user space) .nl_groups = 1, // Only receive kernel events belonging to the base group. }; socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); if (socket_fd < 0) { perror("socket"); exit(EXIT_FAILURE); } ret = bind(socket_fd, (const struct sockaddr *)&nl, sizeof(struct sockaddr_nl)); if (ret < 0) { perror("bind"); close(socket_fd); exit(EXIT_FAILURE); } printf("Listening for uevents...\n"); while (1) { memset(buf, 0, sizeof(buf)); len = recv(socket_fd, buf, sizeof(buf), 0); if (len < 0) { perror("recv"); break; } for (i = 0; i < len && i < sizeof(buf) - 1; i++) { if (*(buf + i) == '\0') { buf[i] = '\n'; } } buf[len] = '\0'; // Ensure null termination printf("\n--- Uevent Received ---\n%s", buf); } close(socket_fd); return 0;} |
Test:
123456789101112131415161718 | root@topeet:/root# ./netlink_test.o &[1] 2275root@topeet:/root# Listening for uevents...root@topeet:/root# insmod kset_uevent_ops_test.ko--- Uevent Received ---add@/mykset/mykobject02ACTION=addDEVPATH=/mykset/mykobject02SUBSYSTEM=my_ksetMYDEVICE=RK3568SEQNUM=3288--- Uevent Received ---root@topeet:/root# add@/module/kset_uevent_ops_testACTION=addDEVPATH=/module/kset_uevent_ops_testSUBSYSTEM=moduleSEQNUM=3289 |
uevent_helper
Beforekobject_uevent_env()In the function:
1234567891011121314151617181920212223242526 | /* call uevent_helper, usually only enabled during early boot */ if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { struct subprocess_info *info; retval = add_uevent_var(env, "HOME=/"); if (retval) goto exit; retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); if (retval) goto exit; retval = init_uevent_argv(env, subsystem); if (retval) goto exit; retval = -ENOMEM; info = call_usermodehelper_setup(env->argv[0], env->argv, env->envp, GFP_KERNEL, NULL, cleanup_uevent_env, env); if (info) { retval = call_usermodehelper_exec(info, UMH_NO_WAIT); env = NULL; /* freed by cleanup_uevent_env */ } } |
Line 3 is an if expression that checksuevent_helperwhether the first element of the array is true. And it callskobj_usermode_filterfunction to perform user-mode filtering,uevent_helperdefined as follows:
1 | char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH; |
CONFIG_UEVENT_HELPER_PATHis a macro defined in the kernel source’sinclude/generated/autoconf.hfile, as shown below:
1 | |
This macro is empty, so to enableuevent_helperthe feature needs to be enabled in the graphical configuration interfaceCONFIG_UEVENT_HELPERmacro to enableuevent_helperandCONFIG_UEVENT_HELPER_PATHmacro to set the defaultuevent_helperpath (such as/sbin/mdev);
uevent_helper configuration method 1
1234567891011 | Device Drivers Generic Driver Options [*] Support for uevent helper // Selected (/sbin/mdev) path to uevent helper // Set the mdev path File systems Pseudo filesystems. [*]/proc file system support // Selected [*] Sysctl support(/proc/sys) // Selected[*]Networking support // Selected |
Save configuration:
123 | cp .config arch/arm64/configs/rockchip_linux_defconfig../build.sh kernelcp boot.img ~/tftp |
Set in configuration 1 aboveuevent helperand the corresponding path, this is configuration method 1, but this method requires recompiling the kernel, which is rather troublesome to use. Besides method 1, there are also quicker methods 2 and 3:
uevent_helper configuration method 2
whether or not it is configuredCONFIG_UEVENT_HELPER_PATH, after the system starts, you can use the following command to setuevent_helper:
1 | echo /sbin/mdev > /sys/kernel/uevent_helper |
This willuevent_helperSet to/sbin/mdev。
uevent_helper configuration method 3
whether or not it is configuredCONFIG_UEVENT_HELPER_PATH, after the system starts, you can use the following command to setuevent_helper:
1 | echo /sbin/mdev > /proc/sys/kernel/hotplug |
This willuevent_helperSet to/sbin/mdev。
It should be noted that configuration method 2 and configuration method 3 depend on the above configuration.Support for uevent helper,File systemsandNetworkingOption, and the values already written in configuration method 1 can be modified through configuration method 2 and configuration method 3. Right./proc/sys/kernel/hotplugand/sys/kernel/uevent_helperBoth reading and writing are for performing read and write operations on the uevent_helper attribute.
/sys/kernel/uevent_helperis a file in the sysfs filesystem, it isuevent_helperThe interface of the attribute. By performing read/write operations on this file, the value of the uevent_helper attribute can be read or modified. In the kernel source code’skernel/ksysfs.cYou can find the pair in the directory.uevent_helperThe definition of attributes and the implementation of related operations are as follows:
12345678910111213141516171819202122 | // kernel/ksysfs.c/* uevent helper program, used during early boot */static ssize_t uevent_helper_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ return sprintf(buf, "%s\n", uevent_helper);}static ssize_t uevent_helper_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){ if (count+1 > UEVENT_HELPER_PATH_LEN) return -ENOENT; memcpy(uevent_helper, buf, count); uevent_helper[count] = '\0'; if (count && uevent_helper[count-1] == '\n') uevent_helper[count-1] = '\0'; return count;}KERNEL_ATTR_RW(uevent_helper); |
uevent_helper_showThe function is used touevent_helperWrites the value into buf and returns the number of characters written.uevent_helper_storeThe function is used to copy the value in buf touevent_helperIn it, process as needed, and then return the number of characters written.
/proc/sys/kernel/hotplugis a virtual file used to configure the hotplug event handler in the kernel. By writing to this file, you can setuevent_helperthe value of the attribute. In the kernel source code’skernel/sysctl.cfile, you can see that thehotplugoperation is actually onuevent_helperoperating on.
12345678910 | // kernel/sysctl.c { .procname = "hotplug", .data = &uevent_helper, .maxlen = UEVENT_HELPER_PATH_LEN, .mode = 0644, .proc_handler = proc_dostring, }, |
This code defines a file namedhotplugwhich is used to handle uevent events. It is associated with theuevent_helperattribute.
.procnamerepresents the file name, i.e.,/proc/sys/kernel/hotplug。.datais a pointer touevent_helpera pointer to the structure, used to store data related to the file. This pointer points to theuevent_helperstructure, used to handle uevent events..maxlenrepresents the maximum length of the file, i.e., the maximum length of the file content. This value isUEVENT_HELPER_PATH_LEN, indicating the maximum length of the file content..moderepresents the access permission of the file. This value is 0644, meaning the file’s permission is-rw-r--r--, i.e., all users can read the file, but only the root user can write to the file.
example
In the kernel, the user-spaceuevent_helperprogram is called to handle uevent events.
uevent_helperis a user-space program that can be called when a uevent event is generated in kernel space. If theCONFIG_UEVENT_HELPERmacro is defined, the kernel will call theuevent_helperprogram when generating uevent events, so as to handle these events in user space.
Beforekobject_uevent_env()In the function, if theuevent_helpervariable is not empty and thekobj_usermode_filterfunction returns false, then thecall_usermodehelper_setupfunction is called to start a user-space process and pass the parameters in env to the process. In this process, the parameters in env will be converted into environment variables and passed to the user-space process.
12345678910111213 | int main(int argc, char *argv[]){ int fd = open("/dev/ttyFIQ0", O_WRONLY); dup2(fd, STDOUT_FILENO); printf("SUBSYSTEM is %s\n", getenv("SUBSYSTEM")); return 0;} |
Method 2:
12345678 | root@topeet:/root# echo /root/uevent_helper_test.o > /sys/kernel/uevent_helperroot@topeet:/root# insmod kset_uevent_ops_test.ko[ 63.445449] kset_uevent_ops_test: loading out-of-tree module taints kernel.root@topeet:/root# SUBSYSTEM is my_ksetSUBSYSTEM is moduleroot@topeet:/root# rmmod kset_uevent_ops_test.koSUBSYSTEM is my_ksetSUBSYSTEM is module |
Method 3:
1234567 | root@topeet:/root# echo /root/uevent_helper_test.o > /proc/sys/kernel/hotplugroot@topeet:/root# insmod kset_uevent_ops_test.koroot@topeet:/root# SUBSYSTEM is moduleSUBSYSTEM is my_ksetroot@topeet:/root# rmmod kset_uevent_ops_test.koSUBSYSTEM is moduleSUBSYSTEM is my_kset |
It can be seen that both of the above configurations can print out theSUBSYSTEMenvironment variables.
Using udev to mount USB drives and TF cards
Configure buildroot to support udev
123456 | System configuration /dev management (Dynamic using devtmpfs + eudev) --> ( ) Static using device table ( ) Dynamic using devtmpfs only ( ) Dynamic using devtmpfs + mdev (X) Dynamic using devtmpfs + eudev |
Mount USB drive
To use udev to implement automatic mounting of USB drives, you also need to/etc/udev/rules.dcreate the corresponding rule file in the directory.
12 | KERNEL=="sd[a-z][0-9]", SUBSYSTEM=="block", ACTION=="add", RUN+="/etc/udev/rules.d/usb/usb-add.sh %k"SUBSYSTEM=="block", ACTION=="remove", RUN+="/etc/udev/rules.d/usb/usb-remove.sh |
KERNEL=="sd[a-z][0-9]KERNEL: Indicates the kernel name of the matching device.sd[a-z][0-9]: is a regular expression pattern,sd: indicates that the device name starts with “sd”[a-z]: indicates that the third character of the device name is a lowercase letter[0-9]: indicates that the fourth character of the device name is a digit.
This pattern is used to match block device nodes of USB storage devices, such as/dev/sda1、/dev/sdb2etc.
SUBSYSTEM=="block"SUBSYSTEM: Indicates the subsystem name of the matching device.block: indicates that the device’s subsystem is the block device subsystem, i.e., devices related to disks, partitions, etc. This part of the rule is to ensure that only devices under the block device subsystem are matched.
ACTION=="add"andACTION=="remove"ACTION: Indicates the action of the matched device.add: Indicates that the device is added.remove: Indicates that the device is removed.
This part of the rules is for handling events of devices being added and removed.
RUN+="/etc/udev/rules.d/usb/usb-add.sh %kRUN+="...": Indicates that the specified command is executed on the matched device."/etc/udev/rules.d/usb/usb-add.sh": Is the path of the command to be executed, that is, executed when the device is added/etc/udev/rules.d/usb/usb-add.shscript file.%k: Is a variable provided by udev, representing the kernel name of the matched device.
When a block device is added, it will execute/etc/udev/rules.d/usb/usb-add.shthe script; when a block device is removed, it will execute/etc/udev/rules.d/usb/usb-remove.shthe script
/etc/udev/rules.d/usb/usb-add.shThe content is as follows12
/bin/mount -t vfat /dev/$1 /mnt
/bin/mount: Call the mount command (explicitly specifying the full path).-t vfat: Specify the file system type to be mounted as VFAT (i.e., FAT32 or FAT16, commonly used for USB drives, SD cards, etc.)./dev/ $ 1: Indicates the device file, where$ 1is the first argument passed to the script or command line.
/etc/udev/rules.d/usb/usb-remove.shThe content is as follows123
sync/bin/umount -l /mnt
-l: Indicates ‘lazy unmount’. It immediately removes the mount point from the file system namespace, but the actual cleanup (such as releasing the device) is deferred until the device is no longer busy (no process is using it). It is often used when the device is ‘busy’ and cannot be unmounted normally (for example, when a shell is currently in the/mntdirectory).
Then grant execute permissions to these two scripts.
1 | chmod a+x /etc/udev/rules.d/usb/usb-add.sh /etc/udev/rules.d/usb/usb-remove.sh |
Mount TF card
/etc/udev/rules.d/In the directory, create a file named002.rulesfile
12 | KERNEL=="mmcblk[0-9]p[0-9]", SUBSYSTEM=="block", ACTION=="add", RUN+="/etc/udev/rules.d/tf/tf-add.sh %k"SUBSYSTEM=="block", ACTION=="remove", RUN+="/etc/udev/rules.d/tf/tf-remove.sh" |
KERNEL=="mmcblk[0-9]p[0-9]"KERNEL: Indicates the kernel name of the matching device."mmcblk[a-z][0-9]": Is a regular expression patternmmcblk: Indicates that the device name"mmcblk"starts with[0-9]: Indicates that the 7th and 9th characters of the device name are digits.
This pattern is used to match block device nodes of TF card storage devices, such as/dev/mmcblk1p1etc.
SUBSYSTEM=="block"SUBSYSTEM: Indicates the subsystem name of the matching device."block": Indicates that the device’s subsystem is the block device subsystem, i.e., devices related to disks, partitions, etc.
This part of the rules is to ensure that only devices under the block device subsystem are matched.
ACTION=="add"andACTION=="remove"ACTION: Indicates the action of the matched device."add": Indicates that the device is added."remove": Indicates that the device is removed.
This part of the rules is for handling events of devices being added and removed.
RUN+="/etc/udev/rules.d/tf/tf-add.sh %k"RUN+="...": Indicates that the specified command is executed on the matched device."/etc/udev/rules.d/tf/tf-add.sh": Is the path of the command to be executed, that is, executed when the device is added/etc/udev/rules.d/tf/tf-add.shscript file.%k: Is a variable provided by udev, representing the kernel name of the matched device.
When the TF card block device is added, it will execute/etc/udev/rules.d/tf/tf-add.shscript, and when the TF card block device is removed, it will execute/etc/udev/rules.d/tf/tf-remove.shthe script
Before/etc/udev/rules.d/tf/tf-add.shWrite the following content to the file:
12 | /bin/mount -t vfat /dev/$1 /mnt |
Before/etc/udev/rules.d/tf/tf-remove.shWrite the following content to the file
123 | sync/bin/umount -l /mnt |
Then grant execute permissions to these two scripts.
1 | chmod a+x /etc/udev/rules.d/tf/tf-add.sh /etc/udev/rules.d/tf/tf-remove.sh |
Use mdev to mount USB drives and TF cards.
Configure buildroot to support mdev.
12 | make rockchip_rk3568_defconfigmake menuconfig |
The configuration is as follows:
123456 | System configuration /dev management (Dynamic using devtmpfs + eudev) --> ( ) Static using device table ( ) Dynamic using devtmpfs only (X) Dynamic using devtmpfs + mdev ( ) Dynamic using devtmpfs + eudev |
Besides configuring buildroot, you also need to configure the relevant options of busybox, because buildroot uses busybox.
1 | make busybox-menuconfig |
The configuration is as follows:
12345 | Linux System Utilities [*] mdev (17 kb) [*] Support /etc/mdev.conf [*] Support subdirs/symlinks [ ] Support regular expressions substitutions when renaming device |
Mount USB drive
Like udev, mdev also needs to add corresponding rules; the difference is that mdev uses/etc/mdev.confa file to configure the rules and behavior of the mdev tool. To use mdev to automatically mount USB drives, you need to add to the/etc/mdev.conffile the following two rules.
12 | sd[a-z][0-9] 0:0 666 @/etc/mdev/usb_insert.shsd[a-z] 0:0 666 $/etc/mdev/usb_remove.sh |
These two rules are used to handle hotplug events of USB drives and perform corresponding operations. In/etc/mdev.confthe file, each line is a rule and has the following format:
1 | <设备节点正则表达式> <设备的所有者:设备的所属组> <设备的权限> <设备插入或移除时需要执行的命令> |
The following is a detailed introduction to the above two rules:
sd[a-z][0-9]is a regular expression pattern used to match device nodes that start with “sd”, followed by a lowercase letter and a digit, for example/dev/sda1etc.0:0 666indicates setting the owner and permissions of the device node. 0:0 means the user ID and group ID of the owner and the group are both 0, i.e., the root user. 666 means the permission is readable and writable.@/etc/mdev/usb_insert.shindicates that when a device matching the rule is inserted, mdev will execute/etc/mdev/usb_insert.shscript.@The symbol indicates that a shell command is executed.$/etc/mdev/usb_remove.shindicates that when a device matching the rule is removed, mdev will execute/etc/mdev/usb_remove.shscript.$The symbol indicates that the command being executed is an internal command.
/etc/mdev/usb_insert.shand/etc/mdev/usb_remove.shThe two files are shown in the following figure:
/etc/mdev/usb_insert.sh
123456 | if [ -d /sys/block/*/$MDEV ]; then mount /dev/$MDEV /mnt syncfi |
/etc/mdev/usb_remove.sh
123 | sync/bin/umount -l /mnt |
Then grant execute permissions to these two scripts.
1 | chmod a+x /etc/mdev/usb_insert.sh /etc/mdev/usb_remove.sh |
Mount TF card
To/etc/mdev.conffile the following two rules.
12 | mmcblk[0-9]p[0-9] 0:0 666 @/etc/mdev/tf_insert.shmmcblk[0-9] 0:0 666 $/etc/mdev/tf_remove.sh |
mmcblk[0-9]p[0-9]is a regular expression pattern used to match"mmcblk"TF card block devices that start with, for example/dev/mmcblk1p1etc.0:0 666indicates setting the owner and permissions of the device node.0:0means the user ID and group ID of the owner and the group are both 0, i.e., the root user.666means the permission is readable and writable.@/etc/mdev/tf_insert.shindicates that when a device matching the rule is inserted, mdev will execute/etc/mdev/tf_insert.shscript.@The symbol indicates that a shell command is executed.$/etc/mdev/tf_remove.shindicates that when a device matching the rule is removed, mdev will execute/etc/mdev/tf_remove.shscript.$The symbol indicates that the command being executed is an internal command.
/etc/mdev/tf_insert.shand/etc/mdev/tf_remove.shas shown below
/etc/mdev/tf_insert.sh
12345 | if [ -d /sys/block/*/$MDEV ]; then mount /dev/$MDEV /mnt syncfi |
/etc/mdev/tf_remove.sh
123 | sync/bin/umount -l /mnt |
Then grant execute permissions to these two scripts.
1 | chmod a+x /etc/mdev/tf_insert.sh /etc/mdev/tf_remove.sh |
Fix the device node of the USB device.
Requirement
In Linux systems, when using multiple USB-to-serial devices, it is common to encounter unstable device nodes due to changes in the order of plugging and unplugging the USB-to-serial adapters. To solve this problem, we can use udev to fix the device nodes, ensuring that the device nodes are not affected by the order in which the USB-to-serial adapters are plugged in or unplugged.
Solution
Insert the USB device:
1 | udevadm info -a -n /dev/ttyUSB0 |
udevadm info -a -n /dev/ttyUSB0is a command used to obtain detailed information about a specific device node, including device attributes, drivers, device paths, etc. The meanings of the parameters of these commands are as follows:
-a: Displays all attributes associated with the specified device node.-n: Specifies the path or name of the device node.

The output of this command may contain the following information:
- Device path (DEVPATH): the path of the device in the system, for example
/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/ttyUSB0/tty/ttyUSB0。 - Device node (DEVNAME): the node of the device in the file system, i.e.,
/dev/ttyUSB0。 - Device attributes: including the device’s vendor ID, product ID, serial number, and other information.
- Device driver: the name and path of the driver used by the device.
- Device type (SUBSYSTEM):
usb
Next, in the development board’setc/udev/rules.d/directory, create a rule file named 001.rules:
1 | KERNELS==”5-1:1.0”,SUBSYSTEMS==”usb”,MODE:=”0777”,SYMLINK+=”myusb” |
KERNELS=="5-1:1.0"The matching device’s kernel device name (Kernel Device Name)
5-1:1.0is the path identifier of the USB device in the kernel:5: USB bus number (Bus)1: USB device number (Device)1.0: Interface number (Interface), indicating the 0th endpoint of the 1st interface.- This value comes from
udevadm infoin the outputKERNELSfield
SUBSYSTEMS=="usb"that the matched device belongs to subsystem
indicates that this is a USB type device
can be used to filter all USB devices
MODE:="0777"- set the device file’s permissions (mode)
0777represents:- all users (root, normal users, other users) have read, write, and execute permissions
- That is:
rwxrwxrwx, so any program can directly access the device (e.g./dev/ttyUSB0)
⚠️ Note:
MODEThe preceding is:=, not==。:=means ‘assignment’, while==is ‘match’.
SYMLINK+="myusb"For the device, create a symbolic link
For example, the original device is
/dev/ttyUSB0, now there will be an additional soft link:/dev/myusb -> /dev/ttyUSB0you can use
myusbto replacettyUSB0, which is more convenient for naming and script invocation


