Linux内核编程(十二)热插拔

news2024/9/27 21:22:13

本文目录

  • 一、知识点
    • 1. 热插拔概念
    • 2. 热插拔机制
    • 3. Netlink机制
  • 二、内核发送uevent事件到用户空间
    • 1. kobject发送uevent事件
    • 2. udevadm命令查看
    • ★示例代码:
    • ★优化:完善kset_uevent_ops(热插拔事件结构体)
  • 三、用户空间使用Netlink接收uevent事件
    • 1. 创建socket
    • 2. 绑定socket
    • 3. 接收uevent事件信息
    • ★示例代码

  

一、知识点

在这里插入图片描述

1. 热插拔概念

   热插拔就是带电插拔,用人话讲就是允许用户在不关闭系统,不切断电源的情况下拆卸或安装硬盘,板卡等设备。热插拔是内核和用户空间之间,通过调用用户空间程序实现交互来实现的。当内核发生了某种热拔插事件时,内核就会调用用户空间的程序来实现交互。

2. 热插拔机制

   热插拔机制有devfs、udev、mdev。其中devfs已经不再使用。嵌入式设备上一般使用mdev,X86上一般用udev,当然嵌入式设备上也可以用udev。与 udev 不同,mdev 的设计更加简洁,是udev的简化版本。

(1) udev是基于Netlink 机制实现的。 工作原理如下:
   ① 当有设备插入或移除时,内核会生成一个 uevent 事件。
   ② 内核通过 Netlink 套接字将 uevent 事件发送给用户空间。
   ③用户空间的 udev 守护进程会打开一个 Netlink 套接字并持续监听,通过监听内核发送的 uevent 来执行相应的热插拔操作,如创建设备节点、设置权限、运行脚本等。

(2)mdev 主要工作机制是基于 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 //用于显示设备的详细信息。常用选项包括 --query=all(显示所有信息)和 --name=DEVICE(指定设备节点,例如 /dev/sda)。
udevadm monitor //用于实时监视 udev 事件。可以使用 --udev(仅显示 udev 事件)和 --kernel(显示内核事件)选项。
udevadm test //用于测试 udev 规则对设备的作用。常用选项包括 --action=ACTION(指定动作类型,如 add 或 remove),需要指定设备路径,例如 /sys/class/net/eth0。
udevadm trigger //用于手动触发 udev 事件。可以使用 --action=ACTION(指定触发的动作类型,例如 add 或 remove)和 --subsystem-match=SUBSYSTEM(仅触发匹配指定子系统的设备)选项。
udevadm control //用于控制 udev 守护进程的行为。常用选项包括 --reload(重新加载 udev 配置文件和规则)、--stop(停止 udev 守护进程)和 --start(启动 udev 守护进程)。
udevadm settle //用于等待所有当前的 udev 事件处理完毕。可以设置超时时间(秒),使用 --timeout=TIME 选项,默认超时时间是 30 秒。
udevadm --version //显示 udevadm 的版本信息.

★示例代码:

uevent.c

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h>  // For kzalloc

struct kobject *mykobject01;
struct kset *my_kset;
struct kobj_type mytype;

static int __init mykobj_init(void)
{
    int ret;
    //1. 创建kest
    my_kset=kset_create_and_add("my_kset",NULL,NULL); //在sys下创建my_kset目录.
    //2. 创建kobject1
    mykobject01= kzalloc(sizeof(struct kobject), GFP_KERNEL); 
    mykobject01->kset=my_kset;
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "mykobject01");
    // 发送uevent事件
	ret=kobject_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,一个通知会从内核空间发送到用户空间,这就是热插拔事件。热插拔事件会导致用户空间中的处理程序(如udev,mdev)被调用,这些处理程序会通过加载驱动程序,创建设备节点等来响应热插拔事件。

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 kzalloc

struct 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={
    .filter=my_filter,
   	.name= my_name,
   	.uevent =my_uevent,
};

static int __init mykobj_init(void)
{
    int ret;
    //1. 创建kest
    my_kset=kset_create_and_add("my_kset", &my_uevent_ops, NULL); //在sys下创建my_kset目录.
    
    //2. 创建kobject1
    mykobject01= kzalloc(sizeof(struct kobject), GFP_KERNEL); 
    mykobject01->kset=my_kset;
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "mykobject01");

	//3. 创建kobject2
    mykobject02= kzalloc(sizeof(struct kobject), GFP_KERNEL); 
    mykobject02->kset=my_kset;
    ret = kobject_init_and_add(mykobject02, &mytype, NULL, "mykobject02");
    
   // 发送uevent事件
	ret=kobject_uevent(mykobject01, KOBJ_CHANGE);
	ret=kobject_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_NETLINK
	int type : 选择 SOCK_RAW
	int protocol :在#include <linux/netlink.h>中选择。
*/

2. 绑定socket

注意:对于sockaddr_nl 结构体成员填写内容:nl_family(AF_NETLINK) 、nl_pad (0) 、nl_pid(0)、nl_groups(1)。

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.h>

int 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(i=0;i<len; i++){
           if(buf[i]=='\0') buf[i]='\n';
        }
        // 打印接收到的消息
        printf("%s\n", buf);  // 使用 %.*s 打印指定长度的字符串
    }

    close(socketed);  // 关闭套接字
    return 0;
}

  实验现象:我们使用完善kset_uevent_ops的代码作为内核代码,通过本示例来获取内核中uevent事件信息,结果如下所示。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1992460.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL数据分析进阶(十二)设计数据库——PART2

※食用指南&#xff1a;文章内容为‘CodeWithMosh’SQL进阶教程系列学习笔记&#xff0c;笔记整理比较粗糙&#xff0c;主要目的自存为主&#xff0c;记录完整的学习过程。&#xff08;图片超级多&#xff0c;慎看&#xff01;&#xff09; 【中字】SQL进阶教程 | 史上最易懂S…

大模型(LLMs)LLM生成SFT数据方法面

一、SFT数据集如何生成&#xff1f; SFT数据集构建通常有两种方法&#xff1a;人工标注和使用LLM&#xff08;比如GPT-4&#xff09;来生成的&#xff0c;人工标注对于构 建垂直领域比较合适&#xff0c;可以减少有偏数据&#xff0c;但是成本略高&#xff1b;使用LLM生成&…

【算法设计题】计算有向图G中每个结点的入度和出度,第4题(C/C++)

目录 第4题 计算有向图G中每个结点的入度和出度 得分点&#xff08;必背&#xff09; 题解&#xff1a;计算有向图G中每个结点的入度和出度 数据结构定义 边表结点 顶点表结点 图的邻接表存储表示 计算图G中每个结点的入度和出度 详细解释 1. 初始化入度和出度数组 2…

容器适配器的介绍和模拟实现

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; Stack的介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上…

如何看到公司所有员工的收发件,并以员工名义一键发信

对于企业管理层来说, 了解并监控员工的企业邮箱成为了日常工作中的一部分。这不仅可以帮助企业更好地掌握业务进展, 还能够提高工作效率。本文将详细介绍如何通过Zoho邮箱实现这一目标, 包括相关的技术原理、实现的好处以及具体的实施步骤。 一、实现的技术: POP3 和 IMAP 要…

C++类和对象(2)——构造函数和析构函数

###前言&#xff1a;此文主要介绍C中的六种默认成员函数&#xff1b;默认的意思就是我们不写编译器会自动生成&#xff1b;这些函数在类里面自动生成&#xff1b;但是我们也可以自己写&#xff1b;学习这几种默认成员函数从两个方面入手&#xff1a; &#xff08;1&#xff09…

“AI大语言模型+”助力大气科学相关交叉领域实践技术应用

查看原文>>>“AI大语言模型”助力大气科学相关交叉领域实践技术应用 目录 专题一、预备知识 专题二、科研辅助专题 专题三、可视化专题——基于GPT实现 专题四、站点数据处理 专题五、WRF专题——基于GPT和Python实现 专题六、遥感降水专题——基于GPT和Python…

#java学习笔记(面向对象)----(未完结)

一基础相关知识点&#xff1a; 1. 一个对象的调用 首先我们创建一个Phone类 public class Phone {//成员变量String name;int age;String favourite;//成员方法public void myName(){System.out.println(name);}public void myAge(){System.out.println(age);}public void m…

免费写作神器,自动生成高质量文章

在当今数字化的时代&#xff0c;信息的传播和创作变得前所未有的重要。无论是企业的营销推广、个人的博客写作&#xff0c;还是学术研究报告&#xff0c;优质的文章都能发挥巨大的作用。而随着人工智能技术的飞速发展&#xff0c;免费的ai写作工具应运而生&#xff0c;为我们带…

虚拟内存惹

二、理解 虚拟内存 虚拟内存存在的原因物理地址和虚拟地址虚拟内存的其他介绍 虚拟内存存在的原因 计算机系统有两种地址&#xff1a;1、物理地址 2、虚拟地址 物理地址&#xff1a;是指真实的地址&#xff0c;是物理存在的&#xff0c;比如RAM、flash等 虚拟地址&#xff1a;…

使用相同模型相同数据集,为什么每次运行得到的损失值都不一样?

今天小编在学习 PyTorch 时,突然发现咋每次运行所得损失绘制的曲线都不一样呢&#xff1f;即使小编使用torch.manual_seed()函数固定 torch 的随机数种子每次运行的结果还是不一样&#xff0c;因此小编就写一篇文章记录一下。 数据集 本次使用的数据集是小编自定义的小型数据…

MySQL数据管理 - 查询语句

文章目录 查询数据1 查询指定列2 条件查询3 合并查询4 模糊查询5 聚合函数查询6 对值进行排序7 分组查询8 分页查询9 数据库关联查询1 内连接 INNER JOIN2 LEFT JOIN3 右连接 10 数据库子查询参考 查询数据 数据库最常用的操作就是查询&#xff0c;也是数据操作的基础&#xf…

MySQL基础练习题23-门店处理

目录 题目 准备数据 分析数据 方法一 方法二 题目 从分店明细表中获取门店面积。 准备数据 -- 创建库 drop database if exists db_1; create database db_1; use db_1;-- 创建门店面积表 tb_store_area CREATE TABLE tb_store_area (store_no VARCHAR(50),area…

[器械财讯]TRiCares完成近4亿融资,推动Topaz三尖瓣置换系统发展

一、融资成功助力临床研究 法国医疗器械公司TRiCares近期宣布&#xff0c;其D轮融资成功筹集5000万美元&#xff0c;所得资金将专用于支持其核心产品——经导管三尖瓣置换系统&#xff08;TTVR&#xff09;Topaz的临床研究和开发。这笔资金将用于在美国和欧盟开展临床研究&…

手持红外热成像仪的使用方法_鼎跃安全

手持红外热成像仪是一种便携设备&#xff0c;方便在现场进行温度检测和成像。他们能将物体发出的不可见红外能量转化为可见的热图像&#xff0c;直观展示物体表面的温度分布情况&#xff1b;广泛应用于电气设备维修、环保检查、应急救援等领域。接下来&#xff0c;我们一起来了…

行业原型:智慧教育线上平台-学院原型

行业原型预览链接&#xff1a; 文件类型&#xff1a;.rp 支持版本&#xff1a;Axrure RP 8 文档名称&#xff1a;智慧教育线上平台-学院 文件大小&#xff1a;1.80 MB 目录内容介绍 文档内容介绍 回复“211110” 领取

《最新出炉》系列小成篇-Python+Playwright自动化测试-66 - 等待元素至指定状态(出现、移除、显示和隐藏)

1.简介 在我们日常工作中进行UI自动化测试时&#xff0c;保证测试的稳定性至关重要。其中一个关键方面是正确地定位和操作网页中的元素。在网页中&#xff0c;元素可能处于不同的状态&#xff0c;有些可能在页面加载完成之前不在DOM中&#xff0c;需要某些操作后才会出现&…

视频剪辑sdk,跨平台部署,助力企业差异化竞争

在这个内容为王的时代&#xff0c;视频已成为连接用户、传递价值的核心媒介。无论是社交媒体、在线教育、短视频平台还是新闻资讯&#xff0c;高质量的视频内容都是吸引用户、提升用户体验的关键。然而&#xff0c;对于众多企业而言&#xff0c;如何高效、专业地处理视频内容&a…

字节跳动春节抖音视频红包系统设计与实现--图文解析

字节跳动春节抖音视频红包系统设计与实现–图文解析 原作者&#xff1a;字节跳动技术团队 原文链接&#xff1a;https://www.toutiao.com/article/7114224228030841374 原标题&#xff1a;2022 春节抖音视频红包系统设计与实现 我们做了什么 业务背景 在春节活动期间&…

洛谷 P10034 「Cfz Round 3」Circle

[Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription] [Solution] \color{blue}{\texttt{[Solution]}} [Solution] 这是道好题。 建图&#xff0c;对每一个 i → p i i \to p_{i} i→pi​ 都建立一个有向边&#xff0c;就可以得到一个…