一、設(shè)備類(lèi)相關(guān)知識(shí)
設(shè)備類(lèi)是虛擬的,并沒(méi)有直接相應(yīng)的物理實(shí)物。僅僅是為了更好地管理同一類(lèi)設(shè)備導(dǎo)出到用戶空間而產(chǎn)生的文件夾和文件。整個(gè)過(guò)程涉及到sysfs文件系統(tǒng),該文件系統(tǒng)是為了展示linux設(shè)備驅(qū)動(dòng)模型而構(gòu)建的文件系統(tǒng),是基于ramfs,linux根文件夾中的/sysfs即掛載了sysfs文件系統(tǒng)。
Struct kobject數(shù)據(jù)結(jié)構(gòu)是sysfs的基礎(chǔ)。kobject在sysfs中代表一個(gè)文件夾,而linux的驅(qū)動(dòng)(struct driver)、設(shè)備(struct device)、設(shè)備類(lèi)(struct class)均是從kobject進(jìn)行派生的,因此他們?cè)趕ysfs中都相應(yīng)于一個(gè)文件夾。而數(shù)據(jù)結(jié)構(gòu)中附屬的struct device_attribute、driver_attribute、class_attribute等屬性數(shù)據(jù)結(jié)構(gòu)在sysfs中則代表一個(gè)普通的文件。
Struct kset是struct kobject的容器。即Struct kset能夠成為同一類(lèi)struct kobject的父親,而其自身也有kobject成員。因此其又可能和其它kobject成為上一級(jí)kset的子成員。
本文無(wú)意對(duì)sysfs和linux設(shè)備驅(qū)動(dòng)模型進(jìn)行展開(kāi),以后再另寫(xiě)文章進(jìn)行分析。
二、兩種創(chuàng)建設(shè)備文件的方式
在設(shè)備驅(qū)動(dòng)中cdev_add將struct file_operations和設(shè)備號(hào)注冊(cè)到系統(tǒng)后,為了可以自己主動(dòng)產(chǎn)生驅(qū)動(dòng)相應(yīng)的設(shè)備文件。須要調(diào)用class_create和device_create,并通過(guò)uevent機(jī)制調(diào)用mdev(嵌入式linux由busybox提供)來(lái)調(diào)用mknod創(chuàng)建設(shè)備文件。當(dāng)然也可以不調(diào)用這兩個(gè)接口。那就手工通過(guò)命令行mknod來(lái)創(chuàng)建設(shè)備文件。
三、設(shè)備類(lèi)和設(shè)備相關(guān)數(shù)據(jù)結(jié)構(gòu)
1include/linux/kobject.h
struct kobject {
const char *name;//名稱(chēng)
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;//能夠成為上一級(jí)父kset的子文件夾
const struct kset_uevent_ops *uevent_ops;//uevent處理接口
};
如果Kobject A代表一個(gè)文件夾,kset B代表幾個(gè)文件夾(包含A)的共同的父文件夾。
則A.kset=B; A.parent=B.kobj.
2include/linux/device.h
struct class {//設(shè)備類(lèi)
const char *name; //設(shè)備類(lèi)名稱(chēng)
struct module *owner;//創(chuàng)建設(shè)備類(lèi)的module
struct class_attribute *class_attrs;//設(shè)備類(lèi)屬性
struct device_attribute *dev_attrs;//設(shè)備屬性
struct kobject *dev_kobj;//kobject再sysfs中代表一個(gè)文件夾
….
struct class_private *p;//設(shè)備類(lèi)得以注冊(cè)到系統(tǒng)的連接件
};
3drivers/base/base.h
struct class_private {
//該設(shè)備類(lèi)相同是一個(gè)kset,包括以下的class_devices。同一時(shí)候在class_subsys填充父kset
struct kset class_subsys;
struct klist class_devices;//設(shè)備類(lèi)包括的設(shè)備(kobject)
…
struct class *class;//指向設(shè)備類(lèi)數(shù)據(jù)結(jié)構(gòu),即要?jiǎng)?chuàng)建的本級(jí)文件夾信息
};
4include/linux/device.h
struct device {//設(shè)備
struct device *parent;//sysfs/devices/中的父設(shè)備
struct device_private *p;//設(shè)備得以注冊(cè)到系統(tǒng)的連接件
struct kobject kobj;//設(shè)備文件夾
const char *init_name;//設(shè)備名稱(chēng)
struct bus_type *bus;//設(shè)備所屬總線
struct device_driver *driver; //設(shè)備使用的驅(qū)動(dòng)
struct klist_node knode_class;//連接到設(shè)備類(lèi)的klist
struct class *class;//所屬設(shè)備類(lèi)
const struct attribute_group **groups;
…
}
5drivers/base/base.h
struct device_private {
struct klist klist_children;//連接子設(shè)備
struct klist_node knode_parent;//增加到父設(shè)備鏈表
struct klist_node knode_driver;//增加到驅(qū)動(dòng)的設(shè)備鏈表
struct klist_node knode_bus;//增加到總線的鏈表
struct device *device;//相應(yīng)設(shè)備結(jié)構(gòu)
};
6解釋
class_private是class的私有結(jié)構(gòu)。class通過(guò)class_private注冊(cè)到系統(tǒng)中。device_private是device的私有結(jié)構(gòu),device通過(guò)device_private注冊(cè)到系統(tǒng)中。注冊(cè)到系統(tǒng)中也是將對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)增加到系統(tǒng)已經(jīng)存在的鏈表中,可是這些鏈接的細(xì)節(jié)并不希望暴露給用戶,也沒(méi)有必要暴露出來(lái),所以才有private的結(jié)構(gòu)。
而class和device則通過(guò)sysfs向用戶層提供信息。
四、創(chuàng)建設(shè)備類(lèi)文件夾文件
1.在驅(qū)動(dòng)通過(guò)cdev_add將struct file_operations接口集和設(shè)備注冊(cè)到系統(tǒng)后。即利用class_create接口來(lái)創(chuàng)建設(shè)備類(lèi)文件夾文件。
led_class = class_create(THIS_MODULE, "led_class");
__class_create(owner, name, &__key);
cls->name = name;//設(shè)備類(lèi)名
cls->owner = owner;//所屬module
retval = __class_register(cls, key);
struct class_private *cp;
//將類(lèi)的名字led_class賦值給相應(yīng)的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;//通過(guò)cp能夠找到class
cls->p = cp;//通過(guò)class能夠找到cp
//創(chuàng)建led_class設(shè)備類(lèi)文件夾
kset_register(&cp->class_subsys);
//在led_class文件夾創(chuàng)建class屬性文件
add_class_attrs(class_get(cls))。
2.繼續(xù)展開(kāi)kset_register
kset_register(&cp->class_subsys);
kobject_add_internal(&k->kobj);
// parent即class_kset.kobj,即/sysfs/class相應(yīng)的文件夾
parent = kobject_get(kobj->parent);
create_dir(kobj);
//創(chuàng)建一個(gè)led _class設(shè)備類(lèi)文件夾
sysfs_create_dir(kobj);
//該接口是sysfs文件系統(tǒng)接口,代表創(chuàng)建一個(gè)文件夾,不再展開(kāi)。
3.上述提到的class_kset在class_init被創(chuàng)建
class_kset = kset_create_and_add("class", NULL, NULL);
第三個(gè)傳參為NULL。代表默認(rèn)在/sysfs/創(chuàng)建class文件夾。
五、創(chuàng)建設(shè)備文件夾和設(shè)備屬性文件
1.利用class_create接口來(lái)創(chuàng)建設(shè)備類(lèi)文件夾文件后,再利用device_create接口來(lái)創(chuàng)建詳細(xì)設(shè)備文件夾和設(shè)備屬性文件。
led_device = device_create(led_class, NULL, led_devno, NULL, "led");
device_create_vargs
dev->devt = devt;//設(shè)備號(hào)
dev->class = class;//設(shè)備類(lèi)led_class
dev->parent = parent;//父設(shè)備。這里是NULL
kobject_set_name_vargs(&dev->kobj, fmt, args)//設(shè)備名”led”
device_register(dev) 注冊(cè)設(shè)備
2.繼續(xù)展開(kāi)device_register(dev)
device_initialize(dev);
dev->kobj.kset = devices_kset;//設(shè)備所屬/sysfs/devices/
device_add(dev)
device_private_init(dev)//初始化device_private
dev_set_name(dev, "%s", dev->init_name);//賦值dev->kobject的名稱(chēng)
setup_parent(dev, parent);//建立device和父設(shè)備的kobject的聯(lián)系
//kobject_add在/sysfs/devices/文件夾下創(chuàng)建設(shè)備文件夾led,kobject_add是和kset_register相似的接口。僅僅只是前者針對(duì)kobject。后者針對(duì)kset。
kobject_add(&dev->kobj, dev->kobj.parent, NULL);
kobject_add_varg
kobj->parent = parent;
kobject_add_internal(kobj)
create_dir(kobj);//創(chuàng)建設(shè)備文件夾
//在剛創(chuàng)建的/sysfs/devices/led文件夾下創(chuàng)建uevent屬性文件,名稱(chēng)是”uevent”
device_create_file(dev, &uevent_attr);
//在剛創(chuàng)建的/sysfs/devices/led文件夾下創(chuàng)建dev屬性文件,名稱(chēng)是”dev”。該屬性文件的內(nèi)容就是設(shè)備號(hào)
device_create_file(dev, &devt_attr);
//在/sysfs/class/led_class/文件夾下建立led設(shè)備的符號(hào)連接,所以打開(kāi)/sysfs/class/led_class/led/文件夾也能看到dev屬性文件,讀出設(shè)備號(hào)。
device_add_class_symlinks(dev);
//創(chuàng)建device屬性文件,包含設(shè)備所屬總線的屬性和attribute_group屬性
device_add_attrs()
bus_add_device(dev) //將設(shè)備增加總線
//觸發(fā)uevent機(jī)制,并通過(guò)調(diào)用mdev來(lái)創(chuàng)建設(shè)備文件。
kobject_uevent(&dev->kobj, KOBJ_ADD);
//匹配設(shè)備和總線的驅(qū)動(dòng),匹配成功就調(diào)用驅(qū)動(dòng)的probe接口,不再展開(kāi)
bus_probe_device(dev);
3.展開(kāi)kobject_uevent(&dev->kobj, KOBJ_ADD);
kobject_uevent_env(kobj, action, NULL);
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops; //即device_uevent_ops
// subsystem即設(shè)備所屬的設(shè)備類(lèi)的名稱(chēng)”led_class”
subsystem = uevent_ops->name(kset, kobj);
//devpath即/sysfs/devices/led/
devpath = kobject_get_path(kobj, GFP_KERNEL);
//加入各種環(huán)境變量
add_uevent_var(env, "ACTION=%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, "MINOR=%u", MINOR(dev->devt));
add_uevent_var(env, "DEVNAME=%s", name);
add_uevent_var(env, "DEVTYPE=%s", dev->type->name);
//還會(huì)添加總線相關(guān)的一些屬性環(huán)境變量等等。
#if defined(CONFIG_NET)//假設(shè)是PC的linux會(huì)通過(guò)socket的方式向應(yīng)用層發(fā)送uevent事件消息,但在嵌入式linux中不啟用該機(jī)制。
#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_WAIT_EXEC);
4.上述提到的devices_kset在devices_init被創(chuàng)建
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
//第三個(gè)傳參為NULL,代表默認(rèn)在/sysfs/創(chuàng)建devices文件夾
5.上述設(shè)備屬性文件
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);//即返回設(shè)備的設(shè)備號(hào)
}
6.devices設(shè)備文件夾響應(yīng)uevent事件的操作
static const struct kset_uevent_ops device_uevent_ops = {
.filter =dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
};
7.call_usermodehelper是從內(nèi)核空間調(diào)用用戶空間程序的接口。
8.對(duì)于嵌入式系統(tǒng)來(lái)說(shuō),busybox採(cǎi)用的是mdev,在系統(tǒng)啟動(dòng)腳本rcS中會(huì)使用命令
echo /sbin/mdev > /proc/sys/kernel/hotplug
uevent_helper[]數(shù)組即讀入/proc/sys/kernel/hotplug文件的內(nèi)容,即 “/sbin/mdev” .
六、創(chuàng)建設(shè)備文件
輪到mdev出場(chǎng)了,以上描寫(xiě)敘述都是在sysfs文件系統(tǒng)中創(chuàng)建文件夾或者文件,而應(yīng)用程序訪問(wèn)的設(shè)備文件則須要?jiǎng)?chuàng)建在/dev/文件夾下。該項(xiàng)工作由mdev完畢。
Mdev的原理是解釋/etc/mdev.conf文件定義的命名設(shè)備文件的規(guī)則。并在該規(guī)則下依據(jù)環(huán)境變量的要求來(lái)創(chuàng)建設(shè)備文件。Mdev.conf由用戶層指定,因此更具靈活性。本文無(wú)意展開(kāi)對(duì)mdev配置腳本的分析。
Busybox/util-linux/mdev.c
int mdev_main(int argc UNUSED_PARAM, char **argv)
xchdir("/dev");
if (argv[1] && strcmp(argv[1], "-s")//系統(tǒng)啟動(dòng)時(shí)mdev –s才會(huì)運(yùn)行這個(gè)分支
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屬性文件。得到設(shè)備號(hào)
open_read_close(path, dev_maj_min + 1, 64);
….
mknod(node_name, rule->mode | type, makedev(major, minor))
終于我們會(huì)跟蹤到mknod在/dev/文件夾下創(chuàng)建了設(shè)備文件。
?
評(píng)論