当前位置: 首页 > news >正文

珠海集团网站建设报价万网

珠海集团网站建设报价,万网,买域名可以自己做网站吗,正规流量卡代理平台本文有部分借鉴 wowo tech ,感谢作者的无私分享 目录 1、简介 2、Platform 软件架构 3、Platform 模块向其它模块提供的 APIs 3.1、数据结构 3.1.1、platform_device 3.1.2、platform_driver 3.2、APIs 3.2.1、Platform Device 提供的 APIs 3.2.2、Platfor…

本文有部分借鉴 wowo tech ,感谢作者的无私分享

 

目录

1、简介

2、Platform 软件架构

3、Platform 模块向其它模块提供的 APIs

3.1、数据结构

3.1.1、platform_device

3.1.2、platform_driver

3.2、APIs

3.2.1、Platform Device 提供的 APIs

3.2.2、Platform Driver 提供的 APIs

4、Platform 初始化

4.1、Platform 总线初始化

5、Device 和 Driver 匹配执行 probe

5.1、platform_driver_register 情况

5.2、platform_device_register 情况

5.3、小结


 

1、简介

在Linux设备模型的抽象中,存在着一类称作“Platform Device”的设备,内核是这样描述它们的(Documentation/driver-model/platform.txt):

Platform devices are devices that typically appear as autonomous entities in the system. This includes legacy port-based devices and host bridges to peripheral buses, and most controllers integrated into system-on-chip platforms.  What they usually have in common is direct addressing from a CPU bus.  Rarely, a platform_device will be connected through a segment of some other kind of bus; but its registers will still be directly addressable.

概括来说,Platform设备包括:基于端口的设备(已不推荐使用,保留下来只为兼容旧设备,legacy);连接物理总线的桥设备;集成在SOC平台上面的控制器;连接在其它bus上的设备(很少见)。等等。

这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。因此,由于这个共性,内核在设备模型的基础上(device和device_driver),对这些设备进行了更进一步的封装,抽象出paltform bus、platform device和platform driver,以便驱动开发人员可以方便的开发这类设备的驱动。

platform总线是虚拟的平台总线,是linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。 
总线将设备和驱动绑定,系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

可以说,paltform设备对Linux驱动工程师是非常重要的,因为我们编写的大多数设备驱动,都是为了驱动plaftom设备。本文我们就来看看Platform设备在内核中的实现。

 

2、Platform 软件架构

内核中Platform设备有关的实现位于 include/linux/platform_device.h 和 drivers/base/platform.c 两个文件中,它的软件架构如下:

 

由图片可知,Platform设备在内核中的实现主要包括三个部分:

Platform Bus:基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备; 
Platform Device:基于底层device模块,抽象出Platform Device,用于表示Platform设备; 
Platform Driver:基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。

 

3、Platform 模块向其它模块提供的 APIs

Platform 提供的接口包括:Platform Device 和 Platform Driver 两个数据结构,以及它们的操作函数。

3.1、数据结构

3.1.1、platform_device

platform_device 代表了一种 device,定义在 include/linux/platform_device.h 文件

struct platform_device {const char	* name;int		id;struct device	dev;u32		num_resources;struct resource	* resource;const struct platform_device_id	*id_entry;/* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata	archdata;
};

它的字段解释如下:

dev:真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现)。

name:设备的名称,和 struct device 结构中的 init_name 一样。实际上,该名称在设备注册时,会拷贝到 dev.init_name中。

id:用于表示该设备的 ID。

id_auto:指示在注册设备时,是否自动赋予ID值(不需要人为指定啦,可以懒一点啦)。

num_resources、resource:该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象。

在Linux中,系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。 
当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。

id_entry:和内核模块相关的内容,暂不说明。

mfd_cell:和MFD设备相关的内容,暂不说明。

archdata:不管它了

可以看出 platform_device 其实是裹了一层 struct device 的一种设备的抽象。

 

3.1.2、platform_driver

platform_driver 是一种特殊的 driver,它定义在 include/linux/platform_device.h 文件:

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;
};

struct platform_driver结构和struct device_driver非常类似,无非就是提供probe、remove、suspend、resume等回调函数,这里不再细说。

另外这里有一个 id_table 的指针,该指针和 of_match_table、acpi_match_table 的功能类似:提供其它方式的设备probe。 内核会在合适的时机检查device和device_driver的名字,如果匹配,则执行probe。其实除了名称之外,还有一些宽泛的匹配方式,例如这里提到的各种match table,具体原理就先不罗嗦了,徒添烦恼!就当没看见,呵呵。 

 

3.2、APIs

3.2.1、Platform Device 提供的 APIs

Platform Device主要提供设备的分配、注册等接口,供其它driver使用,常用的包括:

1、注册/注销一个 Platform 设备:

int platform_device_register(struct platform_device *);
void platform_device_unregister(struct platform_device *);int platform_add_devices(struct platform_device **, int); // 增加多个 devices

2、获取资源:

struct resource *platform_get_resource(struct platform_device *,unsigned int, unsigned int);struct resource *platform_get_resource_byname(struct platform_device *,unsigned int,const char *);

3、获取 irq:

int platform_get_irq(struct platform_device *, unsigned int);
int platform_get_irq_byname(struct platform_device *, const char *);

4、向 platform_device 增加资源:

int platform_device_add_resources(struct platform_device *pdev,const struct resource *res,unsigned int num);

5、向 platform device 中添加自定义的数据(保存在pdev->dev.platform_data指针中)

int platform_device_add_data(struct platform_device *pdev,const void *data, size_t size);

 

3.2.2、Platform Driver 提供的 APIs

Platform Driver提供struct platform_driver的分配、注册等功能,常用的包括:

1、注册/注销 platform_driver 接口:

int platform_driver_register(struct platform_driver *);
void platform_driver_unregister(struct platform_driver *);

2、主动执行 probe 动作接口:

int platform_driver_probe(struct platform_driver *driver,int (*probe)(struct platform_device *));

3、设置/获取私有数据接口:

inline void *platform_get_drvdata(const struct platform_device *pdev);
inline void platform_set_drvdata(struct platform_device *pdev,void *data)

 

4、Platform 初始化

platform驱动工作流程: 

1. 系统开机内核初始化阶段,初始化 platform 总线; 

2. platform_device 初始化调用 platform_add_devices(一次注册多个设备)或者 platform_device_register(一次注册单个设备),这部分一般在 arch/arm/mach 配置文件中,在上电开机的时候完成 platform_device 初始化

3. platform_driver 的初始化调用 platform_driver_register 或者 driver_register,该过程一般在驱动程序的 init 函数中;

 

4.1、Platform 总线初始化

内核在启动过程中,会调用到  kernel_init() –> do_basic_setup() –> driver_init() –> platform_bus_init()  相关的东西就在这里初始化的 driver/base/platform.c

int __init platform_bus_init(void)
{int error;early_platform_cleanup();error = device_register(&platform_bus); ------- (1)if (error)return error;error =  bus_register(&platform_bus_type);  --- (2)if (error)device_unregister(&platform_bus);return error;
}

先看(1)部分,通过 device_register 它注册了一个 platform_bus 的设备,它的定义在 driver/base ,是:

struct device platform_bus = {.init_name	= "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

定义一个名为 platform 的总线设备,其他的platform设备都是它的子设备,所以这里先注册这个设备!

在看(2)部分,通过 bus_register 注册了一个 platform_bus_type 的 bus,它定义是:

struct bus_type platform_bus_type = {.name		= "platform",.dev_attrs	= platform_dev_attrs,.match		= platform_match,.uevent		= platform_uevent,.pm		= &platform_dev_pm_ops,
};

 

注册平台类型的 bus,将出现 sys 文件系统在 bus 目录下,创建一个 platform 的目录,以及相关属性文件。

这里创建好 platform 的 bus 了,就等相关的设备驱动去注册自己的 platform_device 和 platform_driver;

这里我们着重关心一下 probe 的执行时机。

 

5、Device 和 Driver 匹配执行 probe

现在 platform_bus 已经 Ready,那么我们的 device 和 driver 是如何 match 上,并且执行 driver 的 probe 的呢?

在总线上 device 和 driver 的名字匹配,就会调用 driver 的 probe 函数

那么就会存在一个问题,到底是先有 device 还是先有 driver,因为他们俩注册肯定是有先后顺序的,所以需要看看源代码

 

5.1、platform_driver_register 情况

platform_driver_register 实现了注册一个 platform_driver,它的代码在 driver/base/platform.c :

/*** platform_driver_register - register a driver for platform-level devices* @drv: platform driver structure*/
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);
}
EXPORT_SYMBOL_GPL(platform_driver_register);

先把 driver 的 bus 赋值成为了 platform_bus_type,代表是属于 platform bus 的,最后直接调用到 driver_register 注册到更底层的设备模型,它的代码在 driver/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); ------------(1)if (other) {put_driver(other);printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv); ----------------------------(2)if (ret)return ret;ret = driver_add_groups(drv, drv->groups);if (ret)bus_remove_driver(drv);return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

先看(1),调用了 driver_find
 

struct device_driver *driver_find(const char *name, struct bus_type *bus)
{struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);struct driver_private *priv;if (k) {priv = to_driver(k);return priv->driver;}return NULL;
}
EXPORT_SYMBOL_GPL(driver_find);

看看核心函数 kset_find_obj

struct kobject *kset_find_obj(struct kset *kset, const char *name)
{return kset_find_obj_hinted(kset, name, NULL);
}struct kobject *kset_find_obj_hinted(struct kset *kset, const char *name,struct kobject *hint)
{struct kobject *k;struct kobject *ret = NULL;
.........list_for_each_entry(k, &kset->list, entry) {if (kobject_name(k) && !strcmp(kobject_name(k), name)) {ret = kobject_get(k);break;}}
.........
}

kset_find_obj通过循环操作,根据我们给的名字name在指定的bus中循环对比,查看是否有相同的名字name(这个name存放在kobj中)。其实这就是一个循环链表的遍历过程

所以,driver_find 通过我们给定的name在某bus中寻找驱动,比对名字,看看驱动是否已经装载!

总结driver_find过程如下:

1. driver_find,拿到了drv->name和drv->bus开始找驱动

2. kset_find_obj 通过driver_find传递的bus->p->drivers_kset,利用list_for_each_entry遍历kset循环链表。(kset结构体中有循环链表指针next和prev)

3. 遍历循环链表中每一个kobj中的成员变量name

4. 通过strcmp(kobject_name(k), name)比较drv->name 和kobj中的name,如果有相同则表示查找成功

5. return :如果找到,则返回device_driver的指针,如果没有找到则返回了NULL。

返回到 driver_register 函数,如果已经装载了驱动,那么直接返回 -EBUSY

在看 (2),bus_add_driver

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;
....priv = kzalloc(sizeof(*priv), GFP_KERNEL);
....klist_init(&priv->klist_devices, NULL, NULL);
....if (drv->bus->p->drivers_autoprobe) {error = driver_attach(drv);if (error)goto out_unregister;}
....
}

内容较多,略去了部分内容,主要是王指定的 bus 上增加 driver,同时给底层的 kobject,kset 等等赋值,添加链表等等,这里我们关心:

	if (drv->bus->p->drivers_autoprobe) {error = driver_attach(drv);if (error)goto out_unregister;}

这个 drv->bus->p->drivers_autoprobe,其实就是 platform_bus_type->subsys_private->drivers_autoprobe 的值,在初始化 platform_bus 的时候,调用 bus_register 的时候,这个值没有设置,被默认设置成为了 1:

int bus_register(struct bus_type *bus)
{int retval;struct subsys_private *priv;priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);if (!priv)return -ENOMEM;priv->subsys.kobj.kset = bus_kset;priv->subsys.kobj.ktype = &bus_ktype;priv->drivers_autoprobe = 1;}

所以,这里将会执行到 driver_attach

int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{struct klist_iter i;struct device *dev;int error = 0;if (!bus)return -EINVAL;klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL));while ((dev = next_device(&i)) && !error)error = fn(dev, data);klist_iter_exit(&i);return error;
}

进而执行到 __driver_attach 函数:

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_probe_device(drv, dev);device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;
}

看注释,希望在前方,这个 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;
}

其实便是执行到了 platform_bus 的 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 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);
}

可以看到,有多种 match 上的方式,当然,最简单的还是 name 一致!

当 match 上了后,执行 __driver_attach driver_probe_device 函数调用:

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_get_noresume(dev);pm_runtime_barrier(dev);ret = really_probe(dev, drv);pm_runtime_put_sync(dev);return ret;
}

直接走到了 really_probe 函数,看来快看到想要的了:

static int really_probe(struct device *dev, struct device_driver *drv)
{int ret = 0;
...atomic_inc(&probe_count);
...dev->driver = 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;}
...
}

这里执行到了 driver->probe 函数!!

总体的流程是:

platform_driver_register -> driver_register

-> bus_add_driver

-> driver_attach -> __driver_attach

-> match ? (0 return 1 ->)

-> driver_probe_device (matched)

-> really_probe

-> probe

所以执行到 driver 的 probe 的前提是,match 成功,也就是,device 先存在了,再去注册 driver 的情况,我们再来看看另外一种情况!

 

5.2、platform_device_register 情况

直接看实现:

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

先将 platform_device 的 device 结构初始化,然后调用 platform_device_add

int platform_device_add(struct platform_device *pdev)
{int i, ret = 0;if (!pdev)return -EINVAL;if (!pdev->dev.parent)pdev->dev.parent = &platform_bus;pdev->dev.bus = &platform_bus_type;
....if (pdev->id != -1)dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);elsedev_set_name(&pdev->dev, "%s", pdev->name);
....ret = device_add(&pdev->dev);if (ret == 0)return ret;}
EXPORT_SYMBOL_GPL(platform_device_add);

设置 bus 为 platform bus type 后,算是挂靠到这个 bus 上,然后做一些初始化的动作,调用到 device_add,进入到更底层的设备模块抽象:

int device_add(struct device *dev)
{struct device *parent = NULL;struct class_interface *class_intf;int error = -EINVAL;dev = get_device(dev);if (!dev)goto done;...if (dev->init_name) {dev_set_name(dev, "%s", dev->init_name);dev->init_name = NULL;}
...if (!dev_name(dev)) {error = -EINVAL;goto name_error;}
...parent = get_device(dev->parent);setup_parent(dev, parent);/* 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);bus_probe_device(dev);
...
}

内容较多,主要是和底层 kobject,kset,parent,sys 等等相关操作,这里关心到 bus_add_device 函数,往期望的 bus 上增加一个 device,成功后,调用 bus_probe_device

void bus_probe_device(struct device *dev)
{struct bus_type *bus = dev->bus;int ret;if (bus && bus->p->drivers_autoprobe) {ret = device_attach(dev);WARN_ON(ret < 0);}
}

这里不用多说了吧,调用到了 device_attach,和上面的 <5.2、platform_driver_register 情况>一样,形成了匹配后,调用了 probe!!

总体的流程是:

platform_device_register -> platform_device_add

-> device_add 

-> bus_probe_device

-> driver_attach -> __driver_attach

-> match ? (0 return 1 ->)

-> driver_probe_device (matched)

-> really_probe

-> probe

 

5.3、小结

所以,不管是先注册 platform_device 还是 platform_driver,只要匹配上了,都会去执行到 probe 函数,也就是驱动的初始化函数!

 

参考文献:

https://blog.csdn.net/Richard_LiuJH/article/details/45825333

https://blog.csdn.net/Richard_LiuJH/article/details/48245715

https://blog.csdn.net/Hansomewang/article/details/78969006

https://blog.csdn.net/fml1997/article/details/77622860

https://blog.csdn.net/thl789/article/details/6723350


文章转载自:
http://expressage.rjbb.cn
http://lithoscope.rjbb.cn
http://changepocket.rjbb.cn
http://plotz.rjbb.cn
http://controvertible.rjbb.cn
http://cathedratic.rjbb.cn
http://ppfa.rjbb.cn
http://masthead.rjbb.cn
http://gradate.rjbb.cn
http://assertory.rjbb.cn
http://bema.rjbb.cn
http://hairweaving.rjbb.cn
http://seymouriamorph.rjbb.cn
http://hurtful.rjbb.cn
http://thermostatic.rjbb.cn
http://wirepuller.rjbb.cn
http://ascogonial.rjbb.cn
http://washwoman.rjbb.cn
http://bargainee.rjbb.cn
http://graunch.rjbb.cn
http://possess.rjbb.cn
http://buttock.rjbb.cn
http://algebraist.rjbb.cn
http://bioglass.rjbb.cn
http://lizzie.rjbb.cn
http://complice.rjbb.cn
http://icehouse.rjbb.cn
http://syndeton.rjbb.cn
http://spermatophyte.rjbb.cn
http://bookend.rjbb.cn
http://moody.rjbb.cn
http://herniorrhaphy.rjbb.cn
http://campaigner.rjbb.cn
http://rectory.rjbb.cn
http://yird.rjbb.cn
http://permeability.rjbb.cn
http://periwinkle.rjbb.cn
http://scald.rjbb.cn
http://lowdown.rjbb.cn
http://exultancy.rjbb.cn
http://polychromatophil.rjbb.cn
http://peritrack.rjbb.cn
http://winery.rjbb.cn
http://thermoscope.rjbb.cn
http://granitoid.rjbb.cn
http://didactically.rjbb.cn
http://scintillant.rjbb.cn
http://helicab.rjbb.cn
http://firestorm.rjbb.cn
http://conspicuous.rjbb.cn
http://philanthropic.rjbb.cn
http://hundredweight.rjbb.cn
http://windsor.rjbb.cn
http://parmentier.rjbb.cn
http://galantine.rjbb.cn
http://centrifugalization.rjbb.cn
http://swimming.rjbb.cn
http://potzer.rjbb.cn
http://stringent.rjbb.cn
http://antismoking.rjbb.cn
http://undelivered.rjbb.cn
http://autoclave.rjbb.cn
http://decoy.rjbb.cn
http://pseudomonad.rjbb.cn
http://precipitator.rjbb.cn
http://allah.rjbb.cn
http://theodicy.rjbb.cn
http://requitable.rjbb.cn
http://natrolite.rjbb.cn
http://dvm.rjbb.cn
http://anthroposophy.rjbb.cn
http://moonscape.rjbb.cn
http://citric.rjbb.cn
http://anabas.rjbb.cn
http://fate.rjbb.cn
http://florigen.rjbb.cn
http://appellatively.rjbb.cn
http://edam.rjbb.cn
http://namaqua.rjbb.cn
http://soterial.rjbb.cn
http://woful.rjbb.cn
http://pecs.rjbb.cn
http://kissinger.rjbb.cn
http://syntactical.rjbb.cn
http://vasoactive.rjbb.cn
http://whaleback.rjbb.cn
http://tenonitis.rjbb.cn
http://hemispheroid.rjbb.cn
http://desirous.rjbb.cn
http://luminarist.rjbb.cn
http://radiophony.rjbb.cn
http://firefang.rjbb.cn
http://dooda.rjbb.cn
http://grown.rjbb.cn
http://splanchnology.rjbb.cn
http://indiscernible.rjbb.cn
http://saugh.rjbb.cn
http://executioner.rjbb.cn
http://tornadic.rjbb.cn
http://graben.rjbb.cn
http://www.dt0577.cn/news/109891.html

相关文章:

  • 深圳官网网站建设企业网页设计与推广
  • 成都网站建设与维护seo超级外链工具免费
  • 做网站服务器 用mac pro 怎么样一年的百度指数
  • 更合公司网站建设360网站推广怎么做
  • 政府网站建设的基本流程杭州网站优化企业
  • 哈尔滨网站建设有哪些推广竞价
  • 门户网站建设好处网络广告公司
  • 网站服务器租赁价格个人网站的制作模板
  • 服装行业网站建设方案企业网站开发费用
  • 天津网上商城网站建设上海网站seo优化
  • 网上做二建题那个网站好如何写软文推广产品
  • 手机网站注意哪些问题吗学生个人网页制作成品代码
  • 中国建设银行湖北省分行网站北京seo关键词
  • 东莞微信网站建设推荐seo优化需要多少钱
  • 宿迁房产网签查询系统泰州seo推广
  • 做网站的公司倒闭了百度客服电话24小时
  • 大连做网站哪家公司好推广怎么做才可以赚钱
  • 什么网站做的好看企业排名优化公司
  • 如何建设电商网站优量汇广告平台
  • 重庆铜梁网站建设公司搜索排名竞价
  • 绍兴网站建设方案推广北京最新疫情
  • 凡科官网app下载网络营销推广及优化方案
  • 如何快速做企业网站包括商城seo技术教程博客
  • 网站建设支出账务处理站外推广渠道
  • 做网站主要显哪些内容重庆seo1
  • 拥有服务器后如何做网站百度竞价优化
  • 学生做防溺水题的网站宁波seo关键词
  • 学习网站建设嘉兴seo
  • 网站后台数据分析怎么做域名被墙污染查询
  • 招生就业网站开发详情广州市运营推广公司