`
isiqi
  • 浏览: 16074881 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

Linux那些事儿之我是Sysfs

阅读更多

Linux那些事儿之我是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文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。

去/sys看一看,
localhost:/sys#ls /sys/
block/ bus/ class/ devices/ firmware/ kernel/ module/ power/
Block目录:包含所有的块设备
Devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
Bus目录:包含系统中所有的总线类型
Drivers目录:包括内核中所有已注册的设备驱动程序
Class目录:系统中的设备类型(如网卡设备,声卡设备等)

sys下面的目录和文件反映了整台机器的系统状况。比如bus,
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类型的文件系统,它的信息来源是在第一块硬盘的第2个分区。

但是,你可能根本没有去关心过sysfs的挂载过程,她是这样被挂载的。

mount -t sysfs sysfs /sys

ms看不出她的信息来源在哪。sysfs是一个特殊文件系统,并没有一个实际存放文件的介质。断电后就玩完了。简而言之,sysfs的信息来源是kobject层次结构,读一个sysfs文件,就是动态的从kobject结构提取信息,生成文件。

所以,首先,我要先讲一讲sysfs文件系统的信息来源 -- kobject层次结构。kobject层次结构就是linux的设备模型。

§1 Kobject
Kobject 是Linux 2.6引入的新的设备管理机制,在内核中由struct kobject表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux2.6设备模型的核心结构,它与sysfs文件系统紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。Kobject是组成设备模型的基本结构。类似于C++中的基类,它嵌入于更大的对象的对象中--所谓的容器--用来描述设备模型的组件。如bus,devices, drivers 都是典型的容器。这些容器就是通过kobject连接起来了,形成了一个树状结构。这个树状结构就与/sys向对应。
kobject 结构为一些大的数据结构和子系统提供了基本的对象管理,避免了类似机能的重复实现。这些机能包括
- 对象引用计数.
- 维护对象链表(集合).
- 对象上锁.
- 在用户空间的表示.

Kobject结构定义为:
struct kobject {
char * k name; 指向设备名称的指针
char name[KOBJ NAME LEN]; 设备名称
struct kref kref; 对象引用计数
struct list head entry; 挂接到所在kset中去的单元
struct kobject * parent; 指向父对象的指针
struct kset * kset; 所属kset的指针
struct kobj type * ktype; 指向其对象类型描述符的指针
struct dentry * dentry; sysfs文件系统中与该对象对应的文件节点路径指针
};
其中的kref域表示该对象引用的计数,内核通过kref实现对象引用计数管理,内核提供两个函数kobject_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 对象的引用计数加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(struct kobject * kobj);kobject注册函数。通过调用kobject init()初始化kobj,再调用kobject_add()完成该内核对象的注册。
void kobject_del(struct kobject * kobj);从Linux设备层次(hierarchy)中删除kobj对象。
void kobject_unregister(struct 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数据结构包含三个域:一个release方法用于释放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对应的目录下面。文件
名就是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的集合,在内核中用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域正是该链表的头。Ktype域指向一个kobj type结构,被该kset中的所有kobject共享,表示这些对象的类型。Kset数据结构还内嵌了一个kobject对象(由kobj域表示),所有属于这个kset 的kobject对象的parent域均指向这个内嵌的对象。此外,kset还依赖于kobj维护引用计数:kset的引用计数实际上就是内嵌的kobject对象的引用计数。
见图1,kset与kobject的关系图
clip_image002

这幅图很经典,她反映了整个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 的集合,同理,subsystem 就是管理kset 的集合。它描述系统中某一类设备子系统,如block subsys表示所有的块设备,对应于sysfs文件系统中的block目录。类似的,devices subsys对应于sysfs中的devices目录,描述系统中所有的设备。Subsystem由struct subsystem数据结构描述,定义为:
struct subsystem {
struct kset kset; 内嵌的kset对象
struct rw semaphore rwsem; 互斥访问信号量
};

可以看出,subsystem与kset的区别就是多了一个信号量,所以在后来的代码中,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 subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys)
void subsys_put(struct subsystem *subsys);

关于那些函数的用法,会在后面的举例中详细讲。这里仅仅是一个介绍。

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

§2 device
系统中的任一设备在设备模型中都由一个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_id[BUS_ID_SIZE];
struct bus_type *bus;
struct device_driver *driver;
void *driver_data;
/* Several fields omitted */
};
g_list 将该device对象挂接到全局设备链表中,所有的device对象都包含在devices subsys中,并组织成层次结构。Node域将该对象挂接到其兄弟对象的链表中,而bus list则用于将连接到相同总线上的设备组织成链表,driver list则将同一驱动程序管理的所有设备组织为链表。此外,children域指向该device对象子对象链表头,parent域则指向父对象。Device对象还内嵌一个kobject对象,用于引用计数管理并通过它实现设备层次结构。Driver域指向管理该设备的驱动程序对象,而driver data则是提供给驱动程序的数据。Bus域描述设备所连接的总线类型。

内核提供了相应的函数用于操作device对象。其中device_register()函数将一个新的device对象插入设备模型,并自动在/sys/devices下创建一个对应的目录。device_unregister()完成相反的操作,注销设备对象。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; 该驱动所管理的设备挂接的总线类型
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对象,如get_driver()增加引用计数,driver_register()用于向设备模型插入新的driver对象,同时在sysfs文件系统中创建对应的目录。device_driver()结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件。

可能你面对刚刚列举出来的一些列数据结构,感到很苦恼,很莫名其妙。没关系,我接下来讲个例子您就明白了。

对了,你得把ldd3的examples代码下下来。不然没法继续了。

接下来我们从例子着手,
localhost:/home/XX/examples/lddbus#insmod lddbus.ko
此时再看/sys/bus/ 这时就多了一个文件夹ldd。里面的文件构成是这样的
/sys/bus/ldd/
|--device
|--driver
`--version
localhost:/sys/bus/ldd#cat version
$Revision: 1.9$
这表示系统中多了一种名叫ldd的总线类型。同时再看/sys/device/,也多出来一个ldd0的文件夹。这表示系统中多了一个名叫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 (ret)
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()是在sysfs下创建一个文件夹。

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;
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_set_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 = 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:
723 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).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) \
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 ktype_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(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) : "<null>", <br> 168 kobj-&gt;kset ? kobj-&gt;kset-&gt;kobj.name : "<null>" ); <br> 169 <br> 170 if (kobj-&gt;kset) { <br> 171 down_write(&amp;kobj-&gt;kset-&gt;subsys-&gt;rwsem); <br> 172 <br> 173 if (!parent) <br> 174 parent = kobject_get(&amp;kobj-&gt;kset-&gt;kobj); <br> 175 <br> 176 list_add_tail(&amp;kobj-&gt;entry,&amp;kobj-&gt;kset-&gt;list); <br> 177 up_write(&amp;kobj-&gt;kset-&gt;subsys-&gt;rwsem); <br> 178 } <br> 179 kobj-&gt;parent = parent; <br> 180 <br> 181 error = create_dir(kobj); <br> 182 if (error) { <br> 183 /* unlink does the kobject_put() for us */ <br> 184 unlink(kobj); <br> 185 if (parent) <br> 186 kobject_put(parent); <br> 187 } else { <br> 188 kobject_hotplug(kobj, KOBJ_ADD); <br> 189 } <br> 190 <br> 191 return error; <br> 192 } <br>代码的170-178就是把自己连入到父辈上级kset中。我们注意到在kobject_add()函数中181行调用了create_dir(kobj),这个函数作用是在sysfs下创建一个文件夹。可见kobject和sysfs是同时更新的。 <br>kset_register(&amp;bus-&gt;devices) 和kset_register(&amp;bus-&gt;drivers)作用类似,把bus-&gt;devices这个kset加入到bus-&gt;subsys这个subsystem中去。最后形成图1的层次结构。<a href="http://hi.csdn.net/attachment/201104/8/0_1302231551SLLv.gif"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="clip_image003" border="0" alt="clip_image003" src="http://hi.csdn.net/attachment/201104/8/0_1302231552kB88.gif" width="244" height="147"></a></null></null>

图1:lddbus kobject层次结构

同理,我们可以看看device_register()的代码,它也是向devices_subsys这个subsystem注册,最后形成这样的结构与图1类似。

目前为止,我们知道了所谓的xx_register函数,就是通过其内嵌的kobject链入对应的subsystem,或是kset的层次结构中去。这样就可以通过一些全局的变量找到它们了。

不妨再把sculld的代码也分析一下,先看初始函数
sculld_init()
-> register_ldd_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) {
538 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_write(&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
545行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 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_WARNING
369 "%s: probe of %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->bus_id);
463 list_add_tail(&dev->bus_list, &dev->bus->devices.list);
464 465 up_write(&dev->bus->subsys.rwsem);
466 device_add_attrs(bus, dev);
467 sysfs_create_link(&bus->devices.kobj, &dev->kobj, dev->bus_id);
468 }
469 return error;
470 }
463,把设备连入其总线的devices.list链表中。
464,device_attach(dev)与driver_attach()相对应,它在总线的驱动中寻找,看有没有一个driver能驱动这个设备。
467,创建了一个链接。
最后形成的kobject层次结构如图所示。

clip_image005

变化
计划赶不上变化,当前的内核版本已经是2.6.22了,其中不少数据结构作了变动,而且subsystem这个数据结构已经没有了,完全被kset取缔了。但是原理上并没有变,我认为,学习知识是一方面,更重要的是学习方法。只要懂了方法,我们才可"以不变应万变"。

17大马上要召开了,刘翔又夺冠了,奥尔默特与阿巴斯也会面了,明年就奥运了。和谐的社会里充满着希望与绝望。不管怎样,终于把设备模型介绍完毕,接下来进入sysfs部分。

分享到:
评论

相关推荐

    Linux那些事儿之我是sysfs

    Linux那些事儿之我是sysfs

    Linux那些事儿 之 我是Sysfs下.pdf

    "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/file...

    Linux那些事儿

    《Linux那些事儿》分为9个部分。 Linux那些事儿之我是U盘 Linux那些事儿之我是Hub ...Linux那些事儿之我是Sysfs 今天本人将9个单独的文档整理出来,做成了一个单独的文档,配有书签,更加方便读者阅读。

    Linux那些事儿 之 我是Sysfs上.pdf

    "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/file...

    Linux那些事儿1-9合集

    由复旦fudan_abc写的,风趣的文笔,深入浅出地讲解了Linux内核相关模块,主要涉及了USB相关的模块,但是同样也解析了Linux驱动模型相关的知识,很值得一读。读过《linux那些事儿之我是U盘》...linux那些事儿之我是Sysfs

    linux那些事儿(EHCI Block SCSI Sysfs PCI USB U 盘 UHCI Hub)

    Linux那些事儿之我是EHCI主机...Linux那些事儿之我是Sysfs.pdf Linux那些事儿之我是PCI.pdf Linux那些事儿之我是USB core.pdf Linux 那些事儿之我是U 盘.pdf Linux那些事儿之我是Hub.pdf Linux那些事儿之我是UHCI.pdf

    Linux那些事儿(linux内核写的很详细)

    Linux那些事儿 Linux那些事儿之我是Block层 Linux那些事儿之我是Sysfs Linux那些事儿之我是U盘

    Linux那些事儿系列.rar

    》包括《Linux那些事儿之我是Hub》、《Linux那些事儿之我是Sysfs》《Linux那些事儿之我是UHCI》、《Linux那些事儿之我是USB core》、《Linux那些事儿之我是U盘》,令人叹为观止的一个linux系列书籍。只能说,江山代...

    linux的那些事儿全集

    Linux那些事儿之我是Block层 Linux那些事儿之我是EHCI主机控制器 Linux那些事儿之我是Hub ...Linux那些事儿之我是Sysfs Linux那些事儿之我是UHCI Linux那些事儿之我是USB core Linux那些事儿之我是U盘

    linux那些事全集

    Linux那些事儿之我是U盘 ...Linux那些事儿之我是Sysfs Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是PCI Linux那些事儿之我是Hub Linux那些事儿之我是EHCI主机控制器 Linux那些事儿之我是Block层

    Linux那些事儿系列

    本人整理的fudan_abc的专栏中以完结的文章,在此向原作者表示感谢,给...内容包括:linux那些事儿之我是U盘,linux那些事儿之我是USB,linux那些事儿之我是HUB,linux那些事儿之我是UHCI,linux那些事儿之我是Sysfs。

    Linux那些事儿.rar

    包括:Linux那些Linux那些事儿之我是SCSI硬盘,Linux那些事儿之我是Block层,Linux那些事儿之我是EHCI主机控制器,Linux那些事儿之我是HUB,Linux那些事儿之我是PCI,Linux那些事儿之我是Sysfs,Linux那些事儿之我是...

    Linux那些事系列

    Linux那些事儿之我是Hub Linux那些事儿之我是Sysfs Linux那些事儿之我是UHCI Linux那些事儿之我是USB core Linux那些事儿之我是U盘 Linux那些事之我是HUB1

    Linux那些事儿 系列之2 Block+EHCI+PCI+SCSI

    Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是PCI.pdf Linux那些事儿之我是SCSI硬盘.pdf 注: 之前有人已经上传了《Linux那些事儿 系列》,其已经包含了:hub,sysfs...

    linux哪些事儿之我是usbcore echi pci u盘 sysfs

    linux哪些事儿之我是usbcore echi pci u盘 sysfs linux哪些事儿之我是usbcore echi pci u盘 sysfs

    usb那些事的全集

    usb那些事的全集,包含: 1 Linux那些事儿之我是Block层 2 Linux那些事儿之我是EHCI主机控制器 ...6 Linux那些事儿之我是Sysfs 7 Linux那些事儿之我是UHCI 8 Linux那些事儿之我是USB+core 9 Linux那些事儿之我是U盘

Global site tag (gtag.js) - Google Analytics