百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

linux 设备驱动模型-Uevent(linux 设备驱动模型 知乎)

sinye56 2024-11-10 11:27 13 浏览 0 评论

1. Uevent的功能

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

2. Uevent在kernel中的位置

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

3. Uevent的内部逻辑解析

3.1 Source Code位置

Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件,如下:

  • include/linux/kobject.h
  • lib/kobject_uevent.c

3.2 数据结构描述

kobject.h定义了uevent相关的常量和数据结构,如下:

  • kobject_action
/* include/linux/kobject.h, line 50 */
enum kobject_action { 
KOBJ_ADD,
KOBJ_REMOVE, 
KOBJ_CHANGE, 
KOBJ_MOVE,
KOBJ_ONLINE, 
KOBJ_OFFLINE,
KOBJ_MAX 
};

kobject_action定义了event的类型,包括:

ADD/REMOVE,Kobject(或上层数据结构)的添加/移除事件。

ONLINE/OFFLINE,Kobject(或上层数据结构)的上线/下线事件,其实是是否使能。

CHANGE,Kobject(或上层数据结构)的状态或者内容发生改变。

MOVE,Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)。

CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。

  • kobj_uevent_env
 1 /* include/linux/kobject.h, line 31 */
 2: #define UEVENT_NUM_ENVP 32 /* number of env pointers */
 3: #define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */
 4: 
 5: /* include/linux/kobject.h, line 116 */
 6: struct kobj_uevent_env {
 7: char *envp[UEVENT_NUM_ENVP];
 8: int envp_idx;
 9: char buf[UEVENT_BUFFER_SIZE];
 10: int buflen;
 11: };

前面有提到过,在利用Kmod向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在Linux系统,可执行文件的执行,依赖于环境变量,因此kobj_uevent_env用于组织此次事件上报时的环境变量。

envp,指针数组,用于保存每个环境变量的地址,最多可支持的环境变量数量为UEVENT_NUM_ENVP。

envp_idx,用于访问环境变量指针数组的index。

buf,保存环境变量的buffer,最大为UEVENT_BUFFER_SIZE。

buflen,访问buf的变量。

  • kset_uevent_ops
 1: /* include/linux/kobject.h, line 123 */
 2: struct kset_uevent_ops {
 3: int (* const filter)(struct kset *kset, struct kobject *kobj);
 4: const char *(* const name)(struct kset *kset, struct kobject *kobj);
 5: int (* const uevent)(struct kset *kset, struct kobject *kobj,
 6: struct kobj_uevent_env *env);
 7: };

kset_uevent_ops是为kset量身订做的一个数据结构,里面包含filter和uevent两个回调函数,用处如下:

filter,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,阻止不希望上报的event,从而达到从整体上管理的目的。

name,该接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有Kobject将不允许上报uvent.

uevent,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了

3.2 内部实现

#if defined(CONFIG_HOTPLUG)
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
 char *envp[]);
__printf(2, 3)
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
int kobject_action_type(const char *buf, size_t count,
 enum kobject_action *type);
 
kobject_uevent_env ,以 envp 为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
 return kobject_uevent_env(kobj, action, NULL);
}
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;
#ifdef CONFIG_NET
 struct uevent_sock *ue_sk;
#endif
 pr_debug("kobject: '%s' (%p): %s\n",
 kobject_name(kobj), kobj, __func__);
 /* search the kset we belong to */
 //1.查找当前kobject或其parent是否从属于某个kset;如果都不从属于某个kset,则返回错误。(说明一个kobject若没有加入kset,是不会上报uevent的)
 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*/
 //2.查看kobj->uevent_suppress是否被设置;如果设置了,则忽略所有的uevent上报,并返回0.
 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. */
 //3.如果所属的kset有uevent_ops->filter,则调用该函数,若该函数返回0,则过滤此次上报。(kset 可以通过filter接口过滤不希望上报的event)
 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 */
 //4.判断所属的kset是否有合法的名称,若uevent_ops->name存在就用其返回的名称作为subsystem;若uevent_ops->name不存在就用kset本身的kobject的名称作为subsystem;
 //若没有合法的名称,则不上报uevent
 if (uevent_ops && uevent_ops->name)
 subsystem = uevent_ops->name(kset, kobj);
 else
 subsystem = 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 */
 //5.分配一个此次上报的用于保存环境变量的buffer,
 env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
 if (!env)
 return -ENOMEM;
 /* complete object path */
 //6.获得该kobject在sysfs中路径
 devpath = kobject_get_path(kobj, GFP_KERNEL);
 if (!devpath) {
 retval = -ENOENT;
 goto exit;
 }
 /* default keys */
 //7.添加ACTION到env
 retval = add_uevent_var(env, "ACTION=%s", action_string);
 if (retval)
 goto exit;
 //8.添加DEVPATH(kobject路径信息)到env
 retval = add_uevent_var(env, "DEVPATH=%s", devpath);
 if (retval)
 goto exit;
 //9.添加SUBSYSTEM到env
 retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
 if (retval)
 goto exit;
 /* keys passed in from the caller */
 //10.如果传入的envp_ext不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中
 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 */
 //11.如果 uevent_ops->uevent 存在,调用该接口,添加kset统一的环境变量到env指针
 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;
 }
 }
 /*
 * Mark "add" and "remove" events in the object to ensure proper
 * events 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.
 */
 //12.根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
 if (action == KOBJ_ADD)
 kobj->state_add_uevent_sent = 1;
 else if (action == KOBJ_REMOVE)
 kobj->state_remove_uevent_sent = 1;
 mutex_lock(&uevent_sock_mutex);
 /* we will send an event, so request a new sequence number */
 //13.调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号
 retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
 if (retval) {
 mutex_unlock(&uevent_sock_mutex);
 goto exit;
 }
//14.如果定义了"CONFIG_NET”,则使用netlink发送该uevent
#if defined(CONFIG_NET)
 /* send netlink message */
 list_for_each_entry(ue_sk, &uevent_sock_list, list) {
 struct sock *uevent_sock = ue_sk->sk;
 struct sk_buff *skb;
 size_t len;
 if (!netlink_has_listeners(uevent_sock, 1))
 continue;
 /* allocate message with the maximum possible size */
 len = strlen(action_string) + strlen(devpath) + 2;
 skb = alloc_skb(len + env->buflen, GFP_KERNEL);
 if (skb) {
 char *scratch;
 /* add header */
 scratch = skb_put(skb, len);
 sprintf(scratch, "%s@%s", action_string, devpath);
 /* copy keys to our continuous event payload buffer */
 for (i = 0; i < env->envp_idx; i++) {
 len = strlen(env->envp[i]) + 1;
 scratch = skb_put(skb, len);
 strcpy(scratch, env->envp[i]);
 }
 NETLINK_CB(skb).dst_group = 1;
 retval = netlink_broadcast_filtered(uevent_sock, skb,
 0, 1, GFP_KERNEL,
 kobj_bcast_filter,
 kobj);
 /* ENOBUFS should be handled in userspace */
 if (retval == -ENOBUFS || retval == -ESRCH)
 retval = 0;
 } else
 retval = -ENOMEM;
 }
#endif
 mutex_unlock(&uevent_sock_mutex);
 /* call uevent_helper, usually only enabled during early boot */
 //15.以uevent_helper、 subsystem 以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,
 // 调用kmod模块提供的call_usermodehelper函数,上报uevent。
 if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
 char *argv [3];
 argv [0] = uevent_helper;//在/sys/kernel/uevent_helper文件中可以存入用户空间可执行程序的路径,当内核有事件发生时,将会执行该程序
 argv [1] = (char *)subsystem;
 argv [2] = NULL;
 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 = call_usermodehelper(argv[0], argv,
 env->envp, UMH_WAIT_EXEC);
 }
exit:
 kfree(devpath);
 kfree(env);
 return retval;
}

uevent模块通过kmod上报uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper)处理该event。 而该uevent helper的路径保存在uevent_helper数组中。

可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。 但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。 因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。因此内核编译时,需要把该配置项留空。

在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。 这可以通过把helper的路径写入到"/sys/kernel/uevent_helper"文件中实现。 实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码。

在/etc/init.d/rcS脚本中添加 echo "/sbin/mdev" > /proc/sys/kernel/hotplug,会发现cat /sys/kernel/uevent_helper 即是/sbin/mdev。 说明/proc/sys/kernel/hotplug中的可执行文件路径最终还是会写到/sys/kernel/uevent_helper中。

自己手动echo "/kernel/main" > uevent_helper(之前的/sbin/mdev会被覆盖),当lsmod、rmmod时,/sys/kernel/uevent_helper中的/kernel/main会执行, 表明事件已经上报给用户空间。

call_usermodehelper函数能够方便的在内核中直接新建和运行用户空间的程序,并且该程序有root权限。 call_usermodeheler函数的参数用法和execve函数一致。 call_usermodehelper()->call_usermodehelper_exec()

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&amp;配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: