网站设计公司官网,WordPress开发过程,seo电商,门户网站建设和运行保障招标文件本文目录 一、知识点1. 热插拔概念2. 热插拔机制3. Netlink机制 二、内核发送uevent事件到用户空间1. kobject发送uevent事件2. udevadm命令查看★示例代码#xff1a;★优化#xff1a;完善kset_uevent_ops#xff08;热插拔事件结构体#xff09; 三、用户空间使用Netlin… 本文目录 一、知识点1. 热插拔概念2. 热插拔机制3. Netlink机制 二、内核发送uevent事件到用户空间1. kobject发送uevent事件2. udevadm命令查看★示例代码★优化完善kset_uevent_ops热插拔事件结构体 三、用户空间使用Netlink接收uevent事件1. 创建socket2. 绑定socket3. 接收uevent事件信息★示例代码 一、知识点 1. 热插拔概念 热插拔就是带电插拔用人话讲就是允许用户在不关闭系统不切断电源的情况下拆卸或安装硬盘板卡等设备。热插拔是内核和用户空间之间通过调用用户空间程序实现交互来实现的。当内核发生了某种热拔插事件时内核就会调用用户空间的程序来实现交互。
2. 热插拔机制 热插拔机制有devfs、udev、mdev。其中devfs已经不再使用。嵌入式设备上一般使用mdevX86上一般用udev当然嵌入式设备上也可以用udev。与 udev 不同mdev 的设计更加简洁是udev的简化版本。
1 udev是基于Netlink 机制实现的。 工作原理如下 ① 当有设备插入或移除时内核会生成一个 uevent 事件。 ② 内核通过 Netlink 套接字将 uevent 事件发送给用户空间。 ③用户空间的 udev 守护进程会打开一个 Netlink 套接字并持续监听通过监听内核发送的 uevent 来执行相应的热插拔操作如创建设备节点、设置权限、运行脚本等。
2mdev 主要工作机制是基于 uevent_helper。工作原理如下 ①当设备插入、移除或状态改变时内核会生成一个 uevent 事件。 ②内核通过 uevent_helper 机制调用用户空间的程序来处理这些事件。uevent_helper 的路径存储在 /proc/sys/kernel/hotplug 文件中通常指向 /sbin/mdev。 ③mdev 作为一个可执行程序被内核调用通过读取环境变量中的事件信息来处理设备事件。它根据配置文件通常是 /etc/mdev.conf中定义的规则执行相应的操作。
3. Netlink机制 Linux提供了多种方式实现内核和用户空间的数据交换比如我们之前讲过的系统调用sysfs,等但是这种通信机制均为单工通信机制。而 netlink,是基于 socket 通信机制具体双工的特点。可以很好的满足内核和用户空间的数据交换。
二、内核发送uevent事件到用户空间
前提需要创建kest来完成。
1. kobject发送uevent事件
返回 0 表示成功返回负值表示发生了错误。
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
/*
struct kobject *kobj指向 kobject 结构体的指针表示发生事件的内核对象。
enum kobject_action action表示事件的类型。KOBJ_ADD, // 表示有新的设备或内核对象被添加到系统中。KOBJ_REMOVE, // 表示设备或内核对象被从系统中移除。KOBJ_CHANGE, // 表示设备或内核对象的状态发生变化。KOBJ_MOVE, // 表示设备或内核对象在系统中被移动到另一个位置。KOBJ_ONLINE, // 表示设备或内核对象上线准备使用。KOBJ_OFFLINE, // 表示设备或内核对象下线不再可用。KOBJ_MAX // 这是枚举值的最大值通常用于检查枚举的范围。
*/2. udevadm命令查看 udevadm 是 udev 的命令行工具提供了用于调试和管理 udev 设备管理器的各种功能。它允许用户查询设备信息、模拟设备事件、测试规则和管理 udev 数据库等。
udevadm info //用于显示设备的详细信息。常用选项包括 --queryall显示所有信息和 --nameDEVICE指定设备节点例如 /dev/sda。
udevadm monitor //用于实时监视 udev 事件。可以使用 --udev仅显示 udev 事件和 --kernel显示内核事件选项。
udevadm test //用于测试 udev 规则对设备的作用。常用选项包括 --actionACTION指定动作类型如 add 或 remove需要指定设备路径例如 /sys/class/net/eth0。
udevadm trigger //用于手动触发 udev 事件。可以使用 --actionACTION指定触发的动作类型例如 add 或 remove和 --subsystem-matchSUBSYSTEM仅触发匹配指定子系统的设备选项。
udevadm control //用于控制 udev 守护进程的行为。常用选项包括 --reload重新加载 udev 配置文件和规则、--stop停止 udev 守护进程和 --start启动 udev 守护进程。
udevadm settle //用于等待所有当前的 udev 事件处理完毕。可以设置超时时间秒使用 --timeoutTIME 选项默认超时时间是 30 秒。
udevadm --version //显示 udevadm 的版本信息.★示例代码
uevent.c
#include linux/module.h
#include linux/kobject.h
#include linux/slab.h // For kzallocstruct kobject *mykobject01;
struct kset *my_kset;
struct kobj_type mytype;static int __init mykobj_init(void)
{int ret;//1. 创建kestmy_ksetkset_create_and_add(my_kset,NULL,NULL); //在sys下创建my_kset目录.//2. 创建kobject1mykobject01 kzalloc(sizeof(struct kobject), GFP_KERNEL); mykobject01-ksetmy_kset;ret kobject_init_and_add(mykobject01, mytype, NULL, mykobject01);// 发送uevent事件retkobject_uevent(mykobject01,KOBJ_CHANGE);return 0;
}static void __exit mykobj_exit(void)
{kobject_put(mykobject01);kset_unregister(my_kset);
}module_init(mykobj_init);
module_exit(mykobj_exit);MODULE_LICENSE(GPL);测试首先使用udevadm monitor 命令来后台实时监视kobject 的 udev 事件然后我们将uevent.ko文件加载、卸载时查看输出信息如下。因为我们检测的是状态改变只要发送改变就会发送uevent 事件。 ★优化完善kset_uevent_ops热插拔事件结构体 热插拔事件意思就是当kset目录下有任何变动包括目录的移动增加目录或者属性文件等操作。 当系统配置发生变化时如添加kset到系统或移动kobject一个通知会从内核空间发送到用户空间这就是热插拔事件。热插拔事件会导致用户空间中的处理程序如udevmdev被调用这些处理程序会通过加载驱动程序创建设备节点等来响应热插拔事件。
struct kset_uevent_ops {
//过滤事件决定是否产生事件如果返回0将不产生事件。int (* const filter)(struct kset *kset, struct kobject *kobj);
//向用户空间传递一个合适的字符串const char *(* const name)(struct kset *kset, struct kobject *kobj);
//通过环境变量传递任何热插拔脚本需要的信息他会在(udev或mdev)调用之前提供添加环境变量的机会。 int (* const uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);
};优化代码 功能会屏蔽mykobject01发送的uevent事件只响应mykobject02的事件。
#include linux/module.h
#include linux/kobject.h
#include linux/slab.h // For kzallocstruct kobject *mykobject01;
struct kobject *mykobject02;
struct kset *my_kset;
struct kobj_type mytype;int my_filter(struct kset *kset, struct kobject *kobj)
{if(strcmp(kobj-name,mykobject01)0){ //过滤掉mykobject01的uevent事件。 return 0;}else{return 1;}
}const char *my_name(struct kset *kset, struct kobject *kobj)
{return QJL_test; //向用户空间传递一个合适的字符串
}int my_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)
{add_uevent_var(env,MYDEVICE:%s,QJL); //添加环境变量return 0;
}struct kset_uevent_ops my_uevent_ops{.filtermy_filter,.name my_name,.uevent my_uevent,
};static int __init mykobj_init(void)
{int ret;//1. 创建kestmy_ksetkset_create_and_add(my_kset, my_uevent_ops, NULL); //在sys下创建my_kset目录.//2. 创建kobject1mykobject01 kzalloc(sizeof(struct kobject), GFP_KERNEL); mykobject01-ksetmy_kset;ret kobject_init_and_add(mykobject01, mytype, NULL, mykobject01);//3. 创建kobject2mykobject02 kzalloc(sizeof(struct kobject), GFP_KERNEL); mykobject02-ksetmy_kset;ret kobject_init_and_add(mykobject02, mytype, NULL, mykobject02);// 发送uevent事件retkobject_uevent(mykobject01, KOBJ_CHANGE);retkobject_uevent(mykobject02, KOBJ_ADD);return 0;
}static void __exit mykobj_exit(void)
{kobject_put(mykobject01);kobject_put(mykobject02);kset_unregister(my_kset);
}module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE(GPL);三、用户空间使用Netlink接收uevent事件 因为netlink 是基于socket通信机制在用户空间使用socket接口如socket、bind、sendmsg、recvmsg、close 就可以使用 netlink上手容易。这里我就不再讲解socket的API函数详情查看socket使用步骤详情查看地址。
1. 创建socket 对于netlink使用下面固定的协议类型。其中protocol指定 netlink协议类型目前已经支持的协议类型在 linux/netlink.h 中定义,所以需要包含头文件#include linux/netlink.h。
int socket(int domain, int type, int protocol);
/*int domain: 选择 AF_NETLINKint type 选择 SOCK_RAWint protocol 在#include linux/netlink.h中选择。
*/2. 绑定socket
注意对于sockaddr_nl 结构体成员填写内容nl_familyAF_NETLINK 、nl_pad 0 、nl_pid0、nl_groups1。
int bind(int sockfd, struct sockaddr* my_addr, int addrlen);
/*
sockfd :socket 描述符
addr:指向一个 struct sockaddr 类型指针。这里我们使用sockaddr_nl结构体然后进行类型转换。struct sockaddr_nl {__kernel_sa_family_t nl_family; // 套接字地址族。 这里使用 AF_NETLINK。unsigned short nl_pad; // 填充用于对齐。 这里使用 0。__u32 nl_pid; // 进程标识符 (PID) 也可以设置为0表示不加入任何多播组。__u32 nl_groups; // 多播组掩. 设置为1时表示用户空间进程只会接收内核事件的基本组的内核事件。};int addrlen 结构体长度。
*/3. 接收uevent事件信息
包含头文件 #include sys/types.h#include sys/socket.h注意 netlink中是不用调用listen 所监听的。可以直接使用recv函数进行接收。
ssize_t recv(int sockfd, void *buf, size_t len, int flags); // 从套接字接收数据
// 参数// sockfd - 套接字文件描述符// buf - 存储接收到数据的缓冲区// len - 缓冲区的长度// flags - 操作标志通常设置为0.
// 返回值接收到的字节数如果出错则返回 -1★示例代码
app.c
#include stdio.h
#include unistd.h
#include string.h
#include linux/netlink.h
#include sys/socket.h // 修正了包含的头文件
#include sys/types.hint main() {int ret;int socketed;ssize_t len;int i;char buf[4096] {0};// 创建 Netlink 套接字socketed socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);if (socketed 0) {perror(socket error); // 更改为 perror 可以输出更详细的错误信息return -1;}// 定义并初始化 sockaddr_nl 结构体struct sockaddr_nl my_sockaddr_nl {.nl_family AF_NETLINK,.nl_pad 0,.nl_pid 0, // 绑定到内核.nl_groups 1 // 监听内核组播消息};// 绑定 Netlink 套接字ret bind(socketed, (struct sockaddr*)my_sockaddr_nl, sizeof(struct sockaddr_nl));if (ret 0) {perror(bind error); // 错误处理close(socketed);return -1;}// 循环接收并打印消息while (1) {memset(buf, 0, sizeof(buf)); // 清空缓冲区len recv(socketed, buf, sizeof(buf), 0);for(i0;ilen; i){if(buf[i]\0) buf[i]\n;}// 打印接收到的消息printf(%s\n, buf); // 使用 %.*s 打印指定长度的字符串}close(socketed); // 关闭套接字return 0;
}实验现象我们使用完善kset_uevent_ops的代码作为内核代码通过本示例来获取内核中uevent事件信息结果如下所示。