Linux设备模型3

Linux设备模型3(基于Linux6.6)---uevnet介绍

 

一、 Uevent概述

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

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

Uevent 是 Linux 内核中的一种机制,用于在设备发生变化时通知用户空间。这种机制基于内核的设备模型(kobject),通过 uevent 通知用户空间有关设备状态、配置、属性或事件的信息。Uevent 是设备管理系统的重要组成部分,尤其在 udev(用户空间设备管理器)中扮演着核心角色。

1.1 Uevent 触发的背景

Uevent 主要在以下几种设备事件发生时触发:

  • 设备添加(例如,一个新的硬件设备被插入)
  • 设备删除(例如,设备被移除)
  • 设备属性更新(例如,设备的配置文件发生变化)
  • 设备状态改变(例如,设备的电源状态或连接状态改变)

当这些事件发生时,内核会生成一个 Uevent,并将它发送到用户空间,通常是通过 udev 来处理和响应这些事件。

1.2 Uevent 工作原理

  1. 内核生成 Uevent 内核通过 kobject_uevent() 函数生成一个 Uevent,并将其发送到用户空间。这通常是在设备驱动的 addremovechange 操作中发生的。

  2. Uevent 通知机制 内核会向用户空间发送一个包含设备相关信息的 Uevent,用户空间可以通过监听这些 Uevent 来了解设备状态变化。Uevent 通常包含:

    • 设备的名称(例如,/dev/sda
    • 设备的类型(例如,blocknet 等)
    • 设备的属性(例如,设备的厂商、型号、驱动等信息)

    Uevent 通常以字符串的形式传递,格式类似于:
    ACTION=add DEVPATH=/devices/pci0000:00/0000:00:1f.2

  3. 用户空间处理 Uevent 用户空间通过 udev(或其他相似工具)接收到 Uevent 后,进行相应的处理。udev 负责管理 /dev 目录的设备节点,并执行相应的规则或脚本来配置设备。

  4. udev 规则 udev 可以根据 Uevent 中的信息(如设备类型、ID、路径等)应用不同的规则,执行相应的操作(例如,创建设备节点、加载模块、设置权限等)。

1.3 Uevent 消息格式

Uevent 通常以以下格式发送给用户空间:

ACTION=<action>
DEVPATH=<device-path>
SUBSYSTEM=<subsystem>
DEVNAME=<device-name>
...

常见的字段包括:

  • ACTION: 事件类型,表示设备的状态变化。常见的值有:

    • add: 设备添加
    • remove: 设备移除
    • change: 设备状态或属性更改
  • DEVPATH: 设备路径,指示设备在 /sys 中的路径。

  • DEVNAME: 设备名称,通常对应 /dev 目录下的设备节点名称(例如 sdaeth0)。

  • SUBSYSTEM: 子系统,指示设备所属的内核子系统(如 blocknetinput 等)。

  • PRODUCT: 设备的产品信息(如供应商 ID、产品 ID)。

  • ID_VENDOR: 设备的供应商名称。

  • ID_MODEL: 设备的型号。

1.4 Uevent 与 udev

Ueventudev 的核心基础,udev 监听内核发送的 Uevent,并根据 Uevent 执行相应的设备管理任务,如:

  • 创建设备节点:为新设备在 /dev 目录下创建设备文件。
  • 加载驱动程序:根据设备信息加载相应的内核模块。
  • 配置权限:为设备设置合适的权限。
  • 触发脚本:运行与设备相关的用户自定义脚本。

udev 在内核和用户空间之间提供了设备管理的桥梁,基于 Uevent,可以动态地响应硬件设备的变化。

1.5 生成 Uevent 的常见操作

  1. 设备添加:当一个设备被识别并添加到系统时,内核会发送一个 Uevent,通知用户空间设备的添加事件。

  2. 设备移除:当一个设备被移除或卸载时,内核会发送一个 Uevent,通知用户空间设备的删除事件。

  3. 设备属性更新:设备的属性(如状态、能力)发生变化时,内核会生成 Uevent,并通知用户空间更新设备的信息。

  4. 设备状态变化:例如,设备的挂载状态、网络连接状态等发生变化时,也会触发 Uevent

1.6 相关的函数和文件

  • kobject_uevent():用于生成 Uevent 的内核函数。
  • /sys 目录:设备和驱动的配置信息存储在 /sys 目录中,Uevent 也通过该目录传递设备信息。
  • udevadm 工具:用于查询和调试 udev 规则的工具。
    • 例如:udevadm info --name=/dev/sda 可以显示 /dev/sda 的相关信息。

1.7 Uevent 在实际中的应用

  • 自动加载驱动模块udev 根据设备信息自动加载必要的驱动程序。
  • 设备权限管理udev 会根据设备信息设置设备文件的权限。
  • 创建设备文件udev 会在 /dev 目录下创建新的设备文件。
  • 触发用户空间脚本udev 可以配置触发特定的用户空间脚本来执行与设备相关的操作(如格式化磁盘、启动网络接口等)。

二、 Uevent机制

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

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

Uevent 机制的基本流程如下:

  1. 内核事件生成

    • 当一个设备的状态发生变化时,内核中的设备模型(kobject)会触发一个 Uevent 事件。例如,当新硬件设备插入或设备被删除时,内核会调用 kobject_uevent() 函数生成一个 Uevent 事件。
  2. Uevent 事件格式

    • 生成的 Uevent 事件会携带有关设备的详细信息,通常以环境变量的形式表示。这些信息包括但不限于:
      • ACTION:设备操作类型(addremovechange
      • DEVNAME:设备名称(例如 /dev/sda
      • DEVPATH:设备在 /sys 中的路径
      • SUBSYSTEM:设备所属的内核子系统(如 blocknet
      • ID_*:设备的标识符(如 ID_VENDORID_MODEL

    例如,设备添加事件的 Uevent 消息可能是:

    
    
  • ACTION=add
    DEVPATH=/devices/pci0000:00/0000:00:1f.2
    DEVNAME=/dev/sda
    SUBSYSTEM=block
    
  • 用户空间处理 Uevent

    • 内核将 Uevent 事件通过 uevent 通知机制发送到用户空间。通常,udev(用户空间设备管理器)会监听这些事件并根据规则对设备进行处理。
    • udev 会根据 Uevent 中携带的信息执行相应的操作,如创建设备节点、加载驱动程序、设置设备文件权限等。
  • udev 处理 Uevent

    • udev 是 Linux 系统中用于设备管理的核心工具,它会监听内核发送的所有 Uevent 事件并执行规则。
    • 例如,udev 可以根据 Uevent 中的设备信息来决定:
      • 是否创建一个新的设备文件(例如 /dev/sda
      • 是否加载相应的内核模块
      • 是否设置设备的权限或属性
      • 是否触发用户定义的脚本或应用程序

 

三、 Uevent的内部逻辑解析

3.1、 Source Code位置

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

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

3.2、 数据结构描述

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

/*
 * 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,
	KOBJ_CHANGE,
	KOBJ_MOVE,
	KOBJ_ONLINE,
	KOBJ_OFFLINE,
	KOBJ_BIND,
	KOBJ_UNBIND,
};

以下是每个枚举值的含义:
1. KOBJ_ADD

    描述:表示一个新的 kobject 被添加到系统中,通常指设备或对象首次被创建或加入内核管理。
    应用场景:设备驱动程序加载时,内核会创建新的 kobject 来代表设备,并且会发出 KOBJ_ADD 事件。

2. KOBJ_REMOVE

    描述:表示一个已有的 kobject 被从系统中移除。
    应用场景:设备被卸载或删除时,内核会发出 KOBJ_REMOVE 事件来通知设备已经被移除。

3. KOBJ_CHANGE

    描述:表示一个已存在的 kobject 的属性发生了变化。
    应用场景:当设备的状态或其他属性发生变化时,内核会发出 KOBJ_CHANGE 事件。

4. KOBJ_MOVE

    描述:表示一个 kobject 被移动或重新定位。
    应用场景:某些内核对象可能需要在不同的父节点下重新排列。例如,设备从一个父设备(或父目录)迁移到另一个父设备时,内核会发出 KOBJ_MOVE 事件。

5. KOBJ_ONLINE

    描述:表示一个 kobject 变为在线状态。
    应用场景:设备或对象被激活或进入可用状态时,内核会发出 KOBJ_ONLINE 事件。例如,网络接口从 DOWN 状态变为 UP 时,可能会发出这个事件。

6. KOBJ_OFFLINE

    描述:表示一个 kobject 变为离线状态。
    应用场景:设备或对象被禁用或不再可用时,内核会发出 KOBJ_OFFLINE 事件。例如,设备进入关闭或未激活状态时,可能会发出这个事件。

7. KOBJ_BIND

    描述:表示一个 kobject 与某个资源或对象绑定。
    应用场景:设备或内核对象可能需要与其他资源(如驱动程序、特定设备等)建立绑定关系时,会发出 KOBJ_BIND 事件。

8. KOBJ_UNBIND

    描述:表示一个 kobject 与某个资源或对象解绑。
    应用场景:当设备或内核对象不再与某个资源(如驱动程序、设备等)绑定时,内核会发出 KOBJ_UNBIND 事件。

  • kobj_uevent_env

include/linux/kobject.h 

#define UEVENT_HELPER_PATH_LEN		256
#define UEVENT_NUM_ENVP			64	/* number of env pointers */
#define UEVENT_BUFFER_SIZE		2048	/* buffer for the variables */
 
struct kobj_uevent_env {
	char *argv[3];
	char *envp[UEVENT_NUM_ENVP];
	int envp_idx;
	char buf[UEVENT_BUFFER_SIZE];
	int buflen;
};

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

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

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

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

buflen,访问buf的变量。

  • kset_uevent_ops
 struct kset_uevent_ops {
	int (* const filter)(struct kset *kset, struct kobject *kobj);
	const char *(* const name)(struct kset *kset, struct kobject *kobj);
	int (* const uevent)(struct kset *kset, struct kobject *kobj,
		      struct kobj_uevent_env *env);
};

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.3、 内部动作

通过kobject.h,uevent模块提供了如下的APIinclude/linux/kobject.h

int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
			char *envp[]);
int kobject_synth_uevent(struct kobject *kobj, const char *buf, size_t count);

__printf(2, 3)
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
 
static int kobject_action_type(const char *buf, size_t count,
			       enum kobject_action *type,
			       const char **args)

lib/kobject_uevent.c

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 知道到该kobj从属的kset*/
	top_kobj = kobj;
	while (!top_kobj->kset && top_kobj->parent)
		top_kobj = top_kobj->parent;    /* 找的方法很简单,若它的kset不存在,则查找其父节点的kset是否存在,不存在则继续查找父父节点.... */
 
	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;        /* 找到与之相关的kset */
	uevent_ops = kset->uevent_ops;
 
	/* skip the event, if uevent_suppress is set*/
	if (kobj->uevent_suppress) {        /* uevent_suppress被置位,则忽略上报uevent */
		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)       /* 所属的筛选函数存在则筛选,返回0表示被筛掉了,不再上报 */
		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);    /* name函数存在,则使用kset返回的kset的name */
	else
		subsystem = kobject_name(&kset->kobj);       /* 否则用kset里kobj的name做kset的name */
	if (!subsystem) {        /* kset的name不存在,也不允许上报 */
		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);    /* 分配一个用于此次环境变量的buffer */
	if (!env)
		return -ENOMEM;
 
	/* complete object path */
	devpath = kobject_get_path(kobj, GFP_KERNEL);    /* 根据kobj得到它在sysfs中的路径 */
	if (!devpath) {
		retval = -ENOENT;
		goto exit;
	}
 
	/* default keys 添加当前要上报的行为,path,name到env的buffer中 */
	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) {    /* 自己传的外部环境变量要不为空,则解析并添加到env的buffer中 */
		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) {    /* 如果uevent_ops中的uevent存在,则调用该接口发送该kobj的env */
		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.
	 */
	if (action == KOBJ_ADD)    /* 如果action是add或remove的话要更新kobj中的state */
		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 */
    /* 每次发送一个事件,都要有它的事件号,该事件号不能重复,u64 uevent_seqnum,把它也作为环境变量添加到buffer最后面 */
	retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++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 */
		}
	}
#endif

exit:
	kfree(devpath);
	kfree(env);
	return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);

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

  • 查找kobj本身或者其parent是否从属于某个kset,如果不是,则报错返回(注2:由此可以说明,如果一个kobject没有加入kset,是不允许上报uevent的)
  • 查看kobj->uevent_suppress是否设置,如果设置,则忽略所有的uevent上报并返回(注3:由此可知,可以通过Kobject的uevent_suppress标志,管控Kobject的uevent的上报)
  • 如果所属的kset有uevent_ops->filter函数,则调用该函数,过滤此次上报(注4:这佐证了3.2小节有关filter接口的说明,kset可以通过filter接口过滤不希望上报的event,从而达到整体的管理效果)
  • 判断所属的kset是否有合法的名称(称作subsystem,和前期的内核版本有区别),否则不允许上报uevent
  • 分配一个用于此次上报的、存储环境变量的buffer(结果保存在env指针中),并获得该Kobject在sysfs中路径信息(用户空间软件需要依据该路径信息在sysfs中访问它)
  • 调用add_uevent_var接口(下面会介绍),将Action、路径信息、subsystem等信息,添加到env指针中
  • 如果传入的envp不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中
  • 如果所属的kset存在uevent_ops->uevent接口,调用该接口,添加kset统一的环境变量到env指针
  • 根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
  • 调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号
  • 以uevent_helper、subsystem以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehelper函数,上报uevent。 
    其中uevent_helper的内容是由内核配置项CONFIG_UEVENT_HELPER_PATH(位于./drivers/base/Kconfig)决定的(可参考lib/kobject_uevent.c, line 32),该配置项指定了一个用户空间程序(或者脚本),用于解析上报的uevent,例如"/sbin/hotplug”。 
    call_usermodehelper的作用,就是fork一个进程,以uevent为参数,执行uevent_helper。

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

 lib/kobject_uevent.c

 /**
 * kobject_uevent - notify userspace by sending an uevent
 *
 * @action: action that is happening
 * @kobj: struct kobject that the action is happening to
 *
 * 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);
}

add_uevent_var,以格式化字符的形式(类似printf、printk等),将环境变量copy到env指针中。

 lib/kobject_uevent.c

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

	va_start(args, format);
	len = vsnprintf(&env->buf[env->buflen],
			sizeof(env->buf) - env->buflen,
			format, args);
	va_end(args);

	if (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++] = &env->buf[env->buflen];
	env->buflen += len + 1;
	return 0;
}
EXPORT_SYMBOL_GPL(add_uevent_var);

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

 lib/kobject_uevent.c

/* the strings here must match the enum in include/linux/kobject.h */
static const char *kobject_actions[] = {
	[KOBJ_ADD] =		"add",
	[KOBJ_REMOVE] =		"remove",
	[KOBJ_CHANGE] =		"change",
	[KOBJ_MOVE] =		"move",
	[KOBJ_ONLINE] =		"online",
	[KOBJ_OFFLINE] =	"offline",
	[KOBJ_BIND] =		"bind",
	[KOBJ_UNBIND] =		"unbind",
};

/**
 * kobject_action_type - translate action string to numeric type
 *
 * @buf: buffer containing the action string, newline is ignored
 * @len: length of buffer
 * @type: pointer to the location to store the action type
 *
 * Returns 0 if the action string was recognized.
 */
static int kobject_action_type(const char *buf, size_t count,
			       enum kobject_action *type,
			       const char **args)
{
	enum kobject_action action;
	size_t count_first;
	const char *args_start;
	int ret = -EINVAL;
 

	if (count && (buf[count-1] == '\n' || buf[count-1] == '\0'))
		count--;

	if (!count)
		goto out;

	args_start = strnchr(buf, count, ' ');
	if (args_start) {
		count_first = args_start - buf;
		args_start = args_start + 1;
	} else
		count_first = count;

	for (action = 0; action < ARRAY_SIZE(kobject_actions); action++) {
		if (strncmp(kobject_actions[action], buf, count_first) != 0)
			continue;
		if (kobject_actions[action][count_first] != '\0')
			continue;
		if (args)
			*args = args_start;
		*type = action;    /* 比较成功则数字cation就是对应的action */
		ret = 0;
		break;
	}
out:
	return ret;
}

说明:怎么指定处理uevent的用户空间程序(简称uevent helper)? 

上面介绍kobject_uevent_env的内部动作时,有提到,Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper )处理该event。而该uevent helper的路径保存在uevent_helper数组中。 

 

char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;

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

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

要指定处理 uevent 的用户空间程序(即 uevent helper),你需要配置 /sys 文件系统中的相关设置。具体步骤如下:

  1. 设置 uevent helper: 可以通过向 /sys/class/net/<interface>/device/uevent_helper 文件写入指定程序的路径来设置 uevent helper。这个路径是一个可执行的程序,当内核触发 uevent 时,会调用这个程序。

    例如,假设要为某个网络接口设置 uevent helper:

  • echo "/path/to/your/uevent_helper" > /sys/class/net/eth0/device/uevent_helper
    
  • 全局设置 uevent helper: 你还可以设置一个全局的 uevent helper,这通常通过编辑 /etc/udev/udev.conf 配置文件来实现,文件中有一项是 UEVENT_HELPER,你可以设置为你想使用的程序路径:

  • UEVENT_HELPER="/path/to/your/uevent_helper"
    

    默认 udev 处理: 如果没有明确设置,udev 会自动处理所有的 uevent,udev 通常位于 /lib/udev//etc/udev/ 目录下,默认的 uevent 处理程序就是 udevd

这些方法允许你为特定设备或全局设备设置指定的 uevent 处理程序。

四、实例分析

Uevent 事件示例,展示了设备状态变化时内核发送的事件信息:

设备添加事件:
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1f.2
DEVNAME=/dev/sda
SUBSYSTEM=block
ID_VENDOR=ATA
ID_MODEL=Samsung_SSD
设备删除事件:
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:1f.2
DEVNAME=/dev/sda
SUBSYSTEM=block
设备属性变化事件:
ACTION=change
DEVPATH=/devices/pci0000:00/0000:00:1f.2
DEVNAME=/dev/sda
SUBSYSTEM=block
ID_MODEL=Samsung_SSD
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值