Linux设备模型(五) - uevent kernel实现

2024-02-27 08:36

本文主要是介绍Linux设备模型(五) - uevent kernel实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. Uevent的功能

Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。

该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。

2. Uevent在kernel中的位置

下面图片描述了Uevent模块在内核中的位置:

由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口。Uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。

3,数据结构描述

  • kobject_action

/*
* The actions here must match the index to the string array
* in lib/kobject_uevent.c
*
* Do not add new actions here without checking with the driver-core
* maintainers. Action strings are not meant to express subsystem
* or device specific properties. In most cases you want to send a
* kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
* specific variables added to the event environment.
*/
enum kobject_action {KOBJ_ADD, KOBJ_REMOVE, //Kobject(或上层数据结构)的添加/移除事件KOBJ_CHANGE, //Kobject(或上层数据结构)的状态或者内容发生改变; 如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。KOBJ_MOVE, //Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)KOBJ_ONLINE,KOBJ_OFFLINE, //Kobject(或上层数据结构)的上线/下线事件,其实是是否使能KOBJ_BIND,KOBJ_UNBIND,
};
  • kobj_uevent_env
#define UEVENT_NUM_ENVP            64    /* number of env pointers */
#define UEVENT_BUFFER_SIZE        2048    /* buffer for the variables */
/* environment buffer */
struct kobj_uevent_env {char *argv[3];char *envp[UEVENT_NUM_ENVP]; //环境变量的指针数组,envp指向每一个环境变量int envp_idx; //环境变量的索引char buf[UEVENT_BUFFER_SIZE]; //存储所有环境变量的bufferint buflen; //环境变量的buffer的长度
};
  • kset_uevent_ops
struct kset {struct list_head list;spinlock_t list_lock;struct kobject kobj;const struct kset_uevent_ops *uevent_ops;ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
} __randomize_layout;kset_uevent_ops 是为kset量身订做的一个数据结构,里面包含filter和uevent两个回调函数
* @uevent_ops: the set of uevent operations for this kset.  These are
* called whenever a kobject has something happen to it so that the kset
* can add new environment variables, or filter out the uevents if so
* desired.
struct kset_uevent_ops {int (* const filter)(struct kset *kset, struct kobject *kobj); //当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,阻止不希望上报的event,从而达到从整体上管理的目的const char *(* const name)(struct kset *kset, struct kobject *kobj); //接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有Kobject将不允许上报uventint (* const uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env); //当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量
};eg:
static const struct kset_uevent_ops bus_uevent_ops = {.filter = bus_uevent_filter,
};bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);kset = kset_create(name, uevent_ops, parent_kobj);kset->uevent_ops = uevent_ops;kset_register(kset);kobject_uevent(&k->kobj, KOBJ_ADD);

4,常用API

4.1 kobject_uevent_env

以envp为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。

/**
* 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;const char *action_string = kobject_actions[action];const char *devpath = NULL;const char *subsystem;struct kobject *top_kobj;struct kset *kset;const struct kset_uevent_ops *uevent_ops;int i = 0;int retval = 0;/** 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)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)top_kobj = top_kobj->parent;//查找kobj本身或者其parent是否从属于某个kset,如果不是,则报错返回(由此可以说明,如果一个kobject没有加入kset,是不允许上报uevent的)if (!top_kobj->kset) {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;//查看kobj->uevent_suppress是否设置,如果设置,则忽略所有的uevent上报并返回(注3:由此可知,可以通过Kobject的uevent_suppress标志,管控Kobject的uevent的上报)/* skip the event, if uevent_suppress is set*/if (kobj->uevent_suppress) {pr_debug("kobject: '%s' (%p): %s: uevent_suppress ""caused the event to drop!\n",kobject_name(kobj), kobj, __func__);return 0;}//如果所属的kset有uevent_ops->filter函数,则调用该函数,过滤此次上报(注4:这佐证了3.2小节有关filter接口的说明,kset可以通过filter接口过滤不希望上报的event,从而达到整体的管理效果)/* 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;}//判断所属的kset是否有合法的名称(称作subsystem,和前期的内核版本有区别),否则不允许上报uevent/* originating subsystem */if (uevent_ops && uevent_ops->name)subsystem = uevent_ops->name(kset, kobj);elsesubsystem = 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;}//* 分配一个用于此次上报的、存储环境变量的buffer(结果保存在env指针中),并获得该Kobject在sysfs中路径信息(用户空间软件需要依据该路径信息在sysfs中访问它)/* 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;}//调用add_uevent_var接口(下面会介绍),将Action、路径信息、subsystem等信息,添加到env指针中/* 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;}}//如果所属的kset存在uevent_ops->uevent接口,调用该接口,添加kset统一的环境变量到env指针/* 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;}}//根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态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;}//调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号mutex_lock(&uevent_sock_mutex);/* we will send an event, so request a new sequence number */retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);if (retval) {mutex_unlock(&uevent_sock_mutex);goto exit;}//如果定义了"CONFIG_NET”,则使用netlink发送该ueventretval = kobject_uevent_net_broadcast(kobj, env, action_string,devpath);mutex_unlock(&uevent_sock_mutex);//以uevent_helper、subsystem以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehelper函数,上报uevent
#ifdef CONFIG_UEVENT_HELPER/* 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 */}}
#endifexit:kfree(devpath);kfree(env);return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);

Android在源码目录:system/extras/tests/uevents/中,可以监听底层UEvent事件上报的程序,该程序没有自动编译到系统中,需要单独编译。

编译完成后,可以编译成system/bin/uevents的可执行程序,可以通过adb shell,输入uenvets可以查看上报事件:

change@/devices/platform/soc/soc:mmi,charger/power_supply/mmi_battery ACTION=change DEVPATH=/devices/platform/soc/soc:mmi,charger/power_supply/mmi_battery SUBSYSTEM=power_supply POWER_SUPPLY_NAME=mmi_battery POWER_SUPPLY_TYPE=Mains POWER_SUPPLY_STATUS=Full POWER_SUPPLY_HEALTH=Good POWER_SUPPLY_TEMP=250 POWER_SUPPLY_CAPACITY=100 POWER_SUPPLY_CYCLE_COUNT=2 POWER_SUPPLY_CHARGE_FULL=4015000 POWER_SUPPLY_CHARGE_FULL_DESIGN=4015000 POWER_SUPPLY_VOLTAGE_NOW=4379000 POWER_SUPPLY_CURRENT_NOW=-704000 POWER_SUPPLY_CHARGE_COUNTER=4015000 SEQNUM=54900

change@/devices/platform/soc/ae00000.qcom,mdss_mdp/backlight/panel0-backlight ACTION=change DEVPATH=/devices/platform/soc/ae00000.qcom,mdss_mdp/backlight/panel0-backlight SUBSYSTEM=backlight SOURCE=sysfs SEQNUM=54903

主动向uevent 节点写入add也会导致内核生成并重新发送当前注册设备的uevent消息:

console 1

/sys/bus/platform/devices/goodix_ts.0 # echo add > uevent

console 2

lynkco:/system/bin # uevents

add@/devices/platform/goodix_ts.0 ACTION=add DEVPATH=/devices/platform/goodix_ts.0 SUBSYSTEM=platform SYNTH_UUID=0 DRIVER=goodix_ts MODALIAS=platform:goodix_ts SEQNUM=55327

4.2 kobject_uevent

和kobject_uevent_env功能一样,只是没有指定任何的环境变量。

/**
* 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);

4.3 add_uevent_var

/**
* add_uevent_var - add key value string to the environment buffer
* @env: environment buffer structure
* @format: printf format for the key=value pair
*
* Returns 0 if environment variable was added successfully or -ENOMEM
* if no space was available.
*/
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
{va_list args;int len;//环境变量的个数不能超过最大值if (env->envp_idx >= ARRAY_SIZE(env->envp)) {WARN(1, KERN_ERR "add_uevent_var: too many keys\n");return -ENOMEM;}//把环境变量格式化输出到 env->bufva_start(args, format);len = vsnprintf(&env->buf[env->buflen],sizeof(env->buf) - env->buflen,format, args);va_end(args);//检查buffer sizeif (len >= (sizeof(env->buf) - env->buflen)) {WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");return -ENOMEM;}//env->envp[env->envp_idx++]指向此次添加的环境变量,通过envp_idx能直接取出key=value pairenv->envp[env->envp_idx++] = &env->buf[env->buflen];//增加buflen计数,两个key=value中间有空格env->buflen += len + 1;return 0;
}
EXPORT_SYMBOL_GPL(add_uevent_var);

4.4 kobject_action_type

将enum kobject_action类型的Action,转换为字符串。

5,API使用示例

向user space发送自定义的uevent事件。

1)env = kzalloc(sizeof(*env), GFP_KERNEL_ACCOUNT);if (!env)return;add_uevent_var(env, "CREATED=%llu", created);add_uevent_var(env, "COUNT=%llu", active);if (type == KVM_EVENT_CREATE_VM) {add_uevent_var(env, "EVENT=create");kvm->userspace_pid = task_pid_nr(current);} else if (type == KVM_EVENT_DESTROY_VM) {add_uevent_var(env, "EVENT=destroy");}add_uevent_var(env, "PID=%d", kvm->userspace_pid);if (kvm->debugfs_dentry) {char *tmp, *p = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT);if (p) {tmp = dentry_path_raw(kvm->debugfs_dentry, p, PATH_MAX);if (!IS_ERR(tmp))add_uevent_var(env, "STATS_PATH=%s", tmp);kfree(p);}}/* no need for checks, since we are adding at most only 5 keys */env->envp[env->envp_idx++] = NULL;kobject_uevent_env(&kvm_dev.this_device->kobj, KOBJ_CHANGE, env->envp);kfree(env);2)char *envp[4] = { "FC_EVENT=nvmediscovery", hostaddr, tgtaddr, NULL };kobject_uevent_env(&fc_udev_device->kobj, KOBJ_CHANGE, envp);

参考链接:

Linux设备模型(3)_Uevent

这篇关于Linux设备模型(五) - uevent kernel实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/751825

相关文章

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机