转自:OpenAtom OpenHarmony    作者:yuanbo,华为高级工程师

在IoT时代下,终端设备差异较大、形态各异、尺寸各异、交互方式各异,解决设备适配问题无疑是实现万物互联的一个关键。但是,在驱动框架的开发和部署过程中,由于终端设备对硬件的计算和存储能力的需求不同、设备厂商提供的设备软硬件操作接口不同、内核提供的操作接口不同,这就使得OEM厂商部署系统的时候需要投入大量的精力来适配和维护驱动代码。

能否提供一个跨芯片平台、跨内核的驱动框架,使得设备驱动软件可以在不同的设备上运行?OpenHarmony作为一个自主研发、全新技术生态的全领域下一代开源操作系统,提供了一套驱动框架来满足此诉求。

下面我们将带着大家解读OpenHarmony驱动框架。

一、OpenHarmony驱动框架解读

1. 设计目标

为解决在开发和部署过程中遇到的困难,OpenHarmony驱动框架设计目标如下:

  • 支持百K级~G级容量的设备部署,如手机、手环等
  • 提供统一硬件IO抽象,屏蔽SoC芯片差异,兼容不同内核,如Linux、LiteOS等。
  • 屏蔽驱动和系统组件间交互。可动态拆解,满足不同容量设备的部署。
  • 面向不同容量的设备,提供统一的配置界面。

2. 设计思路

OpenHarmony驱动框架(下面简称为HDF)通过提供驱动与芯片平台、内核解耦的底座,规范硬件驱动接口,实现驱动软件在不同设备中部署。

HDF驱动框架架构如下图所示。

图1 驱动架构

为了达成设计目标,OpenHarmony驱动框架采用如下核心设计思路:

(1)弹性化架构

  • 框架可动态伸缩:通过对象管理器,多态加载不同容量设备实现方式,实现弹性伸缩部署。
  • 驱动可动态伸缩:支持统一的设备驱动插件管理,实现设备驱动任意分层,积木式组合拼接

(2)组件化设备模型

  • 提供设备功能模型抽象,屏蔽设备驱动与系统交互的实现,为开发者提供统一的驱动开发接口
  • 提供主流IC的公版驱动能力,支持配置化部署

(3)归一化平台底座

提供规范化的内核、SoC硬件IO适配接口,兼容不同内核、SoC芯片,对外开发规范化的平台驱动接口

(4)统一配置界面

构建全新的配置语言,面向不同容量的设备,提供统一配置界面,支持硬件资源配置和设备信息配置

3. 构建策略

面向Liteos的轻量级设备,主要基于HDF构建主流IC驱动,形成公版驱动和通用设备功能模型,支撑不同硬件芯片、不同内核(LiteOS-M/LiteOS-A)部署。

图2 轻量级设备部署模式

面向标准设备,除了支持内核态驱动,还支持用户态驱动。用户态驱动的重点在于构建设备抽象模型,为系统提供统一的设备接口,兼容Linux原生驱动和HDF驱动。内核态则使用Linux驱动与HDF驱动并存的策略,提供端到端的解决方案。

图3 标准设备部署模式

4. 现状与演进

目前HDF驱动框架已经支持Liteos-m、Liteos-a、Linux内核,以及OpenHarmony轻量级、标准级上部署,并且在标准系统上同时支持内核态与用户态部署。

图4 OpenHarmony驱动框架演进图

经过开发者的不断努力,OpenHarmony驱动框架正在不断完善和增强,在OpenHarmony LTS3.0中,基础框架新增了对热插拔设备的管理以及HDI编译工具hdi-gen,驱动模型部分新增了Audio、Camera、Senso、USB DDK等多个模块的支持。

二、OpenHarmony驱动开发

OpenHarmony驱动为了避免与具体内核产生依赖,实现可迁移目标,开发时需要遵循以下约定:

  • 系统相关接口使用HDF OSAL接口;
  • 总线和硬件资源相关接口使用平台驱动提供的相关接口。

基于HDF框架,驱动开发的通常流程包含驱动代码的实现、编译脚本、配置文件添加、以及用户态程序和驱动交互的流程。下面将详细介绍HDF驱动开发一般步骤。

1. 实现驱动代码

在HDF驱动框架中,HdfDriverEntry对象被用来描述一个驱动实现。

struct HdfDriverEntry {
int32_t moduleVersion;
<span class="hljs-keyword">const</span> char *moduleName;
int32_t (*Bind)(struct HdfDeviceObject *deviceObject);
int32_t (*Init)(struct HdfDeviceObject *deviceObject);
<span class="hljs-keyword">void</span> (*Release)(struct HdfDeviceObject *deviceObject);
};

编写一个简单的驱动,首先需要实现驱动程序(Driver Entry)入口中的三个主要接口:

  • Bind接口:实现驱动接口实例化绑定,如果需要发布驱动接口,会在驱动加载过程中被调用,实例化该接口的驱动服务并和DeviceObject绑定。当用户态发起调用时,Bind中绑定的服务对象的Dispatch方法将被回调,在该方法中处理用户态调用的消息。
  • Init接口:实现驱动或者硬件的初始化,返回错误将中止驱动加载流程。
  • Release接口:实现驱动的卸载,在该接口中释放驱动实例的软硬件资源。

一个基于HDF框架编写的简单驱动代码如下,其功能是用户态消息回环,即驱动收到用户态发送的消息后将相同内容的消息再发送给用户态:

#include <span class="hljs-string">"hdf_base.h"</span>
#include <span class="hljs-string">"hdf_device_desc.h"</span>
#include <span class="hljs-string">"hdf_log.h"</span>
#define HDF_LOG_TAG <span class="hljs-string">"sample_driver"</span>
#define SAMPLE_WRITE_READ <span class="hljs-number">0xFF00</span>
static int EchoString(struct HdfDeviceObject *deviceObject, struct HdfSBuf *data, struct HdfSBuf *reply)
{
<span class="hljs-keyword">const</span> char *readData = HdfSbufReadString(data);
<span class="hljs-keyword">if</span> (readData == NULL) {
HDF_LOGE(<span class="hljs-string">"%s: failed to read data"</span>, __func__);
<span class="hljs-keyword">return</span> HDF_ERR_INVALID_PARAM;
}
<span class="hljs-keyword">if</span> (!HdfSbufWriteInt32(reply, INT32_MAX)) {
HDF_LOGE(<span class="hljs-string">"%s: failed to reply int32"</span>, __func__);
<span class="hljs-keyword">return</span> HDF_FAILURE;
}
<span class="hljs-keyword">return</span> HdfDeviceSendEvent(deviceObject, id, data); <span class="hljs-comment">// 发送事件到用户态</span>
}
int32_t HdfSampleDriverDispatch(struct HdfDeviceObject *deviceObject, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
{
<span class="hljs-keyword">const</span> char *readData = NULL;
int ret = HDF_SUCCESS;
<span class="hljs-keyword">switch</span> (id) {
<span class="hljs-keyword">switch</span> SAMPLE_WRITE_READ:
ret = EchoString(deviceObject, data, reply);
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">default</span>:
HDF_LOGE(<span class="hljs-string">"%s: unsupported command"</span>);
ret = HDF_ERR_INVALID_PARAM;
}
<span class="hljs-keyword">return</span> ret;
}
<span class="hljs-keyword">void</span> HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
<span class="hljs-comment">// 在这里释放驱动申请的软硬件资源</span>
<span class="hljs-keyword">return</span>;
}
int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
<span class="hljs-keyword">if</span> (deviceObject == NULL) {
<span class="hljs-keyword">return</span> HDF_FAILURE
}
static struct IDeviceIoService testService = {
.Dispatch = HdfSampleDriverDispatch,
};
deviceObject->service = &testService;
<span class="hljs-keyword">return</span> HDF_SUCCESS;
}
int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
<span class="hljs-keyword">if</span> (deviceObject == NULL) {
HDF_LOGE(<span class="hljs-string">"%s::ptr is null!"</span>, __func__);
<span class="hljs-keyword">return</span> HDF_FAILURE;
}
HDF_LOGE(<span class="hljs-string">"Sample driver Init success"</span>);
<span class="hljs-keyword">return</span> HDF_SUCCESS;
}
struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = <span class="hljs-number">1</span>,
.moduleName = <span class="hljs-string">"sample_driver"</span>,
.Bind = HdfSampleDriverBind,
.Init = HdfSampleDriverInit,
.Release = HdfSampleDriverRelease,
};
HDF_INIT(g_sampleDriverEntry);

2. 配置设备信息

在HDF框架的配置文件(例如vendor/hisilicon/xxx/config/device_info.hcs)中添加该驱动的配置信息,配置目录与具体开发板关联,如下所示:

root {
device_info {
match_attr = <span class="hljs-string">"hdf_manager"</span>;
template host {
hostName = <span class="hljs-string">""</span>;
priority = <span class="hljs-number">100</span>;
template device {
template deviceNode {
policy = <span class="hljs-number">0</span>;
priority = <span class="hljs-number">100</span>;
preload = <span class="hljs-number">0</span>;
permission = <span class="hljs-number">0664</span>;
moduleName = <span class="hljs-string">""</span>;
serviceName = <span class="hljs-string">""</span>;
deviceMatchAttr = <span class="hljs-string">""</span>;
}
}
}
sample_host :: host{
hostName = <span class="hljs-string">"host0"</span>; <span class="hljs-comment">// host名称,host节点是用来存放某一类驱动的容器</span>
priority = <span class="hljs-number">100</span>; <span class="hljs-comment">// host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序</span>
device_sample :: device { <span class="hljs-comment">// sample设备节点</span>
device0 :: deviceNode { <span class="hljs-comment">// sample驱动的DeviceNode节点</span>
policy = <span class="hljs-number">1</span>; <span class="hljs-comment">// policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍</span>
priority = <span class="hljs-number">100</span>; <span class="hljs-comment">// 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序</span>
preload = <span class="hljs-number">0</span>; <span class="hljs-comment">// 驱动加载策略,参考《5.2 HDF驱动框架章节》</span>
permission = <span class="hljs-number">0664</span>; <span class="hljs-comment">// 驱动创建设备节点权限</span>
moduleName = <span class="hljs-string">"sample_driver"</span>; <span class="hljs-comment">// 驱动名称,该字段的值必须和驱动入口结构体的moduleName值一致</span>
serviceName = <span class="hljs-string">"sample_service"</span>; <span class="hljs-comment">// 驱动对外发布服务的名称,必须唯一</span>
deviceMatchAttr = <span class="hljs-string">"sample_config"</span>; <span class="hljs-comment">// 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等</span>
}
}
}
}
}

定义设备列表时使用了HCS的模板语法,template host节点下的内容由HDF框架定义,新增host以及host中的device只需要继承该模板并填充具体内容即可。

在配置中定义的device将在加载过程中产生一个设备实例,配置中通过moduleName字段指定设备对应的驱动名称,从而将设备与驱动关联起来。其中,设备与驱动可以是一对多的关系,即可以实现一个驱动支持多个同类型设备。

3. 用户态程序与驱动交互

用户态程序和驱动交互基于HDF IoService模型实现,该设计屏蔽了具体内核的差异,将驱动接口抽象为IoService对象,调用者基于名称获取该对象,并可以使用IoService系列接口进行接口调用和事件监听。值得一提的是消息传递时使用了HDF Sbuf对象进行参数的序列化和反序列化,这样可以避免不受控的内存访问,也简化了消息传递和分发过程中的内存所有权问题,有利于提升用户态和内核态数据传递的安全性和便利性。HDF Sbuf相关接口可以参考HarmonyOS设备开发官网API Reference中头文件hdf_sbuf.h部分。

基于HDF框架编写的用户态程序和驱动交互的代码如下:

#include <span class="hljs-string">"hdf_log.h"</span>
#include <span class="hljs-string">"hdf_sbuf.h"</span>
#include <span class="hljs-string">"hdf_io_service_if.h"</span>
#define HDF_LOG_TAG <span class="hljs-string">"sample_test"</span>
#define SAMPLE_SERVICE_NAME <span class="hljs-string">"sample_service"</span>
#define SAMPLE_WRITE_READ <span class="hljs-number">0xFF00</span>
int g_replyFlag = <span class="hljs-number">0</span>;
static int OnDevEventReceived(<span class="hljs-keyword">void</span> *priv, uint32_t id, struct HdfSBuf *data)
{
<span class="hljs-keyword">const</span> char *string = HdfSbufReadString(data);
int ret = HDF_SUCCESS;
<span class="hljs-keyword">if</span> (string == NULL) {
HDF_LOGE(<span class="hljs-string">"failed to read string in event data"</span>);
ret = HDF_FAILURE;
} <span class="hljs-keyword">else</span> {
HDF_LOGE(<span class="hljs-string">"%s"</span>, string);
}
g_replyFlag = <span class="hljs-number">1</span>;
<span class="hljs-keyword">return</span> ret;
}
static int SendEvent(struct HdfIoService *serv, char *eventData)
{
int ret = <span class="hljs-number">0</span>;
struct HdfSBuf *data = HdfSBufObtainDefaultSize(); <span class="hljs-comment">// 申请需要发送的序列化对象</span>
<span class="hljs-keyword">if</span> (data == NULL) {
HDF_LOGE(<span class="hljs-string">"failed to obtain sbuf data"</span>);
<span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
}
struct HdfSBuf *reply = HdfSBufObtainDefaultSize(); <span class="hljs-comment">// 申请返回数据的序列化对象</span>
<span class="hljs-keyword">if</span> (reply == NULL) {
HDF_LOGE(<span class="hljs-string">"failed to obtain sbuf reply"</span>);
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
<span class="hljs-keyword">if</span> (!HdfSbufWriteString(data, eventData)) { <span class="hljs-comment">// 准备消息内容</span>
HDF_LOGE(<span class="hljs-string">"failed to write sbuf"</span>);
ret = HDF_FAILURE;
goto out;
}
ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply); <span class="hljs-comment">// 发起接口调用</span>
<span class="hljs-keyword">if</span> (ret != HDF_SUCCESS) {
HDF_LOGE(<span class="hljs-string">"failed to send service call"</span>);
goto out;
}
int replyData = <span class="hljs-number">0</span>;
<span class="hljs-keyword">if</span> (!HdfSbufReadInt32(reply, &replyData)) { <span class="hljs-comment">// 反序列化返回数据</span>
HDF_LOGE(<span class="hljs-string">"failed to get service call reply"</span>);
ret = HDF_ERR_INVALID_OBJECT;
goto out;
}
HDF_LOGE(<span class="hljs-string">"Get reply is: %d"</span>, replyData);
out:
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
<span class="hljs-keyword">return</span> ret;
}
int main()
{
struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME); <span class="hljs-comment">// 通过名称获取IoService对象,与驱动配置中的名称一致</span>
<span class="hljs-keyword">if</span> (serv == NULL) {
HDF_LOGE(<span class="hljs-string">"failed to get service %s"</span>, SAMPLE_SERVICE_NAME);
<span class="hljs-keyword">return</span> HDF_FAILURE;
}
static struct HdfDevEventlistener listener = { <span class="hljs-comment">// 构造驱动事件监听器对象</span>
.callBack = OnDevEventReceived, <span class="hljs-comment">// 填充事件处理方法</span>
.priv = NULL;
};
<span class="hljs-keyword">if</span> (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) { <span class="hljs-comment">// 注册事件监听</span>
HDF_LOGE(<span class="hljs-string">"failed to register event listener"</span>);
<span class="hljs-keyword">return</span> HDF_FAILURE;
}
<span class="hljs-keyword">if</span> (SendEvent(serv, <span class="hljs-string">"Hello World, HDF Driver!"</span>)) { <span class="hljs-comment">// 调用驱动接口,样例驱动收到事件</span>
HDF_LOGE(<span class="hljs-string">"failed to send event"</span>);
<span class="hljs-keyword">return</span> HDF_FAILURE;
}
<span class="hljs-keyword">while</span> (g_replyFlag == <span class="hljs-number">0</span>) { <span class="hljs-comment">// 等待驱动上报事件</span>
sleep(<span class="hljs-number">1</span>);
}
HdfDeviceUnregisterEventListener(serv, &listener)); <span class="hljs-comment">// 去注册事件监听器</span>
HdfIoServiceRecycle(serv); <span class="hljs-comment">// 回收IoService对象</span>
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}

该示例执行后会在终端中打印出"Hello World, HDF Driver!"字符串,表明我们的用户态测试程序和驱动成功地进行了一次交互。

三、使用DevEco Device Tool进行驱动开发

上一小节介绍了OpenHarmony驱动的一般开发方法,那么有没有更简单的方法添加一款驱动呢?答案就是华为南向开发IDE——DevEco Device Tool。DevEco Device Tool最新版本已经集成了HDF驱动开发功能,下面介绍如何使用DevEco Device Tool进行驱动开发。

DevEco Device Tool下载链接:https://device.harmonyos.com/cn/develop/ide#download_release

1. 创建驱动

(1)导入工程

参考DevEco Device Tool手册,通过npm或网络下载的方式导入OHOS工程。

图5 DevEco Device Tool启动界面

(2)使用HDF页面工具创建新驱动,按照需求填写Module名称,工具将根据Module名称创建对应驱动代码与。

图6 Device Eco Tool HDF插件界面

DevEco Device Tool将自动生成驱动实现代码:

图7 Device Eco Tool 生成驱动代码

为源码文件自动生成编译脚本:

图8 Device Eco Tool 生成驱动编译脚本

DevEco Device Tool还会在对应单板的驱动配置中生成驱动设备配置信息:

图9 Device Eco Tool 生成驱动配置信息

2. 修改驱动

图10 Device Eco Tool驱动快速编辑界面

DevEco Device Tool提供了快捷方式直达源码、编译脚本、配置文件,点击链接修改相关文件,实现驱动功能。DevEco Device Tool自动生成代码已经提供了DriverEntry的基础实现,只需填充对应函数的实际功能即可。

3. 编译版本

使用DevEco Device Tool build功能一键编译版本,编译输出显示在终端窗口:

图11 Device Eco Tool编译界面

4. 烧录验证

DevEco Device Tool提供了一站式的烧录、调试环境。使用upload功能将编译好的镜像烧录进开发板。

图12 Device Eco Tool烧写功能界面

烧录过程和进度显示在终端窗口:

图13 Device Eco Tool烧写输出

四、总结

除了在此次HDC大会与大家分享驱动框架的设计和最新进展,开放原子基金会还在OpenHarmony公众号、gitee社区等渠道发布了一系列技术分享、指导文档等资料,欢迎大家关注并一起建设OpenHarmony驱动生态。

HDC2021技术分论坛:OpenHarmony驱动框架解读和开发实践的更多相关文章

  1. HDC2021技术分论坛:“积木拼装”,HarmonyOS弹性部署大揭秘!

    作者:peitaiyi,华为终端OS产品交付专家 HarmonyOS是一款面向万物互联时代的.全新的分布式操作系统.在传统的单设备系统能力基础上,HarmonyOS提出了基于同一套系统能力.适配多种终 ...

  2. HDC2021技术分论坛:组件通信、硬件池化,这些创新技术你get了吗?

    作者:ligang 华为分布式硬件技术专家,sunbinxin 华为应用框架技术专家 HarmonyOS是一款全新的分布式操作系统,为开发者提供了元能力框架.事件通知.分布式硬件等分布式技术,使能开发 ...

  3. HDC2021技术分论坛:进程崩溃/应用卡死,故障频频怎么办?

    ​作者:jiwenqiang,DFX技术专家 提到开发一个产品,我们通常首先想到的是要实现什么样的功能,但是除了功能之外,非功能属性也会很大程度上影响一个产品的体验效果,比如不定时出现的应用卡死.崩溃 ...

  4. HDC2021技术分论坛:如何高效完成HarmonyOS分布式应用测试?

    作者:liuxun,HarmonyOS测试架构师 HarmonyOS是新一代的智能终端操作系统,给开发者提供了设备发现.设备连接.跨设备调用等丰富的分布式API.随着越来越多的开发者投入到Harmon ...

  5. HDC2021技术分论坛:异构组网如何解决共享资源冲突?

    作者:lijie,HarmonyOS软总线领域专家 相信大家对HarmonyOS的"超级终端"比较熟悉了.那么,您知道超级终端场景下的多种设备在不同环境下是如何组成一个网络的吗?这 ...

  6. HDC技术分论坛:HarmonyOS新一代UI框架的全面解读

    作者:yuzhiqiang,UI编程框架首席技术专家 在Harmony 3.0.0开发者预览版中,包含了新一代的声明式UI框架ArkUI 3.0.多语言跨平台编译器ArkCompiler 3.0.跨端 ...

  7. 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)

    作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...

  8. Linux设备驱动框架设计

    引子 Linux操作系统的一大优势就是支持数以万计的芯片设备,大大小小的芯片厂商工程师都在积极地向Linux kernel提交设备驱动代码.能让这个目标得以实现,这背后隐藏着一个看不见的技术优势:Li ...

  9. (转)S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析 (By liukun321 咕唧咕唧)

    作者:liukun321 咕唧咕唧 日期:2014.1.18 转载请标明作者.出处:http://blog.csdn.net/liukun321/article/details/18452663 本文 ...

  10. S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析

    作者:liukun321 咕唧咕唧 日期:2014.1.18 转载请标明作者.出处:http://blog.csdn.net/liukun321/article/details/18452663 本文 ...

随机推荐

  1. 【Azure 应用服务】App Service 配置 Application Settings 访问Storage Account得到 could not be resolved: '*.file.core.windows.net'的报错。没有解析成对应中国区 Storage Account地址 *.file.core.chinacloudapi.cn

    问题描述 App Service 配置 Application Settings 访问Storage Account.如下: { "name": "WEBSITE_CON ...

  2. Taurus.MVC WebMVC 入门开发教程6:路由配置与路由映射

    前言: 在本篇 Taurus.MVC WebMVC 入门开发教程的第六篇文章中, 我们将讨论如何配置路由并映射到控制器和操作方法. 路由是决定应用程序如何响应客户端请求的重要组成部分,因此在 Web ...

  3. Task Manager 的设计简述

    讲解 Task Manager 之前,在这里先介绍一些 Task Manager 会使用到的概念术语. 图数据库 Nebula Graph 中,存在一些长期在后台运行的任务,我们称之为 Job.存储层 ...

  4. nginx 代理 sftp转发流量

    首先需要nginx安装有 stream模块,使用 nginx -V 查看下是否有 --with-stream,没有使用命令: yum -y install nginx-all-modules.noar ...

  5. 手机端User-agent

    转载: http://www.fynas.com/ua 设备 系统 浏览器 User-Agent vivo X20Plus A Android 手机百度 Mozilla/5.0 (Linux; And ...

  6. powershell配置自动补全

    powershell配置自动补全 一.需求: 看到老师上课用mac命令行有自动补全功能,发现真的爽.但是自己的windows powershell不能使用自动补全功能.有了需求,就想找到能完成目前的任 ...

  7. Python 动态网页Fetch/XHR爬虫——以获取NBA球员信息为例

    Python 动态网页Fetch/XHR爬虫--以获取NBA球员信息为例 动态网页抓取信息,一般利用F12开发者工具-网络-Fetch/XHR获取信息,实现难点有: 动态网页的加载方式 获取请求Url ...

  8. PlayBook 详解

    4)Playbook 4.1)Playbook 介绍 PlayBook 与 ad-hoc 相比,是一种完全不同的运用 Ansible 的方式,类似与 Saltstack 的 state 状态文件.ad ...

  9. Some characters cannot be mapped using 'ISO-8859-1'解决方案

    问题: MyEclipse中:Some characters cannot be mapped using 'ISO-8859-1' character encoding   分析:由于默认的字符是I ...

  10. linux介绍、安装、shell

    1-Linux发展介绍 零 什么是Linux Linux:和我们常见的Windows一样,都是操作系统,但不同的是: Windows: 收费,不开源,主要用于日常办公.游戏.娱乐多一些. Linux: ...