`
地球小野花
  • 浏览: 163948 次
  • 性别: Icon_minigender_1
  • 来自: 马赛大回旋
社区版块
存档分类
最新评论

透过HAL(硬件抽象层)看顶层API是如何访问硬件

阅读更多


        文章导读:HAL硬件抽象层的实现及架构、Android API 与硬件平台的衔接、NDK的一些看法。

 

简介:Hardware Abstraction Layer 硬件抽象层是一个轻量级(lightweight)的的运行环境,提供了简单的设备驱动程序接口,应用程序使用设备驱动程序与底层硬件之间进行通信。HAL应用程序接口和ANSIC标准库结合在一起,这样用户可以使用C语言库函数来访问Android文件系统。下图是其直观的概念:

 

 

从图中,我们可以看到HAL是基于Linux KernelLibrariesAndroid Runtime之间。也就是说,HAL是底层硬件设备驱动程序暴露给Application Framework (也就是通常我们使用的Android API )的一个接口层。(可以浏览http://hi.baidu.com/aokikyon/blog/item/a66e0f87d8f55326c75cc32b.html HAL分析报告获得更详细的认识)

 

在网上也看到一些朋友写了重力感应器的api使用,那么以重力感应器Sensor为例子,看看重力感应器如何和Applications、Application Framework衔接。

1、下面Sensors.h的定义重力感应器对驱动程序部分的操作:

在源码./platform/hardware/Libardware /Include/Sensors.h目录下

 

#include <...>
__BEGIN_DECLS

/**
 * The id of this module
 */
#define SENSORS_HARDWARE_MODULE_ID "sensors"

/**
 * Name of the sensors device to open
 */
#define SENSORS_HARDWARE_CONTROL    "control"
#define SENSORS_HARDWARE_DATA       "data"

.....

/** convenience API for opening and closing a device */

static inline int sensors_control_open(const struct hw_module_t* module, 
        struct sensors_control_device_t** device) {
    return module->methods->open(module, 
            SENSORS_HARDWARE_CONTROL, (struct hw_device_t**)device);
}

static inline int sensors_control_close(struct sensors_control_device_t* device) {
    return device->common.close(&device->common);
}

static inline int sensors_data_open(const struct hw_module_t* module, 
        struct sensors_data_device_t** device) {
    return module->methods->open(module, 
            SENSORS_HARDWARE_DATA, (struct hw_device_t**)device);
}

static inline int sensors_data_close(struct sensors_data_device_t* device) {
    return device->common.close(&device->common);
}


__END_DECLS

#endif  // ANDROID_SENSORS_INTERFACE_H
  

 

 2、JNI部分代码

 

    加载该驱动的访问程序./framework/Jni/onLoad.cpp

 

#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
#include "JNIHelp.h"
namespace android {
...
int register_android_server_SensorService(JNIEnv* env);
...
};

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }
    LOG_ASSERT(env, "Could not retrieve the env!");

    //注册SensorService
    register_android_server_SensorService(env);
  

    return JNI_VERSION_1_4;
}

 

    向Application Framework提供接口部分./framework/Jni/com_android_server_SensorService.cpp

 

 

 

#define LOG_TAG "SensorService"
#define LOG_NDEBUG 0
#include "utils/Log.h"
//加载sensor.h文件
#include <hardware/sensors.h>

#include "jni.h"
#include "JNIHelp.h"

namespace android {

static struct file_descriptor_offsets_t
{
    jclass mClass;
    jmethodID mConstructor;
    jfieldID mDescriptor;
} gFileDescriptorOffsets;

static struct parcel_file_descriptor_offsets_t
{
    jclass mClass;
    jmethodID mConstructor;
} gParcelFileDescriptorOffsets;

static struct bundle_descriptor_offsets_t
{
    jclass mClass;
    jmethodID mConstructor;
    jmethodID mPutIntArray;
    jmethodID mPutParcelableArray;
} gBundleOffsets;

/*
 * The method below are not thread-safe and not intended to be 
 */

static sensors_control_device_t* sSensorDevice = 0;

static jint
android_init(JNIEnv *env, jclass clazz)
{
    sensors_module_t* module;
    if (hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
        if (sensors_control_open(&module->common, &sSensorDevice) == 0) {
            const struct sensor_t* list;
            int count = module->get_sensors_list(module, &list);
            return count;
        }
    }
    return 0;
}

static jobject
android_open(JNIEnv *env, jclass clazz)
{
    native_handle_t* handle = sSensorDevice->open_data_source(sSensorDevice);
    if (!handle) {
        return NULL;
    }

    // new Bundle()
    jobject bundle = env->NewObject(
            gBundleOffsets.mClass,
            gBundleOffsets.mConstructor);

    if (handle->numFds > 0) {
        jobjectArray fdArray = env->NewObjectArray(handle->numFds,
                gParcelFileDescriptorOffsets.mClass, NULL);
        for (int i = 0; i < handle->numFds; i++) {
            // new FileDescriptor()
            jobject fd = env->NewObject(gFileDescriptorOffsets.mClass,
                    gFileDescriptorOffsets.mConstructor);
            env->SetIntField(fd, gFileDescriptorOffsets.mDescriptor, handle->data[i]);
            // new ParcelFileDescriptor()
            jobject pfd = env->NewObject(gParcelFileDescriptorOffsets.mClass,
                    gParcelFileDescriptorOffsets.mConstructor, fd);
            env->SetObjectArrayElement(fdArray, i, pfd);
        }
        // bundle.putParcelableArray("fds", fdArray);
        env->CallVoidMethod(bundle, gBundleOffsets.mPutParcelableArray,
                env->NewStringUTF("fds"), fdArray);
    }

    if (handle->numInts > 0) {
        jintArray intArray = env->NewIntArray(handle->numInts);
        env->SetIntArrayRegion(intArray, 0, handle->numInts, &handle->data[handle->numInts]);
        // bundle.putIntArray("ints", intArray);
        env->CallVoidMethod(bundle, gBundleOffsets.mPutIntArray,
                env->NewStringUTF("ints"), intArray);
    }

    // delete the file handle, but don't close any file descriptors
    native_handle_delete(handle);
    return bundle;
}

static jint
android_close(JNIEnv *env, jclass clazz)
{
    if (sSensorDevice->close_data_source)
        return sSensorDevice->close_data_source(sSensorDevice);
    else
        return 0;
}

static jboolean
android_activate(JNIEnv *env, jclass clazz, jint sensor, jboolean activate)
{
    int active = sSensorDevice->activate(sSensorDevice, sensor, activate);
    return (active<0) ? false : true;
}

static jint
android_set_delay(JNIEnv *env, jclass clazz, jint ms)
{
    return sSensorDevice->set_delay(sSensorDevice, ms);
}

static jint
android_data_wake(JNIEnv *env, jclass clazz)
{
    int res = sSensorDevice->wake(sSensorDevice);
    return res;
}

//提供给顶层实现的访问函数
static JNINativeMethod gMethods[] = {
    {"_sensors_control_init",     "()I",   (void*) android_init },
    {"_sensors_control_open",     "()Landroid/os/Bundle;",  (void*) android_open },
    {"_sensors_control_close",     "()I",  (void*) android_close },
    {"_sensors_control_activate", "(IZ)Z", (void*) android_activate },
    {"_sensors_control_wake",     "()I", (void*) android_data_wake },
    {"_sensors_control_set_delay","(I)I", (void*) android_set_delay },
};

int register_android_server_SensorService(JNIEnv *env)
{
    jclass clazz;

    clazz = env->FindClass("java/io/FileDescriptor");
    gFileDescriptorOffsets.mClass = (jclass)env->NewGlobalRef(clazz);
    gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");

    clazz = env->FindClass("android/os/ParcelFileDescriptor");
    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>",
            "(Ljava/io/FileDescriptor;)V");

    clazz = env->FindClass("android/os/Bundle");
    gBundleOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gBundleOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
    gBundleOffsets.mPutIntArray = env->GetMethodID(clazz, "putIntArray", "(Ljava/lang/String;[I)V");
    gBundleOffsets.mPutParcelableArray = env->GetMethodID(clazz, "putParcelableArray",
            "(Ljava/lang/String;[Landroid/os/Parcelable;)V");

    return jniRegisterNativeMethods(env, "com/android/server/SensorService",
            gMethods, NELEM(gMethods));
}

}; // namespace android

 

    到了这里文件系统中底层的部分算是完成了。对于下层类库而言,我们可以通过HAL的方式建立android Api 和硬件设备驱动连接的桥梁。针对不同的硬件平台需要用户自己编写这几个函数的实现方式并通过android kernel里的驱动(当然有部分驱动可能位于文件系统中)来控制硬件行为。对于上层而言,可以看做是给顶层Java实现Android API提供一个访问接口。由于该文件是编译成系统 的*.so库文件,这与NDK中的为系统加载一个*.so相似。

 

 3、Application Framework层是如何监听Sensor的物理数据

    到了这里,用NDK的编程经验,我们可以通过system.load("*.so")获得某个库的访问,并使用里面的函数,进行想要的操作。而Google为了方便用户操作,用Java语言提供一些便捷访问的Application Framework将底层的c/c++实现的驱动或者其他细节封装起来,也就是我们所说的API(\(^o^)/~个人理解)

    

    终于到了熟悉的Android API 层了。

    对于重力感应器,Android在./framework/base/service/java/com/android/server/SensorsService.java

 

import android.content.Context;
import android.hardware.ISensorService;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.IBinder;
import android.util.Config;
import android.util.Slog;
import android.util.PrintWriterPrinter;
import android.util.Printer;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;

import com.android.internal.app.IBatteryStats;
import com.android.server.am.BatteryStatsService;


/**
 * Class that manages the device's sensors. It register clients and activate
 * the needed sensors. The sensor events themselves are not broadcasted from
 * this service, instead, a file descriptor is provided to each client they
 * can read events from.
 */

class SensorService extends ISensorService.Stub {
    static final String TAG = SensorService.class.getSimpleName();
    private static final boolean DEBUG = false;
    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
    private static final int SENSOR_DISABLE = -1;
    private int mCurrentDelay = 0;
    
    /**
     * Battery statistics to be updated when sensors are enabled and disabled.
     */
    final IBatteryStats mBatteryStats = BatteryStatsService.getService();

    private final class Listener implements IBinder.DeathRecipient {
        final IBinder mToken;
        final int mUid;

        int mSensors = 0;
        int mDelay = 0x7FFFFFFF;
        
        Listener(IBinder token, int uid) {
            mToken = token;
            mUid = uid;
        }
        
        void addSensor(int sensor, int delay) {
            mSensors |= (1<<sensor);
            if (delay < mDelay)
            	mDelay = delay;
        }
        
        void removeSensor(int sensor) {
            mSensors &= ~(1<<sensor);
        }

        boolean hasSensor(int sensor) {
            return ((mSensors & (1<<sensor)) != 0);
        }

        public void binderDied() {
            if (localLOGV) Slog.d(TAG, "sensor listener died");
            synchronized(mListeners) {
                mListeners.remove(this);
                mToken.unlinkToDeath(this, 0);
                // go through the lists of sensors used by the listener that 
                // died and deactivate them.
                for (int sensor=0 ; sensor<32 && mSensors!=0 ; sensor++) {
                    if (hasSensor(sensor)) {
                        removeSensor(sensor);
                        deactivateIfUnusedLocked(sensor);
                        try {
                            mBatteryStats.noteStopSensor(mUid, sensor);
                        } catch (RemoteException e) {
                            // oops. not a big deal.
                        }
                    }
                }
                if (mListeners.size() == 0) {
                    _sensors_control_wake();
                    _sensors_control_close();
                } else {
                    // TODO: we should recalculate the delay, since removing
                    // a listener may increase the overall rate.
                }
                mListeners.notify();
            }
        }
    }

    @SuppressWarnings("unused")
    public SensorService(Context context) {
        if (localLOGV) Slog.d(TAG, "SensorService startup");
        _sensors_control_init();
    }
    
    public Bundle getDataChannel() throws RemoteException {
        // synchronize so we do not require sensor HAL to be thread-safe.
        synchronized(mListeners) {
            return _sensors_control_open();
        }
    }

    public boolean enableSensor(IBinder binder, String name, int sensor, int enable)
            throws RemoteException {
        
        if (localLOGV) Slog.d(TAG, "enableSensor " + name + "(#" + sensor + ") " + enable);

        if (binder == null) {
            Slog.e(TAG, "listener is null (sensor=" + name + ", id=" + sensor + ")");
            return false;
        }

        if (enable < 0 && (enable != SENSOR_DISABLE)) {
            Slog.e(TAG, "invalid enable parameter (enable=" + enable +
                    ", sensor=" + name + ", id=" + sensor + ")");
            return false;
        }

        boolean res;
        int uid = Binder.getCallingUid();
        synchronized(mListeners) {
            res = enableSensorInternalLocked(binder, uid, name, sensor, enable);
            if (res == true) {
                // Inform battery statistics service of status change
                long identity = Binder.clearCallingIdentity();
                if (enable == SENSOR_DISABLE) {
                    mBatteryStats.noteStopSensor(uid, sensor);
                } else {
                    mBatteryStats.noteStartSensor(uid, sensor);
                }
                Binder.restoreCallingIdentity(identity);
            }
        }
        return res;
    }

    private boolean enableSensorInternalLocked(IBinder binder, int uid,
            String name, int sensor, int enable) throws RemoteException {

        // check if we have this listener
        Listener l = null;
        for (Listener listener : mListeners) {
            if (binder == listener.mToken) {
                l = listener;
                break;
            }
        }

        if (enable != SENSOR_DISABLE) {
            // Activate the requested sensor
            if (_sensors_control_activate(sensor, true) == false) {
                Slog.w(TAG, "could not enable sensor " + sensor);
                return false;
            }

            if (l == null) {
                /*
                 * we don't have a listener for this binder yet, so
                 * create a new one and add it to the list.
                 */
                l = new Listener(binder, uid);
                binder.linkToDeath(l, 0);
                mListeners.add(l);
                mListeners.notify();
            }

            // take note that this sensor is now used by this client
            l.addSensor(sensor, enable);

        } else {

            if (l == null) {
                /*
                 *  This client isn't in the list, this usually happens
                 *  when enabling the sensor failed, but the client
                 *  didn't handle the error and later tries to shut that
                 *  sensor off.
                 */
                Slog.w(TAG, "listener with binder " + binder +
                        ", doesn't exist (sensor=" + name +
                        ", id=" + sensor + ")");
                return false;
            }

            // remove this sensor from this client
            l.removeSensor(sensor);

            // see if we need to deactivate this sensors=
            deactivateIfUnusedLocked(sensor);

            // if the listener doesn't have any more sensors active
            // we can get rid of it
            if (l.mSensors == 0) {
                // we won't need this death notification anymore
                binder.unlinkToDeath(l, 0);
                // remove the listener from the list
                mListeners.remove(l);
                // and if the list is empty, turn off the whole sensor h/w
                if (mListeners.size() == 0) {
                    _sensors_control_wake();
                    _sensors_control_close();
                }
                mListeners.notify();
            }
        }

        // calculate and set the new delay
        int minDelay = 0x7FFFFFFF;
        for (Listener listener : mListeners) {
            if (listener.mDelay < minDelay)
                minDelay = listener.mDelay;
        }
        if (minDelay != 0x7FFFFFFF) {
            mCurrentDelay = minDelay;
            _sensors_control_set_delay(minDelay);
        }

        return true;
    }

    private void deactivateIfUnusedLocked(int sensor) {
        int size = mListeners.size();
        for (int i=0 ; i<size ; i++) {
            if (mListeners.get(i).hasSensor(sensor)) {
                // this sensor is still in use, don't turn it off
                return;
            }
        }
        if (_sensors_control_activate(sensor, false) == false) {
            Slog.w(TAG, "could not disable sensor " + sensor);
        }
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        synchronized (mListeners) {
            Printer pr = new PrintWriterPrinter(pw);
            int c = 0;
            pr.println(mListeners.size() + " listener(s), delay=" + mCurrentDelay + " ms");
            for (Listener l : mListeners) {
                pr.println("listener[" + c + "] " +
                        "sensors=0x" + Integer.toString(l.mSensors, 16) +
                        ", uid=" + l.mUid +
                        ", delay=" +
                        l.mDelay + " ms");
                c++;
            }
        }
    }

    private ArrayList<Listener> mListeners = new ArrayList<Listener>();
    //JNI提供的访问函数,将Application Framework和驱动衔接起来。
    private static native int _sensors_control_init();
    private static native Bundle _sensors_control_open();
    private static native int _sensors_control_close();
    private static native boolean _sensors_control_activate(int sensor, boolean activate);
    private static native int _sensors_control_set_delay(int ms);
    private static native int _sensors_control_wake();
}
 

   接下来就是Core提供给我们使用的API了,这部分不在详解。

   总结一下:对于NDK也不在啰唆,它和HAL一样,一个是提供商业保密程序,一个是google应厂商要求不希望公开源码。对于HAL的分析,我们也可以看到它大致的开发流程,也隐隐约约看到NDK的影子。若NDK涉及到硬件平台,它又该如何考虑夸平台呢?

 

 

2
0
分享到:
评论
2 楼 地球小野花 2011-03-18  
是的。
1 楼 sharp_lover 2011-01-22  
楼主的意思是通过这个来访问google 没有公开的api 吗?

相关推荐

    Android GPS HAL 硬件抽象层

    Android GPS HAL 硬件抽象层 Android GPS HAL(Hardware Abstraction Layer)是 Android 操作系统中负责与 GPS 硬件交互的抽象层。该层提供了一个统一的接口,使得 Android 操作系统可以与不同的 GPS 硬件进行交互...

    硬件抽象层( HAL), 应用库( CUL)

    硬件抽象层(HAL)是一种软件层,位于操作系统和硬件设备之间,负责将硬件设备的差异性抽象出来,以便上层软件能够更好地访问和控制硬件设备。HAL提供了一种统一的接口,使得上层软件可以不需要了解硬件设备的具体实现...

    Api.rar_硬件抽象_硬件抽象层

    硬件抽象层(HAL,Hardware Abstraction Layer)是嵌入式系统设计中的一个重要概念,它在软件和硬件之间提供了一个中间层,使得上层软件可以独立于具体的硬件平台进行开发。HAL的主要目标是隐藏底层硬件的复杂性,为...

    Android硬件抽象层(HAL)

    Android硬件抽象层从开发到使用有一个清晰的层次。这个层次恰好对应了Android系统的架构层次,它向下涉及到Linux内核,向上涉及到应用程序框架层的服务,以及应用程序层对它的使用。Android硬件抽象层模块的开发本身...

    java 与Ubuntu为Android增加硬件抽象层(HAL)模块访问

    在Android系统中,硬件抽象层(Hardware Abstraction Layer,简称HAL)是连接底层硬件驱动程序和上层应用程序的关键组件。HAL 提供了一种标准化的方式来访问和控制设备,使得不同的硬件平台能够以统一的方式运行相同...

    理解和使用Linux的硬件抽象层HAL

    Linux的硬件抽象层(Hardware Abstraction Layer,简称HAL)是操作系统与硬件设备之间的一个关键接口。这个层次的主要目的是为了提供一种标准化的方式,使得操作系统能够独立于具体的硬件设备进行操作,从而实现更好...

    硬件抽象层的原理与实现

    硬件抽象层(Hardware Abstraction Layer,简称HAL)是一种软件设计模式,它提供了一个统一的接口来访问底层硬件资源,使得上层软件不必关心具体的硬件细节。在Android操作系统中,HAL的作用尤为关键,它不仅隔离了...

    Android硬件抽象层(HAL)模块编写JNI方法

    其中,硬件抽象层(Hardware Abstraction Layer,简称HAL)是Android系统架构中的关键组件,它为上层应用提供了统一的硬件访问接口,从而实现了硬件与软件的解耦。然而,由于Android应用主要基于Java开发,而硬件...

    嵌入式系统的硬件抽象层 (HAL)_rust_代码_下载

    范围 embedded-hal作为构建平台无关驱动程序生态系统的基础。(驱动程序意味着库 crates,它...不过,可以提议将通用功能的抽象包含在本指南embedded-hal中的描述中。 更多详情、使用方法,请下载后阅读README.md文件

    android overlay系统 overlay的硬件抽象层 camera系统与上层接口和硬件抽象层

    ### Android Video 输入输出系统...这两个系统都依赖于硬件抽象层(HAL),以提供高效且跨平台的解决方案。HAL的存在不仅提高了系统的可移植性,还降低了软件开发的复杂度,使开发者能够更轻松地构建高质量的视频应用。

    EMC2的硬件抽象层原理与实现

    【硬件抽象层(HAL)原理与实现】 硬件抽象层(Hardware Abstraction Layer,简称HAL)是嵌入式系统设计中的核心组成部分,它旨在隔离系统软件和底层硬件,从而提高软件的可移植性和简化系统开发。在嵌入式实时系统...

    基于硬件抽象层HAL的NiosⅡ嵌入式处理器系统设备管理模式研究.pdf

    "基于硬件抽象层HAL的NiosⅡ嵌入式处理器系统设备管理模式研究" 本文研究了基于硬件抽象层(Hardware Abstraction Layer,HAL)的NiosⅡ嵌入式处理器系统设备管理模式。 Hardware Abstraction Layer(HAL)是NiosⅡ...

    android 驱动,HAL层framework层到应用层的总结

    3. 在应用程序框架中添加一个硬件访问服务。 4. 开发一个应用程序来访问该硬件服务。 驱动程序的实现包括: 1. 实现内核驱动程序模块。 2. 修改内核 Kconfig 文件,使得执行 make menuconfig 命令配置内核编译选项...

    微控制器编程的抽象艺术:PIC的硬件抽象层(HAL)解析

    ### 微控制器编程的抽象艺术:PIC的硬件抽象层(HAL)解析 #### 一、引言 在当今数字化时代,微控制器作为自动化系统的核心组件之一,在诸多领域扮演着极其重要的角色。其中,可编程逻辑控制器(Programmable ...

    STM32F1 系列 HAL 库 API文档

    HAL库(Hardware Abstraction Layer,硬件抽象层)是ST为了简化STM32开发而推出的一种高级库,它提供了一套与具体硬件无关的编程接口,使得开发者可以更加专注于应用层的开发,而无需深入理解底层硬件细节。...

    NXP LPC800 系列微控制器的硬件抽象层 (HAL) ,用 Rust 编程语言编写

    NXP LPC800系列微控制器的硬件抽象层 (HAL) ,用Rust编程语言编写。目前支持LPC82x和LPC845。LPC8xx HAL 为 LPC800 MCU 的特性提供了高级接口,安全、方便、高效。 LPC8xx HAL 利用 Rust 的类型系统来防止常见错误...

    android 硬件抽象层点灯

    HAL为Android的各种服务和应用提供了一个标准化的接口,使得上层代码无需直接与硬件交互,而是通过调用HAL提供的API来实现对硬件资源的访问。这样做的好处在于增强了系统的可移植性,因为不同的硬件平台只需提供相应...

    HAL层分析类

    在计算机系统中,硬件抽象层(Hardware Abstraction Layer,简称HAL)是一种重要的软件设计模式,它为上层软件提供了一个统一的接口,屏蔽了底层硬件的具体实现细节。HAL层的主要目标是使得操作系统、驱动程序或者...

    SCA规范下FPGA的硬件抽象层设计.pdf

    HAL-C通过对硬件单元的外部接口进行抽象,定义了一系列标准通信应用编程接口(API),以便软件模块的设计者只需要关注与外部通信的功能,不必关心具体硬件的实现细节。 硬件抽象层的基本结构包括HAL和HC,其连接...

Global site tag (gtag.js) - Google Analytics