详解kobject_uevent_env上报机制

示例

比如插拔sim卡会打印如下信息

[90133.351745] report_usim_event: usim uevent [USIM_NAME=usim0 USIM_EVENT=plugout] is sent
[90133.380554] USIM_NAME=usim0, USIM_EVENT=plugout
[90133.380645] serial_atcmd at+cfun=5[90176.496063] report_usim_event: usim uevent [USIM_NAME=usim0 USIM_EVENT=plugin] is sent
[90176.524383] USIM_NAME=usim0, USIM_EVENT=plugin
[90176.533599] CUR CFUN = 5
[90176.533691] serial_atcmd at+cfun=0
[90181.583862] serial_atcmd at+cfun=1

这是热插拔触后,触发的事件给应用层,然后执行的脚本,跟上面的信息是对上的

#!/bin/sh#usim0_state=`cat /sys/class/usim_event/usim0/state`
usim_name=`echo $USIM_NAME`
usim_event=`echo $USIM_EVENT`if [ -d /sys/devices/virtual/usim_event/usim0 -o -d /sys/devices/virtual/usim_event/usim1 ]; thenecho "USIM_NAME=$usim_name, USIM_EVENT=$usim_event" > /dev/kmsgif [ "$usim_event" == "plugin" ]; thencur_cfun=`serial_atcmd "AT+CFUN?" | grep CFUN | awk -F " " '{print $2}'`echo "CUR CFUN =" $cur_cfun > /dev/kmsgif [ "$cur_cfun" != "0" ]; thenecho "serial_atcmd at+cfun=0" > /dev/kmsgserial_atcmd at+cfun=0sleep 5fiecho "serial_atcmd at+cfun=1" > /dev/kmsgserial_atcmd at+cfun=1ubus send "usim.event" '{"status":1}'elif [ "$usim_event" == "plugout" ]; thenecho "serial_atcmd at+cfun=5" > /dev/kmsgserial_atcmd at+cfun=5ubus call network.interface.wan0 removeubus call network.interface.wan60 removeubus send "usim.event" '{"status":0}'fi
elif [ -d /sys/devices/virtual/usim_event/usimtray ]; thenecho "USIM_NAME=$usim_name, USIM_EVENT=$usim_event" > /dev/kmsg
fi

上报事件的函数如下,这个可以是中断脚来检测,或者上层通过attr属性主动调用触发

static void report_usim_event(struct usim_event_device *uedev, int state)
{char name_buf[50];char *env[3];snprintf(name_buf, sizeof(name_buf), "USIM_NAME=%s", uedev->name);env[0] = name_buf;if (strcmp("usimtray", uedev->name) == 0)env[1] = state ? "USIM_EVENT=trayPlugin" :"USIM_EVENT=trayPlugout";elseenv[1] = state ? "USIM_EVENT=plugin" : "USIM_EVENT=plugout";env[2] = NULL;kobject_uevent_env(&uedev->dev->kobj, KOBJ_CHANGE, env);usim_event_debug(GE_DEBUG_INFO, "%s: usim uevent [%s %s] is sent\n",__func__, env[0], env[1]);
}

.config

uevent相关配置如下

CONFIG_NET=y
CONFIG_UEVENT_HELPER=y
CONFIG_UEVENT_HELPER_PATH=""

uevent把事件上报给用户空间的两种途径:根据上述配置来选择用那种
1.通过kmod模块,直接调用用户空间的可执行程序或脚本。
2..通过netlink通信机制,将事件从内核空间传递到用户空间。

定义了CONFIG_UEVENT_HELPER和CONFIG_UEVENT_HELPER_PATH不为空,就会掉用户空间的程序

/*** 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;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;/* 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;}/* 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 */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;}/* 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 */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);mutex_unlock(&uevent_sock_mutex);#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;
}

定义了CONFIG_NET就会走netlink

static int kobject_uevent_net_broadcast(struct kobject *kobj,struct kobj_uevent_env *env,const char *action_string,const char *devpath)
{int ret = 0;#ifdef CONFIG_NETconst struct kobj_ns_type_operations *ops;const struct net *net = NULL;ops = kobj_ns_ops(kobj);if (!ops && kobj->kset) {struct kobject *ksobj = &kobj->kset->kobj;if (ksobj->parent != NULL)ops = kobj_ns_ops(ksobj->parent);}/* kobjects currently only carry network namespace tags and they* are the only tag relevant here since we want to decide which* network namespaces to broadcast the uevent into.*/if (ops && ops->netlink_ns && kobj->ktype->namespace)if (ops->type == KOBJ_NS_TYPE_NET)net = kobj->ktype->namespace(kobj);if (!net)ret = uevent_net_broadcast_untagged(env, action_string,devpath);elseret = uevent_net_broadcast_tagged(net->uevent_sock->sk, env,action_string, devpath);
#endifreturn ret;
}

netlink

netlink就是socket的一种表现方式,在内核中通过NETLINK_KOBJECT_UEVENT参数,用netlink_kernel_create来创建一个netlink的socket

/lib/kobject_uevent.c
static int uevent_net_init(struct net *net)
{struct uevent_sock *ue_sk;struct netlink_kernel_cfg cfg = {.groups	= 1,.input = uevent_net_rcv,.flags	= NL_CFG_F_NONROOT_RECV};ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);if (!ue_sk)return -ENOMEM;ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);if (!ue_sk->sk) {pr_err("kobject_uevent: unable to create netlink socket!\n");kfree(ue_sk);return -ENODEV;}net->uevent_sock = ue_sk;/* Restrict uevents to initial user namespace. */if (sock_net(ue_sk->sk)->user_ns == &init_user_ns) {mutex_lock(&uevent_sock_mutex);list_add_tail(&ue_sk->list, &uevent_sock_list);mutex_unlock(&uevent_sock_mutex);}return 0;
}static void uevent_net_exit(struct net *net)
{struct uevent_sock *ue_sk = net->uevent_sock;if (sock_net(ue_sk->sk)->user_ns == &init_user_ns) {mutex_lock(&uevent_sock_mutex);list_del(&ue_sk->list);mutex_unlock(&uevent_sock_mutex);}netlink_kernel_release(ue_sk->sk);kfree(ue_sk);
}static struct pernet_operations uevent_net_ops = {.init	= uevent_net_init,.exit	= uevent_net_exit,
};static int __init kobject_uevent_init(void)
{return register_pernet_subsys(&uevent_net_ops);
}postcore_initcall(kobject_uevent_init);

在用户层,通过一个进程来创建一个netlink,这个应用程序可以是mdev,udev或者openwrt的procd,这三种方式都是基于netlink来管理热插拔的,比如procd通过NETLINK_KOBJECT_UEVENT参数来通过socket系统调用创建一个fd来与内核的netlink交互数据,内核上报uevent被这个进程接受后,然后调用/etc/hotplug.d/的各脚本来执行热插拔操作

void hotplug(char *rules)
{struct sockaddr_nl nls = {};int nlbufsize = 512 * 1024;rule_file = strdup(rules);nls.nl_family = AF_NETLINK;nls.nl_pid = 0;nls.nl_groups = -1;if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {ERROR("Failed to open hotplug socket: %m\n");exit(1);}if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) {ERROR("Failed to bind hotplug socket: %m\n");exit(1);}if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize)))ERROR("Failed to resize receive buffer: %m\n");json_script_init(&jctx);queue_proc.cb = queue_proc_cb;uloop_fd_add(&hotplug_fd, ULOOP_READ);
}

 当device_add添加设备时会调用kobject_uevent(&dev->kobj, KOBJ_ADD);来上报事件给procd的netlink,然后根据是否有主从设备号等信息,来创建设备节点文件

static void handle_makedev(struct blob_attr *msg, struct blob_attr *data)
{unsigned int oldumask = umask(0);static struct blobmsg_policy mkdev_policy[3] = {{ .type = BLOBMSG_TYPE_STRING },{ .type = BLOBMSG_TYPE_STRING },{ .type = BLOBMSG_TYPE_STRING },};struct blob_attr *tb[3];char *minor = hotplug_msg_find_var(msg, "MINOR");char *major = hotplug_msg_find_var(msg, "MAJOR");char *subsystem = hotplug_msg_find_var(msg, "SUBSYSTEM");blobmsg_parse_array(mkdev_policy, 3, tb, blobmsg_data(data), blobmsg_data_len(data));if (tb[0] && tb[1] && minor && major && subsystem) {mode_t m = S_IFCHR;char *d = strdup(blobmsg_get_string(tb[0]));d = dirname(d);mkdir_p(d, 0755);free(d);if (!strcmp(subsystem, "block"))m = S_IFBLK;mknod(blobmsg_get_string(tb[0]),m | strtoul(blobmsg_data(tb[1]), NULL, 8),makedev(atoi(major), atoi(minor)));if (tb[2])chgrp_target(tb[2], tb[0]);}umask(oldumask);
}

或者收到sim的切换事件后,调用最上面的位于/etc/hotplug.d里的sim热插拔脚本,来执行对应的行为

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

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

相关文章

嵌入式linux移植篇之kernel

Linux的启动过程概述 Linux内核的启动过程是一个复杂而又有序的流程,涉及到硬件初始化、引导加载、内核初始化等多个步骤。以下是Linux内核的典型启动流程: BIOS/UEFI阶段: 电源启动:计算机通电后,BIOS(…

#从零开始# 在深度学习环境中,如何用 pycharm配置使用 pipenv 虚拟环境

为Python项目创建虚拟环境 在深度学习环境和一般python环境中安装pipenv基本一致,只需要确认好pipenv指定的python版本即可,安装pipenv前,可以通过python --version来确认安装版本 快捷键:crtl alt S 查看interpreter,查看所有…

聊聊比特币----比特币地址

⽐特币地址是⼀个标识符(帐号),包含27-34个字母数字拉丁字符(0,O,I除外)。地址可以以QR码形式表⽰,是匿名的,不包含关于所有者的信息。 地址⽰例:14qViLJfdG…

【51单片机】开发板和单片机的介绍(2)

前言 大家好吖,欢迎来到 YY 滴单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! YY的《C》专栏YY的《C11》专栏YY的…

[Python] 什么是KMeans聚类算法以及scikit-learn中的KMeans使用案例

什么是无监督学习? 无监督学习是机器学习中的一种方法,其主要目的是从无标签的数据集中发现隐藏的模式、结构或者规律。在无监督学习中,算法不依赖于任何先验的标签信息,而是根据数据本身的特征和规律进行学习和推断。无监督学习…

论文分享:利用对象存储进行高性能数据分析

本次分享的是慕尼黑工业大学(TUM) Dominik Durner,Viktor Leis,和 Thomas Neumann 于 2023 年 7 月发表在 PVLDB(Volume 16 No.11) 的论文: Exploiting Cloud Object Storage for High-Performance Analyt…

Kubernetes - DAEMONSET 与 DEPLOYMENT 区别

Deployment 部署的副本 Pod 会分布在各个 Node 上,每个 Node 都可能运行好几个副本。DaemonSet 的不同之处在于:每个 Node 上最多只能运行一个副本。 主要区别 The scalability is much better when using a Deployment, because you will have a Single…

PyQT——蓝牙收发数据(上位机案例-小车控制器)

实现功能 由于本人水平有限,仅用了最简单的进行实现,主要功能: 蓝牙设备扫描以及刷新蓝牙连接蓝牙数据发送蓝牙数据接收 页面实现效果 代码目录结构 代码案例 代码已经全部添加注释,故不再做单独解释。 Main.py ble_contr…

大数据信用报告在线查询平台哪个好?

随着大数据技术在金融风控的运用,大数据信用越来越被人熟知,由于线下没有查询大数据信用的地方,想要查询大数据信用报告只有在线上查询,那大数据信用报告在线查询平台哪个好呢?本文贷你一起去了解市面上比较好的三个平台。 大数据…

【Springcloud篇】学习笔记九(十五、十六章):Cloud Alibaba介绍、Nacos服务注册、服务配置中心

第十五章_Cloud Alibaba简介 1.出现SpringCloud Alibaba的原因 SpringCloud Netflix项目进入维护模式 技术的发展 2.SpringCloud Alibaba简介 2.1是什么 2.2能干嘛 2.3去哪下 阿里巴巴中文文档下载网站: spring-cloud-alibaba/README-zh.md at 2022.x alibaba…

学成在线:采用XXL-JOB任务调度方案使用FFmpeg处理视频转码业务

分片技术方案 概述 XXL-JOB并不直接提供数据处理的功能,它只会给所有注册的执行器分配好分片序号,在向执行器下发任务调度的同时携带分片总数和当前分片序号等参数 设计作业分片方案保证多个执行器之间不会查询到重复的任务,保证任务不会重复执行 任…

由于误删了node依赖,导致这后面的一系列操作

文章目录 1. 事发原因:Delete select files2. Delete select files引起的cross-env报错3. cross-env是node_modules的依赖工具4. 那么Delete selected files到底是什么操作5. 重装node_modules依赖包,也报错6. 报错:cb() never called!7. 算了…

JSR303参数校验-SpringMVC

文章目录 JSR303技术标准简介JSR303标准几个具体实现框架validation-apijakarta.validation-apihibernate-validatorspring-boot-starter-validation Spring Validationjavax.validation.constraints包下提供的注解org.hibernate.validator.constraints包扩展的注解校验注解默认…

deb 打包

二进制打包 dpkg-deb -b [package] file.db二进制解包 dpkg-deb -R file.db [dir]快速打包脚本 #!/bin/bash -e if ! [[ $# -eq 1 ]] || ! [[ -d $1 ]] ; thenecho "$0 [Package]"exit 1 fipkg$(basename $1)/DEBIAN mkdir -p ${pkg} $(basename $1)/opt mkdir -p …

内衣洗衣机是不是鸡肋?好用的小型洗衣机全自动推荐

随着大家工作的压力越来越大,下了班之后只能想躺平,在洗完澡之后看着还需要手洗的内衣裤真的很头疼。有些小伙伴还有会攒几天再丢进去洗衣机里面一起,而且这样子是非常不好的,用过的内衣裤长时间不清洗容易滋生细菌,而…

无人机遥感技术在地质灾害监测应用分析,多旋翼无人机应急救援技术探讨

地质灾害是指在地球的发展演变过程中, 由各种自然地质作用和人类活动所形成的灾害性地质事件。给人民的生命和财产安全带来严重威胁,因此有必要开展地质灾害预测预报、灾害应急和风险区划 遥感技术的快速发展为我们提供了一种获取实时灾害信息的可靠手段…

陪女朋友学习计算机二级之数据库笛卡尔积和自然连接

数据库中的基本关系运算 交 和数学中的交集类似,但是需要相同的表模式 如果俩个表有相同的关系模式如表1为(ID,姓名,学号)表2为ID,姓名,学号) 表1 表2 交之后就可以变成 并 和数…

水面漂浮物监测识别摄像机

水面漂浮物监测识别摄像机是一种用于监测水域表面上漂浮物的设备,可以帮助环保部门或海洋研究机构快速发现和识别水中的浮游物,有助于保护水质和生态环境。这种摄像机通常具有以下功能和特点: 高分辨率摄像头:配备高清晰度摄像头&…

【数据分享】1929-2023年全球站点的逐日降雪深度数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、能见度等指标,说到气象数据,最详细的气象数据是具体到气象监测站点的数据! 之前我们分享过1929-2023年全球气象站点的逐日平均气温数据、逐日最高气温数据…