在 Linux内核输入子系统框架_Bin Watson的博客-CSDN博客 这篇文章中,我们详细分析了输入子系统。了解到了 dev 和 handler 分层的思想。而在 jz2440_输入子系统驱动程序_Bin Watson的博客-CSDN博客 这篇文章中,我们实际操作编写了一个基于输入子系统的按键驱动程序。

而这种分层思想,在内核中是非常常见的。我们也可以实现我们自己的分层驱动程序,这种驱动模型称为 bus-drv-dev 模式,总线驱动设备模型。

总线驱动程序与具体设备的驱动程序相比,总线驱动程序与核心内核代码的工作要密切得多。另外,总线驱动程序向相关的设备驱动程序提供功能和选项的方式,也不存在标准的接口。这是因为,不同的总线系统之间,使用的硬件技术可能差异很大。但这并不意味着,负责管理不同总线的代码没有共同点。相似的总线采用相似的概念,还引入了通用驱动程序模型,在一个主要数据结构的集合中管理所有系统总线,采用最小公分母的方式,尽可能降低不同总线驱动程序之间的差异。

内核支持大量总线,可能涉及多种硬件平台,也有可能只涉及一种平台。所以我不可能详细地讨论所有版本,这里我们只会仔细讨论 PCI 总线。因为其设计相对现代,而且具备一种强大的系统总线所应有的所有共同和关键要素。此外,在Linux支持的大多数体系结构上都使用了 PCI 总线。我还会讨论广泛使用、系统无关的的 USB 总线,该总线用于外设。

1. 总线驱动设备框架

总线设备驱动模式由三个部分组成:

  1. 虚拟总线 bus;
  2. 设备 device;
  3. 驱动 driver;

bus:总线是处理器和设备之间的通道。总线有多种类型,每种总线可以挂载多个设备。
在设备模型中,所有的设备都通过总线相连,以总线来管理设备和驱动函数。总线在内核中由 bus_type 结构体表示。

device:设备就是连接在总线上的物理实体。设备是有功能之分的。具有相同功能的设备被归到一个类,如输入设备(鼠标,键盘,游戏杆等)。Linux 系统中每个设备都用一个 device 结构体的表示。

当添加一个新的 device 时,会执行下列操作:

  1. 把 device 放入 bus 的 dev 链表上;
  2. 从 bus 的 drv 链表取出每个 driver,用 bus 的 .match 函数判断 drv 能否支持 dev;
  3. 若可以支持,则调用 drv 的 .probe 函数。

driver:驱动程序是在 CPU 运行时,提供操作的软件接口。所有的设备必须有与之配套驱动程序才能正常工作。一个驱动程序可以驱动多个类似或者完全不同的设备。

当添加一个新的 driver 时,会执行下列操作:

  1. 把 driver 放入 bus 的 drv 链表上;
  2. 从 bus 的 dev 链表取出每个 device,用 bus 的 .match 函数判断 drv 能否支持 dev;
  3. 若可以支持,则调用 drv 的 .probe 函数。

总线 bus 上有两条链表,分别挂载当前总线上所有的 device 和所有的 driver。当注册(添加 device_register/driver_register)一个新的 device/driver 到总线上时,会触发总线上的 .match 函数被调用,进行该 device/driver 去和另一个链表 driver/device 上的设备进行匹配,若匹配成功就会调用 driver 中的 .probe 函数。

注意:.probe 函数是提供一种将 device 和 driver 连接起来的机制。而具体 .probe 函数如何实现,是否要记录 device 的信息?这些操作都由实现者去决定,并不是强制的。

2. 通用总线模型

2.1 数据结构定义

device

<device.h>

struct device { struct klist klist_children; struct klist_node knode_parent; /* 兄弟结点链表中的结点 */ struct klist_node knode_driver; struct klist_node knode_bus; struct device * parent;struct kobject kobj; char bus_id[BUS_ID_SIZE];         /* 在父总线上的位置 */ /*...省略...*/ struct bus_type * bus;          /* 所在总线设备的类型 */ struct device_driver *driver;   /* 分配当前device实例的驱动程序 */ void *driver_data;              /* 驱动程序的私有数据 */ void *platform_data;            /* 特定于平台的数据,设备模型代码不会访问 */ /*...省略...*/ void (*release)(struct device * dev);
};
  • klist 和 klist_node 数据结构是我们熟悉的 list_head 数据结构的增强版,其中增加了与锁和引用计数相关的成员。

    klist 是一个表头,而 klist_node 是一个链表元素。这种类型的链表只用于通用设备模型,内核的其余部分不会使用。

  • 嵌入的 kobject 控制通用对象属性。

  • 有一些成员用于建立设备之间的层次关系

    klist_children 是一个链表的表头,该链表包含了指定设备的所有子设备。如果设备包含于父设备的 klist_children 链表中,则将 knode_parent 用作链表元素。

    parent 指向父结点的 device 实例。

    因为一个设备驱动程序能够服务多个设备(例如,系统中安装了两个相同的扩展卡),

  • knode_driver 用作链表元素,用于将所有被同一驱动程序管理的设备连接到一个链表中。

  • driver 指向控制该设备的设备驱动程序的数据结构(下面的成员包括更多相关信息)。

  • bus_id 唯一指定了该设备在宿主总线上的位置(不同总线类型使用的格式也会有所不同)。例如,设备在 PCI 总线上的位置由一个具有以下格式的字符串唯一地定义:<总线编号>:<插槽编号>.<功能编号>。

  • bus 是一个指针,指向该设备所在总线(更多信息见下文)的数据结构的实例。

  • driver_data 是驱动程序的私有成员,不能由通用代码修改。它可用于指向与设备协作必需、但又无法融入到通用方案的特定数据。platform_data 和 firmware_data 也是私有成员,可用于将特定于体系结构的数据和固件信息关联到设备。通用驱动程序模型也不会访问这些数据。

  • release 是一个析构函数,用于在设备(或device实例)不再使用时,将分配的资源释放回内核。

内核提供了标准函数 device_register,用于将一个新设备添加到内核的数据结构。该函数在下文讨论。device_get 和 device_put 一对函数用于引用计数。

driver

<driver.h>

struct device_driver { const char * name;struct bus_type * bus;struct kobject kobj; struct klist klist_devices; struct klist_node knode_bus; /*...省略...*/ int (*probe) (struct device * dev); int (*remove) (struct device * dev); void (*shutdown) (struct device * dev); int (*suspend) (struct device * dev, pm_message_t state); int (*resume) (struct device * dev);
};

各个成员的语义如下。

  • name 指向一个正文串,用于唯一标识该驱动程序。

  • bus 指向一个表示总线的对象,并提供特定于总线的操作(更详细的内容,请参见下文)。

  • klist_devices 是一个标准链表的表头,其中包括了该驱动程序控制的所有设备的 device 实例。链表中的各个设备通过 device->knode_driver 彼此连接。

  • knode_bus 用于连接一条公共总线上的所有设备。

  • probe 是一个函数,用于检测系统中是否存在能够用该设备驱动程序处理的设备。

  • 删除系统中的设备时会调用 remove。

  • shutdown、suspend 和 resume 用于电源管理。

驱动程序使用内核的标准函数 driver_register 注册到系统中,该函数在下文讨论。

通用驱动程序模型不仅表示了设备,还用另一个数据结构表示了总线,定义如下:

bus

<device.h>

struct bus_type { const char * name;/*...省略...*/  struct kset subsys; struct kset drivers; struct kset devices; struct klist klist_devices; struct klist klist_drivers; /*...省略...*/  int (*match)(struct device * dev, struct device_driver * drv); int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device * dev); int (*remove)(struct device * dev); void (*shutdown)(struct device * dev); int (*suspend)(struct device * dev, pm_message_t state); /*...省略...*/  int (*resume)(struct device * dev); /*...省略...*/
};
  • name 是总线的文本名称。特别地,它用于在 sysfs 文件系统中标识该总线。

  • 与总线关联的所有设备和驱动程序,使用 drivers 和 devices 成员,作为集合进行管理。

  • 内核创建两个链表(klist_devices 和 klist_drivers )来保存相同的数据。这些链表使内核能够快速扫描所有资源(设备和驱动程序)

  • subsys 提供与总线子系统的关联。对应的总线出现在 /sys/bus/busname。

  • match 指向一个函数,试图查找与给定设备匹配的驱动程序。

  • add 用于通知总线新设备已经添加到系统。

  • 在有必要将驱动程序关联到设备时,会调用 probe。该函数检测设备在系统中是否真正存在。

  • remove 删除驱动程序和设备之间的关联。例如,在将可热插拔的设备从系统中移除时,会调用该函数。

  • shutdown、suspend 和 resume 函数用于电源管理。

2.2 注册

注册总线

在可以注册设备及其驱动程序之前,需要有总线。因此我们从 bus_register 开始,该函数向系统添加一个新总线。

首先,通过嵌入的 kset 类型成员 subsys,将新总线添加到总线子系统:

drivers/base/bus.c

int bus_register(struct bus_type * bus)
{ int retval; retval = kobject_set_name(&bus->subsys.kobj, "%s", bus->name); bus->subsys.kobj.kset = &bus_subsys; retval = subsystem_register(&bus->subsys); /*...省略...*/
};

总线需要了解相关设备及其驱动程序的所有有关信息,因此总线对二者注册了 kset。

两个 kset 分别是 drivers 和 devices,都将总线作为父结点:

drivers/base/bus.c

int bus_register(struct bus_type * bus)
{ /*...省略...*/  kobject_set_name(&bus->devices.kobj, "devices"); bus->devices.kobj.parent = &bus->subsys.kobj; retval = kset_register(&bus->devices); kobject_set_name(&bus->drivers.kobj, "drivers"); bus->drivers.kobj.parent = &bus->subsys.kobj; bus->drivers.ktype = &driver_ktype; retval = kset_register(&bus->drivers); /*...省略...*/
}

注册设备

注册设备包括两个独立的步骤,如图6-22所示。具体是:初始化设备的数据结构,并将其加入到数据结构的网络中。

  • device_initialize主 要通过 kobj_set_kset_s(dev, devices_subsys) 将新设备添加到设备子系统

  • device_add 首先,将通过 device->parent 指定的父子关系转变为一般的内核对象层次结构

    int device_add(struct device *dev)
    { struct device *parent = NULL; /*...省略...*/ parent = get_device(dev->parent); kobj_parent = get_device_parent(dev, parent); dev->kobj.parent = kobj_parent; /*...省略...*/
    }
    

    在设备子系统中注册该设备只需要调用一次 kobject_add,因为在 device_initialize 中已经将该设备设置为子系统的成员了。

    int device_add(struct device *dev)
    { /*...省略...*/ kobject_set_name(&dev->kobj, "%s", dev->bus_id); error = kobject_add(&dev->kobj); /*...省略...*/
    }
    

    然后调用 bus_add_device 在 sysfs 中添加两个链接:一个在总线目录下指向设备,另一个在设备的目录下指向总线子系统。 bus_attach_device 试图自动探测设备。如果能够找到适当的驱动程序,则将设备添加到 bus->klist_devices。设备还需要添加到父结点的子结点链表中(此前,设备知道其父结点,但父结点不知道该子结点的存在)。

    int device_add(struct device *dev)
    { /*...省略...*/error = bus_add_device(dev); bus_attach_device(dev); if (parent) klist_add_tail(&dev->knode_parent, &parent->klist_children); /*...省略...*/
    }
    

注册设备驱动程序

在进行一些检查和初始化工作之后,driver_register 调用 bus_add_driver 将一个新驱动程序添加到一个总线。同样,驱动程序首先要有名字,然后注册到通用数据结构的框架中:

drivers/base/bus.c

int bus_add_driver(struct device_driver *drv)
{ struct bus_type * bus = bus_get(drv->bus); int error = 0; /*...省略...*/ error = kobject_set_name(&drv->kobj, "%s", drv->name); drv->kobj.kset = &bus->drivers; error = kobject_register(&drv->kobj); /*...省略...*/
}

如果总线支持自动探测,则调用 driver_attach。该函数迭代总线上的所有设备,使用驱动程序的 match 函数进行检测,确定是否有某些设备可使用该驱动程序管理。

最后,将该驱动程序添加到总线上注册的所有驱动程序的链表中。

drivers/base/bus.

int bus_add_driver(struct device_driver *drv)
{ /*...省略...*/ if (drv->bus->drivers_autoprobe) /* 该函数迭代总线上的所有设备,* 使用驱动程序的 match 函数进行检测,* 确定是否有某些设备可使用该驱动程序管理 */error = driver_attach(drv); /*...省略...*/ /* 将驱动程序添加到总线上注册的所有驱动程序的链表中 */klist_add_tail(&drv->knode_bus, &bus->klist_drivers); /*...省略...*/
}

3. 平台设备

在 Linux 内核中有多种设备类型,如:PCI、USB 等,其中一种为平台设备。

平台设备的总线是 platform_bus_type,是基于通用总线 bus_type 实现的一种内核中已经定义好的了的总线。

3.1 平台设备结构

总线

platform_bus_type 定义如下:

struct bus_type platform_bus_type = {.name      = "platform",.dev_attrs  = platform_dev_attrs,.match        = platform_match,.uevent       = platform_uevent,.suspend = platform_suspend,.suspend_late   = platform_suspend_late,.resume_early  = platform_resume_early,.resume        = platform_resume,
};

设备:平台设备的数据结构是 platform_device,对 device 进行了扩展:

struct platform_device {const char   * name;u32      id;struct device    dev;u32     num_resources;      /* resource这数组的长度 */struct resource * resource; /* 资源数组,用于记录该设备所具有的硬件资源 */
};
  • resource:用于记录设备的资源

    struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;struct resource *parent, *sibling, *child;
    };
    

    一个独立的挂接在 CPU 总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身。
    Linux 采用 struct resource 结构体来描述一个挂接在 CPU 总线上的设备实体(32位 CPU 的总线地址范围是 0~4G):

    • start:描述设备实体在 CPU 总线上的线性起始物理地址;
    • end:描述设备实体在 CPU 总线上的线性结尾物理地址;
    • name:描述这个设备(资源)实体的名称。这个名字开发人员可以随意起,但最好贴切;
    • flag:描述这个设备(资源)实体的一些共性和特性的标志位;

驱动:平台设备的数据结构是 platform_driver,对 device_driver 进行了扩展:

struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*suspend_late)(struct platform_device *, pm_message_t state);int (*resume_early)(struct platform_device *);int (*resume)(struct platform_device *);struct device_driver driver;
};

3.2 驱动注册函数

platform_driver_register 函数分析

我们以 gpio_keys.c 为例:

在其 init 函数中,通过 platform_driver_register 函数注册一个平台设备的驱动:

static int __init gpio_keys_init(void)
{return platform_driver_register(&gpio_keys_device_driver);
}
  • gpio_keys_device_driver 的实现如下:

    struct platform_driver gpio_keys_device_driver = {.probe      = gpio_keys_probe,.remove      = __devexit_p(gpio_keys_remove),.driver        = {.name   = "gpio-keys",}
    };
    

platform_driver_register 的定义如下:

int platform_driver_register(struct platform_driver *drv)
{drv->driver.bus = &platform_bus_type;  /* 将drv总线类型设置为platform_bus_type *//* 设置默认的操作函数 */if (drv->probe)drv->driver.probe = platform_drv_probe;if (drv->remove)drv->driver.remove = platform_drv_remove;if (drv->shutdown)drv->driver.shutdown = platform_drv_shutdown;if (drv->suspend)drv->driver.suspend = platform_drv_suspend;if (drv->resume)drv->driver.resume = platform_drv_resume;return driver_register(&drv->driver); /* 注册驱动到总线 */
}

driver_register 的执行流程分析

driver_register 的执行流程如下:

driver_register(&drv->driver);|-> error = driver_attach(drv);|-> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);|       while ((dev = next_device(&i)) && !error)|         error = __driver_attach(dev, data);|-> __driver_attach(dev, data);  |       if (!dev->driver)|           driver_probe_device(drv, dev);|-> driver_probe_device(drv, dev);|        if (drv->bus->match && !drv->bus->match(dev, drv))|         goto done;|     ret = really_probe(dev, drv);|-> really_probe(dev, drv);|       if (dev->bus->probe) {|            ret = dev->bus->probe(dev);|            if (ret)|                goto probe_failed;|        } else if (drv->probe) {|            ret = drv->probe(dev);|            if (ret)|                goto probe_failed;|        }
  1. 首先 driver_register 会调用 driver_attach 函数,后者会遍历 bus 上的每一个 dev,对每一个 dev 都执行 __driver_attach

  2. __driver_attach 会调用 driver_probe_device 函数。

    后者首先会使用 bus 的 match 函数,进行 driver 和 device 的匹配,若 bus 没有实现 match 函数,则会使用 driver 的 match 函数进行匹配。

  3. match 匹配成功后会调用 really_probe 函数。该函数最终会调用 bus 的 probe 函数,如果 bus 没有实现 probe 函数,则会调用 driver 的 probe 函数。

而前面我们知道 platform_bus_type 中是定义了 .match = platform_match 函数的。

platform_match

platform_match 的函数实现如下:

static int platform_match(struct device * dev, struct device_driver * drv)
{struct platform_device *pdev = container_of(dev, struct platform_device, dev);return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

平台设备的匹配规则很简单,使用 strncmp()比对 pdev->name 和 drv->name,如果一样就说明它们可以匹配。

那么就会调用 .probe 函数,由于 platform_bus_type 没有实现 .probe 函数,因此最终是调用 driver 的 .probe 函数。

我们以这个LED驱动程序(jz2440_基于平台设备的LED驱动程序_Bin Watson的博客-CSDN博客)为例:

static int leds_device_probe(struct platform_device *pdev)
{struct resource *res, *pin;int error;/*...省略...*//* 根据platform_device的资源,进行ioremap操作 */res = platform_get_resource(pdev, IORESOURCE_MEM, 0);pin = platform_get_resource(pdev, IORESOURCE_IO, 0);if (!res || !pin) {printk(DRIVER_NAME "leds driver get platform resource [%s] failed.\n",(res == NULL ? "IORESOURCE_MEM" : "IORESOURCE_IO"));error = -1;goto fail3;}gpio_con = (volatile unsigned long*)ioremap(res->start, res->end - res->start + 1);gpio_dat = gpio_con + 1;led_pin  = pin->start;/*...省略...*/
}

在 driver 的 .probe 函数中调用了 platform_get_resource 来获取设备中的资源,也就是 platform_device 中指定的 resource 资源。

3.3 平台设备注册

platform_device_register 函数分析

我们以 jz2440_基于平台设备的LED驱动程序_Bin Watson的博客-CSDN博客 这个程序为例分析:入口函数调用了 platform_device_register 进行注册。

static int __init leds_dev_init(void)
{platform_device_register(&leds_device);printk(KERN_INFO "leds device register.\n");return 0;
}
  • leds_device 是一个 platform_device 设备,其定义如下:

    static struct platform_device leds_device = {.name      = DEVICE_NAME,.id          = -1,.num_resources  = ARRAY_SIZE(leds_resource),.resource        = leds_resource,.dev       = {.release = leds_dev_release,},
    };
    

    主要是 leds_resource,记录了该设备所具有的硬件资源,其定义如下:

    static struct resource leds_resource[] = {[0] = {    /* 配置和数据 寄存器 */.start = S3C2440_GPFCON,.end   = S3C2440_GPFCON + 8 - 1,.flags = IORESOURCE_MEM,},[1] = {.start = 4,   /* 第4位 */.end   = 4,.flags = IORESOURCE_IO,.name  = "led"},
    };
    

    详细的成员分析参考 Linux内核 struct resource 结构体_Bin Watson的博客-CSDN博客

    在前面我们分析了平台驱动的 .probe 函数会通过 platform_get_resource 来获取平台设备的资源。通过这种操作,我们就可以将设备的资源与驱动程序进行分离,提高了驱动程序的通用性。

platform_device_register 的定义如下:

int platform_device_register(struct platform_device * pdev)
{device_initialize(&pdev->dev);return platform_device_add(pdev);
}

device_initialize 完成通用设备的初始化工作。

详细看 platform_device_add 的定义如下:

int platform_device_add(struct platform_device *pdev)
{/*...省略...*/pdev->dev.bus = &platform_bus_type;    /* 总线类型设置为platform_bus_type *//*...省略...*/for (i = 0; i < pdev->num_resources; i++) {struct resource *p, *r = &pdev->resource[i];if (r->name == NULL) /* 如果资源没有指定名字,则设置名字为设备id */r->name = pdev->dev.bus_id;p = r->parent;if (!p) {if (r->flags & IORESOURCE_MEM)    p = &iomem_resource;        else if (r->flags & IORESOURCE_IO)p = &ioport_resource;}/* 将 IORESOURCE_MEM 类型的资源加入到 iomem_resource 类型的资源树上* 将 IORESOURCE_IO 类型的资源加入到 ioport_resource 类型的资源树上 * 这么做的目的是防止资源冲突,多个设备使用了相同的资源。*/if (p && insert_resource(p, r)) {printk(KERN_ERR"%s: failed to claim resource %d\n",pdev->dev.bus_id, i);ret = -EBUSY;goto failed;}}/*...省略...*/ret = device_add(&pdev->dev);    /* 将设备添加到内核中,这个与通用设备的相同。 */if (ret == 0)return ret;/*...省略...*/
}

4. 其它设备模型

Linux 内核中除了平台设备之外,还有其它类型的设备,如:

USB设备,其定义如下:

struct bus_type usb_bus_type = {.name =        "usb",.match =   usb_device_match,.uevent = usb_uevent,.suspend =  usb_suspend,.resume =  usb_resume,
};

PCI设备,其定义如下:

struct bus_type pci_bus_type = {.name       = "pci",.match       = pci_bus_match,.uevent        = pci_uevent,.probe        = pci_device_probe,.remove     = pci_device_remove,.suspend   = pci_device_suspend,.suspend_late = pci_device_suspend_late,.resume_early    = pci_device_resume_early,.resume      = pci_device_resume,.shutdown  = pci_device_shutdown,.dev_attrs   = pci_dev_attrs,
};

这些类型将在其它后续文章中进行分析。

参考

《深入Linux内核架构》第6章第7节。

韦东山《高级驱动第二期》。

Linux内核总线系统 —— 通用总线和平台设备相关推荐

  1. 嵌入式系统Linux内核开发实战指南(ARM平台) 书评

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! < ...

  2. Linux内核时钟系统和定时器实现

    1. Linux内核时钟系统和定时器实现 Linux 2.6.16之前,内核只支持低精度时钟,内核定时器的工作方式: 系统启动后,会读取时钟源设备(RTC, HPET,PIT-),初始化当前系统时间: ...

  3. Linux内核构建系统之六

    转自:http://www.juliantec.info/julblog/yihect/linux-kernel-build-system-6 Linux内核构建系统之六 yihect | 10 元月 ...

  4. linux 内核 网卡驱动 移植,linux内核移植步骤添加dm9000网卡驱动(设备树).docx

    linux内核移植步骤添加dm9000网卡驱动(设备树).docx LINUX内核移植步骤2015年05月13日星期三上午1105往设备树中添加网卡驱动1.选平台,指定交叉编译工具链1.在MAKEFI ...

  5. linux内核源码实战_3.2理解设备驱动和文件系统

    linux内核源码实战_3.2理解设备驱动和文件系统 linux内核源码实战_理解设备驱动和文件系统 理解设备驱动和文件系统 理解设备驱动和文件系统详解 7-文件系统-proc文件系统实现 总结 li ...

  6. Linux内核网络栈1.2.13-网卡设备的初始化流程

    参考资料 <<linux内核网络栈源代码情景分析>> 网卡设备的初始化 本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相 ...

  7. Linux内核原理之通用块设备层

    文章目录 通用块设备层 I/O体系结构 访问设备 设备文件 字符设备.块设备与其它设备 使用ioctl进行设备寻址 设备数据库 与文件系统关联 inode中的设备文件成员 标准文件操作 用于字符设备的 ...

  8. 嵌入式linux智能家居系统,以Arm-Linux为平台的智能家居控制系统的设计详解

    嵌入式系统以其占用资源少.专用性强.功耗低的特点使其广泛应用在移动通信.工业生产.安全监控等领域.针对人们对高效.舒适.安全.便利.环保的居住环境的要求,提出了以Arm-Linux为平台的智能家居控制 ...

  9. linux内核配置系统浅析(转)

    随着 Linux 操作系统的广泛应用,特别是 Linux 在嵌入式领域的发展,越来越多的人开始投身到 Linux 内核级的开发中.面对日益庞大的 Linux 内核源代码,开发者在完成自己的内核代码后, ...

最新文章

  1. antd Tree组件中,自定义右键菜单
  2. ORACLE逻辑DATAGUARD创建表
  3. 【面试】Java面试常见问题汇总(不含答案)、面试指导学习笔记
  4. mysql 不在另一张表_mysql查询在一张表不在另外一张表的记录
  5. 这是我见过最全面的Python装饰器详解 没有学不会这种说法
  6. freeMarker(十二)——模板语言补充知识
  7. 匿名对象和类名为数据类型(java)
  8. jQury+Ajax与C#后台交换数据
  9. 对短链接服务暴露的URL进行网络侦察
  10. OneAlert 入门(三)——事件分析
  11. python不同版本切换_Python版本切换,python,的
  12. matlab 非线性系统仿真,非线性控制系统毕业论文--基于Matlab的非线性系统控制仿真研究...
  13. python程序实例视频教程_python从入门到精通视频(全60集)马哥Python未压缩版
  14. 《缠中说禅108课》37:背驰的再分辨
  15. 硬盘的种类及运行原理
  16. 用python画爱心动图_python 动态绘制爱心的示例
  17. [敏捷开发培训] 燃尽图(Burndown Chart)
  18. VB编程:DoWhile...Loop当循环计算0~100累加和-15_彭世瑜_新浪博客
  19. Lumberjack库在GCDAsyncSocket上的打印输出
  20. 【研究生本科】如何与导师有效沟通你的论文选题?

热门文章

  1. ANOMALY: use of REX.w is meaningless (default operand size is 64)问题处理
  2. spark远程桌面连接:使用机器人平台
  3. 贪吃蛇大作战:C语言代码
  4. 数据库触发器(转自http://blog.csdn.net/chinayuan/article/details/6292335/#)
  5. 13c语言——运算符
  6. cad找形心插件 lisp_九款CAD达人必备的插件,你都装了吗?
  7. 神经网络分为哪几类技术,神经网络分为哪几类型
  8. Mac anaconda相关
  9. 美国西北大学 计算机工程专业排名,[转载]美国西北大学计算机工程研究生最新专业排名...
  10. Oracle 用户无法登录 LOCKED(TIMED)