一、设备类相关知识
设备类是虚拟的,并没有直接相应的物理实物。仅仅是为了更好地管理同一类设备导出到用户空间而产生的文件夹和文件。整个过程涉及到sysfs文件系统,该文件系统是为了展示 linux 设备驱动模型而构建的文件系统,是基于 ram fs,linux根文件夹中的/sysfs即挂载了sysfs文件系统。
Struct kobject数据结构是sysfs的基础。kobject在sysfs中代表一个文件夹,而linux的驱动(struct driver)、设备(struct device)、设备类(struct class)均是从kobject进行派生的,因此他们在sysfs中都相应于一个文件夹。而数据结构中附属的struct device_attribu te 、driver_attribute、class_attribute等属性数据结构在sysfs中则代表一个普通的文件。
Struct kset是struct kobject的容器。即Struct kset能够成为同一类struct kobject的父亲,而其自身也有kobject成员。因此其又可能和其它kobject成为上一级kset的子成员。
本文无意对sysfs和linux设备驱动模型进行展开,以后再另写文章进行分析。
二、两种创建设备文件的方式
在设备驱动中cdev_add将struct file_opera ti ons和设备号注冊到系统后,为了可以自己主动产生驱动相应的设备文件。须要调用class_create和device_create,并通过uevent机制调用mdev(嵌入式linux由busybox提供)来调用mknod创建设备文件。当然也可以不调用这两个接口。那就手工通过命令行mknod来创建设备文件。
三、设备类和设备相关数据结构
1include/linux/kobject.h
struct kobject {
const char *name;//名称
struct list_head entry;//kobject链表
struct kobject *parent;//即所属kset的kobject
struct kset *kset;//所属kset
struct kobj_type *ktype;//属性操作接口
…
};
struct kset {
struct list_head list;//管理同属于kset的kobject
struct kobject kobj;//能够成为上一级父kset的子文件夹
const struct kset_uevent_ops *uevent_ops;//uevent处理接口
};
如果Kobject A代表一个文件夹,kset B代表几个文件夹(包含A)的共同的父文件夹。
则A.kset=B; A.parent=B.kobj.
2include/linux/device.h
struct class {//设备类
const char *name; //设备类名称
struct module *owner;//创建设备类的module
struct class_attribute *class_att rs ;//设备类属性
struct device_attribute *dev_attrs;//设备属性
struct kobject *dev_kobj;//kobject再sysfs中代表一个文件夹
….
struct class_private *p;//设备类得以注冊到系统的连接件
};
3drivers/base/base.h
struct class_private {
//该设备类相同是一个kset,包括以下的class_devices。同一时候在class_subsys填充父kset
struct kset class_subsys;
struct klist class_devices;//设备类包括的设备(kobject)
…
struct class *class;//指向设备类数据结构,即要创建的本级文件夹信息
};
4include/linux/device.h
struct device {//设备
struct device *parent;//sysfs/devices/中的父设备
struct device_private *p;//设备得以注冊到系统的连接件
struct kobject kobj;//设备文件夹
const char *init_name;//设备名称
struct bus_type *bus;//设备所属总线
struct device_driver *driver; //设备使用的驱动
struct klist_node knode_class;//连接到设备类的klist
struct class *class;//所属设备类
const struct attribute_group **groups;
…
}
5drivers/base/base.h
struct device_private {
struct klist klist_children;//连接子设备
struct klist_node knode_parent;//增加到父设备链表
struct klist_node knode_driver;//增加到驱动的设备链表
struct klist_node knode_bus;//增加到总线的链表
struct device *device;//相应设备结构
};
6解释
class_private是class的私有结构。class通过class_private注冊到系统中。device_private是device的私有结构,device通过device_private注冊到系统中。注冊到系统中也是将对应的数据结构增加到系统已经存在的链表中,可是这些链接的细节并不希望暴露给用户,也没有必要暴露出来,所以才有private的结构。
而class和device则通过sysfs向用户层提供信息。
四、创建设备类文件夹文件
1.在驱动通过cdev_add将struct file_opera TI ons接口集和设备注冊到系统后。即利用class_create接口来创建设备类文件夹文件。
led_class = class_create(THIS_MODULE, "led_class");
__class_create(owner, name, &__key);
cls->name = name;//设备类名
cls->owner = owner;//所属module
retval = __class_register(cls, key);
struct class_private *cp;
//将类的名字led_class赋值给相应的kset
kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name);
//填充class_subsys所属的父kset:ket:sysfs/class.
cp->class_subsys.kobj.kset = class_kset;
//填充class属性操作接口
cp->class_subsys.kobj.ktype = &class_ktype;
cp->class = cls;//通过cp能够找到class
cls->p = cp;//通过class能够找到cp
//创建led_class设备类文件夹
kset_register(&cp->class_subsys);
//在led_class文件夹创建class属性文件
add_class_attrs(class_get(cls))。
2.继续展开kset_register
kset_register(&cp->class_subsys);
kobject_add_internal(&k->kobj);
// parent即class_kset.kobj,即/sysfs/class相应的文件夹
parent = kobject_get(kobj->parent);
create_dir(kobj);
//创建一个led _class设备类文件夹
sysfs_create_dir(kobj);
//该接口是sysfs文件系统接口,代表创建一个文件夹,不再展开。
3.上述提到的class_kset在class_init被创建
class_kset = kset_create_and_add("class", NULL, NULL);
第三个传參为NULL。代表默认在/sysfs/创建class文件夹。
五、创建设备文件夹和设备属性文件
1.利用class_create接口来创建设备类文件夹文件后,再利用device_create接口来创建详细设备文件夹和设备属性文件。
led_device = device_create(led_class, NULL, led_devno, NULL, "led");
device_create_vargs
dev->devt = devt;//设备号
dev->class = class;//设备类led_class
dev->parent = parent;//父设备。这里是NULL
kobject_set_name_vargs(&dev->kobj, fmt, args)//设备名”led”
device_register(dev) 注冊设备
2.继续展开device_register(dev)
device_ini TI alize(dev);
dev->kobj.kset = devices_kset;//设备所属/sysfs/devices/
device_add(dev)
device_private_init(dev)//初始化device_private
dev_set_name(dev, "%s", dev->init_name);//赋值dev->kobject的名称
setup_parent(dev, parent);//建立device和父设备的kobject的联系
//kobject_add在/sysfs/devices/文件夹下创建设备文件夹led,kobject_add是和kset_register相似的接口。仅仅只是前者针对kobject。后者针对kset。
kobject_add(&dev->kobj, dev->kobj.parent, NULL);
kobject_add_varg
kobj->parent = parent;
kobject_add_internal(kobj)
create_dir(kobj);//创建设备文件夹
//在刚创建的/sysfs/devices/led文件夹下创建uevent属性文件,名称是”uevent”
device_create_file(dev, &uevent_attr);
//在刚创建的/sysfs/devices/led文件夹下创建dev属性文件,名称是”dev”。该属性文件的内容就是设备号
device_create_file(dev, &devt_attr);
//在/sysfs/class/led_class/文件夹下建立led设备的符号连接,所以打开/sysfs/class/led_class/led/文件夹也能看到dev属性文件,读出设备号。
device_add_class_symlinks(dev);
//创建device属性文件,包含设备所属总线的属性和attribute_group属性
device_add_attrs()
bus_add_device(dev) //将设备增加总线
//触发uevent机制,并通过调用mdev来创建设备文件。
kobject_uevent(&dev->kobj, KOBJ_ADD);
//匹配设备和总线的驱动,匹配成功就调用驱动的probe接口,不再展开
bus_probe_device(dev);
3.展开kobject_uevent(&dev->kobj, KOBJ_ADD);
kobject_uevent_env(kobj, ac TI on, NULL);
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops; //即device_uevent_ops
// subsystem即设备所属的设备类的名称”led_class”
subsystem = uevent_ops->name(kset, kobj);
//devpath即/sysfs/devices/led/
devpath = kobject_get_path(kobj, GFP_KERNEL);
//加入各种环境变量
add_uevent_var(env, "AC TI ON=%s", action_string);
add_uevent_var(env, "DEVPATH=%s", devpath);
add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
uevent_ops->uevent(kset, kobj, env);
add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));
add_uevent_var(env, " MI NOR=%u", MINOR(dev->devt));
add_uevent_var(env, "DEVNAME=%s", name);
add_uevent_var(env, "DEVTYPE=%s", dev->type->name);
//还会添加总线相关的一些属性环境变量等等。
#if defined(CONFIG_NET)//假设是PC的linux会通过socket的方式向应用层发送uevent事件消息,但在嵌入式linux中不启用该机制。
#endif
argv [0] = uevent_helper;//即/sbin/mdev
argv [1] = (char *)subsystem;//”led_class”
argv [2] = NULL;
add_uevent_var(env, "HOME=/");
add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
call_usermodehelper(argv[0], argv,env->envp, UMH_W AI T_EXEC);
4.上述提到的devices_kset在devices_init被创建
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
//第三个传參为NULL,代表默认在/sysfs/创建devices文件夹
5.上述设备属性文件
static struct device_attribute devt_attr =
__ATTR(dev, S_IRUGO, show_dev, NULL);
static ssize_t show_dev(struct device *dev, struct device_attribute *attr,char *buf){{
return print_dev_t(buf, dev->devt);//即返回设备的设备号
}
6.devices设备文件夹响应uevent事件的操作
static const struct kset_uevent_ops device_uevent_ops = {
.filter =dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
};
7.call_usermodehelper是从内核空间调用用户空间程序的接口。
8.对于嵌入式系统来说,busybox採用的是mdev,在系统启动脚本rcS中会使用命令
echo /sbin/mdev > /proc/sys/kernel/hotplug
uevent_helper[]数组即读入/proc/sys/kernel/hotplug文件的内容,即 “/sbin/mdev” .
六、创建设备文件
轮到mdev出场了,以上描写叙述都是在sysfs文件系统中创建文件夹或者文件,而应用程序訪问的设备文件则须要创建在/dev/文件夹下。该项工作由mdev完毕。
Mdev的原理是解释/etc/mdev.conf文件定义的命名设备文件的规则。并在该规则下依据环境变量的要求来创建设备文件。Mdev.conf由用户层指定,因此更具灵活性。本文无意展开对mdev配置脚本的分析。
Busybox/util-linux/mdev.c
int mdev_main(int argc UNUSED_PARAM, char **argv)
xc hdi r("/dev");
if (argv[1] && strcmp(argv[1], "-s")//系统启动时mdev –s才会运行这个分支
else
action = getenv("ACTION");
env_path = getenv("DEVPATH");
G.subsystem = getenv("SUBSYSTEM");
snprintf(temp, PATH_MAX, "/sys%s", env_path);//到/sysfs/devices/led文件夹
make_device(temp, /*delete:*/ 0);
strcpy(dev_maj_min, "/dev"); //读出dev属性文件。得到设备号
open_read_close(path, dev_maj_min + 1, 64);
….
mknod(node_name, rule->mode | type, makedev(major, minor))
终于我们会跟踪到mknod在/dev/文件夹下创建了设备文件。