sysfs是一个基于ramfs的文件系统,在2.6内核开始引入,用来导出内核对象(kernel object)的数据、属性到用户空间。与同样用于查看内核数据的proc不同,sysfs只关心具有层次结构的设备信息,比如系统中的总线,驱动以及已经加载的模块等,而诸如PID等信息还是使用proc来管理。本质上,sysfs文件的层次结构就是基于内核中kset与kobject逻辑结构来组织的。从驱动开发的角度,/sysfs为我们提供了除了设备文件/dev/proc之外的另外一种通过用户空间访问内核数据的方式。想要使用sysfs,编译内核的时候需要定义CONFIG_SYSFS,可以通过mount -t sysfs sysfs /sys命令来挂载sysfs到"/sys"目录。本文以ubuntu15.04(3.19)为例分析。

sysfs目录结构

sysfs的布局体现内核的数据结构,顶层的目录有

  1. $ls /sys/
  2. block/ bus/ class/ dev/ devices/ firmware/ fs/ hypervisor/ kernel/ module/ power/

每一个目录都对应内核中的一个kset,每一个kset还会包含一些kobject或其他kset。下面针对常用目录做一个简单的介绍

/sys/block/

块设备的存放目录,这是一个过时的接口,按照sysfs的设计理念,所有的设备都存放在"sys/devices/"同时在"sys/bus/"或(和)"sys/class/"存放相应的符号链接,所以现在这个目录只是为了提高兼容性的设计,里面的文件已经被全部替换成了符号链接,只有在编译内核的时候勾选CONFIG_SYSFS_DEPRECATED才会有这个目录,

  1. sys $ll block/
  2. total 0
  3. lrwxrwxrwx 1 root root 0 12 20 11:29 dm-0 -> ../devices/virtual/block/dm-0/
  4. lrwxrwxrwx 1 root root 0 12 20 11:29 dm-1 -> ../devices/virtual/block/dm-1/
  5. ...

/sys/bus/

bus包含了系统中所有的总线,比如我的系统当前提供的总线有:

  1. sys $ls bus/
  2. acpi/ container/ i2c/ media/ mipi-dsi/ pci/ pnp/ sdio/ usb/ platform/ scsi/ spi/ ...

每一种总线通常还有两个子目录:device和driver,这两个字目录分别对应内核中的两个kset,同时bus本身也对应一个kset,也有自己的kobject和以及(可能)有相应的ktype。我们可以查看相应的kset属性。

  1. sys $ls bus/platform/
  2. devices/ drivers/ drivers_autoprobe drivers_probe uevent
  3. sys $cat bus/platform/drivers_autoprobe
  4. 1

我们可以扒一下3.19的源码,找到这个属性

  1. //include/linux/platform_device.h
  2. 22 struct platform_device {
  3. ...
  4. 26 struct device dev;
  5. ...
  6. 38 };
  1. //include/linux/device.h
  2. 731 struct device {
  3. ...
  4. 744 struct bus_type *bus; /* type of bus device is on */
  5. ...
  6. 800 };
  7. 104 struct bus_type {
  8. ...
  9. 129 struct subsys_private *p;
  10. ...
  11. 131 };
  1. //drivers/base/base.h
  2. 28 struct subsys_private {
  3. 29 struct kset subsys;
  4. 30 struct kset *devices_kset;
  5. ...
  6. 38 unsigned int drivers_autoprobe:1; #Bingo!!!
  7. ...
  8. 43 };

同时,根据kset的组织形式,平台总线的设备kset链接了挂接在平台总线上的所有设备,所以"platform/devices"下应该可以查看到,要注意的事,为了使一个设备在sysfs中只有一个实例,很多目录都是使用符号链接的形式,下面显示的结果也验证了这种设计。

  1. sys $ll bus/platform/devices/
  2. lrwxrwxrwx 1 root root 0 12 19 08:17 ACPI0003:00 -> ../../../devices/pci0000:00/0000:00:14.3/PNP0C09:00/ACPI0003:00/ ...
  3. sys $ll bus/platform/drivers/thinkpad_acpi/
  4. lrwxrwxrwx 1 root root 0 12 20 20:19 thinkpad_acpi -> ../../../../devices/platform/thinkpad_acpi/
  5. --w------- 1 root root 4096 12 20 20:18 uevent
  6. --w------- 1 root root 4096 12 20 20:19 unbind
  7. -r--r--r-- 1 root root 4096 12 20 20:19 version
  8. ...
  9. sys $cat bus/platform/drivers/thinkpad_acpi/version
  10. ThinkPad ACPI Extras v0.25

/sys/class/

按照设备功能对系统设备进行分类的结果放在这个目录,如系统所有输入设备都会出现在 "/sys/class/input"之下。和sys/bus一样,sys/class最终的文件都是符号链接,这种设备可以保证整个系统中每一个设备都只有一个实例。

  1. sys $l class/
  2. ata_device/ i2c-adapter/ net/ rtc/ spi_master/ gpio/ input/ ...
  3. sys $l class/input/
  4. event0@ event10@ event12@ mouse0@ ...

/sys/dev/

按照设备号对字符设备和块设备进行分类的结果放在这个目录,同样,文件依然是使用符号链接的形式链接到"sys/devices/"中的相应文件

  1. sys $ls dev/
  2. block/ char/
  3. sys $ls dev/char/
  4. 10:1@ 10:236@ 108:0@ 1:3@ ...

/sys/devices/

如前所述,所有的设备文件实例都在"sys/devices/"目录下,

  1. sys $ls devices/
  2. amd_nb/ breakpoint/ cpu/ ibs_fetch/ ibs_op/ LNXSYSTM:00/ pci0000:00/ platform/ ...
  3. sys $ls devices/platform/serial8250/
  4. driver@ driver_override modalias power/ subsystem@ tty/ uevent
  5. sys $cat devices/platform/serial8250/driver_override
  6. (null)

"sys/class/","sys/bus/","sys/devices"是设备开发中最重要的几个目录。他们之间的关系可以用下图表示。

/sys/fs

这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中中;

/sys/kernel

这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中 ;

/sys/module

这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中:编译为外部模块(ko文件)在加载后会出现对应的/sys/module/<module_name>/, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的 /sys/module/<module_name>, 这些模块的可用参数会出现在 /sys/modules//parameters/<param_name> 中,如 /sys/module/printk/parameters/time 这个可读写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀;所有内联模块的参数也可以由 "<module_name>.<param_name>="的形式写在内核启动参数上,如启动内核时加上参数 "printk.time=1" 与 向"/sys/module/printk/parameters/time" 写入1的效果相同;没有非0属性参数的内联模块不会出现于此。

/sys/power

这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

/sys/slab

(对应 2.6.23 内核,在 2.6.24 以后移至/sys/kernel/slab) 从2.6.23 开始可以选择 SLAB 内存分配器的实现,并且新的 SLUB(Unqueued Slab Allocator)被设置为缺省值;如果编译了此选项,在 /sys 下就会出现 /sys/slab ,里面有每一个 kmem_cache 结构体的可调整参数。对应于旧的 SLAB 内存分配器下的/proc/slabinfo 动态调整接口, 新式的 /sys/kernel/slab/<slab_name> 接口中的各项信息和可调整项显得更为清晰。

sysfs与kobject、kset

对于每一个注册到内核的kobject,都会在sysfs中创建一个目录!!!一个目录!!!一个目录!!!,目录名就是kobject.name,这个目录会从属于kobject.parent对应的目录,我们就可以实现在sysfs中用树状结构来呈现内核中的kobject。最初的sysfs下顶层目录下的目录使用subsystem的结构,在某些书中还会见到这个概念,不过现在已经被kset替代了。在 kobject 下还有一些符号链接文件,指向其它的 kobject,这些符号链接文件用于组织上面所说的 device, driver, bus_type, class, module 之间的关系。我们再来看看kobject结构:

  1. //include/linux/kobject.h
  2. 63 struct kobject {
  3. 64 const char *name;
  4. 65 struct list_head entry;
  5. 66 struct kobject *parent;
  6. 67 struct kset *kset;
  7. 68 struct kobj_type *ktype;
  8. 69 struct kernfs_node *sd;
  9. 70 struct kref kref;
  10. ...
  11. 79 };
  1. //include/linux/kernfs.h
  2. 106 struct kernfs_node {
  3. ...
  4. 125 union {
  5. 126 struct kernfs_elem_dir dir;
  6. 127 struct kernfs_elem_symlink symlink;
  7. 128 struct kernfs_elem_attr attr;
  8. 129 };
  9. ...
  10. 137 };

这其中的symlink就组成了下面的符号链接,许许多多这样的符号链接就构成了整个sysfs的符号链接体系

  1. sys $ll devices/platform/serial8250/
  2. lrwxrwxrwx 1 root root 0 12 20 16:17 driver -> ../../../bus/platform/drivers/serial8250/
  3. -rw-r--r-- 1 root root 4096 12 20 16:17 driver_override
  4. -rw-r--r-- 1 root root 4096 12 20 16:17 uevent
  5. ...

sysfs与ktype

在sysfs中,kobject的属性(kobject.ktype.attribute)可以以普通文件的形式导出,sysfs还提供了使用文件I/O直接修改内核属性的机制,这些属性一般都是ASCII格式的文本文件(ktype.attribute.name)或二进制文件(通常只用在sys/firmware中),为了提高效率,可以将具有同一类型的属性放置在一个文件中,这样就可以使用数组进行批量修改,不要在一个文件中使用混合类型,也不要使用多行数据,这些做法会大大降低代码的可读性,下面就是一个属性的定义,可以看到,属性中并没有包含读写属性的函数,但是从面向对象的思想看,内核提供了两个用于读写attribute结构的函数。

  1. //include/linux/sysfs.h
  2. 29 struct attribute {
  3. 30 const char *name;
  4. 31 umode_t mode;
  5. 32 #ifdef CONFIG_DEBUG_LOCK_ALLOC
  6. 33 bool ignore_lockdep:1;
  7. 34 struct lock_class_key *key;
  8. 35 struct lock_class_key skey;
  9. 36 #endif
  10. 37 };
  11. int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
  12. void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);

由于一个ktype往往包含很多属性(default_attr是一个二级指针),当用户通过sysfs读写一个kobject的属性的时候,会自动回调ktype中的sysfs_ops->show()sysfops->remove(),所以一个典型的做法是,当我们创建了一个继承自kobject的子类child后,同时还会创建两个调用了sysfs_create_file()sys_remove_file()的读写函数,并将它们注册到struct sysfs_ops中。比如内核使用的struct device就将相应的方法和属性都封装在了一起。

  1. //include/linux/device.h
  2. 512 /* interface for exporting device attributes */
  3. 513 struct device_attribute {
  4. 514 struct attribute attr;
  5. 515 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
  6. 516 char *buf);
  7. 517 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
  8. 518 const char *buf, size_t count);
  9. 519 };
  10. 560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
  11. 562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);

此外,内核甚至还提供了辅助定义这个属性的宏

  1. //include/linux/device.h
  2. 539 #define DEVICE_ATTR(_name, _mode, _show, _store) \
  3. 540 struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
  4. //include/linux/sysfs.h
  5. 75 #define __ATTR(_name, _mode, _show, _store) { \
  6. 76 .attr = {.name = __stringify(_name), \
  7. 77 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
  8. 78 .show = _show, \
  9. 79 .store = _store, \
  10. 80 }

有了这个宏,我们就可以直接通过这个接口创建我们自己的对象

  1. static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

我们可以追一下源码,可以发现,我们使用的自动创建设备文件device_create()就会调用device_create_file()并最终调用sysfs_create_file()

"drivers/base/core.c"

device_create()

   └── device_create_vargs()

            └── device_create_groups_vargs()

                        └── device_add()

                                    └── device_create_file()

                                                ├── "include/linux/sysfs.h"

                                                └── sysfs_create_file()

eg_0:

  1. #define to_dev(obj) container_of(obj, struct device, kobj)
  2. #define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
  3. static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
  4. char *buf)
  5. {
  6. struct device_attribute *dev_attr = to_dev_attr(attr);
  7. struct device *dev = to_dev(kobj);
  8. ssize_t ret = -EIO;
  9. if (dev_attr->show)
  10. ret = dev_attr->show(dev, dev_attr, buf);
  11. if (ret >= (ssize_t)PAGE_SIZE) {
  12. print_symbol("dev_attr_show: %s returned bad count\n",
  13. (unsigned long)dev_attr->show);
  14. }
  15. return ret;
  16. }

读写attribute

当一个子系统定义了一个新的属性,它必须执行一组针对的sysfs操作以便对实现对属性的读写,这些读写操作通过回调ktype.sysfs_ops.show()和store()

  1. //include/linux/sysfs.h
  2. 184 struct sysfs_ops {
  3. 185 ssize_t (*show)(struct kobject *, struct attribute *, char *);
  4. 186 ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
  5. 187 };

当进行读写的时候,sysfs会分配一个PAGE_SIZE大小的buf并把它作为参数传入这两个函数,同时,对于每一次对属性的读写操作,sysfs都会调用这两个函数,所以,调用read系统调用的时候,show()方法应该填满整个buf,注意一个属性应该是一个或一组相似的值,所以这种机制并不会浪费很多系统资源。这种机制允许用户读取一部分内容并且可以任意的移动文件位置指针,如果用户空间将文件指针置为0或以0为偏移量调用了pread()show()会被重新调用并且再填满一个buf。类似地,调用write()系统调用的时候,sysfs希望第一次传入的buf是被填满的,sysfs会在传入的数据最后自动加NUL,这可以让诸如sysfs_strqe()一类的函数用起来更安全。当对sysfs执行写操作时,用户空间应该首先读取整个文件的内容,按自己的需求改变其中的一部分并回写,属性读写操作应该使用同一个buf

tips:

  1. 通过read()/write()传递数据不同,这里的show()/store()里的buf已经是内核空间的了,不需要进行copy_to_user() etc
  2. 写操作会导致show方法重新执行而忽视当前文件位置指针的位置
  3. buf是PAGE_SIZE大小
  4. show()方法返回打印到buf的实际byte数,这个就是scnprintf()的返回值
  5. 在进行格式化打印到用户空间的时候,show必须用scnprintf()除非你能保证栈不会溢出
  6. stor应该返回buf中使用的数据的byte数目
  7. show或store应该设置合适的返回值确保安全

eg_1


  1. static ssize_t show_name(struct device *dev, struct device_attribute *attr,
  2. char *buf)
  3. {
  4. return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
  5. }
  6. static ssize_t store_name(struct device *dev, struct device_attribute *attr,
  7. const char *buf, size_t count)
  8. {
  9. snprintf(dev->name, sizeof(dev->name), "%.*s",
  10. (int)min(count, sizeof(dev->name) - 1), buf);
  11. return count;
  12. }
  13. static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);

内核已实现接口

内核中已经使用sysfs实现了很多的读写函数,下面是几个典型的

设备

  1. /* devices */
  2. /* structure */
  3. //include/linux/device.h)
  4. 512 /* interface for exporting device attributes */
  5. 513 struct device_attribute {
  6. 514 struct attribute attr;
  7. 515 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
  8. 516 char *buf);
  9. 517 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
  10. 518 const char *buf, size_t count);
  11. 519 };
  12. /* Declaring */
  13. 539 #define DEVICE_ATTR(_name, _mode, _show, _store) \
  14. 540 struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
  15. /* Creation/Removal */
  16. 560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
  17. 562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);

总线驱动

  1. /* bus drivers */
  2. /* Structure */
  3. //include/linux/device.h
  4. 44 struct bus_attribute {
  5. 45 struct attribute attr;
  6. 46 ssize_t (*show)(struct bus_type *bus, char *buf);
  7. 47 ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
  8. 48 };
  9. /* Declaring */
  10. 50 #define BUS_ATTR(_name, _mode, _show, _store) \
  11. 51 struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
  12. /* Creation/Removal */
  13. 57 extern int __must_check bus_create_file(struct bus_type *,struct bus_attribute *);
  14. 59 extern void bus_remove_file(struct bus_type *, struct bus_attribute *);

设备驱动

  1. /* device drivers */
  2. /* Structure */
  3. //include/linux/device.h
  4. 265 struct driver_attribute {
  5. 266 struct attribute attr;
  6. 267 ssize_t (*show)(struct device_driver *driver, char *buf);
  7. 268 ssize_t (*store)(struct device_driver *driver, const char *buf,
  8. 269 size_t count);
  9. 270 };
  10. /* Declaring */
  11. 272 #define DRIVER_ATTR(_name, _mode, _show, _store) \
  12. 273 struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
  13. /* Creation/Removal */
  14. 281 extern int __must_check driver_create_file(struct device_driver *driver,
  15. 282 const struct driver_attribute *attr);
  16. 283 extern void driver_remove_file(struct device_driver *driver,
  17. 284 const struct driver_attribute *attr);

彩蛋

Linux中几乎所有的"设备"都是"device"的子类,无论是平台设备还是i2c设备还是网络设备,但唯独字符设备不是,从"Linux字符设备驱动框架"一文中我们可以看出cdev并不是继承自device,从"Linux设备管理(二)_从cdev_add说起"一文中我们可以看出注册一个cdev对象到内核其实只是将它放到cdev_map中,直到"Linux设备管理(四)_从sysfs回到ktype"一文中对device_create的分析才知道此时才创建device结构并将kobj挂接到相应的链表,,所以,基于历史原因,当下cdev更合适的一种理解是一种接口(使用mknod时可以当作设备),而不是而一个具体的设备,和platform_device,i2c_device有着本质的区别

参考文档

Linux设备管理(四)_从sysfs回到ktype的更多相关文章

  1. Linux设备管理(四)_从sysfs回到ktype【转】

    转自:https://www.cnblogs.com/xiaojiang1025/archive/2016/12/21/6202298.html sysfs是一个基于ramfs的文件系统,在2.6内核 ...

  2. Linux设备管理(五)_写自己的sysfs接口

    我们在Linux设备管理(一)_kobject, kset,ktype分析一文中介绍了kobject的相关知识,在Linux设备管理(二)_从cdev_add说起和Linux设备管理(三)_总线设备的 ...

  3. Linux设备管理(一)_kobject, kset,ktype分析

    Linux内核大量使用面向对象的设计思想,通过追踪源码,我们甚至可以使用面向对象语言常用的UML类图来分析Linux设备管理的"类"之间的关系.这里以4.8.5内核为例从kobje ...

  4. Linux操作系统(四)_部署MySQL

    一.部署过程 1.当前服务器的内核版本和发行版本 cat /etc/issue uname -a 2.检查系统有没有自带mysql,并卸载自带版本 yum list installed | grep ...

  5. Linux设备管理(二)_从cdev_add说起

    我在Linux字符设备驱动框架一文中已经简单的介绍了字符设备驱动的基本的编程框架,这里我们来探讨一下Linux内核(以4.8.5内核为例)是怎么管理字符设备的,即当我们获得了设备号,分配了cdev结构 ...

  6. Linux设备管理(三)_总线设备的挂接

    扒完了字符设备,我们来看看平台总线设备,平台总线是Linux中的一种虚拟总线,我们知道,总线+设备+驱动是Linux驱动模型的三大组件,设计这样的模型就是将驱动代码和设备信息相分离,对于稍微复杂一点的 ...

  7. 基于samba实现win7与linux之间共享文件_阳仔_新浪博客

    基于samba实现win7与linux之间共享文件_阳仔_新浪博客 然后启动samba执行如下指令: /dev/init.d/smb start 至此完成全部配置.

  8. Linux操作系统学习_操作系统是如何工作的

    实验五:Linux操作系统是如何工作的? 学号:SA1****369 操作系统工作的基础:存储程序计算机.堆栈(函数调用堆栈)机制和中断机制 首先要整明白的一个问题是什么是存储程序计算机?其实存储程序 ...

  9. C#_02.13_基础四_.NET方法

    C#_02.13_基础四_.NET方法 一.方法概述: 方法是一块具有名称的代码.可以通过方法进行调用而在别的地方执行,也可以把数据传入方法并接受数据输出. 二.方法的结构: 方法头  AND  方法 ...

随机推荐

  1. ABP文档 - 通知系统

    文档目录 本节内容: 简介 发送模式 通知类型 通知数据 通知重要性 关于通知持久化 订阅通知 发布通知 用户通知管理器 实时通知 客户端 通知存储 通知定义 简介 通知用来告知用户系统里特定的事件发 ...

  2. 23种设计模式--单例模式-Singleton

    一.单例模式的介绍 单例模式简单说就是掌握系统的至高点,在程序中只实例化一次,这样就是单例模式,在系统比如说你是该系统的登录的第多少人,还有数据库的连接池等地方会使用,单例模式是最简单,最常用的模式之 ...

  3. Eclipse中启动tomcat报错java.lang.OutOfMemoryError: PermGen space的解决方法

    有的项目引用了太多的jar包,或者反射生成了太多的类,异或有太多的常量池,就有可能会报java.lang.OutOfMemoryError: PermGen space的错误, 我们知道可以通过jvm ...

  4. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  5. mybatis_基础篇

    一.认识mybatis: MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改 ...

  6. UWP开发之Mvvmlight实践七:如何查找设备(Mobile模拟器、实体手机、PC)中应用的Log等文件

    在开发中或者后期测试乃至最后交付使用的时候,如果应用出问题了我们一般的做法就是查看Log文件.上章也提到了查看Log文件,这章重点讲解下如何查看Log文件?如何找到我们需要的Packages安装包目录 ...

  7. JavaScript中事件处理

    先看看下面一道题目,请评价以下代码并给出改进意见: if (window.addEventListener) {//标准浏览器 var addListener = function(el, type, ...

  8. 利用Select2优化@Html.ListBoxFor显示,学会用MultiSelectList

    最近需要用到多选框,Asp.Net MVC自带的@Html.ListBox或@Html.ListBoxFor的显示效果太差,于是找到了Select2进行优化,并正式了解了多选框的操作方法. 首先介绍多 ...

  9. GOF23设计模式归类

    创建型模式:-单例模式.工厂模式.抽象工厂模式.建造者模式.原型模式结构型模式:-适配器模式.桥接模式.装饰模式.组合模式.外观模式.享元模式.代理模式行为型模式:-模板方法模式.命令模式.迭代器模式 ...

  10. SMBus set up a 2-byte EEPROM address for read/write

    Sequencer Engine spec: http://www.analog.com/media/en/technical-documentation/data-sheets/ADM1260.pd ...