Linux 裝置檔案的建立和mdev
引子
一、裝置類相關知識
裝置類是虛擬的,並沒有直接對應的物理實物,只是為了更好地管理同一類裝置匯出到使用者空間而產生的目錄和檔案。整個過程涉及到sysfs檔案系統,該檔案系統是為了展示linux裝置驅動模型而構建的檔案系統,是基於ramfs,linux根目錄中的/sysfs即掛載了sysfs檔案系統。
Struct kobject資料結構是sysfs的基礎,kobject在sysfs中代表一個目錄,而linux的驅動(struct driver)、裝置(struct device)、裝置類(struct class)均是從kobject進行派生的,因此他們在sysfs中都對應於一個目錄。而資料結構中附屬的
Struct kset是struct kobject的容器,即Struct kset可以成為同一類struct kobject的父親,而其自身也有kobject成員,因此其又可能和其他kobject成為上一級kset的子成員。
本文無意對sysfs和linux裝置驅動模型進行展開,以後再另寫文章進行分析。
二、兩種建立裝置檔案的方式
在裝置驅動中cdev_add將struct file_operations和裝置號註冊到系統後,為了能夠自動產生驅動對應的裝置檔案,需要呼叫
三、裝置類和裝置相關資料結構
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_attrs;//裝置類屬性
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_operations介面集和設備註冊到系統後,即利用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_initialize(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, action, 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, "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);
//還會增加匯流排相關的一些屬性環境變數等等。
#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_WAIT_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)
xchdir("/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/目錄下建立了裝置檔案。
請關注本人的微信公眾號-嵌入式企鵝圈,謝謝!