Linux驱动之Kobject、Kset (二)uevent mdev

LDD3中说,Kobject的作用为:

1、sysfs 表述:在 sysfs 中出现的每个对象都对应一个 kobject, 它和内核交互来创建它的可见表述。

2、热插拔事件处理 :kobject 子系统将产生的热插拔事件通知用户空间。

3、数据结构关联:整体来看, 设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。kobject 实现了该结构并将其聚合在一起。

其中,第一条已经在前一篇文章中介绍过了,如果不了解请移驾 http://blog.csdn.net/lizuobin2/article/details/51523693

此文,将从设备总线驱动模型里的设备注册过程, device_register函数入手,分析kobject、kset 在设备这一层面的体系结构,同时主要是分析uevent机制以及 mdev 如何自动创建设备节点,实现自己想要的一些功能,比如U盘自动挂载。

整个设备的起源,应该是/drives/base/core.c 在这里实现了一系列函数,并导出供我们使用。

EXPORT_SYMBOL_GPL(device_for_each_child);

EXPORT_SYMBOL_GPL(device_find_child);

EXPORT_SYMBOL_GPL(device_initialize);

EXPORT_SYMBOL_GPL(device_add);

EXPORT_SYMBOL_GPL(device_register);

EXPORT_SYMBOL_GPL(device_del);

EXPORT_SYMBOL_GPL(device_unregister);

EXPORT_SYMBOL_GPL(get_device);

EXPORT_SYMBOL_GPL(put_device);

EXPORT_SYMBOL_GPL(device_create_file);

EXPORT_SYMBOL_GPL(device_remove_file);

在内核 do_base_setup 初始化的过程中调用driver_init函数,间接调用device_init函数,我们先来看看device_init函数。

int __init devices_init(void)

{

devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);

……

}

创建kset 并 add 到内核里去,它的名字是devices,parent==NULL,devices_kset 对应于/sys/devices目录,device_uevent_ops后面分析。

在设备总线驱动模型中,我们要构造一个 device 结构对象,设置它所属的总线(i2c_bus_type、platform_bus_type…),然后将它注册到内核中去,其中都避免不了调用 device_register 函数。现在我们来看 device_register

int device_register(struct device *dev)

{

device_initialize(dev);

//dev.devt = MKDEV(xxx, yyyy);  // 有些时候会提供设备的 主次设备号

return device_add(dev);

......

}

void device_initialize(struct device *dev)

{

dev->kobj.kset = devices_kset;   // 将设备的 kset 成员指向 devices_kset

kobject_init(&dev->kobj, &device_ktype);   // 初始化 设备的 kobject 成员,并设置它的 Ktype 为 device_ktype,并没有add

......

}

int device_add(struct device *dev)

{

// 将 dev 的 kobject 成员链入 device_kset 链表,并在/sys/devices/目录下创建子目录

error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);

// 创建属性 dev 文件 /sys/devices/xxx/dev ,后边我们会知道 cat dev 会得到设备号,供 mdev 来创建设备节点

if (MAJOR(dev->devt)) {

error = device_create_file(dev, &devt_attr); // 值得一看

error = device_create_sys_dev_entry(dev);

}

// 上报一个 KOBJ_ADD 事件

kobject_uevent(&dev->kobj, KOBJ_ADD);

}

至此,我们可以发现,今后每一个创建 device ,只要你调用 device_register ,device 的 Kobject 都将链入device_kset 链表,然后通过contain_of 函数,就可以实现对 device 的访问。也就是说 kobject 往往是嵌入在其他模块中,通过Kobject、kset之前的关系,实现对更大的模块的关系管理。 也就是我们说的 Kobject 作用的第三条。

现在我们来看看 kobject_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[])

{

/* 如果kobject 不属于一个Kset,则向上查找到一个 属于一个kset的kobject为止 */

top_kobj = kobj;

while (!top_kobj->kset && top_kobj->parent)

top_kobj = top_kobj->parent;

kset = top_kobj->kset;    // 找到 最接近的 kset,这里就是device_kset

uevent_ops = kset->uevent_ops;    // 获取 uevent_ops == dev_uevent

// 如果 uevent_suppress 被设置 则屏蔽 uevent

// 如果设置了 filter 则用 filter 过滤事件,稍后我们会看,只要设置了 bus 或 class 的device 都会通过

// 调用name函数得到subsystem的名字;否则,subsystem的名字是kset中kobject的名字

if (uevent_ops && uevent_ops->name)

subsystem = uevent_ops->name(kset, kobj);

else

subsystem = kobject_name(&kset->kobj);

/*  申请env内存 */

env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);

/* 获取Path 也就是kobj的路径 /sys/devices/xxx */

devpath = kobject_get_path(kobj, GFP_KERNEL);

/*  设置环境变量 */

retval = add_uevent_var(env, "ACTION=%s", action_string);  // KOBJ_ADD

retval = add_uevent_var(env, "DEVPATH=%s", devpath);  // 路径

retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);   // 子系统的名字

// 如果 uevent_ops->uevent 存在则调用,显然存在,后面分析。

if (uevent_ops && uevent_ops->uevent) {

retval = uevent_ops->uevent(kset, kobj, env);

}

retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);

// uevent_helper[0] == /sbin/mdev     这个是通过 /etc/ini.d/rcS 指定的

if (uevent_helper[0]) {

char *argv [3];

argv [0] = uevent_helper;

argv [1] = (char *)subsystem;

argv [2] = NULL;

retval = add_uevent_var(env, "HOME=/");

retval = add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");

// 调用用户空间程序,程序名 argv[0], 并把环境变量当作参数传递过去

retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);

}

}

显然 uevent 的机制,就是设置环境变量,然后调用用户空间程序 mdev 进行更新设备。

上面多次使用到了device_kset->uevent_fops,是时候来看看了(并没有什么卵用)

static struct kset_uevent_ops device_uevent_ops = {

.filter =        dev_uevent_filter, // 只要设置了bus or class 就不会过滤

.name =      dev_uevent_name, // 返回bus or class的name

.uevent =    dev_uevent,  // 设置主次设备号的环境变量

};

    // 如果设置了总线 或 类 返回1

static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)

{

struct kobj_type *ktype = get_ktype(kobj);

if (ktype == &device_ktype) {

struct device *dev = to_dev(kobj);

if (dev->bus)

return 1;

if (dev->class)

return 1;

}

}

    // 返回 Bus  或 类的名字

static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)

{

struct device *dev = to_dev(kobj);

if (dev->bus)

return dev->bus->name;

if (dev->class)

return dev->class->name;

}

static int dev_uevent(struct kset *kset, struct kobject *kobj,

struct kobj_uevent_env *env)

{

if (MAJOR(dev->devt)) {

add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));// 在环境变量中设置主次设备号

add_uevent_var(env, "MINOR=%u", MINOR(dev->devt)); // 然而mdev并不是从这里读取的

name = device_get_devnode(dev, &mode, &tmp);

if (name) {

add_uevent_var(env, "DEVNAME=%s", name);

if (mode)

add_uevent_var(env, "DEVMODE=%#o", mode & 0777);

}

}

return retval;

}

还有一个重点,kobject_init(&dev->kobj, &device_ktype)需要注意。

static struct kobj_type device_ktype = {

.release    = device_release,

.sysfs_ops    = &dev_sysfs_ops, // 它有必要 看一看

};

static struct sysfs_ops dev_sysfs_ops = {

. show    = dev_attr_show, // 这里仅指定了show store函数,并没有指定 属性文件

.store    = dev_attr_store,

};

指定属性文件 是在 error = device_create_file(dev, &devt_attr);

int device_create_file(struct device *dev, struct device_attribute *attr)

{

error = sysfs_create_file(&dev->kobj, &attr->attr);

}

devt_attr 定义 在 static struct device_attribute devt_attr =  __ATTR(dev, S_IRUGO, show_dev, NULL);

#define __ATTR(_name,_mode,_show,_store) { \

.attr = {.name = __stringify(_name), .mode = _mode },    \

.show    = _show,                    \

.store    = _store,                    \

}

将宏展开:

static struct device_attribute devt_attr = {

.attr = {.name = __stringify(dev),.mode = S_IRUGO},

.show    = show_dev,   // 就一行 return print_dev_t(buf, dev->devt) 返回dev的设备号

.store    = NULL,

}

真正的属性文件是:attr = {.name = __stringify(dev),.mode = S_IRUGO},我们在用户空间cat dev的时候调用的是Kobject->ktye->show,也就是:

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,

char *buf)

{

struct device_attribute *dev_attr = to_dev_attr(attr); // 转换成上边devt_attr 的结构类型

struct device *dev = to_dev(kobj);

if (dev_attr->show)    // 调用 show_dev 传递 主次设备号

ret = dev_attr->show(dev, dev_attr, buf);

}

真是大费周章~~

可以总结一下上面的工作了

    1、每一个 device 的kobkect都将被链入kset 链表

    2、每一个 device 的kobkect 在/sys/device/目录下创建子目录

    3、每一个 device 的kobkect 在/sys/device/$(name)/目录下创建属性文件 dev

    4、每一个 device 的kobkect 的ktype 都被设置为device_ktype,通过 show 可以访问到device的主次设备号

    5、将 device 的 kobject 的PATH 、name、主次设备号等等设置到环境变量nev->nevp里

    6、调用用户空间 mdev

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    mdev 是啥,mdev 可以说是udev的精简版,在 busybox 制作文件系统的时候被编译进去,它主要的工作就是根据/sys 目录的信息来帮助我们自动创建设备节点,更详细的概念请自行百度。向要搞清,mdev 创建设备几点的过程,那只能看Busybox的源码了。。

附上一个我做实验时打印出来的环境变量

env[0] ACTION=add

env[1] DEVPATH=/devices/platform/myled

env[2] SUBSYSTEM=platform

env[3] MAJOR=251

env[4] MINOR=0

env[5] DEVNAME=myled

env[6] MODALIAS=platform:myled

env[7] SEQNUM=642

env[8] HOME=/

env[9] PATH=/sbin:/bin:/usr/sbin:/usr/bin

现在我们来看看,从内核空间调用的这个用户程序mdev

int mdev_main(int argc, char **argv)

{

// mdev -s 开机扫描/sys 目录创建设备节点,这里不分析

if (argc == 2 && !strcmp(argv[1],"-s")) {

......

} else {

action = getenv("ACTION");    // 获得action 就是add or remove

env_path = getenv("DEVPATH"); // 获得DEVPATH

if (!action || !env_path)

bb_show_usage();

sprintf(temp, "/sys%s", env_path);   // /sys+DEVPATH 比如/sys/devices/xxx

if (!strcmp(action, "remove"))  // 移除 dev

make_device(temp, 1);

else if (!strcmp(action, "add")) { // 增加 dev

make_device(temp, 0);

}

}

}

static void make_device(char *path, int delete)

{

// 获取主次设备号,看看是如何获取的

if (!delete) {

// 在path 后边 + “/dev”  那么path == /sys/devices/xxx/dev

strcat(path, "/dev");

len = open_read_close(path, temp + 1, 64);
//读dev 我们前边说过了,这里会调用show 传递主次设备号~

*temp++ = 0;

if (len < 1) return;

}

 // 获得设备名字,根据最后一个"/"

device_name = bb_basename(path);

// 根据 path 的第五个字符来判断设备类型,如果是在/sys/class 目录的话 就是字符设备,其他的都是块设备

type = path[5]==‘c‘ ? S_IFCHR : S_IFBLK;

// 如果 /etc/mdev.conf 有这个配置文件的话,根据配置文件的规则来 创建设备节点 并执行一些命令

if (ENABLE_FEATURE_MDEV_CONF) {

// 这个不如直接来看 mdev.conf 来得实在

fd = open("/etc/mdev.conf", O_RDONLY);

......

}

if (!delete) {

if (sscanf(temp, "%d:%d", &major, &minor) != 2) return;

// mknod 创建设备节点

if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)

bb_perror_msg_and_die("mknod %s", device_name);

}

}

关于 /etc/mdev.conf 真是太有用处了 ,附上韦东山老师 uevent 的文档,我就不卖弄了。

---------------------------------------------------------------------------------------------------------------------------------------------------------------

我接上U盘,想自动挂载,怎么办?
	mdev.conf的格式:
	<device regex> <uid>:<gid> <octal permissions> [<@|$|*> <command>]

	device regex:正则表达式,表示哪一个设备
	uid: owner
	gid: 组ID
	octal permissions:以八进制表示的属性
	@:创建设备节点之后执行命令
	$:删除设备节点之前执行命令
	*: 创建设备节点之后 和 删除设备节点之前 执行命令
	command:要执行的命令
<span style="white-space:pre">	// 韦东山老师写了个驱动,有 led led1 led2 led3 这四个设备</span>
	写mdev.conf
	1.
		leds 0:0 777
		led1 0:0 777
		led2 0:0 777
		led3 0:0 777

	2.
		leds?[123]? 0:0 777

	3.
		leds?[123]? 0:0 777 @ echo create /dev/$MDEV > /dev/console

	4.
		leds?[123]? 0:0 777 * if [ $ACTION = "add" ]; then echo create /dev/$MDEV > /dev/console; else echo remove /dev/$MDEV > /dev/console; fi

	5.
		leds?[123]? 0:0 777 * /bin/add_remove_led.sh

	把命令写入一个脚本:
		add_remove_led.sh

		#!/bin/sh
		if [ $ACTION = "add" ];
		then
			echo create /dev/$MDEV > /dev/console;
		else
			echo remove /dev/$MDEV > /dev/console;
		fi

	6. U盘自动加载
		sda[1-9]+ 0:0 777 * if [ $ACTION = "add" ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi

	7.
		sda[1-9]+ 0:0 777 * /bin/add_remove_udisk.sh

		add_remove_udisk.sh
		#!/bin/sh
		if [ $ACTION = "add" ];
		then
			mount /dev/$MDEV /mnt;
		else
			umount /mnt;
		fi

-------------------------------------------------------------------------------------------------------------------------------------------

附上一个 我做实验的代码,仅供参考,基于Linux2.6.32.2内核

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <linux/input.h>
#include <linux/platform_device.h>
// 设备资源
static struct resource led_resource[] = {	//jz2440的参数,驱动未测试
	[0] = {
		.start = 0x56000010,
		.end   = 0x56000010 + 8 - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = 5,
		.end   = 5,
		.flags = IORESOURCE_IRQ,
	},
};

static void led_release(struct device *dev){

}

// 创建一个设备
static struct platform_device led_dev = {
	.name = "myled",	//设备名字 与 驱动相匹配
	.id	  = -1,
	.num_resources = ARRAY_SIZE(led_resource),
	.resource = led_resource,

	.dev = {
		.release = led_release,
		.devt = MKDEV(252, 1),
	},
};

static int led_dev_init(void){

	//向bus注册led_dev match drv链表进行配对
	platform_device_register(&led_dev);
	return 0;
}

static void led_dev_exit(void){
	platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

打印出来的环境变量:

env[0] ACTION=add

env[1] DEVPATH=/devices/platform/myled

env[2] SUBSYSTEM=platform

env[3] MAJOR=251

env[4] MINOR=0

env[5] DEVNAME=myled

env[6] MODALIAS=platform:myled

env[7] SEQNUM=642

env[8] HOME=/

env[9] PATH=/sbin:/bin:/usr/sbin:/usr/bin

我们创建了 dev 并设置它的主次设备号,mdev 就自动为我们创建设备节点了~

最后,我们再来回顾一下整个流程~

kobject:

    1、每一个 device 的kobkect都将被链入kset 链表

    2、每一个 device 的kobkect 在/sys/device/目录下创建子目录

    3、每一个 device 的kobkect 在/sys/device/$(name)/目录下创建属性文件 dev

    4、每一个 device 的kobkect 的ktype 都被设置为device_ktype,通过 show 可以访问到device的主次设备号

uevent:

    5、将 device 的 kobject 的PATH 、name、主次设备号等等设置到环境变量nev->nevp里

    6、调用用户空间 mdev

mdev:

    7、读取环境变量,创建设备节点~

    至此,我们应该对kobject的作用有了一个全面的了解。写的不好,还请多批评指正。

时间: 2024-08-01 15:19:18

Linux驱动之Kobject、Kset (二)uevent mdev的相关文章

嵌入式Linux驱动学习之路(二十三)NAND FLASH驱动程序

NAND FLASH是一个存储芯片. 在芯片上的DATA0-DATA7上既能传输数据也能传输地址. 当ALE为高电平时传输的是地址. 当CLE为高电平时传输的是命令. 当ALE和CLE都为低电平时传输的是数据. 将数据发给nand Flash后,在发送第二次数据之前还要判断芯片是否处于空闲状态.一般是通过引脚RnB来判断,一般是高电平代表就绪,低电平代表正忙. 操作Nand Flash的一般步骤是: 1. 发命令 选中芯片 CLE设置为高电平 在DATA0-DATA7上输出命令值 发出一个写脉冲

嵌入式Linux驱动学习之路(二十六)DM9000C网卡驱动程序

基于DM9000C的原厂代码修改dm9000c的驱动程序. 首先确认内存的基地址 iobase. 确定中断号码. 打开模块的初始化函数定义. 配置内存控制器的相应时序(结合DM9000C.C的手册). 程序代码: /* dm9ks.c: Version 2.08 2007/02/12 A Davicom DM9000/DM9010 ISA NIC fast Ethernet driver for Linux. This program is free software; you can redi

两篇让我理解linux驱动的文章及我的精练总结

第一篇 转载自csdn vipclx 编写Linux驱动八步骤 一.建立Linux驱动框架(装载.卸载Linux驱动) Linux内核在使用驱动时首先要装载驱动,在装载过程中进行一些初始化动作(建立设备文件.分配内存等),在驱动程序中需提供相应函数来处理驱动初始化工作,该函数须使用module_init宏指定:Linux系统在退出是需卸载Linux驱动,卸载过程中进行一些退出工作(删除设备文件.释放内存等),在驱动程序中需提供相应函数来处理退出工作,该函数须使用module_exit宏指定.Li

浅析Linux驱动模型中的底层数据结构kobject和kset

1.kobject Linux内核用kobject来表示一个内核对象.它和Sysfs文件系统联系密切,在内核中注册到系统中的每个kobject对象在sysfs文件系统中对对应着一个文件目录.kobject数据结构通常的用法是嵌入到其对他的数据结构中(即容器,比如cdev结构),用于实现内核对该类数据结构对象的管理.这些数据结构(容器)通过kobject连接起来,形成了一个树状结构. 它在源码中的定义为: /*<include/linux/kobject.h>*/ struct kobject

Linux驱动开发(二)—DMA的使用(一)

1 DMA概念 DMA顾名思义就是指设备和内存之间.内存和外部存储设备之间进行直接的数据读写操作,而不需要CPU的参与. 2 DMA原理 DMA传输需要由DMA控制器DMAC进行,当需要进行DMA传输的时候,DMA控制器会发出占用总线的请求,当CPU响应DMA的请求时,暂时放弃对总线的控制权,当DMA传输结束的时候,DMAC会向I/O接口发出结束命令,并将总线控制权交还给CPU.一个完整的DMA传输过程必须经过DMA请求.DMA响应.DMA传输.DMA结束4个步骤. 3 DMA传输过程 4 DM

Linux 驱动模型初探1——BUS

##写在前面的话## 这几篇文章是2011年,当时的老大对我提出的一个"作业".当时研究了一把,完成了第一篇BUS,老大看过之后,表示满意,要我把后面继续完成.然,世事变迁,老大离开了公司,去了其它公司.之后,我也从S公司离开了.所做的工作也有小范围的调整.近期又回到驱动这块,再看到之前的笔记,感慨万千,我决计是要完成搁浅了近3年"作业". 测试代码我已经提交到github上:https://github.com/koffuxu/kornel/tree/dev/dr

Linux 驱动开发索引

1.嵌入开发环境搭建 Telnet 在 mini2440 上的移植 Opencv-2.4.9 在 mini2440 上的移植 搭建嵌入式开发环境总结 2.Linux 设备驱动 Linux 驱动程序头文件 一步一步学习Linux驱动之驱动模块MakeFile解析 一步一步学习 Linux 驱动之(Kconfig.Makefile) 一步一步学习 Linux 驱动之字符设备 LED 静态编译进 Linux 内核 内核怎么通过主设备号找驱动.次设备号找设备 Linux 驱动之内核空间分配内存 一步一步

Linux 驱动模型初探3——device

讲device之前,我要引入一个比喻,这个比喻来自一个学长(z2007b).driver是帅哥,device是美女,bus是红娘,bus是提供device和driver配对的场所(方法?).好吧,暂时先这样定,现在要讲的就是美女. 1,老规则,先看看struce device这个美女有哪些特性(成员)和方法 struct device { struct device *parent; struct device_private*p; struct kobject kobj; const char

【Linux驱动】自动创建设备节点

开始学习驱动的时候,是将驱动程序编译成模块然后用mknod命令手动建立设备节点以提供给应用程序调用.这对于刚开始调试驱动程序的时候常用的一种方法.但是,当有种需要必须在系统启动的时候就将驱动程序就绪,来供应用层程序调用.这时就不能再手动的建立设备节点了,而必须自动的创建设备节点(不需要人为的操作). ★注册类 注册类的目的是为了使mdev可以在/dev/目录下建立设备节点. 首先要定义一个类,利用struct class结构体.这个结构体定义在头文件include/linux/device.h中