linux 平台驱动分析

目录

  • linux 平台驱动分析
    • 前言
    • 重要的数据结构
    • platform driver 调用关系图
    • platform device 调用关系图
    • 匹配的过程
    • 内存设备树的初始化
    • devicetree 变成platefom_device
    • 这样处理的作用与使用
    • 其它

前言

本文记录在读关于platform 平台驱动的内核源码的一些记录。
平台介绍:
linux 内核版本:3.15 rv1108提供的sdk包中的内核

重要的数据结构

bus_type

struct bus_type {const char      *name;const char        *dev_name;struct device     *dev_root;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 (*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);const struct dev_pm_ops *pm;struct iommu_ops *iommu_ops;struct subsys_private *p;struct lock_class_key lock_key;
};

platform_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 (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;
};

device 的数据结构

struct device {struct device     *parent;struct device_private   *p;struct kobject kobj;const char       *init_name; /* initial name of the device */const struct device_type *type;struct mutex     mutex;  /* mutex to synchronize calls to* its driver.*/struct bus_type  *bus;       /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void     *platform_data; /* Platform specific data, devicecore doesn't touch it */struct dev_pm_info    power;struct dev_pm_domain  *pm_domain;#ifdef CONFIG_PINCTRLstruct dev_pin_info *pins;
#endif#ifdef CONFIG_NUMAint     numa_node;  /* NUMA node this device is close to */
#endifu64       *dma_mask;  /* dma mask (if dma'able device) */u64     coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */struct device_dma_parameters *dma_parms;struct list_head   dma_pools;  /* dma pools (if dma'ble) */struct dma_coherent_mem    *dma_mem; /* internal for coherent memoverride */
#ifdef CONFIG_DMA_CMAstruct cma *cma_area;      /* contiguous memory area for dmaallocations */
#endif/* arch specific additions */struct dev_archdata  archdata;struct device_node *of_node; /* associated device tree node */struct acpi_dev_node acpi_node; /* associated ACPI device node */dev_t           devt;   /* dev_t, creates the sysfs "dev" */u32           id; /* device instance */spinlock_t     devres_lock;struct list_head    devres_head;struct klist_node   knode_class;struct class        *class;const struct attribute_group **groups;   /* optional groups */void   (*release)(struct device *dev);struct iommu_group   *iommu_group;

platform driver 调用关系图

下面以 serial_rk_init (如下图所示) 做为入口分析代码的调用流程。


//平台驱动probe关系图// drivers/tty/serial/rk_serial.c   #ifdef CONFIG_OF
static const struct of_device_id of_rk_serial_match[] = {{ .compatible = "rockchip,serial" },{ /* Sentinel */ }
};
#endif
static struct platform_driver serial_rk_driver = {.probe       = serial_rk_probe,.remove      = serial_rk_remove,.suspend    = serial_rk_suspend,.resume        = serial_rk_resume,.driver     = {.name   = "serial",
#ifdef CONFIG_OF.of_match_table = of_rk_serial_match,
#endif.owner    = THIS_MODULE,},
};static int __init serial_rk_init(void)
{int ret;printk("%s\n", VERSION_AND_TIME);ret = uart_register_driver(&serial_rk_reg);if (ret)return ret;ret = platform_driver_register(&serial_rk_driver);if (ret != 0)uart_unregister_driver(&serial_rk_reg);return ret;
}// drivers/base/platform.c
int platform_driver_register(struct platform_driver *drv)
{drv->driver.bus = &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;return driver_register(&drv->driver);
}// drivers/base/driver.c
int driver_register(struct device_driver *drv)
{int ret;struct device_driver *other;BUG_ON(!drv->bus->p);if ((drv->bus->probe && drv->probe) ||(drv->bus->remove && drv->remove) ||(drv->bus->shutdown && drv->shutdown))printk(KERN_WARNING "Driver '%s' needs updating - please use ""bus_type methods\n", drv->name);other = driver_find(drv->name, drv->bus);if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv);if (ret)return ret;ret = driver_add_groups(drv, drv->groups);if (ret) {bus_remove_driver(drv);return ret;}kobject_uevent(&drv->p->kobj, KOBJ_ADD);return ret;
}// drivers/base/bus.c
int bus_add_driver(struct device_driver *drv)
{struct bus_type *bus;struct driver_private *priv;int error = 0;bus = bus_get(drv->bus);if (!bus)return -EINVAL;......klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);// int bus_register(struct bus_type *bus) 中drivers_autoprobe 被置1if (drv->bus->p->drivers_autoprobe) {error = driver_attach(drv);if (error)goto out_unregister;}......
}// drivers/base/dd.c
int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}// drivers/base/dd.c
static int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;/** Lock device and try to bind to it. We drop the error* here and always return 0, because we need to keep trying* to bind to devices and some drivers will return an error* simply if it didn't support the device.** driver_probe_device() will spit a warning if there* is an error.*/if (!driver_match_device(drv, dev))return 0;if (dev->parent)   /* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probce_device(drv, dev);device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;
}// drivers/base/dd.c
int driver_probe_device(struct device_driver *drv, struct device *dev)
{int ret = 0;if (!device_is_registered(dev))return -ENODEV;pr_debug("bus: '%s': %s: matched device %s with driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);pm_runtime_barrier(dev);ret = really_probe(dev, drv);pm_request_idle(dev);return ret;
}// drivers/base/dd.c
static int really_probe(struct device *dev, struct device_driver *drv)
{int ret = 0;int local_trigger_count = atomic_read(&deferred_trigger_count);......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;}driver_bound(dev);......}

到ret = dev->bus->probe(dev); 可以看到用最前面提到的serial_rk_init
.probe = serial_rk_probe, 至此调用流程分析完成

platform device 调用关系图


如果是没有设备树的方式,则device驱动 调用platform_device_register ( --> platform_device_add–>device_add ) 的方式 详细见图中的调用关系。

如果是使用设备树的形式,见“devicetree 变成platefom_device” 章节

下面的serial的 device 与driver对应
代码如下,对应的详细分析(为什么serial_rk_init 有对应的设备注册到probe,同样probe中为什么也有设备注册,以及与rv1108设备树的联系)待完善…
serial_rk_init

// drivers/tty/serial/rk_serial.c   static int __init serial_rk_init(void)
{int ret;printk("%s\n", VERSION_AND_TIME);ret = uart_register_driver(&serial_rk_reg);if (ret)return ret;ret = platform_driver_register(&serial_rk_driver);if (ret != 0)uart_unregister_driver(&serial_rk_reg);return ret;
}int tty_register_driver(struct tty_driver *driver)
{int error;int i;dev_t dev;struct device *d;if (!driver->major) {error = alloc_chrdev_region(&dev, driver->minor_start,driver->num, driver->name);if (!error) {driver->major = MAJOR(dev);driver->minor_start = MINOR(dev);}} else {dev = MKDEV(driver->major, driver->minor_start);error = register_chrdev_region(dev, driver->num, driver->name);}if (error < 0)goto err;if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {error = tty_cdev_add(driver, dev, 0, driver->num);if (error)goto err_unreg_char;}mutex_lock(&tty_mutex);list_add(&driver->tty_drivers, &tty_drivers);mutex_unlock(&tty_mutex);if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {for (i = 0; i < driver->num; i++) {d = tty_register_device(driver, i, NULL);if (IS_ERR(d)) {error = PTR_ERR(d);goto err_unreg_devs;}}}proc_tty_register_driver(driver);driver->flags |= TTY_DRIVER_INSTALLED;return 0;err_unreg_devs:for (i--; i >= 0; i--)tty_unregister_device(driver, i);mutex_lock(&tty_mutex);list_del(&driver->tty_drivers);mutex_unlock(&tty_mutex);err_unreg_char:unregister_chrdev_region(dev, driver->num);
err:return error;
}//drivers/tty/serial/tty_io.cstruct device *tty_register_device(struct tty_driver *driver, unsigned index,struct device *device)
{return tty_register_device_attr(driver, index, device, NULL, NULL);
}//drivers/tty/ty_io.c
struct device *tty_register_device_attr(struct tty_driver *driver,unsigned index, struct device *device,void *drvdata,const struct attribute_group **attr_grp)
{char name[64];dev_t devt = MKDEV(driver->major, driver->minor_start) + index;struct device *dev = NULL;int retval = -ENODEV;bool cdev = false;if (index >= driver->num) {printk(KERN_ERR "Attempt to register invalid tty line number "" (%d).\n", index);return ERR_PTR(-EINVAL);}if (driver->type == TTY_DRIVER_TYPE_PTY)pty_line_name(driver, index, name);elsetty_line_name(driver, index, name);if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {retval = tty_cdev_add(driver, devt, index, 1);if (retval)goto error;cdev = true;}dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev) {retval = -ENOMEM;goto error;}dev->devt = devt;dev->class = tty_class;dev->parent = device;dev->release = tty_device_create_release;dev_set_name(dev, "%s", name);dev->groups = attr_grp;dev_set_drvdata(dev, drvdata);retval = device_register(dev);if (retval)goto error;return dev;error:put_device(dev);if (cdev)cdev_del(&driver->cdevs[index]);return ERR_PTR(retval);
}//drivers/base/core.c
int device_register(struct device *dev)
{device_initialize(dev);return device_add(dev);
}//drivers/base/core.c
int device_add(struct device *dev)
{struct device *parent = NULL;struct kobject *kobj;struct class_interface *class_intf;int error = -EINVAL;.......error = device_add_class_symlinks(dev);if (error)goto SymlinkError;error = device_add_attrs(dev);if (error)goto AttrsError;error = bus_add_device(dev);if (error)goto BusError;error = dpm_sysfs_add(dev);if (error)goto DPMError;device_pm_add(dev);/* Notify clients of device addition.  This call must come* after dpm_sysfs_add() and before kobject_uevent().*/if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);kobject_uevent(&dev->kobj, KOBJ_ADD);bus_probe_device(dev);......
}//drivers/base/bus.c
void bus_probe_device(struct device *dev)
{struct bus_type *bus = dev->bus;struct subsys_interface *sif;int ret;if (!bus)return;if (bus->p->drivers_autoprobe) {ret = device_attach(dev);WARN_ON(ret < 0);}mutex_lock(&bus->p->mutex);list_for_each_entry(sif, &bus->p->interfaces, node)if (sif->add_dev)sif->add_dev(dev, sif);mutex_unlock(&bus->p->mutex);
}

后面的与上面提到的驱动配置到probe一样

device_attach -->__device_attach -->driver_probe_device

串口这里增加设备,再增加驱动。从上面关系图中,看到serial_rk_probe 调用关系 最后也调用到了bus_probe_device.

匹配的过程

driver_match_device


static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}// drivers/base/platform.c
struct bus_type platform_bus_type = {.name     = "platform",.dev_attrs  = platform_dev_attrs,.match        = platform_match,.uevent       = platform_uevent,.pm      = &platform_dev_pm_ops,
};

调用 platform_match

static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}

从上面的代码分析配置顺序,我们可以看出匹配的优先级。

内存设备树的初始化

//arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{struct machine_desc *mdesc;setup_processor();mdesc = setup_machine_fdt(__atags_pointer);.........if (mdesc->restart)arm_pm_restart = mdesc->restart;unflatten_device_tree();..........
}//drivers/of/fdt.c
void __init unflatten_device_tree(void)
{__unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);/* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */of_alias_scan(early_init_dt_alloc_memory_arch);
}

devicetree 变成platefom_device

dts 如何把devicetree 变成platefom_device的

// arch/arm/kernel.c
static int __init customize_machine(void)
{/** customizes platform devices, or adds new ones* On DT based machines, we fall back to populating the* machine from the device tree, if no callback is provided,* otherwise we would always need an init_machine callback.*/if (machine_desc->init_machine)machine_desc->init_machine();
#ifdef CONFIG_OFelseof_platform_populate(NULL, of_default_bus_match_table,NULL, NULL);
#endifreturn 0;
}// drivers/of/platform.c
int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)
{struct device_node *child;int rc = 0;root = root ? of_node_get(root) : of_find_node_by_path("/");if (!root)return -EINVAL;for_each_child_of_node(root, child) {rc = of_platform_bus_create(child, matches, lookup, parent, true);if (rc)break;}of_node_put(root);return rc;
}// drivers/of/platform.c
static int of_platform_bus_create(struct device_node *bus,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent, bool strict)
{const struct of_dev_auxdata *auxdata;struct device_node *child;struct platform_device *dev;const char *bus_id = NULL;void *platform_data = NULL;int rc = 0;/* Make sure it has a compatible property */if (strict && (!of_get_property(bus, "compatible", NULL))) {pr_debug("%s() - skipping %s, no compatible prop\n",__func__, bus->full_name);return 0;}auxdata = of_dev_lookup(lookup, bus);if (auxdata) {bus_id = auxdata->name;platform_data = auxdata->platform_data;}if (of_device_is_compatible(bus, "arm,primecell")) {of_amba_device_create(bus, bus_id, platform_data, parent);return 0;}dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);if (!dev || !of_match_node(matches, bus))return 0;for_each_child_of_node(bus, child) {pr_debug("   create child: %s\n", child->full_name);rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);if (rc) {of_node_put(child);break;}}of_node_set_flag(bus, OF_POPULATED_BUS);return rc;
}// drivers/of/platform.c
struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,void *platform_data,struct device *parent)
{struct platform_device *dev;if (!of_device_is_available(np) ||of_node_test_and_set_flag(np, OF_POPULATED))return NULL;dev = of_device_alloc(np, bus_id, parent);if (!dev)goto err_clear_flag;#if defined(CONFIG_MICROBLAZE)dev->archdata.dma_mask = 0xffffffffUL;
#endifdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);dev->dev.bus = &platform_bus_type;dev->dev.platform_data = platform_data;/* We do not fill the DMA ops for platform devices by default.* This is currently the responsibility of the platform code* to do such, possibly using a device notifier*/if (of_device_add(dev) != 0) {platform_device_put(dev);goto err_clear_flag;}return dev;err_clear_flag:of_node_clear_flag(np, OF_POPULATED);return NULL;
}//drivers/of/device.c
int of_device_add(struct platform_device *ofdev)
{BUG_ON(ofdev->dev.of_node == NULL);/* name and id have to be set so that the platform bus doesn't get* confused on matching */
#ifdef CONFIG_ARCH_ROCKCHIPofdev->name = kasprintf(GFP_KERNEL, "%s", dev_name(&ofdev->dev));
#elseofdev->name = dev_name(&ofdev->dev);
#endifofdev->id = -1;/* device_add will assume that this device is on the same node as* the parent. If there is no parent defined, set the node* explicitly */if (!ofdev->dev.parent)set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));return device_add(&ofdev->dev);
}//drivers/bas/core.c
int device_add(struct device *dev)
{struct device *parent = NULL;struct kobject *kobj;struct class_interface *class_intf;int error = -EINVAL;dev = get_device(dev);if (!dev)goto done;if (!dev->p) {error = device_private_init(dev);if (error)goto done;}/** for statically allocated devices, which should all be converted* some day, we need to initialize the name. We prevent reading back* the name, and force the use of dev_name()*/if (dev->init_name) {dev_set_name(dev, "%s", dev->init_name);dev->init_name = NULL;}/* subsystems can specify simple device enumeration */if (!dev_name(dev) && dev->bus && dev->bus->dev_name)dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);if (!dev_name(dev)) {error = -EINVAL;goto name_error;}pr_debug("device: '%s': %s\n", dev_name(dev), __func__);parent = get_device(dev->parent);kobj = get_device_parent(dev, parent);if (kobj)dev->kobj.parent = kobj;/* use parent numa_node */if (parent)set_dev_node(dev, dev_to_node(parent));/* first, register with generic layer. *//* we require the name to be set before, and pass NULL */error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);if (error)goto Error;/* notify platform of device entry */if (platform_notify)platform_notify(dev);error = device_create_file(dev, &uevent_attr);if (error)goto attrError;if (MAJOR(dev->devt)) {error = device_create_file(dev, &devt_attr);if (error)goto ueventattrError;error = device_create_sys_dev_entry(dev);if (error)goto devtattrError;devtmpfs_create_node(dev);}error = device_add_class_symlinks(dev);if (error)goto SymlinkError;error = device_add_attrs(dev);if (error)goto AttrsError;error = bus_add_device(dev);if (error)goto BusError;error = dpm_sysfs_add(dev);if (error)goto DPMError;device_pm_add(dev);/* Notify clients of device addition.  This call must come* after dpm_sysfs_add() and before kobject_uevent().*/if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);kobject_uevent(&dev->kobj, KOBJ_ADD);bus_probe_device(dev);if (parent)klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);if (dev->class) {mutex_lock(&dev->class->p->mutex);/* tie the class to the device */klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);/* notify any interfaces that the device is here */list_for_each_entry(class_intf,&dev->class->p->interfaces, node)if (class_intf->add_dev)class_intf->add_dev(dev, class_intf);mutex_unlock(&dev->class->p->mutex);}
done:put_device(dev);return error;DPMError:bus_remove_device(dev);BusError:device_remove_attrs(dev);AttrsError:device_remove_class_symlinks(dev);SymlinkError:if (MAJOR(dev->devt))devtmpfs_delete_node(dev);if (MAJOR(dev->devt))device_remove_sys_dev_entry(dev);devtattrError:if (MAJOR(dev->devt))device_remove_file(dev, &devt_attr);ueventattrError:device_remove_file(dev, &uevent_attr);attrError:kobject_uevent(&dev->kobj, KOBJ_REMOVE);kobject_del(&dev->kobj);Error:cleanup_device_parent(dev);if (parent)put_device(parent);
name_error:kfree(dev->p);dev->p = NULL;goto done;
}

最后又回到了我们前面提到的 bus_add_device

这样处理的作用与使用

待完成

其它

关于linux platform平台设备驱动框架,这里的图会更清晰。点这里

linux 平台驱动分析相关推荐

  1. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  2. Linux spi驱动分析(四)----SPI设备驱动(W25Q32BV)

    一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它采用SPI接口和CPU通信,本文使用的W25Q32BV容量为32M,具体特性如下: 1.1.基本特性 该芯片最大支持10 ...

  3. Linux spi驱动分析----SPI设备驱动(W25Q32BV)

    转载地址:http://blog.chinaunix.net/uid-25445243-id-4026974.html 一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它 ...

  4. Linux网卡驱动分析之RTL8139(五)

    Linux网卡驱动分析之RTL8139(五) deliver_skb(dev.c) // 该函数就是调用个协议的接收函数处理该skb 包,进入第三层网络层处理 static __inline__ in ...

  5. linux pinctrl驱动分析

    linux pinctrl驱动分析 altas200模块 准备 设备树节点 pinctrl驱动分析 pcs_probe函数 pcs_allocate_pin_table函数 pcs_add_pin函数 ...

  6. linux 网卡驱动分析,LINUX_网卡驱动分析

    LINUX_网卡驱动分析 (36页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.9 积分 Linux DM9000网卡驱动程序完全分析说明仁 本文分 ...

  7. Linux UART驱动分析及测试

    1.Linux TTY驱动程序框架 Linux TTY驱动程序代码位于/drivers/tty下面.TTY的层次接口包括TTY应用层.TTY文件层.TTY线路规程层.TTY驱动层.TTY设备驱动层.T ...

  8. Linux网络设备驱动分析,以W5300以太网驱动为例

    前言 本文是笔者在分析Linux网络驱动时记录的笔记,在这里分享给大家.因为笔者目前也属于学习阶段,因此可能会存在分析不清楚甚至分析错误的地方,欢迎大家评判指正!! 版本说明 Linux内核版本:4. ...

  9. linux 网卡驱动分析,基于linux下网卡驱动分析及实现技术研究

    摘    要 Linux技术是当前计算机技术中最大的一个热点,在我国以及全世界得到了迅猛的发展,被广泛的应用于嵌入式系统.服务器.网络系统.安全等领域.从而使得掌握在 Linux环境下的开发技术,成为 ...

  10. linux 触摸屏驱动分析

    mini2440驱动分析系列之 ---------------------------------------Mini2440触摸屏程序分析 By JeefJiang July,8th,2009 这是 ...

最新文章

  1. Mysql报错Fatal error: Can#39;t open and lock privilege tables: Table #39;mysql.host#39; doesn#39;t...
  2. 【Python】青少年蓝桥杯_每日一题_11.03_按要求输出两个正整数之间的数
  3. 学习面试题Day02
  4. 100份Spring面试问答-最终名单(PDF下载)
  5. 统计字符串元素出现的个数_LeetCode 1295. 统计位数为偶数的数字
  6. 最终幻想13 公布发售日期和主题曲
  7. 桌面恶心的无法删除的图标之 淘宝购物 删除办法
  8. finecms如何调用多个指定栏目的内容
  9. python从数据库中取出文件保存到excel,csv表格中的办法:
  10. 文件上传到部署服务器(添加附件)
  11. C/C++[codeup 1805]首字母大写
  12. 处女座的比赛资格(拓扑排序)
  13. windows服务器虚拟机 全屏,虚拟机安装Windowsxp系统后无法全屏的解决方法
  14. php华文行楷,css设置中文字体
  15. VSFTPD (500 Illegal PORT command 500 OOPS: vsf_sysutil_bind) 错误解决方法
  16. 西门子g120变频器接线图_西门子变频器G120应用技术手册
  17. Unity之使用Shader实现背景循环播放
  18. 【例题】【高斯消元】USACO3.2.4 Feed Ratios
  19. 输入三角形的3条边长(均为正整数),如果不能构成一个三角形,则输出“not a triangle”;如果能够构成一个直角三角形,则输出“yes”;如果不能构成直角三角形,则输出“no”。
  20. vue + threejs实战,实现3D 360度 旋转查看物体

热门文章

  1. vs2008 SP1 安装问题小解决方案
  2. Java后台生成NO2016012701(代码+年月日+流水号)这样的流水编号
  3. ad域 禁用账号_大量AD域帐号自动被锁定
  4. 小米解锁过程中验证失败怎么办,有什么处理方式
  5. js分割字符串的方法
  6. SpringBoot 中如何使用JSP页面开发?
  7. PPAPI插件与浏览器的通信
  8. 车机没有carlife可以自己下载吗_视频实测:苹果CarPlay和百度CarLife到底哪个更好用...
  9. 神经网络中经常使用的激活函数--sigmoid函数
  10. 情侣博客源码php,wordpress如何搭建简单的情侣博客