最近在移植百度Apollo Cyber通信框架至安卓系统中,发现Cyber本身依赖于环境变量来实现服务的初始化配置。相应地,我也需要在安卓系统中引入这些环境变量,并确保在Native服务启动时这些环境变量已经准备就绪。
由于此前我对环境变量的了解并不多,于是研究学习了一下Android系统中关于环境变量的相关配置,这篇博文即对此做了一个记录。
背景简介
首先我们需要探究一下为什么我们需要环境变量。环境变量是一组动态的,可手动编辑、设置的值。一般而言,我们程序的运行往往会依赖于各种各样的配置,这些配置我们可以以配置文件的形式存放于本地文件系统中,再让程序在运行时进行解析从而获取配置,这是一种比较通用的做法;而另一种比较通用的做法是将这些配置写入环境变量,程序在运行时自动读取环境变量即可。前者适合配置比较多的情况,而后者则适合配置少且配置可能需要多进程共享的情况,比较灵活。
可以说环境变量是非常重要的,没有环境变量的支持,无论是Windows系统中的程序还是Linux系统中的程序都无法正常运行。
环境变量分为两类:全局环境变量和局部环境变量。全局环境变量是全局可见的,所有进程可见;而局部变量则针对部分进程可见。
查看环境变量
如何查看系统的环境变量呢,这里提供两个命令:env或者printenv命令,如下所示:
_=/system/bin/env
ANDROID_DATA=/data
HOME=/
ANDROID_TZDATA_ROOT=/apex/com.android.tzdata
ANDROID_STORAGE=/storage
ANDROID_ASSETS=/system/app
TERM=xterm-256color
ANDROID_SOCKET_adbd=19
ANDROID_ART_ROOT=/apex/com.android.art
CYBER_DOMAIN_ID=71
CYBER_PATH=/data/cyber
EXTERNAL_STORAGE=/sdcard
DOWNLOAD_CACHE=/data/cache
LOGNAME=root
SYSTEMSERVERCLASSPATH=/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.jar
DEX2OATBOOTCLASSPATH=/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
BOOTCLASSPATH=/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.jar
SHELL=/bin/sh
ANDROID_BOOTLOGO=1
ASEC_MOUNTPOINT=/mnt/asec
HOSTNAME=trout_x86
USER=root
TMPDIR=/data/local/tmp
PATH=/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
ANDROID_ROOT=/system
ANDROID_I18N_ROOT=/apex/com.android.i18n
trout_x86:/ #
除了这个命令以外,我们还可以使用echo来获取某个具体环境变量的值,如下所示:
TMPDIR=/data/local/tmp
PATH=/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
ANDROID_ROOT=/system
ANDROID_I18N_ROOT=/apex/com.android.i18n
trout_x86:/ # echo $USER
root
trout_x86:/ # echo $ANDROID_SOCKET_adbd
19
需要说明的是,上述方法仅针对全局环境变量有效,如果是查看局部环境变量,我们可以使用set命令,如下所示:
trout_x86:/ $ set
DOWNLOAD_CACHE=/data/cache
EPOCHREALTIME=1682412013.723827
EXTERNAL_STORAGE=/sdcard
HOME=/
HOSTNAME=trout_x86
IFS=$' \t\n'
KSHEGID=2000
KSHGID=2000
KSHUID=2000
KSH_VERSION='@(#)MIRBSD KSH R59 2020/05/16 Android'
LINES
LOGNAME=shell
OPTIND=1
PATH=/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
PATHSEP=:
PGRP=24010
PIPESTATUS[0]=1
PPID=574
PS1=$'${|\n\tlocal e=$?\n\n\t(( e )) && REPLY+="$e|"\n\n\treturn $e\n}$HOSTNAME:${PWD:-?} $ '
PS2='> '
PS3='#? '
PS4='[$EPOCHREALTIME] '
PWD=/
RANDOM=30514
SECONDS=3878
SHELL=/bin/sh
SYSTEMSERVERCLASSPATH=/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.jar
TERM=xterm-256color
TMOUT=0
TMPDIR=/data/local/tmp
USER=shell
USER_ID=
环境变量加载
那么Android系统是如何加载环境变量的呢,我们可以先看看/system/core/rootdir/Android.mk文件中的内容(节选):
$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/init.environ.rc.in
@echo "Generate: $< -> $@"
@mkdir -p $(dir $@)
$(hide) cp $< $@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_ASAN_OPTIONS%?$(EXPORT_GLOBAL_ASAN_OPTIONS)?g' $@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_GCOV_OPTIONS%?$(EXPORT_GLOBAL_GCOV_OPTIONS)?g' $@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%?$(EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS)?g' $@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_HWASAN_OPTIONS%?$(EXPORT_GLOBAL_HWASAN_OPTIONS)?g' $@
Android.mk中这部分其实就引入了一个关键的文件:init.environ.rc.in,在Android12.1中其内容定义如下:
# set up the global environment
on 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.rc
include $(CLEAR_VARS)
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE := init.environ.rc
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_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)
endif
EXPORT_GLOBAL_HWASAN_OPTIONS :=
ifneq ($(filter hwaddress,$(SANITIZE_TARGET)),)
ifneq ($(HWADDRESS_SANITIZER_GLOBAL_OPTIONS),)
EXPORT_GLOBAL_HWASAN_OPTIONS := export HWASAN_OPTIONS $(HWADDRESS_SANITIZER_GLOBAL_OPTIONS)
endif
endif
EXPORT_GLOBAL_GCOV_OPTIONS :=
ifeq ($(NATIVE_COVERAGE),true)
EXPORT_GLOBAL_GCOV_OPTIONS := export GCOV_PREFIX /data/misc/trace
endif
EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS :=
ifeq ($(CLANG_COVERAGE),true)
EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profraw
endif
由此,在编译阶段对 init.environ.rc.in 进行处理,得到init.environ.rc,打包后位于根目录;init进程在启动时就会读取该rc启动文件,进行环境变量的设置。
在我的环境中, init.environ.rc 文件内容如下:
# set up the global environment
on 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) << "processing action (" << trigger_name << ") from (" << action->filename()
<< ":" << action->line() << ")";
}
action->ExecuteOneCommand(current_command_);
// If this was the last command in the current action, then remove
// the action from the executing list.
// If this action was oneshot, then also remove it from actions_.
++current_command_;
if (current_command_ == action->NumCommands()) {
current_executing_actions_.pop();
current_command_ = 0;
if (action->oneshot()) {
auto eraser = [&action](std::unique_ptr<Action>& a) { return a.get() == action; };
actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser),
actions_.end());
}
}
}
void Action::ExecuteCommand(const Command& command) const {
android::base::Timer t;
auto result = command.InvokeFunc(subcontext_);
auto duration = t.duration();
// Any action longer than 50ms will be warned to user as slow operation
if (!result.has_value() || duration > 50ms ||
android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
std::string trigger_name = BuildTriggersString();
std::string cmd_str = command.BuildCommandString();
LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name << " (" << filename_
<< ":" << command.line() << ") took " << duration.count() << "ms and "
<< (result.ok() ? "succeeded" : "failed: " + result.error().message());
}
}
除了export导入环境变量,rc启动文件中定义的action都会通过类似的方式执行。
现在我们可以明确知道的是,Android系统中的环境变量可以通过rc来进行配置,但若我们观察得更仔细一点,会发现还有一些环境变量是没有通过rc配置的,那么这部分环境变量是怎么加载的呢——其实是通过setenv系统调用进行配置的,这里我们以PATH环境变量为例子:
86_64:/ # echo $PATH
/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
我们通过在rc中进行搜索,会发现没有地方去export该环境变量,但倘若我们在init进程源码中使用搜索setenv,我们就会有惊喜的发现:
// Update $PATH in the case the second stage init is newer than first stage init, where it is first set.
if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";
}
很明显,PATH环境变量是通过setenv去进行设置的,这属于在代码内硬编码;我们看看_PATH_DEFPATH宏定义:
/** Default shell search path. */
#define _PATH_DEFPATH "/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin"
这与我们观察到的环境变量是一致的。
添加环境变量
前面我们已经了解到环境变量是如何加载的了,现在假设我们需要在Android中添加自己的环境变量,需要怎么做呢,这里列举几种办法:
1.在 init.environ.rc.in中进行添加,这种方式适合全局环境变量;可以在不修改init.rc的情况下添加环境变量,比较推荐使用。此时可以参考rc启动文件的语法规则使用export命令导入即可。
2.在init.rc中进行添加,这种方式与上述第一种方式类似,适合添加全局环境变量,不过这种方式会直接修改init.rc,可能会导致其他问题,如果不小心修改错误,可能会导致系统无法开机。这里给一个AOSP Android12中的示例:
// system/etc/init/hw/init.rc
# 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"
# This is used by Bionic to select optimized routines.
write /dev/cpu_variant:${ro.bionic.arch} ${ro.bionic.cpu_variant}
chmod 0444 /dev/cpu_variant:${ro.bionic.arch}
write /dev/cpu_variant:${ro.bionic.2nd_arch} ${ro.bionic.2nd_cpu_variant}
3.如果我们需要的并非全局环境变量,如只是某些进程需要使用的环境变量,可以在对应进程的rc启动文件内通过setenv进行导入,如下所示:
service myservice /system/bin/myservice
user system
group root
setenv MY_VARIABLE value
其中MY_VARIABLE为环境变量名,value为对应的值。
4.在init.rc中使用单独的服务来进行导入,示例如下:
service setenv /system/bin/sh -c "export FOO=bar; exec sleep 3600"
class main
oneshot
这部分定义了一个名为setenv的服务,其执行的内容为:通过sh终端export名为FOO,值为bar的环境变量,之后sleep 3600s;其中oneshot表示只在开机阶段执行一次。
5.直接修改init进程源码,通过setenv系统调用设置环境变量
使用环境变量
在程序内使用环境变量,我们会用到两个函数:setenv与getenv.
在C/C++中使用,两者皆需要引用stdlib.h头文件。
getenv函数:
char *getenv(const char *name);
使用示例:
const char* reject_kill_server = getenv("ADB_REJECT_KILL_SERVER");
if (reject_kill_server && strcmp(reject_kill_server, "1") == 0) {
adb_set_reject_kill_server(true);
}
返回值为指针,空指针则表明没有匹配的环境变量存在。
setenv函数:
int setenv(const char name, const char value, int overwrite);
返回值0代表成功,返回-1代表失败,同时会设置errno标志位。
对于应用(Java)而言,我们则可以通过System.getenv与System.setenv进行环境变量的获取与设置。文章来源:https://www.toymoban.com/news/detail-830720.html
需要注意的是setenv函数设置的环境变量不是全局的,只对对应进程以及子进程有效。这也是为什么Android系统所有的全局环境变量都是放在rc启动文件内,因为只有init进程会去解析这些rc启动文件导入环境变量,并传递给后续的所有进程,自然也就成为“全局环境变量”。文章来源地址https://www.toymoban.com/news/detail-830720.html
到了这里,关于Android12系统环境变量设置的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!