高通 sensor 从native到HAL
app注册传感器监听
Android Sensor Framework 的整体架构如下图所示:

前几篇sensor相关的文章介绍了sensor的hal的知识,以press_sensor实时显示气压坐标来分析,app层数据获取的过程,其实实现数据监控非常简单,主要分为下面三个步骤:
- 获取Sensor服务:getSystemService;
- 获取具体Sensor对象:getDefaultSensor;
- 注册数据监听器:registerListener;
SensorService启动
开机后,system server启动时,就会初始化sensor service,也就是说,开机后她一直都在后台运行着,客户端部分,直接connect就行了。至于怎么connect,这一切都被封装到SensorManager里了。
SensorService服务启动后,在随后的第一次被强引用时,其onFirstRef会被调用,紧接着,它会获取我们的SensorDevice实例:
void SensorService::onFirstRef() {
    ALOGD("nuSensorService starting...");
    SensorDevice& dev(SensorDevice::getInstance());
    sHmacGlobalKeyIsValid = initializeHmacKey();
    if (dev.initCheck() == NO_ERROR) {
        sensor_t const* list;
        ssize_t count = dev.getSensorList(&list);
        if (count > 0) {
附上这部分的流程

SensorDevice作为Sensor架构中native的最后一个文件,与Hal层进行通信,故而在SensorDevice的构造方法中,我们就可以看到著名的hw_get_module和sensors_open_1方法了:
SensorDevice::SensorDevice()
    :  mSensorDevice(0),
       mSensorModule(0) {
    status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,
            (hw_module_t const**)&mSensorModule);
    ALOGE_IF(err, "couldn't load %s module (%s)",
            SENSORS_HARDWARE_MODULE_ID, strerror(-err));
    if (mSensorModule) {
        err = sensors_open_1(&mSensorModule->common, &mSensorDevice);
        ALOGE_IF(err, "couldn't open device for module %s (%s)",
                SENSORS_HARDWARE_MODULE_ID, strerror(-err));
        if (mSensorDevice) {
            if (mSensorDevice->common.version == SENSORS_DEVICE_API_VERSION_1_1 ||
                mSensorDevice->common.version == SENSORS_DEVICE_API_VERSION_1_2) {
                ALOGE(">>>> WARNING <<< Upgrade sensor HAL to version 1_3");
            }
            sensor_t const* list;
            ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list);
            mActivationCount.setCapacity(count);
            Info model;
            for (size_t i=0 ; i<size_t(count) ; i++) {
                mActivationCount.add(list[i].handle, model);
                mSensorDevice->activate(
                        reinterpret_cast<struct sensors_poll_device_t *>(mSensorDevice),
                        list[i].handle, 0);
            }
        }
    }
}
其中SENSORS_HARDWARE_MODULE_ID是在hardware/sensors.h中定义的module名字:
/**
 * The id of this module
 */
#define SENSORS_HARDWARE_MODULE_ID "sensors"
而mSensorModule就是我们的sensors_module_t结构体,这些都是在hal层sensors.h中定义的:
/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of this data structure must begin with hw_module_t
 * followed by module specific information.
 */
struct sensors_module_t {
    struct hw_module_t common;
    /**
     * Enumerate all available sensors. The list is returned in "list".
     * @return number of sensors in the list
     */
    int (*get_sensors_list)(struct sensors_module_t* module,
            struct sensor_t const** list);
    /**
     *  Place the module in a specific mode. The following modes are defined
     *
     *  0 - Normal operation. Default state of the module.
     *  1 - Loopback mode. Data is injected for the supported
     *      sensors by the sensor service in this mode.
     * @return 0 on success
     *         -EINVAL if requested mode is not supported
     *         -EPERM if operation is not allowed
     */
    int (*set_operation_mode)(unsigned int mode);
};
可以看到sensors_module_t结构体扩展了hw_module_t,它里面额外提供了get_sensor_list方法来获取系统支持的sensor列表以及一个模式设置方法。
接下来,我们跟进hw_get_module方法,看看它到底做了什么?
hw_get_module
该函数具体实现在hardware/libhardware/hardware.c中
int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}
int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t **module)
{
    int i = 0;
    char prop[PATH_MAX] = {0};
    char path[PATH_MAX] = {0};
    char name[PATH_MAX] = {0};
    char prop_name[PATH_MAX] = {0};
    if (inst)
        snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
    else
        strlcpy(name, class_id, PATH_MAX);
    /*
     * Here we rely on the fact that calling dlopen multiple times on
     * the same .so will simply increment a refcount (and not load
     * a new copy of the library).
     * We also assume that dlopen() is thread-safe.
     */
    /* First try a property specific to the class and possibly instance */
    snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
    if (property_get(prop_name, prop, NULL) > 0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }
    /* Loop through the configuration variants looking for a module */
    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }
    /* Nothing found, try the default */
    if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
        goto found;
    }
    return -ENOENT;
found:
    /* load the module, if this fails, we're doomed, and we should not try
     * to load a different variant. */
    return load(class_id, path, module);
}
我们主要看hw_get_module_by_class,这里传入的参数分别是“sensors”,null,以及我们的mSensorModule结构体。
首先将字符串拷贝给name:
strlcpy(name, class_id, PATH_MAX);
接着拼接prop_name为ro.hardware.name,即prop_name=ro.hardware.sensors
通过property_get方法并没有得到这个值的定义(因为在系统中并没有对其定义),所以接下来会进入下面的循环:
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }
/**
 * There are a set of variant filename for modules. The form of the filename
 * is "<MODULE_ID>.variant.so" so for the led module the Dream variants
 * of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
 *
 * led.trout.so
 * led.msm7k.so
 * led.ARMV6.so
 * led.default.so
 */
static const char *variant_keys[] = {
    "ro.hardware",  /* This goes first so that it can pick up a different
                       file on the emulator. */
    "ro.product.board",
    "ro.board.platform",
    "ro.arch"
};
根据上面的解析我门也可以看到,将会分别查找sensors.variant.so,sensors.product.so,sensors.platform.so,以及sensors.default.so,最终我们会在/system/lib/hw/路径下找到sensors.msm8909.so,然后将其通过load方法加载进内存中运行。由此也可知,我分析的是高通8909平台。
小细节:当我们实现了自己的HAL层module,并且写了一个应用程序测试module是否正常工作,那么在编译的时候,下面的参数应该要这样写:
LOCAL_MODULE := moduleName.default
或者
LOCAL_MODULE := moduleName.$(TARGET_BOARD_PLATFORM)
由于上面源码的原因,如果module名字对应不到,你的这个模块将不会被正常的load进去,因而也就无法正常工作了。
接着我们分析load的实现。
static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
{
    int status = -EINVAL;
    void *handle = NULL;
    struct hw_module_t *hmi = NULL;
    /*
     * load the symbols resolving undefined symbols before
     * dlopen returns. Since RTLD_GLOBAL is not or'd in with
     * RTLD_NOW the external symbols will not be global
     */
    handle = dlopen(path, RTLD_NOW);
    if (handle == NULL) {
        char const *err_str = dlerror();
        ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
        status = -EINVAL;
        goto done;
    }
    /* Get the address of the struct hal_module_info. */
    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    hmi = (struct hw_module_t *)dlsym(handle, sym);
    if (hmi == NULL) {
        ALOGE("load: couldn't find symbol %s", sym);
        status = -EINVAL;
        goto done;
    }
    /* Check that the id matches */
    if (strcmp(id, hmi->id) != 0) {
        ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
        status = -EINVAL;
        goto done;
    }
    hmi->dso = handle;
    /* success */
    status = 0;
    done:
    if (status != 0) {
        hmi = NULL;
        if (handle != NULL) {
            dlclose(handle);
            handle = NULL;
        }
    } else {
        ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
                id, path, *pHmi, handle);
    }
    *pHmi = hmi;
    return status;
}
- 首先通过dlopen打开sensors.xxx.so模块,获得其句柄handle
- 调用dlsym去获取结构体hw_module_t结构体的地址,注意这里传入的字符串为HAL_MODULE_INFO_SYM_AS_STR,定义在hardware.h头文件中
/**
 * Name of the hal_module_info
 */
#define HAL_MODULE_INFO_SYM         HMI
/**
 * Name of the hal_module_info as a string
 */
#define HAL_MODULE_INFO_SYM_AS_STR  "HMI"
这里为什么要去取名字为HMI的地址,我猜想它应该是HAL模块的入口了。
ELF文件格式:
ELF = Executable and Linkable Format,可执行连接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,扩展名为elf。一个ELF头在文件的开始,保存了路线图(road map),描述了该文件的组织情况。sections保存着object 文件的信息,从连接角度看:包括指令,数据,符号表,重定位信息等等。通过file命令我们可知sensors.xx.so是一个ELF文件格式
tiny.hui@build-server:~$ file sensors.msm8909.so
sensors.msm8909.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[md5/uuid]=0x25812b01ab4700281b41f61327075611, not stripped
因此,通过linux的readelf命令我们可以查看该文件的内部布局及符号表等信息。
tiny.hui@build-server:~$ readelf -s sensors.msm8909.so
Symbol table '.dynsym' contains 157 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@LIBC (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@LIBC (2)
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __register_atfork@LIBC (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutex_lock@LIBC (2)
        …………………………// 省略无关信息
    179: 0000c179   120 FUNC    GLOBAL DEFAULT   13 _ZN19NativeSensorManager1
   180: 0000bd21   392 FUNC    GLOBAL DEFAULT   13 _ZN19NativeSensorManager2
   181: 0000a45b   114 FUNC    GLOBAL DEFAULT   13 _ZN24InputEventCircularRe
   182: 000064d9   148 FUNC    GLOBAL DEFAULT   13 _ZN22sensors_poll_context
   183: 0000d889     6 FUNC    GLOBAL DEFAULT   13 _ZN11sensors_XMLC1Ev
   184: 0000663d   156 FUNC    GLOBAL DEFAULT   13 _ZN10SensorBaseC2EPKcS1_P
   185: 000086d5   248 FUNC    GLOBAL DEFAULT   13 _ZN11AccelSensorC1Ev
   186: 000088dd   248 FUNC    GLOBAL DEFAULT   13 _ZN11AccelSensorC2EP13Sen
   187: 00014220     4 OBJECT  GLOBAL DEFAULT   23 _ZN7android9SingletonI11s
   188: 0000a53b    46 FUNC    GLOBAL DEFAULT   13 _ZN18CalibrationManager10
   189: 00007775    56 FUNC    GLOBAL DEFAULT   13 _ZN15ProximitySensorD1Ev
   190: 00014008   136 OBJECT  GLOBAL DEFAULT   22 HMI
   191: 0000721d    26 FUNC    GLOBAL DEFAULT   13 _ZNK11AccelSensor16hasPen
   192: 0000d475    16 FUNC    WEAK   DEFAULT   13 _ZNK7android12SortedVecto
   193: 00006dd9   264 FUNC    GLOBAL DEFAULT   13 _ZN11LightSensorC2EPc
   194: 00006181    48 FUNC    GLOBAL DEFAULT   13 _ZN22sensors_poll_context
   195: 0000d4fd    48 FUNC    GLOBAL DEFAULT   13 _ZN13VirtualSensorD1Ev
   196: 0000aa15    80 FUNC    GLOBAL DEFAULT   13 _ZN18CalibrationManagerD2
   197: 000087cd   272 FUNC    GLOBAL DEFAULT   13 _ZN11AccelSensorC1EPc
由符号表可知,HMI的地址为00014008,拿到函数地址,当然就可以执行对应的代码了。
QualComm Sensor HAL
因此我们接着看sensor_hal层,高通的Sensor实现了自己的HAL,其源码在hardware\qcom\sensors路径下,通过Android.mk我们也可以确定他确实是我们前面load方法打开的动态链接库,其编译后会生成sensor.msm8909.so:
ifneq ($(filter msm8960 msm8610 msm8916 msm8909,$(TARGET_BOARD_PLATFORM)),)
# Exclude SSC targets
ifneq ($(TARGET_USES_SSC),true)
# Disable temporarily for compilling error
ifneq ($(BUILD_TINY_ANDROID),true)
LOCAL_PATH := $(call my-dir)
# HAL module implemenation stored in
include $(CLEAR_VARS)
ifeq ($(USE_SENSOR_MULTI_HAL),true)
  LOCAL_MODULE := sensors.native
else
  ifneq ($(filter msm8610,$(TARGET_BOARD_PLATFORM)),)
    LOCAL_MODULE := sensors.$(TARGET_BOARD_PLATFORM)
    LOCAL_CFLAGS := -DTARGET_8610
  else
    ifneq ($(filter msm8916 msm8909,$(TARGET_BOARD_PLATFORM)),)
      LOCAL_MODULE := sensors.$(TARGET_BOARD_PLATFORM)
    else
      LOCAL_MODULE := sensors.msm8960
    endif
  endif
  ifdef TARGET_2ND_ARCH
    LOCAL_MODULE_RELATIVE_PATH := hw
  else
    LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
  endif
endif
LOCAL_MODULE_TAGS := optional
LOCAL_CFLAGS += -DLOG_TAG=\"Sensors\"
ifeq ($(call is-board-platform,msm8960),true)
  LOCAL_CFLAGS += -DTARGET_8930
endif
LOCAL_C_INCLUDES := $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr/include
LOCAL_ADDITIONAL_DEPENDENCIES := $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr
# Export calibration library needed dependency headers
LOCAL_COPY_HEADERS_TO := sensors/inc
LOCAL_COPY_HEADERS :=   \
                CalibrationModule.h \
                sensors_extension.h \
                sensors.h
LOCAL_SRC_FILES :=      \
                sensors.cpp                     \
                SensorBase.cpp                  \
                LightSensor.cpp                 \
                ProximitySensor.cpp             \
                CompassSensor.cpp               \
                Accelerometer.cpp                               \
                Gyroscope.cpp                           \
                Bmp180.cpp                              \
                InputEventReader.cpp \
                CalibrationManager.cpp \
                NativeSensorManager.cpp \
                VirtualSensor.cpp       \
                sensors_XML.cpp \
                SignificantMotion.cpp
LOCAL_C_INCLUDES += external/libxml2/include    \
ifeq ($(call is-platform-sdk-version-at-least,20),true)
    LOCAL_C_INCLUDES += external/icu/icu4c/source/common
else
    LOCAL_C_INCLUDES += external/icu4c/common
endif
LOCAL_SHARED_LIBRARIES := liblog libcutils libdl libxml2 libutils
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libcalmodule_common
LOCAL_SRC_FILES := \
                   algo/common/common_wrapper.c \
                   algo/common/compass/AKFS_AOC.c \
                   algo/common/compass/AKFS_Device.c \
                   algo/common/compass/AKFS_Direction.c \
                   algo/common/compass/AKFS_VNorm.c
LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_MODULE_TAGS := optional
ifdef TARGET_2ND_ARCH
LOCAL_MODULE_PATH_32 := $(TARGET_OUT_VENDOR)/lib
LOCAL_MODULE_PATH_64 := $(TARGET_OUT_VENDOR)/lib64
else
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_SHARED_LIBRARIES)
endif
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := calmodule.cfg
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_ETC)
LOCAL_SRC_FILES := calmodule.cfg
include $(BUILD_PREBUILT)
endif #BUILD_TINY_ANDROID
endif #TARGET_USES_SSC
endif #TARGET_BOARD_PLATFORM
那么HMI的入口到底定义在这里的那个文件中呢?
功夫不负有心人,在sensors.cpp中,我们终于找到了HMI的入口,即下面的结构体:
static struct hw_module_methods_t sensors_module_methods = {
    .open = sensors_open
};
struct sensors_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = (uint16_t)SENSORS_DEVICE_API_VERSION_1_3,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = SENSORS_HARDWARE_MODULE_ID,
        .name = "QTI Sensors Module",
        .author = "Qualcomm Technologies, Inc.",
        .methods = &sensors_module_methods,
        .dso = NULL,
        .reserved = {0},
    },
    .get_sensors_list = sensors_get_sensors_list,
    .set_operation_mode = sensors_set_operation_mode
};
HAL_MODULE_INFO_SYM即上文提到的HMI变量,恭喜各位,这里我们就开启了QualComm Sensor HAL的大门。
最后这个hw_module_t的结构体句柄会返回给我们的SensorDevice的构造函数里:
SensorDevice::SensorDevice()
    :  mSensorDevice(0),
       mSensorModule(0)
{
    status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,
            (hw_module_t const**)&mSensorModule);
    ALOGE_IF(err, "couldn't load %s module (%s)",
            SENSORS_HARDWARE_MODULE_ID, strerror(-err));
    if (mSensorModule) {
        err = sensors_open_1(&mSensorModule->common, &mSensorDevice);
接着,通过sensors_open_1方法将module->common传入,打开我们的sensor驱动。
// hardware/libhardware/include/hardware/sensors.h
static inline int sensors_open_1(const struct hw_module_t* module,
        sensors_poll_device_1_t** device) {
    return module->methods->open(module,
            SENSORS_HARDWARE_POLL, (struct hw_device_t**)device);
}
static inline int sensors_close_1(sensors_poll_device_1_t* device) {
    return device->common.close(&device->common);
}
回过头去看看HMI的结构体定义,其中module->common->open被赋值为sensors_module_methods,其只有一个open方法,因此,module->methods->open最终会调用sensors_open方法来打开驱动程序。
到这里native到hal层的逻辑其实已经基本上分析完了。
高通 sensor 从native到HAL的更多相关文章
- linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)【转】
		本文转载自:https://blog.csdn.net/radianceblau/article/details/73498303 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ... 
- linux驱动由浅入深系列:高通sensor架构实例分析之三(adsp上报数据详解、校准流程详解)【转】
		本文转载自:https://blog.csdn.net/radianceblau/article/details/76180915 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ... 
- 高通sensor理解
		.1.高通为什么引入adsp? 2.adsp sensor 是如何工作起来的? 3.adsp 和ap 是如何通信的? 4.adsp 架构组成 解答: 1.高通在msm8960之前sensor 是挂在p ... 
- 高通非adsp 架构下的sensor的bug调试
		高通 sensor 从native到HAL 高通HAL层之Sensor HAL 高通HAL层之bmp18x.cpp 问题现象: 当休眠后,再次打开preesure sensor的时候,会出现隔一段时候 ... 
- 高通adsp架构下sensor
		一.高通sensor架构: linux驱动由浅入深系列:高通sensor架构实例分析之一(整体概览+AP侧代码分析) linux驱动由浅入深系列:高通sensor架构实例分析之二(adsp驱动代码结构 ... 
- 高通HAL层之Sensor HAL
		高通的HAL层其实分为两种,一种是直接从kernel这边报数据上来的,由sensor HAL层来监听,另一种是走ADSP的模式,HAL层是通过qmi的形式进行监听的: 走ADSP架构的可以看下面的博客 ... 
- android 6.0 高通平台sensor 工作机制及流程(原创)
		最近工作上有碰到sensor的相关问题,正好分析下其流程作个笔记. 这个笔记分三个部分: sensor硬件和驱动的工作机制 sensor 上层app如何使用 从驱动到上层app这中间的流程是如何 Se ... 
- 高通qxdm抓取sensor的log【学习笔记】
		高通qxdm抓取sensor的log 打开qxdm,打开设置界面,去掉其他无关的log,打开Log packets .Message packets的SNS的log 之后需要把端口打开,把端口打开之后 ... 
- 高通 8x26 andorid light sensor(TSL258x) 开发【转】
		本文转载自:http://www.voidcn.com/blog/u012296694/article/p-1669831.html 前言 8926平台的sensor架构与之前的平台完全不同,实际上已 ... 
随机推荐
- Java之基础学习(数据类型、运算符、分支语句和循环语句)
			在工作用得比较多的是shell和python编程,对于java以前也学习过,使用很少,这次借朋友推荐的java视频教程来温习下. 也是因为现在很多开源测试工具使用java编写的,学习一下更有助于测试工 ... 
- JavaScript “跑马灯”抽奖活动代码解析与优化(二)
			既然是要编写插件.那么叫做"插件"的东西肯定是具有的某些特征能够满足我们平时开发的需求或者是提高我们的开发效率.那么叫做插件的东西应该具有哪些基本特征呢?让我们来总结一下: 1.J ... 
- 堆排序——HeapSort
			基本思想: 图示: (88,85,83,73,72,60,57,48,42,6) 平均时间复杂度: O(NlogN)由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作 ... 
- DevExpress GridControl小结
			1. 如何解决单击记录整行选中的问题 View->OptionsBehavior->EditorShowMode 设置为:Click 2. 如何新增一条记录 (1).gridView.Ad ... 
- JAVA基础--重新整理(1)后版
			比较喜欢用demo来讲解. 变量: public static void main(String[] args) { int age;//变量声明 age = 16;//变量的初始化,第一次赋值 ag ... 
- 经济学人使用Golang构建微服务历程回顾
			关键点 经济学人内容分发系统需要更大的灵活性,将内容传递给日益多样化的数字渠道.为了实现这一灵活性目标并保持高水平的性能和可靠性,平台从一个单体结构过渡到微服务体系结构. 用Go编写的服务是新系统的一 ... 
- Everything(一款用于检索硬盘文件的工具)
			有时候文件夹一多,找不到文件,忘记放哪个盘符怎么办? Everything就能帮你解决,比电脑自带的快多啦,官网在此:http://www.voidtools.com/ (也不大,就几M,没有特别的安 ... 
- Linux-485收发切换延迟的解决方法
			[前言] 本文引用各种资料甚多,而引用出处标明并不详细,若有侵权,请联系删除. 转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10381616.html ... 
- MySQL 事务机制
			事务处理是保证数据安全的重要机制,事务有四个重要属性 ,根据它们的英文名称可以记为ACID: 原子性(Atomic): 事务操作是不可分割的; 事务只存在已执行和未执行两种状态,不存在只执行了部分指令 ... 
- a 标签提交表单
			document.getElementById('ECS_FORMBUY').submit(); 
