105 uevent:内核消息的快递包

本文介绍Linux内核中uevent机制的工作原理及应用实例。详细解释了kobject_uevent函数如何发送消息到用户空间,以及mdev/udev如何监听并处理这些消息。通过一个具体的驱动程序示例展示了uevent的使用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

uevent机制

kobject对象可以通过uevent机制往用户空间发送信息

在这里插入图片描述

  • kobject_uevent:内核消息发送接口

    • 广播方式发送
  • NETLINK:特殊的网络通信,本地主机使用。mdev / udev 负责监控此连接,有消息就获取。

    • 传统做法是内核执行 hotplug 程序 进行消息通知(效率低、不优雅)
  • udev/mdev:用户空间守护进程,监听广播信息

    • 默认开机启动,systemd
    • udevadm monitor:打印uevent事件。
      shell下输入此指令,udev会将其收到的广播包打印出来。
内核消息发送
消息类型
enum kobject_action {
	/* 添加一个 kobject 对象 */
	KOBJ_ADD,
	/* 删除一个 kobject 对象 */
	KOBJ_REMOVE,
	/* 改变一个 kobject 对象 */
	KOBJ_CHANGE,
	/* 移动一个 kobject 对象 */
	KOBJ_MOVE,
	KOBJ_ONLINE,
	KOBJ_OFFLINE,
	KOBJ_BIND,
	KOBJ_UNBIND,
	KOBJ_MAX
};
kobject_uevent()函数
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
	return kobject_uevent_env(kobj, action, NULL);
}
kobject_uevent_env()函数
int kobject_uevent_env(struct kobject *kobj, 
						enum kobject_action action,
		       			char *envp_ext[])
{
	struct kobj_uevent_env *env;
	...
	top_kobj = kobj;
	/* while 循坏查找 kobj 所隶属的最顶层 kobject 或者 kset 指针不为空的 kobj */
	while (!top_kobj->kset && top_kobj->parent)
		top_kobj = top_kobj->parent;
	/* 判断kobj的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;
	}
	/* 得到 kobj 指向的 kset 对象 */
	kset = top_kobj->kset;
	/* 获取 kset 的 uevent_ops */
	uevent_ops = kset->uevent_ops;
	/* 若kobject->uevent_suppress为1,表示kobj不适用uevent。
	 * 即不允许这个 kobject 发送 uevent 事件。
	 */
	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;
	}
	/* 过滤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;
		}
	...
	/* environment buffer */
	env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
	if (!env)
		return -ENOMEM;

	/* 获取kobj在sysfs中的路径 */
	devpath = kobject_get_path(kobj, GFP_KERNEL);
	if (!devpath) {
		retval = -ENOENT;
		goto exit;
	}
	
	/* 经过上面的一系列判断,下面开始正式发送消息到用户空间 */
	/* 向 env 结构体里面填充消息内容 */
	/* 1、填充消息具体类型 */
	retval = add_uevent_var(env, "ACTION=%s", action_string);
	if (retval)
		goto exit;
	/* kobject 在 sysfs 中的路径 */
	retval = add_uevent_var(env, "DEVPATH=%s", devpath);
	if (retval)
		goto exit;
	/* 当前 kobject 的上一层节点 */
	retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
	if (retval)
		goto exit;

	...
	
	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;
		}
	}
	...
	/* 本地socket通信,发送广播信息 */
	retval = kobject_uevent_net_broadcast(kobj, env, action_string,
					      devpath);
	...
 }

驱动源码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>


/* GPIO虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

static struct kset *example_kset;
static struct kobject *led_kobj;

static int foo;

static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
			char *buf)
{
	return sprintf(buf, "%d\n", foo);
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
			 const char *buf, size_t count)
{
	int ret;

	ret = kstrtoint(buf, 10, &foo);
	if (ret < 0)
		return ret;

	return count;
}

static struct kobj_attribute foo_attribute =
	__ATTR(foo, 0664, foo_show, foo_store);


static ssize_t led_show(struct kobject *kobj, struct kobj_attribute *attr,
		      char *buf)
{
	int var;

	if (strcmp(attr->attr.name, "led") == 0)
			var =123;

	return sprintf(buf, "%d\n", var);
}

static ssize_t led_store(struct kobject *kobj, struct kobj_attribute *attr,
		       const char *buf, size_t count)
{

	if (strcmp(attr->attr.name, "led") == 0){
		if(!memcmp(buf,"on",2)) {	
			iowrite32(0 << 4, GPIO1_DR);	
		} else if(!memcmp(buf,"off",3)) {
			iowrite32(1 << 4, GPIO1_DR);
		}
	}
	return count;
}

static struct kobj_attribute led_attribute =
	__ATTR(led, 0664, led_show, led_store);

static struct attribute *attrs[] = {
	&foo_attribute.attr,
	&led_attribute.attr,
	NULL,	/* need to NULL terminate the list of attributes */
};

static struct attribute_group attr_group = {
	.attrs = attrs,
};

static int __init led_init(void)
{
	int retval;

	/* GPIO相关寄存器映射 */
  	IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);
	SW_MUX_GPIO1_IO03 = ioremap(0x20e006c, 4);
  	SW_PAD_GPIO1_IO03 = ioremap(0x20e02f8, 4);
	GPIO1_GDIR = ioremap(0x0209c004, 4);
	GPIO1_DR = ioremap(0x0209c000, 4);


	/* 使能GPIO1时钟 */
	iowrite32(0xffffffff, IMX6U_CCM_CCGR1);

	/* 设置GPIO1_IO03复用为普通GPIO*/
	iowrite32(5, SW_MUX_GPIO1_IO03);
	
    /*设置GPIO属性*/
	iowrite32(0x10B0, SW_PAD_GPIO1_IO03);

	/* 设置GPIO1_IO03为输出功能 */
	iowrite32(1 << 3, GPIO1_GDIR);

	/* LED输出高电平 */
	iowrite32(1<< 3, GPIO1_DR);

	/*创建一个kset对象*/
	example_kset = kset_create_and_add("kset_example", NULL, NULL);
	if (!example_kset)
		return -ENOMEM;

	/* 创建一个kobject对象*/
	led_kobj = kobject_create_and_add("led_kobject", &example_kset->kobj);

	led_kobj->kset = example_kset;
	if (!led_kobj)
		return -ENOMEM;

	/* 为kobject设置属性文件 */
	retval = sysfs_create_group(led_kobj, &attr_group);
	if (retval)
		kobject_put(led_kobj);

	/* 让此 kobject 发送一个 KOBJ_ADD 事件。
	 * 注意一定要设置此 kobject 的 kset,不能让其 kset 为空。
	 * 否则 kobject_uevent 提前判断异常退出。
	 */
	kobject_uevent(led_kobj, KOBJ_ADD);

	return retval;

	return 0;
}

static void __exit led_exit(void)
{
	/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* 注销字符设备驱动 */
	kobject_put(led_kobj);

	/*取消字符设备的注册*/
	kset_unregister(example_kset);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("led_module");
MODULE_ALIAS("led_module");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值