2022年sysfs文件系统 .pdf

上传人:Che****ry 文档编号:27189414 上传时间:2022-07-23 格式:PDF 页数:34 大小:1.43MB
返回 下载 相关 举报
2022年sysfs文件系统 .pdf_第1页
第1页 / 共34页
2022年sysfs文件系统 .pdf_第2页
第2页 / 共34页
点击查看更多>>
资源描述

《2022年sysfs文件系统 .pdf》由会员分享,可在线阅读,更多相关《2022年sysfs文件系统 .pdf(34页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、(一、 sysfs系统)sysfs is a ram-based filesystem initially based on ramfs. It provides a means to export kernel data structures, their attributes, and the linkages between them to userspace.” - documentation/filesystems/sysfs.txt 可以先把documentation/filesystems/sysfs.txt 读一遍。 Sysfs 文件系统是一个类似于proc 文件系统的特殊文件

2、系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。去/sys 看一看,localhost:/sys#ls /sys/ block/ bus/ class/ devices/ firmware/ kernel/ module/ power/ Block 目录:包含所有的块设备Devices 目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构Bus 目录:包含系统中所有的总线类型Drivers 目录:包括内核中所有已注册的设备驱动程序Class 目录:系统中的设备类型(如网卡设备,声卡设备等)sys下面的目录和文件反映了整台机器的系统状况。比如bus

3、,localhost:/sys/bus#ls i2c/ ide/ pci/ pci express/ platform/ pnp/ scsi/ serio/ usb/ 里面就包含了系统用到的一系列总线,比如pci, ide, scsi, usb 等等。比如你可以在usb文件夹中发现你使用的U 盘, USB 鼠标的信息。我们要讨论一个文件系统,首先要知道这个文件系统的信息来源在哪里。所谓信息来源是指文件组织存放的地点。比如,我们挂载一个分区,mount -t vfat /dev/hda2 /mnt/C 我们就知道挂载在/mnt/C 下的是一个vfat 类型的文件系统,它的信息来源是在第一块硬盘的

4、第 2 个分区。但是,你可能根本没有去关心过sysfs的挂载过程,她是这样被挂载的。mount t sysfs sysfs /sys ms 看不出她的信息来源在哪。sysfs 是一个特殊文件系统,并没有一个实际存放文件的介质。断电后就玩完了。简而言之,sysfs的信息来源是kobject 层次结构,读一个sysfs文件,就是动态的从kobject 结构提取信息,生成文件。所以,首先,我要先讲一讲sysfs 文件系统的信息来源- kobject 层次结构。 kobject 层次结构就是 linux 的设备模型。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - -

5、- - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 34 页 - - - - - - - - - (二 linux 设备底层模型)1 Kobject Kobject 是 Linux 2.6 引入的新的设备管理机制,在内核中由struct kobject 表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject 提供基本的对象管理,是构成 Linux2.6设备模型的核心结构,它与sysfs 文件系统紧密关联,每个在内核中注册的kobject 对象都对应于 sysfs文件系统中的一个目录。Kobject 是组成设备模型的基本结构。类似于 C+中的基类

6、,它嵌入于更大的对象的对象中-所谓的容器 -用来描述设备模型的组件。如 bus,devices, drivers 都是典型的容器。这些容器就是通过kobject 连接起来了,形成了一个树状结构。这个树状结构就与/sys 向对应。kobject 结构为一些大的数据结构和子系统提供了基本的对象管理,避免了类似机能的重复实现。这些机能包括- 对象引用计数 . - 维护对象链表 (集合 ). - 对象上锁 . - 在用户空间的表示. Kobject 结构定义为:struct kobject char * k_name; 指向设备名称的指针char nameKOBJ NAME LEN; 设备名称stru

7、ct kref kref; 对象引用计数struct list_head entry; 挂接到所在kset 中去的单元struct kobject * parent; 指向父对象的指针kset中内嵌的struct kobject kobj struct kset * kset; 所属 kset 的指针struct kobj_type * ktype; 指向其对象类型描述符的指针struct dentry * dentry; sysfs 文件系统中与该对象对应的文件节点路径指针; 其中的 kref 域表示该对象引用的计数,内核通过kref 实现对象引用计数管理,内核提供两个函数 kobject_

8、get() 、kobject_put() 分别用于增加和减少引用计数,当引用计数为0 时,所有该对象使用的资源释放。Ktype 域是一个指向kobj_type 结构的指针,表示该对象的类型。相关函数void kobject_init(struct kobject * kobj);kobject 初始化函数。int kobject_set_name(struct kobject *kobj, const char *format, .);设置指定kobject 的名称。struct kobject *kobject_get(struct kobject *kobj);将 kobj 对象的引用计数

9、加1,同时返回该对象的指针。void kobject_put(struct kobject * kobj); 将 kobj 对象的引用计数减1,如果引用计数降为0,则调用 kobject release()释放该 kobject 对象。int kobject_add(struct kobject * kobj);将 kobj 对象加入Linux 设备层次。挂接该kobject 对象到 kset 的 list 链中,增加父目录各级kobject 的引用计数,在其parent 指向的目录下创建文件节点,并启动该类型内核对象的hotplug 函数。int kobject_register(struc

10、t kobject * kobj); kobject 注册函数。通过调用 kobject init() 初始化 kobj,再调用 kobject_add()完成该内核对象的注册。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 34 页 - - - - - - - - - void kobject_del(struct kobject * kobj);从 Linux 设备层次 (hierarchy) 中删除 kobj 对象。void kobject_unregister(s

11、truct kobject * kobj);kobject 注销函数。与kobject register() 相反,它首先调用kobject del 从设备层次中删除该对象,再调用 kobject put() 减少该对象的引用计数,如果引用计数降为0,则释放kobject 对象。2 Kobj_type struct kobj_type void (*release)(struct kobject *); struct sysfs_ops * sysfs_ops; struct attribute * default_attrs; ; Kobj_type 数据结构包含三个域: 一个 releas

12、e方法用于释放kobject 占用的资源;一个 sysfs ops指针指向 sysfs 操作表和一个sysfs文件系统缺省属性列表。Sysfs 操作表包括两个函数store()和 show()。当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer 中返回给用户态;而store()函数用于存储用户态传入的属性值。attribute struct attribute char * name; struct module * owner; mode_t mode; ; attribute, 属性。 它以文件的形式输出到sysfs 的目录当中。 在 kobject 对应的目

13、录下面。文件名就是 name。文件读写的方法对应于kobj type 中的 sysfs_ops。3. kset kset 最重要的是建立上层(sub-system)和下层的 (kobject) 的关联性 。kobject 也会利用它来分辨自已是属于那一個类型,然後在/sys 下建立正确的目录位置。而kset 的优先权比较高,kobject 会利用自已的*kset 找到自已所属的kset,並把 *ktype 指定成該kset 下的 ktype,除非沒有定义kset,才会用ktype 來建立关系。Kobject 通过 kset组织成层次化的结构,kset是具有相同类型的kobject 的集合, 在

14、内核中用kset 数据结构表示,定义为:struct kset struct subsystem * subsys; 所在的 subsystem 的指针struct kobj_type * ktype; 指向该 kset 对象类型描述符的指针struct list head list; 用于连接该kset 中所有 kobject 的链表头struct kobject kobj; 嵌入的 kobject struct kset_hotplug_ops * hotplug ops; 指向热插拔操作表的指针; 包含在 kset 中的所有kobject 被组织成一个双向循环链表,list 域正是该链表

15、的头。Ktype 域指向一个kobj_type 结构,被该kset 中的所有kobject 共享,表示这些对象的类型。Kset 数据结构还内嵌了一个kobject 对象(由kobj 域表示),所有属于这个kset 的 kobject 对象的parent 域均指向这个内嵌的对象。此外, kset 还依赖于kobj 维护引用计数:kset 的引用计数实际上就是内嵌的kobject 对象的引用计数。见图 1, kset 与 kobject 的关系图名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - -

16、 第 3 页,共 34 页 - - - - - - - - - 这幅图很经典,她反映了整个kobject 的连接情况。相关函数与 kobject 相似, kset_init() 完成指定kset 的初始化, kset_get()和 kset_put()分别增加和减少kset 对象的引用计数。Kset_add()和 kset_del()函数分别实现将指定keset 对象加入设备层次和从其中删除; kset_register()函数完成 kset的注册而 kset_unregister()函数则完成kset的注销。4 subsystem 如果說 kset 是管理 kobject 的集合,同理, s

17、ubsystem 就是管理kset 的集合 。它描述系统中某一类设备子系统,如 block subsys表示所有的块设备,对应于 sysfs文件系统中的block目录。 类似的, devices subsys 对应于sysfs中的 devices目录,描述系统中所有的设备。Subsystem 由 struct subsystem 数据结构描述,定义为:struct subsystem struct kset kset; 内嵌的 kset 对象struct rw_semaphore rwsem; 互斥访问信号量; 可以看出, subsystem与 kset 的区别就是多了一个信号量,所以在后来的

18、代码中,subsystem已经完全被kset 取缔了。每个 kset 属于某个subsystem,通过设置kset 结构中的 subsys域指向指定的subsystem可以将一个 kset 加入到该subsystem。所有挂接到同一subsystem 的 kset 共享同一个rwsem 信号量,用于同步访问kset 中的链表。相关函数subsystem有一组类似的函数,分别是:void subsystem_init(struct subsystem *subsys); int subsystem_register(struct subsystem *subsys); void subsyste

19、m_unregister(struct subsystem *subsys); struct subsystem *subsys_get(struct subsystem *subsys) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 34 页 - - - - - - - - - void subsys_put(struct subsystem *subsys); 关于那些函数的用法,会在后面的举例中详细讲。这里仅仅是一个介绍。(三、设备模型上层容器)1 bus 系统中

20、总线由struct bus_type 描述,定义为:struct bus_type char * name; 总线类型的名称struct subsystem subsys; 与该总线相关的subsystem struct kset drivers; 所有与该总线相关的驱动程序集合struct kset devices; 所有挂接在该总线上的设备集合struct bus attribute * bus_attrs; 总线属性struct device attribute * dev_attrs; 设备属性struct driver attribute * drv_attrs; 驱动程序属性int

21、 (*match)(struct device * dev, struct device_driver * drv); int (*hotplug) (struct device *dev, char *envp, int num_envp, char *buffer, int buffer_size); int (*suspend)(struct device * dev, u32 state); int (*resume)(struct device * dev); ;每个 bus_type 对象都内嵌一个subsystem对象, bus_subsys对象管理系统中所有总线类型 的subs

22、ystem对象。 每个 bus_type 对象都对应 /sys/bus目录下的一个子目录,如 PCI 总线类型对应于 /sys/bus/pci。在每个这样的目录下都存在两个子目录:devices和 drivers(分别对应于 bus type 结构中的 devices和 drivers 域) 。其中 devices 子目录描述连接在该总线上的所有设备,而drivers 目录则描述与该总线关联的所有驱动程序。与device_driver 对象类似,bus_type 结构还包含几个函数(match()、hotplug() 等)处理相应的热插拔、即插即拔和电源管理事件。2 device 系统中的任一

23、设备在设备模型中都由一个device 对象描述 ,其对应的数据结构struct device 定义为:struct device struct list_head g_list; struct list_head node; struct list_head bus_list; struct list_head driver_list; struct list_head children; struct device *parent; struct kobject kobj; char bus_idBUS_ID_SIZE; struct bus_type *bus; struct device

24、_driver *driver; void *driver_data; /* Several fields omitted */ ; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 34 页 - - - - - - - - - g_list 将该 device对象挂接到全局设备链表中,所有的device 对象都包含在devices subsys中,并组织成层次结构。Node 域将该对象挂接到其兄弟对象的链表中,而 bus list 则用于将连接到相同总线上的设备组织成链表

25、,driver list 则将同一驱动程序管理的所有设备组织为链表。 此外, children 域指向该device 对象子对象链表头,parent 域则指向父对象。Device对象还内嵌一个kobject 对象,用于引用计数管理并通过它实现设备层次结构。Driver 域指向管理该设备的驱动程序对象,而driver data 则是提供给驱动程序的数据。Bus 域描述设备所连接的总线类型。内核提供了相应的函数用于操作device 对象。其中device_register()函数将一个新的device对象插入设备模型,并自动在 /sys/devices下创建一个对应的目录。device_unreg

26、ister()完成相反的操作, 注销设备对象。 get_device()和 put_device() 分别增加与减少设备对象的引用计数。通常 device 结构不单独使用, 而是包含在更大的结构中作为一个子结构使用,比如描述 PCI设备的 struct pci_dev ,还有我们ldd_dev,其中的 dev 域就是一个device 对象。3. driver 系统中的每个驱动程序由一个device_driver 对象描述, 对应的数据结构定义为:struct device_driver char *name; 设备驱动程序的名称struct bus_type *bus; 该驱动所管理的设备挂接

27、的总线类型struct kobject kobj; 内嵌 kobject 对象struct list_head devices; 该驱动所管理的设备链表头int (*probe)(struct device *dev); 指向设备探测函数,用于探测设备是否可以被该驱动程序管理int (*remove)(struct device *dev); 用于删除设备的函数/* some fields omitted*/ ;与 device 结构类似, device_driver 对象依靠内嵌的kobject 对象实现引用计数管理和层次结构组织。 内核提供类似的函数用于操作device_driver 对象

28、,如get_driver() 增加引用计数,driver_register() 用于向设备模型插入新的driver 对象,同时在 sysfs 文件系统中创建对应的目录。 device_driver() 结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件。可能你面对刚刚列举出来的一些列数据结构,感到很苦恼,很莫名其妙。没关系,我接下来讲个例子您就明白了。(四、举例一 lddbus )对了,你得把ldd3 的 examples 代码下下来。不然没法继续了。接下来我们从例子着手,localhost:/home/XX/examples/lddbus#insmod lddbus.ko 此时再看

29、/sys/bus/ 这时就多了一个文件夹ldd。里面的文件构成是这样的/sys/bus/ldd/ |-device |-driver -version 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 34 页 - - - - - - - - - localhost:/sys/bus/ldd#cat version $Revision: 1.9$ 这表示系统中多了一种名叫ldd 的总线类型。同时再看/sys/device/,也多出来一个ldd0 的文件夹。这表示系统中多了一

30、个名叫ldd0 的硬件。在 lddbus.c 中,定义了一个总线和硬件类型struct bus_type ldd_bus_type = .name = ldd, .match = ldd_match, .hotplug = ldd_hotplug, ; struct device ldd_bus = .bus_id = ldd0, .release = ldd_bus_release ; lddbus 模块初始化时调用这个函数static int _init ldd_bus_init(void) int ret; ret = bus_register(&ldd_bus_type); if (r

31、et) return ret; if (bus_create_file(&ldd_bus_type, &bus_attr_version) printk(KERN_NOTICE Unable to create version attribute/n); ret = device_register(&ldd_bus); if (ret) printk(KERN_NOTICE Unable to register ldd0/n); return ret; 其实就是调用了两个注册函数,bus_register(), device_register() 。bus_create_file()是在sys

32、fs下创建一个文件夹。bus_register(),向系统注册ldd_bus_type 这个总线类型。bus_create_file() 这个就是向sysfs 中创建一个文件。device_register()系统注册ldd_bus 这个硬件类型。注册好了之后,我们就可以在sysfs下看到相应的信息。我们深入下去,仔细看看bus_register 的代码。688 int bus_register(struct bus_type * bus) 689 690 int retval; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - -

33、名师精心整理 - - - - - - - 第 7 页,共 34 页 - - - - - - - - - 691 /调用(二)中的函数692 retval = kobject_set_name(&bus-subsys.kset.kobj, %s, bus-name); 693 if (retval) 694 goto out; 695 696 subsys_set_kset(bus, bus_subsys); 697 retval = subsystem_register(&bus-subsys); 698 if (retval) 699 goto out; 700 701 kobject_se

34、t_name(&bus-devices.kobj, devices); 702 bus-devices.subsys = &bus-subsys; 703 retval = kset_register(&bus-devices); 704 if (retval) 705 goto bus_devices_fail; 706 707 kobject_set_name(&bus-drivers.kobj, drivers); 708 bus-drivers.subsys = &bus-subsys; 709 bus-drivers.ktype = &ktype_driver; 710 retval

35、 = kset_register(&bus-drivers); 711 if (retval) 712 goto bus_drivers_fail; 713 bus_add_attrs(bus); 714 715 pr_debug(bus type %s registered/n, bus-name); 716 return 0; 717 718 bus_drivers_fail: 719 kset_unregister(&bus-devices); 720 bus_devices_fail: 721 subsystem_unregister(&bus-subsys); 722 out: 72

36、3 return retval; 724 692-700是对 bus-subsys 的操作。701-705是操作 bus-devices。 706-710是操作 bus-drivers。692 kobject_set_name() 设 置bus-subsys.kset.kobj的 名 字 。 此 函 数 很 简 单 , 就 是 调 用vsnprintf() 。此不列出。696 subsys_set_kset(bus, bus subsys) #define subsys_set_kset(obj,_subsys) (obj)-subsys.kset.kobj.kset = &(_subsys)

37、.kset 我们先看看bus_subsys 的定义,它是一个subsystem 类型的全局变量。在driver/base/bus.c中,decl_subsys(bus, &ktype bus, NULL); 在 /include/linux/kobject.h中有,decl_subsys的原型,#define decl_subsys(_name,_type,_hotplug_ops) / 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 34 页 - - - - - - -

38、 - - struct subsystem _name#_subsys = / .kset = / .kobj = .name = _stringify(_name) , / .ktype = _type, / .hotplug_ops =_hotplug_ops, / / 就相当于struct subsystem bus_subsys = / .kset = / .kobj = .name = “ bus” , /.ktype = ktype_bus, / .hotplug_ops =NULL, / / 其中 ktype_bus 定义如下,static struct kobj_type kt

39、ype_bus = .sysfs_ops = &bus_sysfs_ops, ; 697 subsystem_register(&bus-subsys) 作用是向全局的bus_subsys”登记” , 把自己加入到bus_subsys的链表中去。subsystem_register() - kset_add() - kobject_add() 155 int kobject_add(struct kobject * kobj) 156 157 int error = 0; 158 struct kobject * parent; 159 160 if (!(kobj = kobject_get

40、(kobj) 161 return -ENOENT; 162 if (!kobj-k_name) 163 kobj-k_name = kobj-name; 164 parent = kobject_get(kobj-parent); 165 166 pr_debug(kobject %s: registering. parent: %s, set: %s/n, 167 kobject_name(kobj), parent ? kobject_name(parent) : , 168 kobj-kset ? kobj-kset-kobj.name : ); 169 170 if (kobj-ks

41、et) 171 down_write(&kobj-kset-subsys-rwsem); 172 173 if (!parent) 174 parent = kobject_get(&kobj-kset-kobj); 175 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 34 页 - - - - - - - - - 176 list_add_tail(&kobj-entry,&kobj-kset-list); 177 up_write(&kobj-kset-subsys

42、-rwsem); 178 179 kobj-parent = parent; 180 181 error = create_dir(kobj); 182 if (error) 183 /* unlink does the kobject_put() for us */ 184 unlink(kobj); 185 if (parent) 186 kobject_put(parent); 187 else 188 kobject_hotplug(kobj, KOBJ_ADD); 189 190 191 return error; 192 代码的 170-178 就是把自己连入到父辈上级kset中。

43、我们注意到在kobject_add() 函数中 181行调用了create_dir(kobj) ,这个函数作用是在sysfs 下创建一个文件夹。可见 kobject 和 sysfs是同时更新的。kset_register(&bus-devices) 和 kset_register(&bus-drivers) 作用类似,把bus-devices 这个kset 加入到 bus-subsys 这个 subsystem中去。最后形成图1 的层次结构。图 1:lddbus kobject 层次结构同理,我们可以看看device_register()的代码,它也是向devices_subsys 这个 su

44、bsystem注册,最后形成这样的结构与图1类似。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 34 页 - - - - - - - - - 目前为止 ,我们知道了所谓的xx_register 函数,就是通过其内嵌的kobject 链入对应的subsystem ,或是 kset的层次结构中去。这样就可以通过一些全局的变量找到它们了。(五、举例二 sculld )不妨再把sculld 的代码也分析一下,先看初始函数sculld_init() - register_ldd

45、_driver() -driver_register() -bus_add_driver() - register_ldd_device() -device_register() -device_add() -kobject_add() -bus_add_device() 首先注册驱动,看bus_add_driver() 532 int bus_add_driver(struct device_driver * drv) 533 534 struct bus_type * bus = get_bus(drv-bus); 535 int error = 0; 536 537 if (bus) 5

46、38 pr_debug(bus %s: add driver %s/n, bus-name, drv-name); 539 error = kobject_set_name(&drv-kobj, %s, drv-name); 540 if (error) 541 put_bus(bus); 542 return error; 543 544 drv-kobj.kset = &bus-drivers; 545 if (error = kobject_register(&drv-kobj) 546 put_bus(bus); 547 return error; 548 549 550 down_w

47、rite(&bus-subsys.rwsem); 551 driver_attach(drv); 552 up_write(&bus-subsys.rwsem); 553 module_add_driver(drv-owner, drv); 554 555 driver_add_attrs(bus, drv); 556 557 return error; 558 559 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 34 页 - - - - - - - - - 545

48、 行 kobject_register() 与 kobject_add() 差不多, 进行注册, 把自己 kobject 链接到内核中去。551,driver_attach(drv); 在总线中寻找,有没有设备可以让这个driver 驱动。353 void driver_attach(struct device_driver * drv) 354 355 struct bus_type * bus = drv-bus; 356 struct list_head * entry; 357 int error; 358 359 if (!bus-match) 360 return; 361 362

49、 list_for_each(entry, &bus-devices.list) 363 struct device * dev = container_of(entry, struct device, bus_list); 364 if (!dev-driver) 365 error = driver_probe_device(drv, dev); 366 if (error & (error != -ENODEV) 367 /* driver matched but the probe failed */ 368 printk(KERN_W ARNING 369 %s: probe of

50、%s failed with error %d/n, 370 drv-name, dev-bus_id, error); 371 372 373 然后注册设备,455 int bus_add_device(struct device * dev) 456 457 struct bus_type * bus = get_bus(dev-bus); 458 int error = 0; 459 460 if (bus) 461 down_write(&dev-bus-subsys.rwsem); 462 pr_debug(bus %s: add device %s/n, bus-name, dev

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 高考资料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com