行业知识
Android12系统环境变量设置
欢迎大家关注我的微信公众号:Xndroid,探索Android技术,解锁移动世界。
最近在移植百度Apollo Cyber通信框架至安卓系统中,发现Cyber本身依赖于环境变量来实现服务的初始化配置。相应地,我也需要在安卓系统中引入这些环境变量,并确保在Native服务启动时这些环境变量已经准备就绪。
由于此前我对环境变量的了解并不多,于是研究学习了一下Android系统中关于环境变量的相关配置,这篇博文即对此做了一个记录。
背景简介
首先我们需要探究一下为什么我们需要环境变量。环境变量是一组动态的,可手动编辑、设置的值。一般而言,我们程序的运行往往会依赖于各种各样的配置,这些配置我们可以以配置文件的形式存放于本地文件系统中,再让程序在运行时进行解析从而获取配置,这是一种比较通用的做法;而另一种比较通用的做法是将这些配置写入环境变量,程序在运行时自动读取环境变量即可。前者适合配置比较多的情况,而后者则适合配置少且配置可能需要多进程共享的情况,比较灵活。
可以说环境变量是非常重要的,没有环境变量的支持,无论是Windows系统中的程序还是Linux系统中的程序都无法正常运行。
环境变量分为两类:全局环境变量和局部环境变量。全局环境变量是全局可见的,所有进程可见;而局部变量则针对部分进程可见。
查看环境变量
如何查看系统的环境变量呢,这里提供两个命令:env或者printenv命令,如下所示:
_=/system/bin/envANDROID_DATA=/dataHOME=/ANDROID_TZDATA_ROOT=/apex/com.android.tzdataANDROID_STORAGE=/storageANDROID_ASSETS=/system/appTERM=xterm-256colorANDROID_SOCKET_adbd=19ANDROID_ART_ROOT=/apex/com.android.artCYBER_DOMAIN_ID=71CYBER_PATH=/data/cyberEXTERNAL_STORAGE=/sdcardDOWNLOAD_CACHE=/data/cacheLOGNAME=rootSYSTEMSERVERCLASSPATH=/system/framework/com.android.location.provider.jar:/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/car-frameworks-service.jar:/apex/com.android.appsearch/javalib/service-appsearch.jar:/apex/com.android.media/javalib/service-media-s.jar:/apex/com.android.permission/javalib/service-permission.jarDEX2OATBOOTCLASSPATH=/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/framework-graphics.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/apex/com.android.i18n/javalib/core-icu4j.jar:/system/framework/android.car.jarBOOTCLASSPATH=/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/framework-graphics.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/apex/com.android.i18n/javalib/core-icu4j.jar:/system/framework/android.car.jar:/apex/com.android.appsearch/javalib/framework-appsearch.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.ipsec/javalib/android.net.ipsec.ike.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.permission/javalib/framework-permission-s.jar:/apex/com.android.scheduling/javalib/framework-scheduling.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.tethering/javalib/framework-connectivity.jar:/apex/com.android.tethering/javalib/framework-tethering.jar:/apex/com.android.wifi/javalib/framework-wifi.jarSHELL=/bin/shANDROID_BOOTLOGO=1ASEC_MOUNTPOINT=/mnt/asecHOSTNAME=trout_x86USER=rootTMPDIR=/data/local/tmpPATH=/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbinANDROID_ROOT=/systemANDROID_I18N_ROOT=/apex/com.android.i18ntrout_x86:/ #
除了这个命令以外,我们还可以使用echo来获取某个具体环境变量的值,如下所示:
TMPDIR=/data/local/tmpPATH=/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbinANDROID_ROOT=/systemANDROID_I18N_ROOT=/apex/com.android.i18ntrout_x86:/ # echo $USERroottrout_x86:/ # echo $ANDROID_SOCKET_adbd19
需要说明的是,上述方法仅针对全局环境变量有效,如果是查看局部环境变量,我们可以使用set命令,如下所示:
trout_x86:/ $ setDOWNLOAD_CACHE=/data/cacheEPOCHREALTIME=1682412013.723827EXTERNAL_STORAGE=/sdcardHOME=/HOSTNAME=trout_x86IFS=$' \t\n'KSHEGID=2000KSHGID=2000KSHUID=2000KSH_VERSION='@(#)MIRBSD KSH R59 2020/05/16 Android'LINESLOGNAME=shellOPTIND=1PATH=/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbinPATHSEP=:PGRP=24010PIPESTATUS[0]=1PPID=574PS1=$'${|\n\tlocal e=$?\n\n\t(( e )) && REPLY+="$e|"\n\n\treturn $e\n}$HOSTNAME:${PWD:-?} $ 'PS2='> 'PS3='#? 'PS4='[$EPOCHREALTIME] 'PWD=/RANDOM=30514SECONDS=3878SHELL=/bin/shSYSTEMSERVERCLASSPATH=/system/framework/com.android.location.provider.jar:/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/car-frameworks-service.jar:/apex/com.android.appsearch/javalib/service-appsearch.jar:/apex/com.android.media/javalib/service-media-s.jar:/apex/com.android.permission/javalib/service-permission.jarTERM=xterm-256colorTMOUT=0TMPDIR=/data/local/tmpUSER=shellUSER_ID= 环境变量加载
那么Android系统是如何加载环境变量的呢,我们可以先看看/system/core/rootdir/Android.mk文件中的内容(节选):
$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/init.environ.rc.in@echo "Generate: $ $@"@mkdir -p $(dir $@)$(hide) cp $Android.mk中这部分其实就引入了一个关键的文件:init.environ.rc.in,在Android12.1中其内容定义如下:
# set up the global environmenton early-init export ANDROID_BOOTLOGO 1 export ANDROID_ROOT /system export ANDROID_ASSETS /system/app export ANDROID_DATA /data export ANDROID_STORAGE /storage export ANDROID_ART_ROOT /apex/com.android.art export ANDROID_I18N_ROOT /apex/com.android.i18n export ANDROID_TZDATA_ROOT /apex/com.android.tzdata export EXTERNAL_STORAGE /sdcard export ASEC_MOUNTPOINT /mnt/asec %EXPORT_GLOBAL_ASAN_OPTIONS% %EXPORT_GLOBAL_GCOV_OPTIONS% %EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS% %EXPORT_GLOBAL_HWASAN_OPTIONS%该文件内定义的内容其实就是全局的环境变量设置,通过在rc启动文件中使用export命令进行导入。但这只是初始定义的文件,是无法直接生效的。
在上面Android.mk展示的片段中,会将 init.environ.rc.in 中的中%EXPORT_GLOBAL_ASAN_OPTIONS%、%EXPORT_GLOBAL_GCOV_OPTIONS%、%EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%、%EXPORT_GLOBAL_HWASAN_OPTIONS%使用sed命令进行替换,替换的值来源于Android.mk中获取到的EXPORT_GLOBAL_ASAN_OPTIONS、EXPORT_GLOBAL_GCOV_OPTIONS、EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS、EXPORT_GLOBAL_HWASAN_OPTIONS变量,这部分变量的赋值逻辑如下:
# init.environ.rcinclude $(CLEAR_VARS)LOCAL_MODULE_CLASS := ETCLOCAL_MODULE := init.environ.rcLOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0LOCAL_LICENSE_CONDITIONS := noticeLOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)EXPORT_GLOBAL_ASAN_OPTIONS :=ifneq ($(filter address,$(SANITIZE_TARGET)),) EXPORT_GLOBAL_ASAN_OPTIONS := export ASAN_OPTIONS include=/system/asan.options LOCAL_REQUIRED_MODULES := asan.options $(ASAN_OPTIONS_FILES) $(ASAN_EXTRACT_FILES)endifEXPORT_GLOBAL_HWASAN_OPTIONS :=ifneq ($(filter hwaddress,$(SANITIZE_TARGET)),) ifneq ($(HWADDRESS_SANITIZER_GLOBAL_OPTIONS),) EXPORT_GLOBAL_HWASAN_OPTIONS := export HWASAN_OPTIONS $(HWADDRESS_SANITIZER_GLOBAL_OPTIONS) endifendifEXPORT_GLOBAL_GCOV_OPTIONS :=ifeq ($(NATIVE_COVERAGE),true) EXPORT_GLOBAL_GCOV_OPTIONS := export GCOV_PREFIX /data/misc/traceendifEXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS :=ifeq ($(CLANG_COVERAGE),true) EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profrawendif由此,在编译阶段对 init.environ.rc.in 进行处理,得到init.environ.rc,打包后位于根目录;init进程在启动时就会读取该rc启动文件,进行环境变量的设置。
在我的环境中, init.environ.rc 文件内容如下:
# set up the global environmenton early-init export ANDROID_BOOTLOGO 1 export ANDROID_ROOT /system export ANDROID_ASSETS /system/app export ANDROID_DATA /data export ANDROID_STORAGE /storage export ANDROID_ART_ROOT /apex/com.android.art export ANDROID_I18N_ROOT /apex/com.android.i18n export ANDROID_TZDATA_ROOT /apex/com.android.tzdata export EXTERNAL_STORAGE /sdcard export ASEC_MOUNTPOINT /mnt/asec除了init.environ.rc.in中定义的全局环境变量,还有部分环境变量是在init.rc中进行导入的,如DOWNLOAD_CACHE环境变量。如下所示:
# enable armv8_deprecated instruction hooks write /proc/sys/abi/swp 1 # Linux's execveat() syscall may construct paths containing /dev/fd # expecting it to point to /proc/self/fd symlink /proc/self/fd /dev/fd export DOWNLOAD_CACHE /data/cache # This allows the ledtrig-transient properties to be created here so # that they can be chown'd to system:system later on boot write /sys/class/leds/vibrator/trigger "transient"那么环境变量具体是如何加载的呢,加载的过程其实就在init进程中进行。在rc文件中定义的export导入环境变量会被init进程以command的形式执行,从而导入到系统环境中,具体代码细节可以参考—— system/core/init/action_manager.cpp与system/core/init/action.cpp:
void ActionManager::ExecuteOneCommand() { { auto lock = std::lock_guard{event_queue_lock_}; // Loop through the event queue until we have an action to execute while (current_executing_actions_.empty() && !event_queue_.empty()) { for (const auto& action : actions_) { if (std::visit([&action](const auto& event) { return action->CheckEvent(event); }, event_queue_.front())) { current_executing_actions_.emplace(action.get()); } } event_queue_.pop(); } } if (current_executing_actions_.empty()) { return; } auto action = current_executing_actions_.front(); if (current_command_ == 0) { std::string trigger_name = action->BuildTriggersString(); LOG(INFO)
上一篇:织梦CMS隐藏路径a揭秘攻略!
下一篇:开源工业软件:SCADA系统开源