LDD3中說,Kobject的作用為:
1、sysfs 表述:在 sysfs 中出現(xiàn)的每一個(gè)對象都對應(yīng)1個(gè) kobject, 它和內(nèi)核交互來創(chuàng)建它的可見表述。
2、熱插拔事件處理 :kobject 子系統(tǒng)將產(chǎn)生的熱插拔事件通知用戶空間。
3、數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián):整體來看, 裝備模型是1個(gè)極端復(fù)雜的數(shù)據(jù)結(jié)構(gòu),通過其間的大量鏈接而構(gòu)成1個(gè)多層次的體系結(jié)構(gòu)。kobject 實(shí)現(xiàn)了該結(jié)構(gòu)并將其聚合在1起。
其中,第1條已在前1篇文章中介紹過了,如果不了解請移駕 http://blog.csdn.net/lizuobin2/article/details/51523693
此文,將從裝備總線驅(qū)動模型里的裝備注冊進(jìn)程, device_register函數(shù)入手,分析kobject、kset 在裝備這1層面的體系結(jié)構(gòu),同時(shí)主要是分析uevent機(jī)制和 mdev 如何自動創(chuàng)建裝備節(jié)點(diǎn),實(shí)現(xiàn)自己想要的1些功能,比如U盤自動掛載。
全部裝備的起源,應(yīng)當(dāng)是/drives/base/core.c 在這里實(shí)現(xiàn)了1系列函數(shù),并導(dǎo)出供我們使用。
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);
在內(nèi)核 do_base_setup 初始化的進(jìn)程中調(diào)用driver_init函數(shù),間接調(diào)用device_init函數(shù),我們先來看看device_init函數(shù)。
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
……
}
創(chuàng)建kset 并 add 到內(nèi)核里去,它的名字是devices,parent==NULL,devices_kset 對應(yīng)于/sys/devices目錄,device_uevent_ops后面分析。
在裝備總線驅(qū)動模型中,我們要構(gòu)造1個(gè) device 結(jié)構(gòu)對象,設(shè)置它所屬的總線(i2c_bus_type、platform_bus_type…),然后將它注冊到內(nèi)核中去,其中都避免不了調(diào)用 device_register 函數(shù)。現(xiàn)在我們來看 device_register
int device_register(struct device *dev)
{
device_initialize(dev);
//dev.devt = MKDEV(xxx, yyyy); // 有些時(shí)候會提供裝備的 主次裝備號
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 成員,并設(shè)置它的 Ktype 為 device_ktype,并沒有add
......
}
int device_add(struct device *dev)
{
// 將 dev 的 kobject 成員鏈入 device_kset 鏈表,并在/sys/devices/目錄下創(chuàng)建子目錄
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
// 創(chuàng)建屬性 dev 文件 /sys/devices/xxx/dev ,后邊我們會知道 cat dev 會得到裝備號,供 mdev 來創(chuàng)建裝備節(jié)點(diǎn)
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr); // 值得1看
error = device_create_sys_dev_entry(dev);
}
// 上報(bào)1個(gè) KOBJ_ADD 事件
kobject_uevent(&dev->kobj, KOBJ_ADD);
}
至此,我們可以發(fā)現(xiàn),今后每個(gè)創(chuàng)建 device ,只要你調(diào)用 device_register ,device 的 Kobject 都將鏈入device_kset 鏈表,然后通過contain_of 函數(shù),就能夠?qū)崿F(xiàn)對 device 的訪問。也就是說 kobject 常常是嵌入在其他模塊中,通過Kobject、kset之前的關(guān)系,實(shí)現(xiàn)對更大的模塊的關(guān)系管理。 也就是我們說的 Kobject 作用的第3條。
現(xiàn)在我們來看看 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 不屬于1個(gè)Kset,則向上查找到1個(gè) 屬于1個(gè)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 被設(shè)置 則屏蔽 uevent
// 如果設(shè)置了 filter 則用 filter 過濾事件,稍后我們會看,只要設(shè)置了 bus 或 class 的device 都會通過
// 調(diào)用name函數(shù)得到subsystem的名字;否則,subsystem的名字是kset中kobject的名字
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
/* 申請env內(nèi)存 */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
/* 獲得Path 也就是kobj的路徑 /sys/devices/xxx */
devpath = kobject_get_path(kobj, GFP_KERNEL);
/* 設(shè)置環(huán)境變量 */
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); // 子系統(tǒng)的名字
// 如果 uevent_ops->uevent 存在則調(diào)用,明顯存在,后面分析。
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 這個(gè)是通過 /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");
// 調(diào)用用戶空間程序,程序名 argv[0], 并把環(huán)境變量當(dāng)作參數(shù)傳遞過去
retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
}
}
明顯 uevent 的機(jī)制,就是設(shè)置環(huán)境變量,然后調(diào)用用戶空間程序 mdev 進(jìn)行更新裝備。
上面屢次使用到了device_kset->uevent_fops,是時(shí)候來看看了(并沒有甚么卵用)
static struct kset_uevent_ops device_uevent_ops = {
.filter = dev_uevent_filter, // 只要設(shè)置了bus or class 就不會過濾
.name = dev_uevent_name, // 返回bus or class的name
.uevent = dev_uevent, // 設(shè)置主次裝備號的環(huán)境變量
};
// 如果設(shè)置了總線 或 類 返回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));// 在環(huán)境變量中設(shè)置主次裝備號
add_uevent_var(env, "MINOR=%u", MINOR(dev->devt)); // 但是mdev其實(shí)不是從這里讀取的
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;
}
還有1個(gè)重點(diǎn),kobject_init(&dev->kobj, &device_ktype)需要注意。
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops, // 它有必要 看1看
};
static struct sysfs_ops dev_sysfs_ops = {
. show = dev_attr_show, // 這里僅指定了show store函數(shù),并沒有指定 屬性文件
.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, // 就1行 return print_dev_t(buf, dev->devt) 返回dev的裝備號
.store = NULL,
}
真實(shí)的屬性文件是:attr = {.name = __stringify(dev),.mode = S_IRUGO},我們在用戶空間cat dev的時(shí)候調(diào)用的是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); // 轉(zhuǎn)換成上邊devt_attr 的結(jié)構(gòu)類型
struct device *dev = to_dev(kobj);
if (dev_attr->show) // 調(diào)用 show_dev 傳遞 主次裝備號
ret = dev_attr->show(dev, dev_attr, buf);
}
真是大費(fèi)周章~~
可以總結(jié)1下上面的工作了
1、每個(gè) device 的kobkect都將被鏈入kset 鏈表
2、每個(gè) device 的kobkect 在/sys/device/目錄下創(chuàng)建子目錄
3、每個(gè) device 的kobkect 在/sys/device/$(name)/目錄下創(chuàng)建屬性文件 dev
4、每個(gè) device 的kobkect 的ktype 都被設(shè)置為device_ktype,通過 show 可以訪問到device的主次裝備號
5、將 device 的 kobject 的PATH 、name、主次裝備號等等設(shè)置到環(huán)境變量nev->nevp里
6、調(diào)用用戶空間 mdev
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
mdev 是啥,mdev 可以說是udev的精簡版,在 busybox 制作文件系統(tǒng)的時(shí)候被編譯進(jìn)去,它主要的工作就是根據(jù)/sys 目錄的信息來幫助我們自動創(chuàng)建裝備節(jié)點(diǎn),更詳細(xì)的概念請自行百度。向要弄清,mdev 創(chuàng)建裝備幾點(diǎn)的進(jìn)程,那只能看Busybox的源碼了。。
附上1個(gè)我做實(shí)驗(yàn)時(shí)打印出來的環(huán)境變量
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
現(xiàn)在我們來看看,從內(nèi)核空間調(diào)用的這個(gè)用戶程序mdev
int mdev_main(int argc, char **argv)
{
// mdev -s 開機(jī)掃描/sys 目錄創(chuàng)建裝備節(jié)點(diǎn),這里不分析
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 我們前邊說過了,這里會調(diào)用show 傳遞主次裝備號~
*temp++ = 0;
if (len < 1) return;
}
// 取得裝備名字,根據(jù)最后1個(gè)"/"
device_name = bb_basename(path);
// 根據(jù) path 的第5個(gè)字符來判斷裝備類型,如果是在/sys/class 目錄的話 就是字符裝備,其他的都是塊裝備
type = path[5]=='c' ? S_IFCHR : S_IFBLK;
// 如果 /etc/mdev.conf 有這個(gè)配置文件的話,根據(jù)配置文件的規(guī)則來 創(chuàng)建裝備節(jié)點(diǎn) 并履行1些命令
if (ENABLE_FEATURE_MDEV_CONF) {
// 這個(gè)不如直接來看 mdev.conf 來得實(shí)在
fd = open("/etc/mdev.conf", O_RDONLY);
......
}
if (!delete) {
if (sscanf(temp, "%d:%d", &major, &minor) != 2) return;
// mknod 創(chuàng)建裝備節(jié)點(diǎn)
if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
bb_perror_msg_and_die("mknod %s", device_name);
}
}
關(guān)于 /etc/mdev.conf 真是太有用途了 ,附上韋東山老師 uevent 的文檔,我就不賣弄了。
---------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
附上1個(gè) 我做實(shí)驗(yàn)的代碼,僅供參考,基于Linux2.6.32.2內(nèi)核
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
我們創(chuàng)建了 dev 并設(shè)置它的主次裝備號,mdev 就自動為我們創(chuàng)建裝備節(jié)點(diǎn)了~
最后,我們再來回顧1下全部流程~
kobject:
1、每個(gè) device 的kobkect都將被鏈入kset 鏈表
2、每個(gè) device 的kobkect 在/sys/device/目錄下創(chuàng)建子目錄
3、每個(gè) device 的kobkect 在/sys/device/$(name)/目錄下創(chuàng)建屬性文件 dev
4、每個(gè) device 的kobkect 的ktype 都被設(shè)置為device_ktype,通過 show 可以訪問到device的主次裝備號
uevent:
5、將 device 的 kobject 的PATH 、name、主次裝備號等等設(shè)置到環(huán)境變量nev->nevp里
6、調(diào)用用戶空間 mdev
mdev:7、讀取環(huán)境變量,創(chuàng)建裝備節(jié)點(diǎn)~
至此,我們應(yīng)當(dāng)對kobject的作用有了1個(gè)全面的了解。寫的不好,還請多批評指正。