前面我们分析了Linux设备模型中kobject、kset以及ktype的使用,它们是设备模型的最基础部分,在其上有更高级一层的bus、device和driver。在这一篇文章中,我们来看一下bus的用法。
 
一、相关数据结构
首先,我们列出本文涉及的相关数据结构。
[cpp] view plaincopy
  1. 51struct bus_type {
  2. 52    const char      *name;
  3. 53    struct bus_attribute    *bus_attrs;
  4. 54    struct device_attribute *dev_attrs;
  5. 55    struct driver_attribute *drv_attrs;
  6. 56
  7. 57    int (*match)(struct device *dev, struct device_driver *drv);
  8. 58    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
  9. 59    int (*probe)(struct device *dev);
  10. 60    int (*remove)(struct device *dev);
  11. 61    void (*shutdown)(struct device *dev);
  12. 62
  13. 63    int (*suspend)(struct device *dev, pm_message_t state);
  14. 64    int (*resume)(struct device *dev);
  15. 65
  16. 66    const struct dev_pm_ops *pm;
  17. 67
  18. 68    struct bus_type_private *p;
  19. 69};
  20. 38struct bus_attribute {
  21. 39    struct attribute    attr;
  22. 40    ssize_t (*show)(struct bus_type *bus, char *buf);
  23. 41    ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
  24. 42};
  25. 336/* interface for exporting device attributes */
  26. 337struct device_attribute {
  27. 338    struct attribute    attr;
  28. 339    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
  29. 340            char *buf);
  30. 341    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
  31. 342             const char *buf, size_t count);
  32. 343};
  33. 162struct driver_attribute {
  34. 163    struct attribute attr;
  35. 164    ssize_t (*show)(struct device_driver *driver, char *buf);
  36. 165    ssize_t (*store)(struct device_driver *driver, const char *buf,
  37. 166             size_t count);
  38. 167};
  39. 2/**
  40. 3 * struct bus_type_private - structure to hold the private to the driver core portions of the bus_type structure.
  41. 4 *
  42. 5 * @subsys - the struct kset that defines this bus.  This is the main kobject
  43. 6 * @drivers_kset - the list of drivers associated with this bus
  44. 7 * @devices_kset - the list of devices associated with this bus
  45. 8 * @klist_devices - the klist to iterate over the @devices_kset
  46. 9 * @klist_drivers - the klist to iterate over the @drivers_kset
  47. 10 * @bus_notifier - the bus notifier list for anything that cares about things
  48. 11 * on this bus.
  49. 12 * @bus - pointer back to the struct bus_type that this structure is associated
  50. 13 * with.
  51. 14 *
  52. 15 * This structure is the one that is the actual kobject allowing struct
  53. 16 * bus_type to be statically allocated safely.  Nothing outside of the driver
  54. 17 * core should ever touch these fields.
  55. 18 */
  56. 19struct bus_type_private {
  57. 20    struct kset subsys;
  58. 21    struct kset *drivers_kset;
  59. 22    struct kset *devices_kset;
  60. 23    struct klist klist_devices;
  61. 24    struct klist klist_drivers;
  62. 25    struct blocking_notifier_head bus_notifier;
  63. 26    unsigned int drivers_autoprobe:1;
  64. 27    struct bus_type *bus;
  65. 28};
  66. 406struct device {
  67. 407    struct device       *parent;
  68. 408
  69. 409    struct device_private   *p;
  70. 410
  71. 411    struct kobject kobj;
  72. 412    const char      *init_name; /* initial name of the device */
  73. 413    struct device_type  *type;
  74. 414
  75. 415    struct mutex        mutex;  /* mutex to synchronize calls to
  76. 416                     * its driver.
  77. 417                     */
  78. 418
  79. 419    struct bus_type *bus;       /* type of bus device is on */
  80. 420    struct device_driver *driver;   /* which driver has allocated this
  81. 421                       device */
  82. 422    void        *platform_data; /* Platform specific data, device
  83. 423                       core doesn't touch it */
  84. 424    struct dev_pm_info  power;
  85. 425
  86. 426#ifdef CONFIG_NUMA
  87. 427    int     numa_node;  /* NUMA node this device is close to */
  88. 428#endif
  89. 429    u64     *dma_mask;  /* dma mask (if dma'able device) */
  90. 430    u64     coherent_dma_mask;/* Like dma_mask, but for
  91. 431                         alloc_coherent mappings as
  92. 432                         not all hardware supports
  93. 433                         64 bit addresses for consistent
  94. 434                         allocations such descriptors. */
  95. 435
  96. 436    struct device_dma_parameters *dma_parms;
  97. 437
  98. 438    struct list_head    dma_pools;  /* dma pools (if dma'ble) */
  99. 439
  100. 440    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
  101. 441                         override */
  102. 442    /* arch specific additions */
  103. 443    struct dev_archdata archdata;
  104. 444#ifdef CONFIG_OF
  105. 445    struct device_node  *of_node;
  106. 446#endif
  107. 447
  108. 448    dev_t           devt;   /* dev_t, creates the sysfs "dev" */
  109. 449
  110. 450    spinlock_t      devres_lock;
  111. 451    struct list_head    devres_head;
  112. 452
  113. 453    struct klist_node   knode_class;
  114. 454    struct class        *class;
  115. 455    const struct attribute_group **groups;  /* optional groups */
  116. 456
  117. 457    void    (*release)(struct device *dev);
  118. 458};
  119. 123struct device_driver {
  120. 124    const char      *name;
  121. 125    struct bus_type     *bus;
  122. 126
  123. 127    struct module       *owner;
  124. 128    const char      *mod_name;  /* used for built-in modules */
  125. 129
  126. 130    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */
  127. 131
  128. 132#if defined(CONFIG_OF)
  129. 133    const struct of_device_id   *of_match_table;
  130. 134#endif
  131. 135
  132. 136    int (*probe) (struct device *dev);
  133. 137    int (*remove) (struct device *dev);
  134. 138    void (*shutdown) (struct device *dev);
  135. 139    int (*suspend) (struct device *dev, pm_message_t state);
  136. 140    int (*resume) (struct device *dev);
  137. 141    const struct attribute_group **groups;
  138. 142
  139. 143    const struct dev_pm_ops *pm;
  140. 144
  141. 145    struct driver_private *p;
  142. 146};
二、注册bus
bus的注册接口为bus_register,该函数代码如下:
[cpp] view plaincopy
  1. 872/**
  2. 873 * bus_register - register a bus with the system.
  3. 874 * @bus: bus.
  4. 875 *
  5. 876 * Once we have that, we registered the bus with the kobject
  6. 877 * infrastructure, then register the children subsystems it has:
  7. 878 * the devices and drivers that belong to the bus.
  8. 879 */
  9. 880int bus_register(struct bus_type *bus)
  10. 881{
  11. 882    int retval;
  12. 883    struct bus_type_private *priv;
  13. 884
  14. 885    priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
  15. 886    if (!priv)
  16. 887        return -ENOMEM;
  17. 888
  18. 889    priv->bus = bus;
  19. 890    bus->p = priv;
  20. 891
  21. 892    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
  22. 893
  23. 894    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
  24. 895    if (retval)
  25. 896        goto out;
  26. 897
  27. 898    priv->subsys.kobj.kset = bus_kset;
  28. 899    priv->subsys.kobj.ktype = &bus_ktype;
  29. 900    priv->drivers_autoprobe = 1;
  30. 901
  31. 902    retval = kset_register(&priv->subsys);
  32. 903    if (retval)
  33. 904        goto out;
  34. 905
  35. 906    retval = bus_create_file(bus, &bus_attr_uevent);
  36. 907    if (retval)
  37. 908        goto bus_uevent_fail;
  38. 909
  39. 910    priv->devices_kset = kset_create_and_add("devices", NULL,
  40. 911                         &priv->subsys.kobj);
  41. 912    if (!priv->devices_kset) {
  42. 913        retval = -ENOMEM;
  43. 914        goto bus_devices_fail;
  44. 915    }
  45. 916
  46. 917    priv->drivers_kset = kset_create_and_add("drivers", NULL,
  47. 918                         &priv->subsys.kobj);
  48. 919    if (!priv->drivers_kset) {
  49. 920        retval = -ENOMEM;
  50. 921        goto bus_drivers_fail;
  51. 922    }
  52. 923
  53. 924    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
  54. 925    klist_init(&priv->klist_drivers, NULL, NULL);
  55. 926
  56. 927    retval = add_probe_files(bus);
  57. 928    if (retval)
  58. 929        goto bus_probe_files_fail;
  59. 930
  60. 931    retval = bus_add_attrs(bus);
  61. 932    if (retval)
  62. 933        goto bus_attrs_fail;
  63. 934
  64. 935    pr_debug("bus: '%s': registered\n", bus->name);
  65. 936    return 0;
  66. 937
  67. 938bus_attrs_fail:
  68. 939    remove_probe_files(bus);
  69. 940bus_probe_files_fail:
  70. 941    kset_unregister(bus->p->drivers_kset);
  71. 942bus_drivers_fail:
  72. 943    kset_unregister(bus->p->devices_kset);
  73. 944bus_devices_fail:
  74. 945    bus_remove_file(bus, &bus_attr_uevent);
  75. 946bus_uevent_fail:
  76. 947    kset_unregister(&bus->p->subsys);
  77. 948out:
  78. 949    kfree(bus->p);
  79. 950    bus->p = NULL;
  80. 951    return retval;
  81. 952}
894行,设置priv->subsys.kobj的名字,priv->subsys是一个kset,代表这个bus。bus是通过一个kset来表示的,而device和device_driver都是通过一个kobject来表示的,由此也可以看出他们的不同。
898行,设置priv->subsys.kobj.kset为bus_kset;
在drivers/base/bus.c文件中,162行有如下定义:
[cpp] view plaincopy
  1. 162static struct kset *bus_kset;
同时,在buses_init函数中对bus_kset进行了初始化:
[cpp] view plaincopy
  1. 1054int __init buses_init(void)
  2. 1055{
  3. 1056    bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
  4. 1057    if (!bus_kset)
  5. 1058        return -ENOMEM;
  6. 1059    return 0;
  7. 1060}
1056行,创建一个名为bus的kset,赋值给bus_kset,对应/sys/bus目录。所有的bus都属性bus_set,即所有的bus对应的sys系统目录都放在/sys/bus目录下。
bus_kset对应的kset_uevent_ops是bus_uevent_ops,其定义如下:
[cpp] view plaincopy
  1. 158static const struct kset_uevent_ops bus_uevent_ops = {
  2. 159    .filter = bus_uevent_filter,
  3. 160};
bus_uevent_filter函数定义如下:
[cpp] view plaincopy
  1. 149static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
  2. 150{
  3. 151    struct kobj_type *ktype = get_ktype(kobj);
  4. 152
  5. 153    if (ktype == &bus_ktype)
  6. 154        return 1;
  7. 155    return 0;
  8. 156}
回忆一下上一篇博客《Linux设备模型分析之kset》介绍的内容,如果kset_uevent_ops.filter函数返回0,将不会处理uevent事件。在bus_uevent_filter函数中,如果kobj->ktype不是bus_ktype,则返回0,即不处理Uevent事件。
bus_ktype定义如下:
[cpp] view plaincopy
  1. 145static struct kobj_type bus_ktype = {
  2. 146    .sysfs_ops  = &bus_sysfs_ops,
  3. 147};
bus_sysfs_ops定义如下:
[cpp] view plaincopy
  1. 119static const struct sysfs_ops bus_sysfs_ops = {
  2. 120    .show   = bus_attr_show,
  3. 121    .store  = bus_attr_store,
  4. 122};
bus_attr_show和bus_attr_store函数定义如下:
[cpp] view plaincopy
  1. 95static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,
  2. 96                 char *buf)
  3. 97{
  4. 98    struct bus_attribute *bus_attr = to_bus_attr(attr);
  5. 99    struct bus_type_private *bus_priv = to_bus(kobj);
  6. 100    ssize_t ret = 0;
  7. 101
  8. 102    if (bus_attr->show)
  9. 103        ret = bus_attr->show(bus_priv->bus, buf);
  10. 104    return ret;
  11. 105}
  12. 106
  13. 107static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,
  14. 108                  const char *buf, size_t count)
  15. 109{
  16. 110    struct bus_attribute *bus_attr = to_bus_attr(attr);
  17. 111    struct bus_type_private *bus_priv = to_bus(kobj);
  18. 112    ssize_t ret = 0;
  19. 113
  20. 114    if (bus_attr->store)
  21. 115        ret = bus_attr->store(bus_priv->bus, buf, count);
  22. 116    return ret;
  23. 117}
bus_attribute结构体定义如下:
[cpp] view plaincopy
  1. 38struct bus_attribute {
  2. 39    struct attribute    attr;
  3. 40    ssize_t (*show)(struct bus_type *bus, char *buf);
  4. 41    ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
  5. 42};
回到bus_register函数:
899行,设置priv->subsys.kobj.ktype为 &bus_ktype;回忆一下前面的文章《Linux设备模型分析之kobject》,对kobject属性的读写操作将调用kobject.ktype定义的sysfs_ops函数集指定的show和store函数。所以对bus属性文件的读写操作将会调用bus_ktyp.sysfs_ops指定的bus_attr_show和bus_attr_store函数,这两个函数我们前面已经分析过了,他们将回溯到bus_attr->show和bus_attr->store函数。
900行,设置priv->drivers_autoprobe为1;
902行,调用kset_register注册priv->subsys,即注册代表bus的kset。注册完成后,将在/sys/bus目录下出现对应该bus的目录。
906行,调用bus_create_file(bus, &bus_attr_uevent),创建uevent属性文件,该函数定义如下:
[cpp] view plaincopy
  1. 124int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
  2. 125{
  3. 126    int error;
  4. 127    if (bus_get(bus)) {
  5. 128        error = sysfs_create_file(&bus->p->subsys.kobj, &attr->attr);
  6. 129        bus_put(bus);
  7. 130    } else
  8. 131        error = -EINVAL;
  9. 132    return error;
  10. 133}
128行,调用sysfs_create_file函数创建了bus_attribute对应的属性文件。
先来看bus_attr_uevent的定义,它是通过宏BUS_ATTR生成的:
[cpp] view plaincopy
  1. 870static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);
BUS_ATTR定义如下:
[cpp] view plaincopy
  1. 44#define BUS_ATTR(_name, _mode, _show, _store)   \
  2. 45struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
__ATTR定义如下:
[cpp] view plaincopy
  1. 70#define __ATTR(_name,_mode,_show,_store) { \
  2. 71    .attr = {.name = __stringify(_name), .mode = _mode },   \
  3. 72    .show   = _show,                    \
  4. 73    .store  = _store,                   \
  5. 74}
从bus_attr_uevent的定义可以看出,对应的uevent属性只有store接口,没有实现show接口。store接口函数bus_uevent_store定义如下:
[cpp] view plaincopy
  1. 861static ssize_t bus_uevent_store(struct bus_type *bus,
  2. 862                const char *buf, size_t count)
  3. 863{
  4. 864    enum kobject_action action;
  5. 865
  6. 866    if (kobject_action_type(buf, count, &action) == 0)
  7. 867        kobject_uevent(&bus->p->subsys.kobj, action);
  8. 868    return count;
  9. 869}
回到bus_register函数:
910 - 915行,创建名为devices的kset,其parent是priv->subsys.kobj,即在相应bus目录下创建一个devices目录。
917 - 922行,创建名为drivers的kset,其parent是priv->subsys.kobj,即在相应bus目录下创建一个drivers目录。
924行,初始化priv->klist_devices,即devices链表。
925行,初始化priv->klist_drivers,即drivers链表。
927行,调用add_probe_files(bus)函数创建drivers_probe和drivers_autoprobe两个属性文件,该函数定义如下:
[cpp] view plaincopy
  1. 606static int add_probe_files(struct bus_type *bus)
  2. 607{
  3. 608    int retval;
  4. 609
  5. 610    retval = bus_create_file(bus, &bus_attr_drivers_probe);
  6. 611    if (retval)
  7. 612        goto out;
  8. 613
  9. 614    retval = bus_create_file(bus, &bus_attr_drivers_autoprobe);
  10. 615    if (retval)
  11. 616        bus_remove_file(bus, &bus_attr_drivers_probe);
  12. 617out:
  13. 618    return retval;
  14. 619}
bus_create_file函数我们前面已经分析过了,610行,创建drivers_probe属性文件,614行,创建drivers_autoprobe属性文件。其对应的bus_attribute定义如下:
[cpp] view plaincopy
  1. 602static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
  2. 603static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
  3. 604        show_drivers_autoprobe, store_drivers_autoprobe);
属性文件对应的操作函数定义如下:
[cpp] view plaincopy
  1. 225static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf)
  2. 226{
  3. 227    return sprintf(buf, "%d\n", bus->p->drivers_autoprobe);
  4. 228}
  5. 229
  6. 230static ssize_t store_drivers_autoprobe(struct bus_type *bus,
  7. 231                       const char *buf, size_t count)
  8. 232{
  9. 233    if (buf[0] == '0')
  10. 234        bus->p->drivers_autoprobe = 0;
  11. 235    else
  12. 236        bus->p->drivers_autoprobe = 1;
  13. 237    return count;
  14. 238}
  15. 239
  16. 240static ssize_t store_drivers_probe(struct bus_type *bus,
  17. 241                   const char *buf, size_t count)
  18. 242{
  19. 243    struct device *dev;
  20. 244
  21. 245    dev = bus_find_device_by_name(bus, NULL, buf);
  22. 246    if (!dev)
  23. 247        return -ENODEV;
  24. 248    if (bus_rescan_devices_helper(dev, NULL) != 0)
  25. 249        return -EINVAL;
  26. 250    return count;
  27. 251}
show_drivers_autoprobe函数用于显示bus->p->drivers_autoprobe的值。
store_drivers_autoprobe函数根据用户的要求设置bus->p->drivers_autoprobe的值。
store_drivers_probe函数245行调用bus_find_device_by_name 在bus上查找用户指定的device,并且在248行调用bus_rescan_devices_helper函数查找匹配device_driver.
回到bus_register函数:
931行,调用bus_add_attrs函数创建bus默认属性文件,该函数定义如下:
[cpp] view plaincopy
  1. 810/**
  2. 811 * bus_add_attrs - Add default attributes for this bus.
  3. 812 * @bus: Bus that has just been registered.
  4. 813 */
  5. 814
  6. 815static int bus_add_attrs(struct bus_type *bus)
  7. 816{
  8. 817    int error = 0;
  9. 818    int i;
  10. 819
  11. 820    if (bus->bus_attrs) {
  12. 821        for (i = 0; attr_name(bus->bus_attrs[i]); i++) {
  13. 822            error = bus_create_file(bus, &bus->bus_attrs[i]);
  14. 823            if (error)
  15. 824                goto err;
  16. 825        }
  17. 826    }
  18. 827done:
  19. 828    return error;
  20. 829err:
  21. 830    while (--i >= 0)
  22. 831        bus_remove_file(bus, &bus->bus_attrs[i]);
  23. 832    goto done;
  24. 833}
820 - 826行,如果bus->bus_attrs指定了bus的默认属性,则创建对应的属性文件。

Linux设备模型分析之bus相关推荐

  1. Linux设备模型分析之kobject(基于3.10.1内核)

    一.kobject结构定义 kobject是Linux设备模型的最底层数据结构,它代表一个内核对象. kobject结构体定义在include/linux/kobject.h文件中: [cpp] vi ...

  2. Linux设备模型分析之kobject

    一.kobject应用举例 Linux设备模型最基本的组成元素是kobject,我们先来看一个kobject的应用例子,该程序在Ubuntu 10.10, 2.6.32-38-generic-pae内 ...

  3. Linux设备模型分析之kset

    上一篇博客我们分析了Linux设备模型中kobject的注册和使用,在这一篇文章中,我们来看一下kset的用法. 首先我们看一个使用kset的例子,代码如下: [cpp] view plaincopy ...

  4. linux kset subsystem 3.10内核,Linux设备模型分析之kset(基于3.10.1内核)

    作者:刘昊昱 内核版本:3.10.1 一.kset结构定义 kset结构体定义在include/linux/kobject.h文件中,其内容如下: 142/** 143 * struct kset - ...

  5. Linux设备模型分析之kset(基于3.10.1内核)

    一.kset结构定义 kset结构体定义在include/linux/kobject.h文件中,其内容如下: [cpp] view plaincopy 142/** 143 * struct kset ...

  6. Linux通常把设备对象抽象为,linux 设备模型(1)

    设备模型(一) 一.概述 从2.6内核引入了sysfs文件系统,与proc, devfs, devpty同类别,属于虚拟的文件系统.目的是展示设备驱动模型中各组件的层次关系,第一层目录:block, ...

  7. linux的层次结构模型,linux 设备模型(1)

    设备模型(一) 一.概述 从2.6内核引入了sysfs文件系统,与proc, devfs, devpty同类别,属于虚拟的文件系统.目的是展示设备驱动模型中各组件的层次关系,第一层目录:block, ...

  8. Linux设备模型_导航篇

    Linux设备模型_导航篇 1. Linux设备模型系列文章说明 2. Linux设备模型系列文章目录 2.0 [Linux内核的整体架构](https://zhuanlan.zhihu.com/p/ ...

  9. linux设备模型:bus概念及pci_bus分析

    上一篇<<linux设备模型:kset及设备驱动抽象类(class)分析>>中分析了kset容器.class设备驱动抽象框架.class_kset用于类(class)的热插拔事 ...

  10. linux设备模型之kset/kobj/ktype分析

    1. 概述 今天来聊一下Linux设备模型的基石:kset/kobject/ktype. sysfs文件系统提供了一种用户与内核数据结构进行交互的方式,可以通过mount -t sysfs sysfs ...

最新文章

  1. 4-1 ADO.NET简介
  2. webkit qt版快速编译 支持wml版本
  3. 【存储知识学习】第三章磁盘原理与技术3.4硬盘接口技术和SCSI硬盘接口--《大话存储》阅读笔记
  4. docusign文档打不开_怎样查看 docusign pdf 电子签名
  5. git工作中常用命令
  6. HISI3536安装交叉编译工具链
  7. python学习第七天--文件系统常用模块os,os.path,pickle
  8. 早教机器人刷固件_父母不要再盲目去买智能儿童早教机器人,知道这几点事半功倍!...
  9. win10 vs2010安装教程(超详细,附下载链接)
  10. EditPlus中文绿色破解版
  11. 英特尔it服务器芯片,intel服务器芯片组驱动程序
  12. 关键字Wait Until Keyword Succeeds
  13. 基于FPGA的卷积神经网络加速器(绪论)
  14. 格律诗的基本知识【一小时学会写格律诗】
  15. 万字总结,行业分析到底应该怎么做!
  16. 【吐槽大会】互联网大厂秋招版
  17. 活动图(Activity Diagram)—UML图(四)
  18. 预备作业:有关技能以及“做中学”
  19. Java函数式编程(基础):第一部分
  20. AIX系统管理(一)

热门文章

  1. Github Actions生成 secrets
  2. 山寨AR手游频出,VRAR正打造一庞大…
  3. Pycharm使用技巧:Split Vertically/Horizontally(垂直/水平拆分窗口)
  4. P5208-[WC2019] I 君的商店【交互,二分】
  5. Unity中游戏的存档与读档
  6. FineReport 11.0 帆软报表 授权文件 补丁
  7. 揭秘中国球员十大豪宅
  8. [转]Cookie详解
  9. Android 使用讯飞语音SDK
  10. 远程控制android盒子,电视盒子ADB教程 通过ADB远程安装应用方法(2)