Android : 供应商原生开发套件 (VNDK)
一、VNDK概述
VNDK(Vendor Native Development Kit)是一组专门用于vendor实现其HAL的lib库,因为自Android 8.0以来,Google引入了Treble架构,希望对vendor和system分区进行解耦处理,期待实现:framwork进程不加载vendor共享库,vendor进程仅加载vendor共享库(和部分framework共享库),而framework进程和vendor进程之间通过HIDL和hwbinder来通信。总结为如下几点:
- platform和Vendor的构建是相互隔离的;
- platform lib对应 system.img;
- vendor lib对应 vendor.img;
- 大多数情况下,Vendor lib跟系统核心不能相互使用;Vendor lib不允许dlopen私有的系统库;
- 合作伙伴不允许为自己的产品在VNDK新增lib,只能贡献到AOSP;
这一切都是为系统库与Vendor库之间的解耦合,在Android P上采用该方案,则下一个大版本Android Q更新,可以直接将新的System Q加上老Vendor P,组成新版本Android。
其中VNDK + Framework libs组成system.img, Vendor libs组成vendor.img。
Android P新添加命名空间namespace:
- System命名空间/system/lib/;
- Vendor命名空间有/system/lib/vndk,/system/lib/vndk-sp,/vendor/lib/vndk,/vendor/lib/vndk-sp
二、framework和vendor之间库的区分
2.1 vendor进程可访问的部分framework共享库
根据特性及使用方法的差异,可将framework共享库大致分为三类:
(1)已知API/ABI稳定的framework共享库 - LL-NDK库,主要包括下面这些:
libEGL.so、libGLESv1_CM.so、libGLESv2.so、libGLESv3.so、libandroid_net.so、libc.so、libdl.so、liblog.so、libm.so、libnativewindow.so、libneuralnetworks.so、libsync.so、libvndksupport.so
和 libvulkan.so。
对于这些库,在Android.bp都包含llndk_library模块的定义,该定义指定了模块名称和符号文件:
llndk_library {
name: "libvndksupport",
symbol_file: "libvndksupport.map.txt",
}
编译系统会根据符号文件为vendor模块生成stub共享库,需满足条件:未在以_PRIVATE或_PLATFORM结尾的部分中定义,不含#platform-only标记,并且不含#introduce*标记或者该标记与目标匹配。
#libvndksupport.map.txt
LIBVNDKSUPPORT {
global:
android_load_sphal_library; # vndk
android_unload_sphal_library; # vndk
local:
*;
};
(2)可以安全复制两次的framework共享库 - Eligible-VNDK,需要满足以下几个条件:
a.不与framework之间进行IPC通信。
b.与ART虚拟机无关。
c.不读写文件格式不稳定的文件或分区。
d.没有特殊的软件许可。
e.code所有者不反对vendor使用。
(3)除上述两种以外的framework共享库 - FWK-Only,通常具有以下几个特点:
a.被视为framework内部实现。
b.不得由vendor访问。
c.不稳定的ABI/API。
d.不会被复制。
2.2 SP-HAL(Same-Pocess HAL)
SP-HAL是一组预先确定的HAL,被加载到framework进程中的vendor共享库。它由链接器命名空间进行隔离,必须仅依赖于LL-NDK和VNDK-SP(部分预定义的Eligible-VNDK库)。无论是SP-HAL还是VNDK-SP都是由Google来定义的。
SP-HAL有:libGLESv1_CM_${driver}.so、libGLESv2_${driver}.so、libGLESv3_${driver}.so、libEGL_${driver}.so、vulkan.${driver}.so、android.hardware.renderscript@1.0-impl.so、android.hardware.graphics.mapper@2.0-impl.so。
SP-HAL可以访问的VNDK-SP有:android.hardware.graphics.common@1.0.so、android.hardware.graphics.mapper@2.0.so、android.hardware.renderscript@1.0.so (Renderscript)、libRS_internal.so (Renderscript)、libbase.so、libc++.so、libcutils.so、libhardware.so、libhidlbase.so、libhidltransport.so、libhwbinder.so、libion.so、libutils.so、libz.so。
SP-HAL不可见的VNDK-SP有:libRSCpuRef.so (RS)、libRSDriver.so (RS)、libbacktrace.so、libblas.so (RS)、libbcinfo.so (RS)、liblzma.so、libunwind.so。
包含渲染脚本(Rendor script)的FWK-ONLY库:libft2.so、libmediandk.so。
2.3 VNDK安全策略
framework进程对应于sepolicy中的coremain,而vendor进程对应于non-coredomain。现将共享库权限整理如下:
类别 | coredomain访问 | 非coredomain访问 | 分区位置 |
---|---|---|---|
LL-NDK | Y | Y | system/lib[64] |
LL-NDK-Private | Y | Y | system/lib[64] |
VNDK-SP/VNDK-SP-Private | Y | Y | system/lib[64] |
VNDK-SP-Ext | Y | Y | vendor/lib[64]/vndk-sp |
VNDK | Y | Y | system/lib[64] |
VNDK-Ext | N | Y | vendor/lib[64] |
FWK-ONLY | Y | N | system/lib[64] |
FWK-ONLY-RS | Y | N | system/lib[64] |
SP-HAL | Y | Y | vendor/lib[64]或vendor/lib[64]/hw |
SP-HAL-Dep | Y | Y | vendor/lib[64]vendor/lib[64]/hw |
VND-ONLY | N | Y | vendor/lib[64] |
为了确保vendor分区中VNDK-SP-Ext、SP-HAL、SP-HAL-Dep库既可以从coredomain访问,也可以从非coredomain访问,需要用same_process_hal_file标签来标记,如file_contexts中所定义:
/vendor/lib(64)?/hw/libMySpHal\.so u:object_r:same_process_hal_file:s0 |
三、VNDK编译
3.1 VNDK使能
针对Android 9.0新设备,会强制ro.vndk.version属性非空。该值用于说明VNDK共享库版本号,存在/vendor/default.prop中。若在makefile中定义的BOARD_VNDK_VERSION不等于current,则将BOARD_VNDK_VERSION定义的值赋给ro.vndk.version。若不相等,则需要先看makefile中PLATFORM_VERSION_CODENAME是否为REL,如果是则采用PLATFORM_SDK_VERSION的值,否则使用PLATFORM_VERSION_CODENAME对应的内容。
若想从Android 8.0之前版本通过OTA升级至Android 9.0,需要在BoardConfig.mk中加入以下内容:
PRODUCT_TREBLE_LINKER_NAMESPACES_OVERRIDE := false
若想从禁用了VNDK运行时增强功能的Android 8.0以后版本通过OTA升级至Android 9.0,需将PRODUCT_USE_VNDK_OVERRIDE := false添加至BoardConfig.mk中,在编译时ro.vndk.lite属性会被设为tru,并会自动添加被添加到/vendor/default.prop文件中。动态链接器将加载/system/etc/ld.config.vndk_lite.txt中的链接器命名空间配置,以隔离SP-HAL和VNDK。
对于first_pai_product属性大于27的版本,不能定义ro.vndk.lite属性,比如Android 9.0的VTS测试结果中将会出现VtsTreblePlatformVersionTest Fail项。
VNDK共享库将安装到/system/lib[64]/vndk-${ro.vndk.version}中。
VNDK-SP共享库将安装到/system/lib[64]/vndk-sp-${ro.vndk.version}中。
动态链接器配置文件将安装到/system/etc/ld.config.${ro.vndk.version}.txt中。
3.2 VNDK编译配置
编译系统包含多种类型的对象,其中包括库(共享、静态或标头)和二进制文件:
core:位于system.img中,由system使用。vendor、vendor_available、vndk或vndk-sp库不能使用此类库。
cc_binary {
name: "libexample",
...
}
vendor-only(proprietary):位于vendor.img中,由vendor使用。
cc_binary {
name: "libexample",
proprietary: true,
...
}
vendor_available:位于vendor.img中,由vendor使用(可能包含core的重复项)。
cc_binary {
name: "libexample",
vendor_available: true,
...
}
vndk:位于system.img中,由vendor使用(vendor_available的子集)。
cc_binary {
name: "libexample",
vendor_available: true,
vndk: {
enabled: true,
}
...
}
vndk-sp:位于system.img中,由system间接使用(core的子集)。
cc_binary {
name: "libexample",
vendor_available: true,
vndk: {
enabled: true,
support_system_process: true,
}
...
}
llndk:同时由system和vendor使用。
cc_binary {
name: "libexample",
...
}
库的vendor版本在bp文件中用-D__ANDROID_VNDK__来标记编译。对于vendor模块,在Android.bp中必须将vendor或proprietary设为true,而在Android.mk中需将LOCAL_VENDOR_MODULE或LOCAL_PROPRIETARY_MODULE设为true。将当被标记为vendor_available:true时,该库将编译2次,1次为平台编译,被安装到/system/lib[64]中;1次为vendor编译,被安装到/vendor/lib[64]、/system/lib[64]/vndk或/system/lib[64]/vndk-sp中)。vendor模块可以设置vendor_available,但不得设置 vndk.enabled和vndk.support_system_proces,因为它们由Google定义,即GSI中不包含这些vendor模块。现将三种属性整理如下表:
vendor_available | vndk enabled | vndk support_same_process | 说明 |
---|---|---|---|
true | false | false | 供应商变体为VND-ONLY。共享库将安装到/vendor/lib[64]中。 |
true | false | true | 无效(编译错误) |
true | true | false | 供应商变体为VNDK。共享库将安装到/system/lib[64]/vndk-${VER}中。 |
true | true | true | 供应商变体为VNDK-SP。共享库将安装到/system/lib[64]/vndk-sp-${VER}中。 |
false | false | false | 没有供应商变体。此模块为FWK-ONLY。 |
false | false | true | 无效(编译错误) |
fasle | true | false | 供应商变体为VNDK-Private。共享库将安装到/system/lib[64]/vndk-${VER}中。供应商模块不得直接使用这些变体。 |
false | false | true | 供应商变体为VNDK-SP-Private。共享库将安装到/system/lib[64]/vndk-sp-${VER}中。供应商模块不得直接使用这些变体。 |
如果启用BOARD_VNDK_VERSION,系统会移除多个默认的全局头文件搜索路径,主要有以下几个目录:frameworks/av/include、frameworks/native/include、frameworks/native/opengl/include、hardware/libhardware/include、hardware/libhardware_legacy/include、hardware/ril/include、libnativehelper/include、libnativehelper/include_deprecated、system/core/include、system/media/audio/include。若某个模块依赖于以上目录中的headers,则必须在Android.bp中指定与header_libs、static_libs和shared_libs的依赖关系。或在Android.mk中的中指定LOCAL_HEADER_LIBRARIES、LOCAL_STATIC_LIBRARIES和LOCAL_SHARED_LIBRARIES,否则编译检查时会报错。
3.3 VNDK Definition工具
VNDK
Definition tool可以帮助vendor将源码树移植到Android
8.0环境。该工具会先扫描system.img和vendor.img中二进制文件,然后解析依赖项。还可以指定system.img与GSI进行对比,以确定扩展后的lib库。
该工具代码路径为:$AOSP/development/vndk/tools/definition-tool/vndk_definition_tool.py。该脚本支持以下参数:
$ python3 ./vndk_definition_tool.py vndk \
--system "${PRODUCT_OUT}/system.img" \
--vendor "${PRODUCT_OUT}/vendor.img" \
--aosp-system "gsi_system_image" \
--load-extra-deps "dlopen.dep" \
--output-format=make
$ python3 ./vndk_definition_tool.py check-dep \
--system "${PRODUCT_OUT}/system.img" \
--vendor "${PRODUCT_OUT}/vendor.img" \
--aosp-system "gsi_system_image" \
--tag-file "eligible-list-v3.0.csv" \
--module-info ${PRODUCT_OUT}/module-info.json \
> check_dep.txt \
> check_dep_err.txt
$ python3 ./vndk_definition_tool.py deps \
--system "${PRODUCT_OUT}/system/" \
--vendor "${PRODUCT_OUT}/vendor/" \
> deps.txt
现将脚本支持的参数说明整理如下表:
第1个参数 | 说明 |
---|---|
vndk | 计算VNDK库集 |
deps | 输出二进制文件依赖信息用于debug |
check-dep | 检查eligible共享库依赖 |
第2个参数 | 说明 |
---|---|
–system | 开发项目编译生成的system.img路径。 |
–vendor | 开发项目编译生成的vendor.img路径。 |
–aosp-system | AOSP的system.img,即由Google释放的用于VTS测试的GSI文件。 |
–tag-file | 由Google释放的对framework共享库进行了分类的表格 |
–output-format | 自动生成Android.mk(适用于vndk ) |
–load-extra-deps | 加载外部模块依赖文件(vndk 不适用) |
–revert | 输出依赖详细情况(适用于deps ) |
–module-info | 开发项目编译生成的module-info.json。(适用于check-dep ) |
dlopen.dep的内容范例如下:
#libart.so依赖于libart-compier.so |
check_dep_err.txt的内容范例如下:
#libRS_internal.so对libmediandk.so的违规依赖 |
deps.txt的内容范例如下:
#ld-android.so没有依赖 |
各类库对应目录:
vndk-sp – /system/lib[64]/vndk-sp。
vndk-sp-ext – /vendor/lib[64]/vndk-sp。
extra-vendor-libs – /vendor/lib[64]
在device/$(VENDOR)/$(DEVICE_NAME)目录下创建vndk文件夹,用于存放配置vndk库的Android.mk,其范例如下:
ifneq ($(filter $(DEVICE_NAME),$(TARGET_DEVICE)),)
##_VNDK_SP_##
VNDK_SP_LIBRARIES := \
##_VNDK_SP_EXT_##
VNDK_SP_EXT_LIBRARIES := \
libpng
##_EXTRA_VENDOR_LIBS_##
EXTRA_VENDOR_LIBRARIES := \
android.hidl.base@1.0 \
com.qualcomm.qti.ant@1.0 \
com.qualcomm.qti.wifidisplayhal@1.0 #-------------------------------------------------------------------------------
# VNDK Modules
#-------------------------------------------------------------------------------
LOCAL_PATH := $(call my-dir) define define-vndk-lib
include $$(CLEAR_VARS)
LOCAL_MODULE := $.$
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_PREBUILT_MODULE_FILE := $$(TARGET_OUT_INTERMEDIATE_LIBRARIES)/$.so
LOCAL_STRIP_MODULE := false
LOCAL_MULTILIB := first
LOCAL_MODULE_TAGS := optional
LOCAL_INSTALLED_MODULE_STEM := $.so
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_RELATIVE_PATH := $
LOCAL_VENDOR_MODULE := $
include $$(BUILD_PREBUILT) ifneq ($$(TARGET_2ND_ARCH),)
ifneq ($$(TARGET_TRANSLATE_2ND_ARCH),true)
include $$(CLEAR_VARS)
LOCAL_MODULE := $.$
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_PREBUILT_MODULE_FILE := $$($$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_INTERMEDIATE_LIBRARIES)/$.so
LOCAL_STRIP_MODULE := false
LOCAL_MULTILIB :=
LOCAL_MODULE_TAGS := optional
LOCAL_INSTALLED_MODULE_STEM := $.so
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_RELATIVE_PATH := $
LOCAL_VENDOR_MODULE := $
include $$(BUILD_PREBUILT)
endif # TARGET_TRANSLATE_2ND_ARCH is not true
endif # TARGET_2ND_ARCH is not empty
endef $(foreach lib,$(VNDK_SP_LIBRARIES),\
$(eval $(call define-vndk-lib,$(lib),vndk-sp-gen,vndk-sp,)))
$(foreach lib,$(VNDK_SP_EXT_LIBRARIES),\
$(eval $(call define-vndk-lib,$(lib),vndk-sp-ext-gen,vndk-sp,true)))
$(foreach lib,$(EXTRA_VENDOR_LIBRARIES),\
$(eval $(call define-vndk-lib,$(lib),vndk-ext-gen,,true))) #-------------------------------------------------------------------------------
# Phony Package
#------------------------------------------------------------------------------- include $(CLEAR_VARS)
LOCAL_MODULE := $(DEVICE_NAME)-vndk
LOCAL_MODULE_TAGS := optional
LOCAL_REQUIRED_MODULES := \
$(addsuffix .vndk-sp-gen,$(VNDK_SP_LIBRARIES)) \
$(addsuffix .vndk-sp-ext-gen,$(VNDK_SP_EXT_LIBRARIES)) \
$(addsuffix .vndk-ext-gen,$(EXTRA_VENDOR_LIBRARIES))
include $(BUILD_PHONY_PACKAGE) endif # ifneq ($(filter $(DEVICE_NAME),$(TARGET_DEVICE)),)
然后在项目的makefile中添加下面一条内容,使得vndk/Android.mk参与编译:
PRODUCT_PACKAGES += $(DEVICE_NAME)-vndk
#查看image类型
file system.img
#将ext4转换为raw类型
out/host/linux-x86/bin/simg2img system.img system.raw.img
mkdir system
#手动挂载system分区
sudo mount -o loop,ro system.raw.img system
四、VNDK延伸
4.1 VNDK扩展
根据模块中定义的功能,可将模块分为DA模块和DX模块:
(1)Defining-only-AOSP模块(DA 模块)不会定义AOSP副本中未包含的新功能。
a.一个未经修改的AOSP库就是一个DA模块。
b.如果vendor使用SIMD指令重写libexample.so中的函数(不添加新函数),那么修改后的libexample.so将是一个DA模块。
(2)Defining-Extension模块(DX模块)要么会定义新功能,要么没有AOSP副本。
a.如果vendor向 libexample.so 添加一个test函数以访问某些内部数据,那么修改后的libexample.so将是一个DX库,而这个新增函数将是该库的扩展部分。
b.如果vendor定义了一个名为libexample.so的非AOSP库,那么libexmple.so将是一个DX库。
根据模块所使用的功能,可将模块分为UA模块和UX模块。
(1)Using-only-AOSP(UA模块)仅会在其实现过程中使用AOSP功能。它们不依赖任何非AOSP扩展功能。
a.一个未经修改且完整无缺的AOSP库即是一个UA模块。
b.如果修改后的共享库libexample.so仅依赖于其他AOSP API,那么它将是一个UA模块。
(2)Using-Extension 模块(UX模块)会在其实现过程中依赖某些非 AOSP 功能。
a.如果修改后的libexample.so依赖另一个名为libexample2.so的非AOSP库,那么修改后的libexample.so将是一个UX模块。
b.如果vendor修改后的libexample.so1添加了一个新函数,并且其修改后的libexample2.so使用libexample1.so中的这个新增函数,那么修改后的libexample2.so将是一个UX模块。
4.2 链接器命名空间
链接器命名空间机制由动态链接器提供,可以隔离不同链接器命名空间中的共享库,以确保具有相同库名称和不同符号的库不会发生冲突。链接器命名空间机制可提供相应的灵活性,从而将由一个链接器命名空间导出的某些共享库用于另一个链接器命名空间。这些导出的共享库可能会成为对其他程序公开的应用编程接口,同时在其链接器命名空间中隐藏实现细节。
动态链接器负责加载DT_NEEDED条目中指定的共享库,由dlopen()或android_dlopen_ext()的参数指定的共享库。在这两种情况下,动态链接器都会找出调用程序所在的链接器命名空间,并尝试将相关依赖项加载到同一个链接器命名空间中。如果动态链接器无法将共享库加载到指定的链接器命名空间中,它会向关联的链接器命名空间索取导出的共享库。
ini配置文件范例如下:
dir.system = /system/bin
dir.system = /system/xbin
dir.vendor = /vendor/bin
[system]
additional.namespaces = sphal,vndk
namespace.default.isolated = true
namespace.default.search.paths = /system/${LIB}
namespace.default.permitted.paths = /system/${LIB}/hw
namespace.default.asan.search.paths = /data/asan/system/${LIB}:/system/${LIB}
namespace.default.asan.permitted.paths = /data/asan/system/${LIB}/hw:/system/${LIB}/hw
namespace.sphal.isolated = true
namespace.sphal.visible = true
namespace.sphal.search.paths = /odm/${LIB}:/vendor/${LIB}
namespace.sphal.permitted.paths = /odm/${LIB}:/vendor/${LIB}
namespace.sphal.asan.search.paths = /data/asan/odm/${LIB}:/odm/${LIB}
namespace.sphal.asan.search.paths += /data/asan/vendor/${LIB}:/vendor/${LIB}
namespace.sphal.asan.permitted.paths = /data/asan/odm/${LIB}:/odm/${LIB}
namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB}:/vendor/${LIB}
namespace.sphal.links = default,vndk
namespace.sphal.link.default.shared_libs = libc.so:libm.so
namespace.sphal.link.vndk.shared_libs = libbase.so:libcutils.so
namespace.vndk.isolated = true
namespace.vndk.search.paths = /system/${LIB}/vndk-sp-
namespace.vndk.permitted.paths = /system/${LIB}/vndk-sp-
namespace.vndk.links = default
namespace.vndk.link.default.shared_libs = libc.so:libm.so
[vendor]
namespace.default.isolated = false
namespace.default.search.paths = /vendor/${LIB}:/system/${LIB}
各字段含义整理如下表:
属性 | 说明 | 示例 |
---|---|---|
dir.name | 指向[name]区段所应用到的目录的路径。每个属性都会将目录下的可执行文件映射到链接器命名空间配置区段。可能会有2个(或多个)属性具有相同的name,却指向不同的目录。 | dir.system=/system/bin, dir.system=/system/xbin, dir.vendor=/vendor/bin这表示在[system]区段中指定的配置适用于从/system/bin或/system/xbin加载的可执行文件。在[vendor]区段中指定的配置适用于从/vendor/bin加载的可执行文件。 |
additional.namespaces | 相应区段的其他命名空间的逗号分隔列表(default命名空间除外)。 | additional.namespaces= sphal,vndk这表示[system]配置中有3个命名空间(default、sphal和vndk)。 |
namespace.name.links | 回退命名空间的逗号分隔列表。如果在当前命名空间中找不到共享库,则动态链接器会尝试从回退命名空间加载共享库。在列表开头指定的命名空间优先级较高。 | namespace.sphal.links=default,vndk 如果某个共享库或可执行文件请求另一个共享库,而后者无法加载到sphal命名空间,则动态链接器会尝试从default命名空间加载此共享库。然后,如果此共享库也无法从default 命名空间加载,则动态链接器会尝试从vndk命名空间加载此共享库。最后,如果所有尝试都失败,则动态链接器会返回一个错误。 |
namespace.name.link.other.shared_libs | 用冒号分隔的共享库列表(如果在name命名空间中找不到这些共享库,则可以在other命名空间中搜索)。此属性无法与namespace.name.link.other.allow_all_shared_libs一起使用。 | namespace.sphal.link.default.shared_libs=libc.so:libm.so这表示回退链接仅接受libc.so或libm.so作为请求的库名称。如果请求的库名称不是libc.so,也不是libm.so,则动态链接器会忽略从sphal到default命名空间的回退链接。 |
namespace.name.link.other.allow_all_shared_libs | 一个布尔值,用于指示在 name 命名空间中找不到共享库时,是否所有共享库都可以在other命名空间中搜索。此属性无法与namespace.name.link.other.shared_libs一起使用。 | namespace.vndk.link.sphal.allow_all_shared_libs=true这表示所有库名称都可以遍历从vndk到sphal命名空间的回退链接。 |
namespace.name.isolated | 一个布尔值,用于指示动态链接器是否应该检查共享库在什么位置。如果isolated为true,则只有某个 search.paths目录(不包含子目录)中或permitted.paths目录(包含子目录)下的共享库才能加载。如果isolated为false,则动态链接器不会检查共享库的路径。 | namespace.sphal.isolated=true这表示只有search.paths中或permitted.paths下的共享库才能加载到sphal命名空间。 |
namespace.name.search.paths | 以冒号分隔的目录列表,用于搜索共享库。如果函数调用dlopen()或DT_NEEDED条目时未指定完整路径,则在search.paths中指定的目录将附加到请求的库名称前面。在列表开头指定的目录优先级较高。如果isolated为true,则任一search.paths目录(不包含子目录)中的共享库都可以加载,无论permitted.paths属性如何设置。 | namespace.default.search.paths=/system/${LIB}这表示动态链接器会在/system/${LIB}中搜索共享库。 |
namespace.name.asan.search.paths | 以冒号分隔的目录列表,用于在启用Address Sanitizer后搜索共享库。在启用 Address Sanitizer后,系统会忽略namespace.name.search.paths。 | namespace.default.asan.search.paths=/data/asan/system/${LIB}:/system/${LIB}这表示在启用Address Sanitizer后,动态链接器会先搜索/data/asan/system/${LIB},然后再搜索 /system/${LIB}。 |
namespace.name.permitted.paths | 以冒号分隔的目录列表(包含子目录),当isolated为true时,动态链接器可在其中加载共享库(除了search.paths以外)。permitted.paths的子目录下的共享库也可以加载。如果isolated为false,则系统会忽略 permitted.paths 并发出相应警告。 | namespace.default.permitted.paths=/system/${LIB}/hw这表示/system/${LIB}/hw下的共享库可以加载到隔离的default命名空间。 |
namespace.name.asan.permitted.paths | 以冒号分隔的目录列表,在启用Address Sanitizer后,动态链接器可在其中加载共享库。在启用Address Sanitizer后,系统会忽略namespace.name.permitted.paths。 | namespace.default.asan.permitted.paths=/data/asan/system/${LIB}/hw:/system/${LIB}/hw这表示在启用Address Sanitizer后,/data/asan/system/${LIB}/hw或/system/${LIB}/hw下的共享库可以加载到隔离的 default命名空间。 |
namespace.name.visible | 一个布尔值,用于指示程序(不包括libc)是否可以包含带有android_get_exported_namespace()的链接器命名空间句柄,以及通过将此句柄传递到android_dlopen_ext()打开链接器命名空间中的共享库。如果visible为true,则android_get_exported_namespace()在命名空间存在时始终返回此句柄。如果visible为false(默认值),则无论命名空间是否存在,android_get_exported_namespace()始终返回NULL。仅当符合以下条件时,共享库才能加载到此命名空间:(1)具有指向此命名空间的回退链接的其他链接器命名空间请求这些共享库;(2)此命名空间中的其他共享库或可执行文件请求这些共享库。 | namespace.sphal.visible = true这表示android_get_exported_namespace(“sphal”)可以返回有效的链接器命名空间句柄。 |
${android-src}/system/core/rootdir/etc中有3个配置文件。系统会根据BoardConfig.mk中 PRODUCT_TREBLE_LINKER_NAMESPACES、BOARD_VNDK_VERSION和BOARD_VNDK_RUNTIME_DISABLE的值选择不同的配置:
PRODUCT_TREBLE_LINKER_NAMESPACES | BOARD_VNDK_VERSION | BOARD_VNDK_RUNTIME_DISABLE | 选择的配置 | VTS要求 |
---|---|---|---|---|
true | current | empty | ld.config.txt | 搭载Android P的设备的必要配置。 |
true | current | true | ld.config.vndk_lite.txt | 搭载Android8.x的设备的必要配置。 |
true | empty | any | ld.config.vndk_lite.txt | 搭载Android 8.x的设备的必要配置。 |
false | any | any | ld.config.legacy.txt | 适用于不支持Treble的设备 |
${AOSP}/system/core/rootdir/etc/ld.config.vndk_lite.txt会隔离SP-HAL和VNDK-SP共享库。在Android 8.0及更高版本中,当PRODUCT_TREBLE_LINKER_NAMESPACES为true时,该配置必须是动态链接器的配置文件。
${AOSP}/system/core/rootdir/etc/ld.config.txt也会隔离SP-HAL和VNDK-SP共享库。此外,ld.config.txt还会提供全面的动态链接器隔离。它可确保系统分区中的模块不依赖于供应商分区中的共享库,反之亦然。
在Android8.1中,ld.config.txt是默认配置文件,强烈建议您启用全面的动态链接器隔离。但是,如果在Android 8.1中需要清理的依赖项太多,您可以将BOARD_VNDK_RUNTIME_DISABLE添加到BoardConfig.mk中。如果BOARD_VNDK_RUNTIME_DISABLE为true,则会安装 ${AOSP}/system/core/rootdir/etc/ld.config.vndk_lite.txt。
ld.config.txt会隔离系统分区和供应商分区之间的共享库依赖项。下文概述了该配置文件与上一小节中提到的ld.config.txt相比有哪些不同:
framework进程:
a.创建了四个命名空间(default、vndk、sphal和rs)。
b.系统会隔离所有命名空间。
c.将系统共享库加载到default命名空间中。
d.将SP-HAL加载到sphal命名空间中。
e.将VNDK-SP共享库加载到vndk命名空间中。
vendor进程
a.创建了三个命名空间(default、vndk和system)。
b.系统会隔离default命名空间。
c.将供应商共享库加载到default命名空间中。
d.将VNDK和VNDK-SP共享库加载到vndk命名空间中。
e.将LL-NDK及其依赖项加载到system命名空间中。
链接器命名空间之间的关系如下图所示:
在上图中,LL-NDK 和 VNDK-SP 代表以下共享库:
LL-NDK:libEGL.so, libGLESv1_CM.so, libGLESv2.so, libGLESv3.so,
libandroid_net.so, libc.so, libdl.so, liblog.so, libm.so,
libnativewindow.so, libneuralnetworks.so, libsync.so, libvndksupport.so,
libvulkan.so
VNDK-SP:android.hardware.graphics.common@1.0.so, android.hardware.graphics.mapper@2.0.so, android.hardware.renderscript@1.0.so, android.hidl.memory@1.0.so,
libRSCpuRef.so, libRSDriver.so, libRS_internal.so, libbase.so,
libbcinfo.so, libc++.so, libcutils.so, libhardware.so, libhidlbase.so,
libhidlmemory.so, libhidltransport.so, libhwbinder.so, libion.so,
libutils.so, libz.so
下表列出了框架进程的命名空间配置(摘自ld.config.txt中的[system]部分):
命名空间 | 属性 | 值 |
---|---|---|
default | search.paths | /system/${LIB}, /product/${LIB} |
default | permitted.paths | /system/${LIB}/drm, system/${LIB}/extractors, /system/${LIB}/hw, /product/${LIB}, /system/framework, /system/app, /system/priv-app, /vendor/app, /vendor/priv-app, /oem/app, /odm/priv-app, /oem/app, /product/framework, /product/app, /product/priv-app, /data, /mnt/expand |
default | isolated | true |
sphal | search.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | permitted.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | isolated | true |
sphal | visible | true |
links | default,vndk,rs | |
links | link.default.shared_libs | LL-NDK |
links | link.vndk.shared_libs | VNDK-SP |
links | link.rs.shared_libs | libRS_internal.so |
vndk(适用于 VNDK-SP) | search.paths | /odm/${LIB}/vndk-sp, /vendor/${LIB}/vndk-sp, /system/${LIB}/vndk-sp-${VER} |
vndk(适用于 VNDK-SP) | permitted.paths | /odm/${LIB}/hw, /odm/${LIB}/egl, /vendor/${LIB}/hw, /vendor/${LIB}/egl, /system/${LIB}/vndk-sp-${VER}/hw |
vndk(适用于 VNDK-SP) | isolated | true |
vndk(适用于 VNDK-SP) | visible | true |
vndk(适用于 VNDK-SP) | links | default、sphal |
vndk(适用于 VNDK-SP) | link.default.shared_libs | LL-NDK |
vndk(适用于 VNDK-SP) | link.default.allow_all_shared_libs | true |
rs(适用于 Renderscript) | search.paths | /odm/${LIB}/vndk-sp, /vendor/${LIB}/vndk-sp, /system/${LIB}/vndk-sp-${VER}, /odm/${LIB}, /vendor/${LIB} |
rs(适用于 Renderscript) | permitted.paths | /odm/${LIB}, /vendor/${LIB}, /data(适用于已编译的 RS 内核) |
rs(适用于 Renderscript) | isolated | true |
rs(适用于 Renderscript) | visible | true |
rs(适用于 Renderscript) | links | default,vndk |
rs(适用于 Renderscript) | link.default.shared_libs | LL-NDK, libmediandk.so, libft2.so |
rs(适用于 Renderscript) | link.vndk.shared_libs | VNDK-SP |
下表列出了供应商进程的命名空间配置(摘自ld.config.txt中的[vendor]部分):
命名空间 | 属性 | 值 |
---|---|---|
default | search.paths | /odm/${LIB}, /vendor/${LIB} |
default | permitted.paths | /odm, /vendor |
default | isolated | true |
default | visible | true |
default | links | system、vndk |
default | link.system.shared_libs | LL-NDK |
default | link.vndk.shared_libs | VNDK、VNDK-SP(供应商可用) |
vndk | search.paths | /odm/${LIB}/vndk, /odm/${LIB}/vndk-sp, /vendor/${LIB}/vndk, /vendor/${LIB}/vndk-sp, /system/${LIB}/vndk-${VER}, /system/${LIB}/vndk-sp-${VER} |
vndk | isolated | true |
vndk | links | system、default |
vndk | link.system.shared_libs | LL-NDK |
vndk | link.default.allow_all_shared_libs | true |
system | search.paths | /system/${LIB} |
system | isolated | false |
从 Android 8.0 开始,动态链接器将配置为隔离 SP-HAL 和 VNDK-SP 共享库,以使其符号不会与其他框架共享库发生冲突。链接器命名空间之间的关系如下所示:
LL-NDK和VNDK-SP代表以下共享库:
LL-NDK:libEGL.so, libGLESv1_CM.so, libGLESv2.so, libc.so,
libdl.so, liblog.so, libm.so, libnativewindow.so,
libstdc++.so(不在ld.config.txt 中), libsync.so, libvndksupport.so,
libz.so(已移到 ld.config.txt中的VNDK-SP)
VNDK-SP:android.hardware.graphics.common@1.0.so, android.hardware.graphics.mapper@2.0.so, android.hardware.renderscript@1.0.so, android.hidl.memory@1.0.so,
libbase.so, libc++.so, libcutils.so, libhardware.so, libhidlbase.so,
libhidlmemory.so, libhidltransport.so, libhwbinder.so, libion.so,
libutils.so
下表列出了框架进程的命名空间配置(摘自ld.config.vndk_lite.txt中的[system]部分):
命名空间 | 属性 | 值 |
---|---|---|
default | search.paths | /system/${LIB}, /odm/${LIB}, /vendor/${LIB}, /product/${LIB} |
default | isolated | false |
sphal | search.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | permitted.paths | /odm/${LIB}, /vendor/${LIB} |
sphal | isolated | true |
sphal | visible | true |
sphal | links | default,vndk,rs |
sphal | link.default.shared_libs | LL-NDK |
sphal | link.vndk.shared_libs | VNDK-SP |
sphal | link.rs.shared_libs | libRS_internal.so |
vndk(适用于 VNDK-SP) | search.paths | /odm/${LIB}/vndk-sp, /vendor/${LIB}/vndk-sp, /system/${LIB}/vndk-sp-${VER} |
vndk(适用于 VNDK-SP) | permitted.paths | /odm/${LIB}/hw, /odm/${LIB}/egl, /vendor/${LIB}/hw, /vendor/${LIB}/egl, /system/${LIB}/vndk-sp-${VER}/hw |
vndk(适用于 VNDK-SP) | solated | true |
vndk(适用于 VNDK-SP) | visible | true |
vndk(适用于 VNDK-SP) | links | default |
vndk(适用于 VNDK-SP) | link.default.shared_libs | LL-NDK |
rs(适用于 Renderscript) | search.paths | /odm/${LIB}/vndk-sp, /vendor/${LIB}/vndk-sp, /system/${LIB}/vndk-sp-${VER}, /odm/${LIB}, /vendor/${LIB} |
rs(适用于 Renderscript) | permitted.paths | /odm/${LIB}, /vendor/${LIB}, /data(适用于已编译的 RS 内核) |
rs(适用于 Renderscript) | isolated, | true |
rs(适用于 Renderscript) | visible | true |
rs(适用于 Renderscript) | links | default,vndk |
rs(适用于 Renderscript) | link.default.shared_libs | LL-NDK, libmediandk.so, libft2.so |
rs(适用于 Renderscript) | link.vndk.shared_libs | VNDK-SP |
下表列出了vendor进程的命名空间配置(摘自ld.config.vndk_lite.txt中的[vendor]部分):
命名空间 | 属性 | 值 |
---|---|---|
default | search.paths | /odm/${LIB}, /odm/${LIB}/vndk, /odm/${LIB}/vndk-sp, /vendor/${LIB}, /vendor/${LIB}/vndk, /vendor/${LIB}/vndk-sp, /system/${LIB}/vndk-${VER}, /system/${LIB}/vndk-sp-${VER}, /system/${LIB}(已弃用), /product/${LIB}(已弃用) |
default | isolated | false |
4.3 VNDK快照
VNDK快照是一组适用于Android版本的VNDK-core和VNDK-SP库。如果system.img包含vendor.img所需的相应VNDK快照,那么只能升级system分区。VNDK快照设计包括两项操作:(1)从当前系统映像生成预编译的VNDK快照;(2)将这些预编译的库安装到更高Android版本的system分区。正式的VNDK快照是在Android编译服务器上自动编译而成,并包含在${AOSP}/prebuilts/vndk中。此外,也支持本地编译VNDK快照。
#编译特定project
lunch aosp_arm64_ab-user
make -j vndk dist [BOARD_VNDK_VERSION=current]
#编译所有project
cd $AOSP
DIST_DIR=%dist_dir% development/vndk/snapshot/build.sh
a.VNDK-core和VNDK-SP共享库的vendor变体。
i.无需LL-NDK 共享库,因为这类库是向后兼容的。
ii.对于64位目标,TARGET_ARCH和TARGET_2ND_ARCH库都将被编译并包含在内。
b.VNDK-core、VNDK-SP、LL-NDK和VNDK-private库的列表位于[vndkcore|vndksp|llndk|vndkprivate].libraries.txt。
c.链接器配置文件为ld.config.txt。
d.许可文件。
e.module_paths.txt。记录所有VNDK库的模块路径;检查GPL项目是否已在指定Android源代码树中发布源代码时,需要用到这种文件。
对于指定VNDK快照ZIP文件android-vndk-{TARGET_ARCH}.zip,系统会根据ABI位数将VNDK预编译库分组到名为arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT} 的单独目录中。
VNDK 快照目录结构:
development/vndk/snapshot/update.py脚本可自动将预编译的VNDK快照添加到源代码树中。此脚本将执行以下任务:
a.在/prebuilts/vndk/v中,使用repo start创建新的git分支。
b.获取VNDK快照编译软件工件并将其解压缩。
c.运行gen_buildfiles.py以自动生成编译文件(Android.mk、Android.bp)。
d.运行check_gpl_license.py以验证根据通用公共许可证(GPL)获得许可的预编译库是否在当前源代码树中发布了源代码。
e.使用git commit提交新的更改。
在指定–local选项的情况下,update.py会从本地$DIST_DIR(而非Android编译服务器中)提取VNDK快照编译工件。但由于本地模式仅用于测试,因此该脚本将跳过 GPL 许可检查和 git commit 步骤。
python update.py <VER> --local
prebuilts/vndk 的目录结构:
system.img在编译时使用BOARD_VNDK_VERSION、PRODUCT_EXTRA_VNDK_VERSIONS和ro.vndk.version中的信息安装VNDK快照库。可以使用以下选项之一控制从/prebuilts/vndk/v中安装哪些VNDK快照:
a.BOARD_VNDK_VERSION。使用快照模块编译当前vendor模块,并仅安装vendor模块所需的快照模块。
b.PRODUCT_EXTRA_VNDK_VERSIONS。无论当前vendor模块有哪些,都安装VNDK快照模块。这将安装PRODUCT_EXTRA_VNDK_VERSIONS中列出的预编译VNDK快照,而不会在编译时将其与任何其他模块相关联。
BOARD_VNDK_VERSION显示的是当前vendor模块需要编译的VNDK版本。如果BOARD_VNDK_VERSION在/prebuilts/vndk目录中有可用的
VNDK快照版本,则系统会安装BOARD_VNDK_VERSION中指明的VNDK快照。如果目录中的VNDK快照不可用,则会出现编译错误。定义
BOARD_VNDK_VERSION也会启用要安装的VNDK模块。vendor模块会在编译时与BOARD_VNDK_VERSION中定义的VNDK快照版本相关联(此操作不会在system源代码中编译当前的VNDK模块)。从代码库中下载完整的源代码树时,system源代码和vendor源代码均基于相同的Android版本。
PRODUCT_EXTRA_VNDK_VERSIONS列出了要安装的其他VNDK版本。正常情况下,当前的vendor分区只需一个VNDK快照就足够了。不过,在某些情况下,您可能需要在一个system.img中提供多个快照。例如,常规系统映像(GSI)具有多个快照,以通过一个system.img支持多个vendor版本。设置PRODUCT_EXTRA_VNDK_VERSIONS后,除了BOARD_VNDK_VERSION中的VNDK版本之外,您还可以安装VNDK快照模块。如果
PRODUCT_EXTRA_VNDK_VERSIONS具有特定的版本列表,则编译系统会在prebuilts/vndk目录中查找版本列表的预编译快照。如果编译系统找到所有列出的快照,便会将这些快照文件安装到每个out/target/product//system/lib[64]/vndk[-sp]-${VER}中。缺少某些版本会导致出现编译错误。
VNDK模块将不会在编译时与vendor模块相关联,但在运行时可能会使用该模块(如果vendor分区中的vendor模块需要某个已安装的VNDK版本)。PRODUCT_EXTRA_VNDK_VERSIONS仅在指定了BOARD_VNDK_VERSION的情况下才有效。例如,若要将O
MR1 VNDK快照安装到system.img中,需要运行以下命令:
m -j PRODUCT_EXTRA_VNDK_VERSIONS=
PLATFORM_VNDK_VERSION在系统源代码中指定了当前VNDK模块的VNDK版本。系统会通过以下方式自动设置该值:
a.在版本发布之前,将PLATFORM_VNDK_VERSION设置为PLATFORM_VERSION_CODENAME。
b.在发布时,将PLATFORM_SDK_VERSION复制到PLATFORM_VNDK_VERSION中。
c.发布Android版本后,当前的VNDK库会被安装到/system/lib[64]/vndk-$SDK_VER和/system/lib[64]/vndk-sp-$SDK_VER,其中$SDK_VER是存储在PLATFORM_VNDK_VERSION中的版本。
vendor模块使用/etc/ld.config.${VER}.txt(其中${VER}是从ro.vndk.version属性中获得的)中的命名空间配置来搜索所需的共享库。命名空间配置中包含带有版本编号的VNDK目录,该目录使用以下语法:
#%VNDK_VER% 在编译时会被替换为PLATFORM_VNDK_VERSION,这样一来,system.img便能够为每个VNDK版本提供多个快照。
/system/${LIB}/vndk-%VNDK_VER%
/system/${LIB}/vndk-sp-%VNDK_VER%
如果将BOARD_VNDK_VERSION设置为current,则PLATFORM_VNDK_VERSION将存储在ro.vndk.version中;否则BOARD_VNDK_VERSION将存储在ro.vndk.version中。PLATFORM_VNDK_VERSION在Android版本发布时会被设置为SDK版本;在发布之前,由字母和数字组成的Android代码名称会用于PLATFORM_VNDK_VERSION。
下表总结了VNDK版本设置。
供应商版本 | 开发板版本 | SDK版本 | 平台版本 | 版本属性 | 安装目录 |
---|---|---|---|---|---|
当前的VNDK模块 | current | 之前<CODE_NAME> | <CODE_NAME> | /system/lib[64]/vndk[-sp]-<CODE_NAME> | |
当前的VNDK模块 | current | 之后 <SDK_ver> | <SDK_ver> | /system/lib[64]/vndk[-sp]-<SDK_ver> | |
预编译的快照模块 | <VNDK_ver>(用于快照) | 之前或之后 | <CODE_NAME>或 <SDK_ver> | <VNDK_ver> | /system/lib[64]/vndk[-sp]-<VNDK_ver> |
a.开发板版本(BOARD_VNDK_VERSION):vendor模块需要编译的VNDK版本。如果vendor模块可与当前系统模块相关联,则将其设置为 current。
b.平台版本(PLATFORM_VNDK_VERSION):当前系统模块正在编译的VNDK版本(仅在BOARD_VNDK_VERSION为当前版本时编译)。
c.版本属性(ro.vndk.version):一种属性,用于指定vendor.img 中的二进制文件和库需要运行的VNDK版本。该属性存储在/vendor/default.prop下的vendor.img中。
参考:https://source.android.google.cn/devices/architecture/vndk
Android : 供应商原生开发套件 (VNDK)的更多相关文章
- Android Compatibility package 兼容性开发套件
我们认为Android 3.0平板电脑操作系统在美国时间2011年2月22日的正式推出,对于Android手机应用程序开发者所象征的意涵是: 之前大家所开发过的Android手机应用,除了可以在And ...
- RN与android原生开发混合后的环境报错问题
RN与android原生开发混合后的环境报错问题 需要先安装nodejs$ yarn --version1.12.1更新当前版本yarn upgrade --latest安装 | Yarnhttps: ...
- Android 原生开发、H5、React-Native使用利弊和场景技术分享
http://m.blog.csdn.net/article/details?id=51778086 发表于2016/6/28 18:52:46 1176人阅读 最近工作中接触到React ...
- Android H5混合开发(3):原生Android项目里嵌入Cordova
前言 如果安卓项目已经存在了,那么如何使用Cordova做混合开发? 方案1(适用于插件会持续增加或变化的项目): 新建Cordova项目并添加Android平台,把我们的安卓项目导入Android平 ...
- 全球首个全流程跨平台界面开发套件,PowerUI分析
一. 首个全流程跨平台界面开发套件,PowerUI正式发布 UIPower在DirectUI的基础上,自主研发全球首个全流程跨平台界面开发套件PowerUI(PUI)正式发布,PowerU ...
- Weex的原生开发
weex概念与特性 最形象的理解就是类似react native. Weex几大特点: 1.帮助你构建原生应用 与 Web App.HTML5 App 或 hybrid App 不同,您可以使用 We ...
- 如此繁荣的移动webapp开发市场:总结当下的一些移动web开发套件
写在前面: 因为移动市场的盛行带动了移动社交.移动购物.手游.智能化硬件等多个新兴领域.智能终端硬件水平越来越高,运行其上的web浏览器能力也越来越强,加上HTML5\JS\CSS的蓬勃发展,Web已 ...
- 【DSP开发】【计算机视觉】TI 视觉软件开发套件ADAS
关键字:TI 视觉软件开发套件 ADAS 日前,德州仪器 (TI) 宣布推出其视觉软件开发套件(SDK),从而为开发人员提供了一款灵活的框架.一组丰富齐全的硬件设备驱动程序和一套适用的开发工具,可 ...
- Android之NDK开发(转)
Android之NDK开发 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...
随机推荐
- Oracle cmd 命令
1.登陆 输入sqlplus,回车.然后输入用户名和密码. 退出exit. 2.查看服务 Window打开服务的cmd命令 windows +R :services.msc---本地服务设置 rege ...
- flask开发环境
1. 创建虚拟环境flask_py3 虚拟环境是一个互相隔离的目录 mkvirtualenv flask_py3 2.安装flask包 pip install flask==0.10.1 其他:导入f ...
- Tomcat 输出日志出现中文乱码
Tomcat 输出日志出现中文乱码 解决方案: 打开到tomcat安装目录下的conf/文件夹 修改logging.properties文件,找到 java.util.logging.ConsoleH ...
- com.android.ddmlib.adbcommandrejectedexception:未经授权的设备。
出现这种问题的原因是adb被杀死了,根据网上的说法在platform-tools下双击adb.exe 也启动不了. 在命令提示符中执行 adb kill-server adb start-ser ...
- git -C <other-git-repo-path> [git-command] 指定其它路径的仓库 执行命令
git -C <other-git-repo-path> [git-command] 指定其它路径的仓库 执行命令 注意,-C 要在命令之前 例如: git -C d:/testRepo ...
- Android利用json进行网络解析
必须单开一个线程,android界面的主线程不会负责通信模块
- React 入门与实战-课时7 虚拟DOM的本质和目的
DOM树的概念: 一个网页呈现的过程: 1.浏览器请求服务器获取页面HTML代码 2.浏览器先在内存中,解析DOM结构,并在浏览器内存中,渲染出一颗DOM树: 3.浏览器把DOM树,呈现到页面上: R ...
- Force git to overwrite local files on pull 使用pull强制覆盖本地文件 转载自:http://snowdream.blog.51cto.com/3027865/1102441
How do I force an overwrite of local files on a git pull? I think this is the right way: $ git fetch ...
- windows OpenCV 2.4.9 Python 2.7配置
1 .下载 OpenCV 2.4.9 .下载OpenCV-2.4.9,使用方便 下载地址 2. OpenCV-自解压文件,直接运行.即可解压.解压到想要的opencv文件夹里E:\Programme\ ...
- <script language= "javascript " for= "window " event= "onload ">
狼-志 EVENT event 设置或获取脚本编写用于的事件 FOR htmlFor 设置或获取绑定到事件脚本的对象. 可以这样理解. <script language= ...