技术标签: 鸿蒙OS开发 java 华为 harmonyos
本系列文章汇总:
- 《【鸿蒙OS开发入门】01 - 搭建Ubuntu虚拟机开发环境》
- 《【鸿蒙OS开发入门】02 - 启动流程代码分析之Uboot 第一阶段:之解压并引导加载u-boot.bin》
- 《【鸿蒙OS开发入门】03 - 启动流程代码分析之Uboot 第二阶段:之board_init初始化》
- 《【鸿蒙OS开发入门】04 - 启动流程代码分析之Uboot 第二阶段:之U_BOOT_CMD原理》
- 《【鸿蒙OS开发入门】05 - 启动流程代码分析之Uboot 第二阶段:之bootm引导加载Kernel OS》
- 《【鸿蒙OS开发入门】06 - 启动流程代码分析之KernelOS:之启动Linux-4.19 Kernel内核》
- 《【鸿蒙OS开发入门】07 - 安装docker环境编译openharmony 2.0代码》
- 《【鸿蒙OS开发入门】08 - 启动流程代码分析之KernelOS:之启动 liteos_a 内核》
- 《【鸿蒙OS开发入门】09 - 启动流程代码分析之KernelOS:之启动Linux-4.19 Kernel内核 中do_basic_setup()所干的大事》
- 《【鸿蒙OS开发入门】10 - 启动流程代码分析之第一个用户态进程:init 进程》
- 《【鸿蒙OS开发入门】11 - 启动流程代码分析之第一个用户态进程:init 进程 之 Services简介》
- 《【鸿蒙OS开发入门】12 - 启动流程代码分析之第一个用户态进程:init 进程 之 pre-init 任务详解》
- 《【鸿蒙OS开发入门】13 - 启动流程代码分析之第一个用户态进程:init 进程 之 init 任务详解》
- 《【鸿蒙OS开发入门】14 - 启动流程代码分析之第一个用户态进程:init 进程 之 post-init 任务详解》
- 《【鸿蒙OS开发入门】15 - 启动流程代码分析之第一个用户态进程:init 进程 之 StartParamService源码分析 及 setparam、getparam代码分析》
- 《【鸿蒙OS开发入门】16 - 重头搭建Ubuntu新环境编译OpenHarmony 3.0 LTS》
- 《【鸿蒙OS开发入门】17 - HDF驱动子系统:hdf_devmgr服务 驱动框架管理模块源码分析》
- 《【鸿蒙OS开发入门】18 - HDF驱动子系统:加速度计传感器 Driver层驱动代码分析》
之所以选择加速度计传感器来作为首篇驱动分析,主要是因为官方文档有对它介绍,《SENSOR》,
本文就参考着官方文档,来学习下加速度计传感器的驱动逻辑。
参考官方文档,先来上一张Sensor驱动模型图:
从上面Sensor
驱动模型图,从下往上,按我的理解,我们大致可以分为三层:
Hardware
层: 这一层是实实在在的物理器件,比如加速度计、陀螺仪等。Platform IF
层: 这一层主要是提供与物理器件的交互方法,比如I2C、SPI等总线驱动。Driver
层:这一层的核心就是各个驱动的struct HdfDriverEntry
结构体,在结构体中封装了相关sensor的具体实现逻辑。HDI
层(Hardware Driver Interface
):这一层,主要是对所有sensor操作方法的封装,给上层app提供操作接口。本文重点分析的就是加速度计传感器的Driver层代码实现。
好,废话不多说,先来看代码吧。
# drivers\framework\model\sensor\driver\chipset\accel\accel_bmi160.c
static int32_t ReadBmi160RawData(struct SensorCfgData *data, struct AccelData *rawData, int64_t *timestamp) {
}
int32_t ReadBmi160Data(struct SensorCfgData *data){
}
static int32_t InitBmi160(struct SensorCfgData *data){
}
static int32_t InitAccelPreConfig(void){
}
static int32_t DispatchBMI160(struct HdfDeviceIoClient *client, int cmd, struct HdfSBuf *data, struct HdfSBuf *reply){
}
int32_t Bmi160BindDriver(struct HdfDeviceObject *device){
} // 加速度计传感器绑定函数
int32_t Bmi160InitDriver(struct HdfDeviceObject *device){
} // 加速度计传感器初始化函数
void Bmi160ReleaseDriver(struct HdfDeviceObject *device){
} // 加速度计传感器资源释放函数
// 注册加速度计传感器入口数据结构体对象
struct HdfDriverEntry g_accelBmi160DevEntry = {
.moduleVersion = 1, // 加速度计传感器模块版本号
.moduleName = "HDF_SENSOR_ACCEL_BMI160", // 加速度计传感器模块名,要与device_info.hcs文件里的加速度计moduleName字段值一样
.Bind = Bmi160BindDriver, // 加速度计传感器绑定函数
.Init = Bmi160InitDriver, // 加速度计传感器初始化函数
.Release = Bmi160ReleaseDriver, // 加速度计传感器资源释放函数
};
// 调用HDF_INIT将驱动入口注册到HDF框架中,
// 在加载驱动时HDF框架会先调用Bind函数, 再调用Init函数加载该驱动,
// 当Init调用异常时,HDF框架会调用Release释放驱动资源并退出
HDF_INIT(g_accelBmi160DevEntry);
如果内核跑的是linux
,那宏控配置主要是在Kconfig
中:
# drivers\adapter\khdf\linux\model\sensor\Kconfig
config DRIVERS_HDF_SENSOR_ACCEL
bool "Enable HDF accel sensor driver"
default n
depends on DRIVERS_HDF_SENSOR
help
Answer Y to enable HDF accel sensor driver.
config DRIVERS_HDF_SENSOR_ACCEL_BMI160
bool "Enable HDF accel sensor driver"
default n
depends on DRIVERS_HDF_SENSOR_ACCEL
help
Answer Y to enable HDF accel bmi160 sensor driver.
Makefile
来控制相应的编译规则:
# drivers\adapter\khdf\linux\model\sensor\Makefile
SENSOR_ROOT_DIR = ../../../../../framework/model/sensor/driver
obj-$(CONFIG_DRIVERS_HDF_SENSOR) += \
$(SENSOR_ROOT_DIR)/common/src/sensor_config_controller.o \
$(SENSOR_ROOT_DIR)/common/src/sensor_config_parser.o \
$(SENSOR_ROOT_DIR)/common/src/sensor_device_manager.o \
$(SENSOR_ROOT_DIR)/common/src/sensor_platform_if.o
obj-$(CONFIG_DRIVERS_HDF_SENSOR_ACCEL) += $(SENSOR_ROOT_DIR)/accel/sensor_accel_driver.o
obj-$(CONFIG_DRIVERS_HDF_SENSOR_ACCEL_BMI160) += $(SENSOR_ROOT_DIR)/chipset/accel/accel_bmi160.o
如果内核跑的是liteos
,则是由BUILD.gn、makefile、Kconfig共同控制:
# drivers\adapter\khdf\liteos\model\sensor\BUILD.gn
import("//drivers/adapter/khdf/liteos/hdf.gni")
module_switch = defined(LOSCFG_DRIVERS_HDF_SENSOR)
module_name = "hdf_sensor_driver"
hdf_driver(module_name) {
FRAMEWORKS_SENSOR_ROOT = "$HDF_FRAMEWORKS_PATH/model/sensor/driver"
sources = [
"$FRAMEWORKS_SENSOR_ROOT/common/src/sensor_config_controller.c",
"$FRAMEWORKS_SENSOR_ROOT/common/src/sensor_config_parser.c",
"$FRAMEWORKS_SENSOR_ROOT/common/src/sensor_device_manager.c",
"$FRAMEWORKS_SENSOR_ROOT/common/src/sensor_platform_if.c",
]
if (defined(LOSCFG_DRIVERS_HDF_SENSOR_ACCEL)) {
sources += [ "$FRAMEWORKS_SENSOR_ROOT/accel/sensor_accel_driver.c" ]
}
if (defined(LOSCFG_DRIVERS_HDF_SENSOR_ACCEL_BMI160)) {
sources += [ "$FRAMEWORKS_SENSOR_ROOT/chipset/accel/accel_bmi160.c" ]
}
include_dirs = [
"$FRAMEWORKS_SENSOR_ROOT/include",
"$FRAMEWORKS_SENSOR_ROOT/common/include",
"$FRAMEWORKS_SENSOR_ROOT/accel",
"$FRAMEWORKS_SENSOR_ROOT/chipset/accel",
]
}
Makefile
内容如下:
# drivers\adapter\khdf\liteos\model\sensor\Makefile
include $(LITEOSTOPDIR)/../../drivers/adapter/khdf/liteos/lite.mk
MODULE_NAME := hdf_sensor_driver
FRAMEWORKS_SENSOR_ROOT = $(LITEOSTOPDIR)/../../drivers/framework/model/sensor/driver
LOCAL_INCLUDE := $(FRAMEWORKS_SENSOR_ROOT)/include \
$(FRAMEWORKS_SENSOR_ROOT)/common/include \
$(FRAMEWORKS_SENSOR_ROOT)/accel
LOCAL_SRCS += $(FRAMEWORKS_SENSOR_ROOT)/common/src/sensor_config_controller.c \
$(FRAMEWORKS_SENSOR_ROOT)/common/src/sensor_config_parser.c \
$(FRAMEWORKS_SENSOR_ROOT)/common/src/sensor_device_manager.c \
$(FRAMEWORKS_SENSOR_ROOT)/common/src/sensor_platform_if.c
ifeq ($(LOSCFG_DRIVERS_HDF_SENSOR_ACCEL), y)
LOCAL_SRCS += $(FRAMEWORKS_SENSOR_ROOT)/accel/sensor_accel_driver.c \
$(FRAMEWORKS_SENSOR_ROOT)/chipset/accel/accel_bmi160.c
endif
include $(HDF_DRIVER)
Kconfig
内容如下:
# drivers\adapter\khdf\liteos\model\sensor\Kconfig
config DRIVERS_HDF_SENSOR
bool "Enable HDF sensor driver"
default n
depends on DRIVERS_HDF
help
Answer Y to enable HDF sensor driver.
config DRIVERS_HDF_SENSOR_ACCEL
bool "Enable HDF accel sensor driver"
default n
depends on DRIVERS_HDF_SENSOR
help
Answer Y to enable HDF accel sensor driver.
config DRIVERS_HDF_SENSOR_ACCEL_BMI160
bool "Enable HDF accel sensor driver"
default n
depends on DRIVERS_HDF_SENSOR_ACCEL
help
Answer Y to enable HDF accel bmi160 sensor driver.
分析到这,我发现其实Makefile 和BUILD.gn的内容其实差不多,这就很奇怪了,为啥要配两份?
以 Hi3516DV300
为例,这个平台的 device_info.hcs
在vendor/hisilicon/Hi3516DV300/hdf_config/khdf/device_info/
目录下:
# vendor/hisilicon/Hi3516DV300/hdf_config/khdf/device_info/device_info.hcs
root {
device_info {
match_attr = "hdf_manager";
template host {
hostName = "";
priority = 100;
template device {
template deviceNode {
policy = 0;
priority = 100;
preload = 0;
permission = 0664;
moduleName = "";
serviceName = "";
deviceMatchAttr = "";
}
}
}
platform :: host {
hostName = "platform_host";
priority = 50;
device_gpio :: device {
device0 :: deviceNode {
policy = 0;
priority = 10;
permission = 0644;
moduleName = "linux_gpio_adapter";
deviceMatchAttr = "linux_gpio_adapter";
}
}
device_i2c :: device {
device0 :: deviceNode {
policy = 2;
priority = 50;
permission = 0644;
moduleName = "HDF_PLATFORM_I2C_MANAGER";
serviceName = "HDF_PLATFORM_I2C_MANAGER";
deviceMatchAttr = "hdf_platform_i2c_manager";
}
device1 :: deviceNode {
policy = 0;
priority = 55;
permission = 0644;
moduleName = "linux_i2c_adapter";
deviceMatchAttr = "linux_i2c_adapter";
}
}
}
display :: host {
hostName = "display_host";
}
input :: host {
hostName = "input_host";
priority = 100;
}
network :: host {
hostName = "network_host";
}
sensor :: host {
hostName = "sensor_host";
device_sensor_manager :: device {
device0 :: deviceNode {
policy = 2;
priority = 100;
preload = 0;
permission = 0664;
moduleName = "HDF_SENSOR_MGR_AP";
serviceName = "hdf_sensor_manager_ap";
}
}
device_sensor_accel :: device {
device0 :: deviceNode {
policy = 1;
priority = 110;
preload = 2;
permission = 0664;
moduleName = "HDF_SENSOR_ACCEL";
serviceName = "hdf_sensor_accel";
deviceMatchAttr = "hdf_sensor_accel_driver";
}
}
device_sensor_bmi160 :: device {
device0 :: deviceNode {
policy = 1;
priority = 120;
preload = 2;
permission = 0664;
moduleName = "HDF_SENSOR_ACCEL_BMI160";
serviceName = "hdf_accel_bmi160";
deviceMatchAttr = "hdf_sensor_accel_bmi160_driver";
}
}
}
usb_pnp_linux :: host {
hostName = "usb_pnp_linux_host";
device_usb_pnp_linux :: device {
}
}
audio :: host {
hostName = "audio_host";
priority = 60;
}
vibrator :: host {
hostName = "vibrator_host";
}
dsoftbus :: host {
hostName = "dsoftbus_host";
}
可以看到,配置方面,相对还是比较简单的。
看到这里,其实您可以返回上一篇文章继续看了: 《【鸿蒙OS开发入门】17 - HDF驱动子系统:hdf_devmgr服务 驱动框架管理模块源码分析》
在前面《【鸿蒙OS开发入门】17 - HDF驱动子系统:hdf_devmgr服务 驱动框架管理模块源码分析》 中,
我们分析到 DevHostServiceAddDevice()
函数,它就是整个驱动的总入口:
主要工作如下:
driverLoader
入口HdfDevice
结构体,其中定义了hostId/deviceId
和 Attach/Detach
方法LoadNode()
,加载设备驱动device->super.Attach()
方法开始探测驱动,实际调用的是.Init
方法# drivers\framework\core\host\src\devhost_service.c
int DevHostServiceAddDevice(struct IDevHostService *inst, const struct HdfDeviceInfo *deviceInfo)
{
int ret = HDF_FAILURE;
struct HdfDevice *device = NULL;
struct HdfDeviceNode *devNode = NULL;
struct DevHostService *hostService = CONTAINER_OF(inst, struct DevHostService, super);
// 1. 获取 driverLoader 入口
struct IDriverLoader *driverLoader = HdfDriverLoaderGetInstance();
================>
static struct HdfDriverLoader driverLoader;
HdfDriverLoaderConstruct(&driverLoader);
--------------->
+ struct IDriverLoader *driverLoaderIf = (struct IDriverLoader *)inst;
+ driverLoaderIf->LoadNode = HdfDriverLoaderLoadNode;
+ driverLoaderIf->UnLoadNode = HdfDriverLoaderUnLoadNode;
+ driverLoaderIf->GetDriverEntry = HdfDriverLoaderGetDriverEntry;
<---------------
return (struct HdfObject *)&driverLoader;
<================
// 2. 获取 HdfDevice 结构体,其中定义了hostId/deviceId 和 Attach/Detach方法
device = DevHostServiceGetDevice(hostService, deviceInfo->deviceId);
------------->
+ device = HdfDeviceNewInstance();
+ ========> return (struct HdfDevice *)HdfObjectManagerGetObject(HDF_OBJECT_ID_DEVICE);
+ + struct HdfDevice *device = (struct HdfDevice *)OsalMemCalloc(sizeof(struct HdfDevice));
+ + HdfDeviceConstruct(device);
+ + --------->
+ + + device->super.Attach = HdfDeviceAttach; // 最终调用驱动的 .Init
+ + + DListHeadInit(&device->devNodes);
+ + <---------
+ <========
+ device->hostId = inst->hostId;
+ device->deviceId = deviceId;
+ DListInsertHead(&device->node, &inst->devices);
<------------
if (device == NULL || device->super.Attach == NULL) {
ret = HDF_DEV_ERR_NO_DEVICE;
goto error;
}
// 3. 调用LoadNode() ,加载设备驱动
devNode = driverLoader->LoadNode(driverLoader, deviceInfo);
// 4. 调用device->super.Attach() 方法开始探测驱动,实际调用的是.Init方法
devNode->hostService = hostService;
ret = device->super.Attach(&device->super, devNode);
return HDF_SUCCESS;
}
我们本文就以ACCEL_BMI160
为例来分析下调用流程:
主要工作如下:
struct HdfDriverEntry g_accelBmi160DevEntry
,其中包含了bmi160
的bind
和init
方法// 注册加速度计传感器入口数据结构体对象
struct HdfDriverEntry g_accelBmi160DevEntry = {
.moduleVersion = 1, // 加速度计传感器模块版本号
.moduleName = "HDF_SENSOR_ACCEL_BMI160", // 加速度计传感器模块名,要与device_info.hcs文件里的加速度计moduleName字段值一样
.Bind = Bmi160BindDriver, // 加速度计传感器绑定函数
.Init = Bmi160InitDriver, // 加速度计传感器初始化函数
.Release = Bmi160ReleaseDriver, // 加速度计传感器资源释放函数
};
HDF_OBJECT_ID_DEVICE_SERVICE
所对应的函数DeviceNodeExtCreate()
Bmi160
的 devNode
结构体,绑定driverEntry
bind
方法开始绑定驱动# drivers\framework\core\host\src\hdf_driver_loader.c
struct HdfDeviceNode *HdfDriverLoaderLoadNode(struct IDriverLoader *loader, const struct HdfDeviceInfo *deviceInfo)
{
struct HdfDriverEntry *driverEntry = NULL;
struct HdfDeviceNode *devNode = NULL;
// 1. 获取驱动函数结构体struct HdfDriverEntry g_accelBmi160DevEntry
driverEntry = loader->GetDriverEntry(deviceInfo);
// 2. 初始化驱动相关的设备节点,调用HDF_OBJECT_ID_DEVICE_SERVICE所对应的函数DeviceNodeExtCreate()
devNode = HdfDeviceNodeNewInstance();
================> return (struct HdfDeviceNode *)HdfObjectManagerGetObject(HDF_OBJECT_ID_DEVICE_SERVICE);
+ struct DeviceNodeExt *instance = (struct DeviceNodeExt *)OsalMemCalloc(sizeof(struct DeviceNodeExt));
+ DeviceNodeExtConstruct(instance);
+ ------------>
+ - struct IDeviceNode *nodeIf = &devNode->super;
+ - HdfDeviceNodeConstruct(&inst->super);
+ - ==========>
+ - HdfDeviceObjectConstruct(&devNode->deviceObject);
+ - -------->
+ - + deviceObject->property = NULL;
+ - + deviceObject->service = NULL;
+ - + deviceObject->deviceClass = DEVICE_CLASS_DEFAULT;
+ - <--------
+ - devNode->token = HdfDeviceTokenNewInstance();
+ - nodeIf->LaunchNode = HdfDeviceLaunchNode; // 调用sensor init方法,初始化sensor
+ - nodeIf->PublishService = HdfDeviceNodePublishPublicService;
+ - <==========
+ - nodeIf->PublishService = DeviceNodeExtPublishService;
+ <------------
+ instance->ioService = NULL;
<================
// 3. 配置Bmi160的 devNode结构体,绑定driverEntry
devNode->driverEntry = driverEntry;
devNode->deviceInfo = deviceInfo; // device list
devNode->deviceObject.property = HcsGetNodeByMatchAttr(HdfGetRootNode(), deviceInfo->deviceMatchAttr);
devNode->deviceObject.priv = (void *)(deviceInfo->private);
// 4. 调用驱动的 bind 方法开始绑定驱动
driverEntry->Bind(&devNode->deviceObject);
return devNode;
}
struct Bmi160DrvData
内存HdfDeviceObject
结构体与驱动结构体Bmi160DrvData
绑定在一起g_bmi160DrvData
# drivers\framework\model\sensor\driver\chipset\accel\accel_bmi160.c
int32_t Bmi160BindDriver(struct HdfDeviceObject *device)
{
// 1. 分配并初始经`struct Bmi160DrvData` 内存
struct Bmi160DrvData *drvData = (struct Bmi160DrvData *)OsalMemCalloc(sizeof(*drvData));
// 2. 将设备节点的HdfDeviceObject结构体与驱动结构体Bmi160DrvData绑定在一起
drvData->ioService.Dispatch = DispatchBMI160;
drvData->device = device;
device->service = &drvData->ioService;
// 3. 配置全局驱动结构体指针g_bmi160DrvData
g_bmi160DrvData = drvData;
return HDF_SUCCESS;
}
# drivers\framework\core\host\src\hdf_device.c
static int HdfDeviceAttach(struct IHdfDevice *devInst, struct HdfDeviceNode *devNode)
{
struct HdfDevice *device = (struct HdfDevice *)devInst;
struct IDeviceNode *nodeIf = (struct IDeviceNode *)devNode;
DListInsertTail(&devNode->entry, &device->devNodes);
return nodeIf->LaunchNode(devNode, devInst);
}
在HdfDeviceAttach()
中主要是调用了DeviceNode
的LaunchNode()
方法,
它是在前面初始化驱动相关的设备节点时配置的,代码为:nodeIf->LaunchNode = HdfDeviceLaunchNode;
我们来看看HdfDeviceLaunchNode()
函数:
.Init()
方法探测并初始化驱动设备device node
对应的service
device node service
中, 对应的函数为 DevmgrServiceAttachDevice()
# drivers\framework\core\host\src\hdf_device_node.c
int HdfDeviceLaunchNode(struct HdfDeviceNode *devNode, struct IHdfDevice *devInst)
{
struct HdfDevice *device = (struct HdfDevice *)devInst; // HdfDevice 结构体,其中定义了hostId/deviceId和Attach/Detach方法
struct HdfDriverEntry *driverEntry = devNode->driverEntry; // 驱动函数结构体
const struct HdfDeviceInfo *deviceInfo = devNode->deviceInfo; // 设备device list结构体
struct IHdfDeviceToken *deviceToken = NULL;
// 1. 调用驱动 .Init() 方法探测并初始化驱动设备
int ret = driverEntry->Init(&devNode->deviceObject);
// 2. 注册device node 对应的service
ret = HdfDeviceNodePublishService(devNode, deviceInfo, devInst);
------------>
- struct IDeviceNode *nodeIf = &devNode->super;
- status = nodeIf->PublishService(devNode, deviceInfo->svcName); // 调用的是 HdfDeviceNodePublishPublicService() 方法
- ===========>
- + return DevSvcManagerClntAddService(svcName, &devNode->deviceObject);
- + --------->
- + struct DevSvcManagerClnt *devSvcMgrClnt = DevSvcManagerClntGetInstance();
- + struct IDevSvcManager *serviceManager = devSvcMgrClnt->devSvcMgrIf;
- + return serviceManager->AddService(serviceManager, svcName, service);
- + <---------
- <===========
<------------
// 3. 将驱动添加进device node service中, 对应的函数为 DevmgrServiceAttachDevice()
deviceToken = devNode->token;
ret = DevmgrServiceClntAttachDevice(deviceInfo, deviceToken);
------------>
struct DevmgrServiceClnt *inst = DevmgrServiceClntGetInstance();
devMgrSvcIf = inst->devMgrSvcIf;
return devMgrSvcIf->AttachDevice(devMgrSvcIf, deviceInfo, deviceToken);
<------------
return ret;
}
此时,终于进入我们驱动的正题了,开始探测并初始化驱动设备。
# drivers\framework\model\sensor\driver\chipset\accel\accel_bmi160.c
int32_t Bmi160InitDriver(struct HdfDeviceObject *device)
{
int32_t ret;
struct AccelOpsCall ops;
struct Bmi160DrvData *drvData = (struct Bmi160DrvData *)device->service;
// 初始化I2C6的控制寄存器:PIN肢配置为I2C模式和配置I2C时钟
ret = InitAccelPreConfig();
//
drvData->sensorCfg = AccelCreateCfgData(device->property);
------------>
GetSensorBaseConfigData(node, drvData->accelCfg);
DetectSensorDevice(drvData->accelCfg);
drvData->detectFlag = true;
InitAccelAfterDetected(drvData->accelCfg);
<------------
ops.Init = NULL;
ops.ReadData = ReadBmi160Data; // 配置 sensor 读取寄存器的方法
ret = AccelRegisterChipOps(&ops);
------------->
drvData->ops.Init = ops->Init;
drvData->ops.ReadData = ops->ReadData; // 配置加速度计传感器寄存器读取方法
<-------------
ret = InitBmi160(drvData->sensorCfg);
------------->
ret = SetSensorRegCfgArray(&data->busCfg, data->regCfgGroup[SENSOR_INIT_GROUP]);
<-------------
return HDF_SUCCESS;
}
Cyclic NacklaceProblem DescriptionCC always becomes very depressed at the end of this month, he has checked his credit card yesterday, without any surprise, there are only 99.9 yuan left. he is too distressed and thinking about how to tide over the last
[[email protected] ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEcentos latest 75835a67d134 3 month...
作为一个AI前沿领域的探索者,纵览其职业生涯,Sutskever的每一次转向似乎都能恰到好处地挖到黄金。
一、前言在应用开发的早期,数据量少,开发人员开发功能时更重视功能上的实现,随着生产数据的增长,很多SQL语句开始暴露出性能问题,对生产的影响也越来越大,有时可能这些有问题的SQL就是整个系统性能的瓶颈。二、SQL优化一般步骤1、通过慢查日志等定位那些执行效率较低的SQL语句2、explain 分析SQL的执行计划需要重点关注type、rows、filtered、extra。type由上至下,效率越来越高。ALL 全表扫描;index 索引全扫描;range 索引范围扫描,常用语<,
ArrayList的底层数据结构就是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是基于数组的ArrayList的扩容机制ArrayList的扩容主要发生在向ArrayList集合中添加元素的时候。由add()方法的分析可知添加前必须确保集合的容量能够放下添加的元素。主要经历了以下几个阶段:第一,在add()方法中调用ensureCapacityIntern...
理论部分1、ScrollView和HorizontalScrollView是为控件或者布局添加滚动条2、上述两个控件只能有一个孩子,但是它并不是传统意义上的容器3、上述两个控件可以互相嵌套4、滚动条的位置现在的实验结果是:可以由layout_width和layout_height设定5、ScrollView用于设置垂直滚动条,HorizontalScrollView用于设置水平滚动条:需要注意的是,
《独立日》总是这么能鼓舞人,特别是总统对飞行员演讲的时候,我一遍一遍的听着,似乎总是听不够……The President:Good morning. In less than an hour, aircraft from here will join others from around the world. And you will be launching the larges...
Bootstrap-selectBootstrap-selectbootstrap-select 搜索,动态加载数据bootstrap-select js设置选中Bootstrap-select 官方APIbootstrap-select 搜索,动态加载数据1.开启搜索&lt;!-- data-live-search="true" --&gt; ...
DUI好处在于可以很方便的构建高效,绚丽的,非常易于扩展的界面。从而很好的将界面和逻辑分离,同时易于实现各种超炫的界面效果如换色,换肤,透明等。DUI使用的是GDI+核心.DirectUI可以理解为一个轻量级的WPF,可以让C++做出C#般绚丽的界面。DUI核心的大体结构图如下:分为几个大部分:1.控件2.容器3.UI构建解析器(XML解析)...
windows中mysql5.7的坑:ERROR 2003 (HY000): Can’t connect to MySQL server on ‘localhost’ (10061)不知道是 windows 自动更新导致的问题还是什么**一、**打开解压的文件夹C:\MySQL Server 5.7(这只是打个比方,各位看官按照自己的目录去找) ,发现里面有my-default.ini配置文件...
//动态规划法//LIS(时间复杂度为n平方)#include <iostream>#include <cstring>#define N 1000using namespace std;int LIS(int A[], int length){ int d[N]; for(int i=1;i<N;i++) d[i]=1; d[0]=0; for(int i=
计算机软件资格考试是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试,其目的是科学、公正地对全国计算机与软件专业技术人员进行职业资格、专业技术资格认定和专业技术水平测试。关于2021下半年软考中项案例分析试题四答案及解析,慧翔天地在这里给大家简单介绍一下。 试题四 阅读下列说明,回各问题1至问题3,将解答填入各题纸的对应栏内。 [说明] A公司承接了某金融行业用户(甲方)信息系统建设项目,服务内容涉及咨询、开发、集成、运维等。公司任命技术经验丰富的张伟担任项目经理,张伟协