这是一个介绍 Android 属性系统的系列文章:
- Android 属性系统入门
- 属性文件生成过程分析
- 如何添加系统属性
- 属性与 SeLinux
- 属性系统源码分析一
- 属性系统源码分析二
- 属性系统源码分析三(本文)
系统 App 可以通过 frameworks/base/core/java/android/os/SystemProperties.java
类提供的 get
和 set
来读写系统属性。
get
方法的内部实现是通过 mmap 映射文件后,读取到属性值。 set
方法稍有不同,在 init 进程中会启动一个 socket 服务,set
方法通过 socket 通讯完成属性的写操作。
1. 属性服务启动过程
在 init 进程中,通过 StartPropertyService
启动属性 Socket 服务:
cpp
void StartPropertyService(Epoll* epoll) {
selinux_callback cb;
cb.func_audit = SelinuxAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
property_set("ro.property_service.version", "2");
// 创建socket
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr);
if (property_set_fd == -1) {
PLOG(FATAL) << "start_property_service socket creation failed";
}
listen(property_set_fd, 8);
// 将 socket fd 绑定到 epoll,回调函数是 handle_property_set_fd
if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
PLOG(FATAL) << result.error();
}
}
这里创建了一个 socket fd,然后与一个 epoll 绑定上,对应的回调函数是 handle_property_set_fd
:
cpp
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
return;
}
ucred cr;
socklen_t cr_size = sizeof(cr);
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
return;
}
SocketConnection socket(s, cr);
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
if (!socket.RecvUint32(&cmd, &timeout_ms)) {
PLOG(ERROR) << "sys_prop: error while reading command from the socket";
socket.SendUint32(PROP_ERROR_READ_CMD);
return;
}
switch (cmd) {
case PROP_MSG_SETPROP: {
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
!socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
return;
}
prop_name[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
const auto& cr = socket.cred();
std::string error;
// 调用 HandlePropertySet
uint32_t result =
HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << prop_name << "' to '" << prop_value
<< "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
<< error;
}
break;
}
case PROP_MSG_SETPROP2: {
std::string name;
std::string value;
if (!socket.RecvString(&name, &timeout_ms) ||
!socket.RecvString(&value, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
socket.SendUint32(PROP_ERROR_READ_DATA);
return;
}
const auto& cr = socket.cred();
std::string error;
// 调用 HandlePropertySet
uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << name << "' to '" << value
<< "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
<< error;
}
socket.SendUint32(result);
break;
}
default:
LOG(ERROR) << "sys_prop: invalid command " << cmd;
socket.SendUint32(PROP_ERROR_INVALID_CMD);
break;
}
}
收到 socket 远程调用后,最终都是调用 HandlePropertySet 设置属性,该函数的具体细节上一节说过了,可参考上文。
2. 客户端初始化过程
在 Android 平台的 C 标准库 bionic 中,有一个函数 __libc_preinit()
:
c
__attribute__((constructor(1))) static void __libc_preinit() {
__stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]);
__libc_preinit_impl();
}
函数使用 constructor(1)
进行了修饰, 根据代码中注释, 加了该属性的函数会放在 libc.so
的 .init_array
段,也就是说,当程序加载 libc.so
动态库的时候, 该函数会尽快被动态 linker
调用, 即程序使用了 libc, 就会尽早执行 __libc_preinit()
函数。
__libc_preinit()
与属性相关的调用栈如下:
cpp
__attribute__((constructor(1))) __libc_preinit()
|
__libc_preinit_impl();
|
__libc_init_common();
|
__system_properties_init();
|
system_properties.Init(PROP_FILENAME)
__libc_preinit 经过层层调用会调用到 __system_properties_init() 函数:
cpp
#define PROP_FILENAME "/dev/__properties__"
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int __system_properties_init() {
return system_properties.Init(PROP_FILENAME) ? 0 : -1;
}
进一步调用到 SystemProperties::Init
函数:
cpp
bool SystemProperties::Init(const char* filename) {
// This is called from __libc_init_common, and should leave errno at 0 (http://b/37248982).
ErrnoRestorer errno_restorer;
if (initialized_) {
contexts_->ResetAccess();
return true;
}
if (strlen(filename) >= PROP_FILENAME_MAX) {
return false;
}
strcpy(property_filename_, filename);
if (is_dir(property_filename_)) {
if (access("/dev/__properties__/property_info", R_OK) == 0) { // 走这个分支
contexts_ = new (contexts_data_) ContextsSerialized();
if (!contexts_->Initialize(false, property_filename_, nullptr)) {
return false;
}
} else {
contexts_ = new (contexts_data_) ContextsSplit();
if (!contexts_->Initialize(false, property_filename_, nullptr)) {
return false;
}
}
} else {
contexts_ = new (contexts_data_) ContextsPreSplit();
if (!contexts_->Initialize(false, property_filename_, nullptr)) {
return false;
}
}
initialized_ = true;
return true;
}
这里会根据不同的情况,给 contexts_ 初始化为不同的子类,一般情况下会走第一个 if,contexts_ 的具体类型是 ContextsSerialized,接着调用 ContextsSerialized 对象的 Initialize 函数。后续流程和上一节的属性系统初始化就一致了。
3. 客户端读写属性流程分析
一般来说,属性值仅支持系统 App 读写,使用 frameworks/base/core/java/android/os/SystemProperties.java
中提供的 get 和 set 方法访问:
java
// frameworks/base/core/java/android/os/SystemProperties.java
@NonNull
@SystemApi
@TestApi
public static String get(@NonNull String key) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
@UnsupportedAppUsage
private static native String native_get(String key);
实际调用的是一个 native 方法,对应的 cpp 函数是:
cpp
jstring SystemProperties_getSS(JNIEnv *env, jclass clazz, jstring keyJ,
jstring defJ)
{
// Using ConvertKeyAndForward is sub-optimal for copying the key string,
// but improves reuse and reasoning over code.
auto handler = [&](const std::string& key, jstring defJ) {
std::string prop_val = android::base::GetProperty(key, "");
if (!prop_val.empty()) {
return env->NewStringUTF(prop_val.c_str());
};
if (defJ != nullptr) {
return defJ;
}
// This function is specified to never return null (or have an
// exception pending).
return env->NewStringUTF("");
};
return ConvertKeyAndForward(env, keyJ, defJ, handler);
}
接着调用 GetProperty 函数:
cpp
std::string GetProperty(const std::string& key, const std::string& default_value) {
std::string property_value;
#if defined(__BIONIC__)
const prop_info* pi = __system_property_find(key.c_str());
if (pi == nullptr) return default_value;
__system_property_read_callback(pi,
[](void* cookie, const char*, const char* value, unsigned) {
auto property_value = reinterpret_cast<std::string*>(cookie);
*property_value = value;
},
&property_value);
#else
auto it = g_properties.find(key);
if (it == g_properties.end()) return default_value;
property_value = it->second;
#endif
// If the property exists but is empty, also return the default value.
// Since we can't remove system properties, "empty" is traditionally
// the same as "missing" (this was true for cutils' property_get).
return property_value.empty() ? default_value : property_value;
}
核心是调用 __system_property_find 函数:
cpp
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
const prop_info* __system_property_find(const char* name) {
return system_properties.Find(name);
}
进一步调用 SystemProperties 类的 Find 的函数:
cpp
const prop_info* SystemProperties::Find(const char* name) {
if (!initialized_) {
return nullptr;
}
// 拿到属性文件对应的 prop_area 对象
prop_area* pa = contexts_->GetPropAreaForName(name);
if (!pa) {
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Access denied finding property \"%s\"", name);
return nullptr;
}
return pa->find(name);
}
这里通过 GetPropAreaForName 函数拿到属性文件对应的 prop_area 对象,就可以读取属性数据了。GetPropAreaForName 的分析可以参考上文。
接着看 set 函数:
java
@UnsupportedAppUsage
public static void set(@NonNull String key, @Nullable String val) {
if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("value of system property '" + key
+ "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
}
if (TRACK_KEY_ACCESS) onKeyAccess(key);
native_set(key, val);
}
private static native void native_set(String key, String def);
一样的 native 方法,对应的 native 函数是 SystemProperties_set:
cpp
void SystemProperties_set(JNIEnv *env, jobject clazz, jstring keyJ,
jstring valJ)
{
auto handler = [&](const std::string& key, bool) {
std::string val;
if (valJ != nullptr) {
ScopedUtfChars key_utf(env, valJ);
val = key_utf.c_str();
}
return android::base::SetProperty(key, val);
};
if (!ConvertKeyAndForward(env, keyJ, true, handler)) {
// Must have been a failure in SetProperty.
jniThrowException(env, "java/lang/RuntimeException",
"failed to set system property");
}
}
接着调用 SetProperty:
cpp
bool SetProperty(const std::string& key, const std::string& value) {
return (__system_property_set(key.c_str(), value.c_str()) == 0);
}
最后在调用 __system_property_set
, 核心的实现都在这里:
cpp
// bionic/libc/bionic/system_property_set.cpp
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int __system_property_set(const char* key, const char* value) {
if (key == nullptr) return -1;
if (value == nullptr) value = "";
if (g_propservice_protocol_version == 0) { // 落实版本
detect_protocol_version();
}
if (g_propservice_protocol_version == kProtocolVersion1) { // 老版本,现在都不用了
// Old protocol does not support long names or values
if (strlen(key) >= PROP_NAME_MAX) return -1;
if (strlen(value) >= PROP_VALUE_MAX) return -1;
prop_msg msg;
memset(&msg, 0, sizeof msg);
msg.cmd = PROP_MSG_SETPROP;
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
return send_prop_msg(&msg);
} else { // 新版本,走这个分支
// New protocol only allows long values for ro. properties only.
if (strlen(value) >= PROP_VALUE_MAX && strncmp(key, "ro.", 3) != 0) return -1;
// Use proper protocol
// 建立 socket 链接
PropertyServiceConnection connection;
if (!connection.IsValid()) {
errno = connection.GetLastError();
async_safe_format_log(
ANDROID_LOG_WARN, "libc",
"Unable to set property \"%s\" to \"%s\": connection failed; errno=%d (%s)", key, value,
errno, strerror(errno));
return -1;
}
SocketWriter writer(&connection);
// 向服务端发动数据 PROP_MSG_SETPROP2 key value
if (!writer.WriteUint32(PROP_MSG_SETPROP2).WriteString(key).WriteString(value).Send()) {
errno = connection.GetLastError();
async_safe_format_log(ANDROID_LOG_WARN, "libc",
"Unable to set property \"%s\" to \"%s\": write failed; errno=%d (%s)",
key, value, errno, strerror(errno));
return -1;
}
int result = -1;
if (!connection.RecvInt32(&result)) { // 检查返回信息
errno = connection.GetLastError();
async_safe_format_log(ANDROID_LOG_WARN, "libc",
"Unable to set property \"%s\" to \"%s\": recv failed; errno=%d (%s)",
key, value, errno, strerror(errno));
return -1;
}
if (result != PROP_SUCCESS) {
async_safe_format_log(ANDROID_LOG_WARN, "libc",
"Unable to set property \"%s\" to \"%s\": error code: 0x%x", key, value,
result);
return -1;
}
return 0;
}
}
这里先初始化一个 PropertyServiceConnection 对象,在其构造函数中会建立到属性服务的 socket 连接,接着通过 SocketWriter 向属性服务发送写属性的数据(PROP_MSG_SETPROP2 key value),接着读取属性服务写入成功的返回值,整个写属性过程完毕。