Linux 内核中的热插拔机制:从检测到处理
引言
作为一名深耕操作系统和嵌入式开发的工程师,我深知系统灵活性的重要性。在系统开发中,良好的灵活性可以提高系统的可用性和可维护性。在 Linux 内核中,热插拔机制是实现设备动态添加和移除的核心机制,它允许在系统运行时添加或移除硬件设备。今天,我们就来深入探讨 Linux 内核中的热插拔机制,从技术原理到实战应用。
技术原理
热插拔的核心概念
Linux 内核的热插拔机制主要包括:
热插拔的实现原理
// 设备事件结构体
struct kobj_uevent_env {
char *argv[3];
char *envp[64];
int envp_idx;
char buf[2048];
int buflen;
};
// 发送 uevent
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]);
// 设备动作
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_BIND,
KOBJ_UNBIND,
};
// 通知链结构体
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block *next;
int priority;
};
// 注册通知链
int register_netdevice_notifier(struct notifier_block *nb);
int unregister_netdevice_notifier(struct notifier_block *nb);
// 设备通知回调
int device_notifier_call(struct notifier_block *nb, unsigned long action, void *data);
创业视角分析
从创业者的角度来看,热插拔机制的设计思路与企业管理中的灵活应变有着密切的联系:
实用技巧
热插拔的使用场景
热插拔的最佳实践
代码示例
监听热插拔事件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#define UEVENT_BUFFER_SIZE 2048
int main(void)
{
int sockfd;
struct sockaddr_nl sa;
char buffer[UEVENT_BUFFER_SIZE];
ssize_t len;
// 创建 netlink socket
sockfd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (sockfd < 0) {
perror("socket failed");
return 1;
}
// 绑定地址
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_pid = getpid();
sa.nl_groups = 1; // 监听内核广播
if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("bind failed");
close(sockfd);
return 1;
}
printf("Listening for uevents…\\n");
// 接收 uevent
while (1) {
len = recv(sockfd, buffer, sizeof(buffer), 0);
if (len < 0) {
perror("recv failed");
break;
}
buffer[len] = '\\0';
printf("Received uevent:\\n%s\\n", buffer);
printf("—\\n");
}
close(sockfd);
return 0;
}
内核模块中的热插拔处理
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/notifier.h>
// 设备通知回调
static int my_device_notifier(struct notifier_block *nb, unsigned long action, void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
printk(KERN_INFO "Device added: %s\\n", dev_name(dev));
break;
case BUS_NOTIFY_DEL_DEVICE:
printk(KERN_INFO "Device removed: %s\\n", dev_name(dev));
break;
case BUS_NOTIFY_BIND_DRIVER:
printk(KERN_INFO "Driver bound to device: %s\\n", dev_name(dev));
break;
case BUS_NOTIFY_UNBOUND_DRIVER:
printk(KERN_INFO "Driver unbound from device: %s\\n", dev_name(dev));
break;
default:
printk(KERN_INFO "Unknown action: %lu for device: %s\\n", action, dev_name(dev));
break;
}
return NOTIFY_OK;
}
static struct notifier_block my_nb = {
.notifier_call = my_device_notifier,
};
// 模块初始化
static int __init hotplug_example_init(void)
{
// 注册设备通知链
bus_register_notifier(&pci_bus_type, &my_nb);
printk(KERN_INFO "Hotplug example initialized\\n");
return 0;
}
// 模块退出
static void __exit hotplug_example_exit(void)
{
// 注销设备通知链
bus_unregister_notifier(&pci_bus_type, &my_nb);
printk(KERN_INFO "Hotplug example exited\\n");
}
module_init(hotplug_example_init);
module_exit(hotplug_example_exit);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Hotplug example");
MODULE_LICENSE("GPL");
发送自定义 uevent
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
static struct kobject *my_kobj;
// 触发 uevent 的 show 函数
static ssize_t trigger_uevent_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
// 发送 uevent
kobject_uevent(kobj, KOBJ_CHANGE);
return sprintf(buf, "Uevent triggered\\n");
}
// 触发 uevent 的 store 函数
static ssize_t trigger_uevent_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
// 发送带环境变量的 uevent
char *envp[] = {"MY_VAR=my_value", NULL};
kobject_uevent_env(kobj, KOBJ_CHANGE, envp);
return count;
}
static struct kobj_attribute trigger_attr = __ATTR(trigger_uevent, 0664, trigger_uevent_show, trigger_uevent_store);
static struct attribute *my_attrs[] = {
&trigger_attr.attr,
NULL,
};
static struct attribute_group my_attr_group = {
.attrs = my_attrs,
};
// 模块初始化
static int __init uevent_example_init(void)
{
int ret;
// 创建 kobject
my_kobj = kobject_create_and_add("my_uevent", kernel_kobj);
if (!my_kobj)
return -ENOMEM;
// 创建属性组
ret = sysfs_create_group(my_kobj, &my_attr_group);
if (ret) {
kobject_put(my_kobj);
return ret;
}
// 发送添加 uevent
kobject_uevent(my_kobj, KOBJ_ADD);
printk(KERN_INFO "Uevent example initialized\\n");
return 0;
}
// 模块退出
static void __exit uevent_example_exit(void)
{
// 发送移除 uevent
kobject_uevent(my_kobj, KOBJ_REMOVE);
sysfs_remove_group(my_kobj, &my_attr_group);
kobject_put(my_kobj);
printk(KERN_INFO "Uevent example exited\\n");
}
module_init(uevent_example_init);
module_exit(uevent_example_exit);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Uevent example");
MODULE_LICENSE("GPL");
热插拔管理命令
# 监听 uevent
udevadm monitor
# 查看设备属性
udevadm info –query=all –name=/dev/sda
# 触发 uevent
udevadm trigger –action=add –sysname-match=sda
# 重新加载 udev 规则
udevadm control –reload-rules
# 查看 USB 设备热插拔日志
dmesg | grep -i usb
dmesg | grep -i "new device"
# 查看 PCIe 设备热插拔
cat /sys/bus/pci/slots/*/power
# 手动触发设备扫描
echo 1 | sudo tee /sys/bus/pci/rescan
总结
Linux 内核中的热插拔机制是实现设备动态添加和移除的核心机制。热插拔机制通过 uevent、netlink、udev 等组件,实现了设备状态变化的检测和处理。
工作也要流程化,热插拔机制就像是系统中的动态适应工具,它确保了系统能够灵活应对设备的变化。在实际应用中,我们需要正确处理事件,管理资源,处理错误,通知用户,以及同步状态,以实现系统的最佳灵活性和可靠性。
这就是生机所在,通过深入理解和应用热插拔机制技术,我们不仅可以构建更灵活、更可靠的系统,也可以从中汲取企业管理的智慧,为创业之路增添一份技术的力量。



