在开发和调试过程中常用到getprop和setprop等相关操作,不是所有进程都可以随意修改任何的系统属性,Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改。
本文以Android 13代码为例。
1,在system/core/init/init.cpp文件中
int SecondStageMain(int argc, char** argv) {
PropertyInit(); //初始化属性服务。调用system/core/init/property_service.cpp中的PropertyInit函数。
StartPropertyService(&property_fd);
}
2,在system/core/init/property_service.cpp文件中
void PropertyInit() {
selinux_callback cb; // 设置SELinux回调,进行权限控制
cb.func_audit = PropertyAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb); // 通过selinux_set_callback函数将cb注册为SELINUX_CB_AUDIT类型的回调。
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); // 使用mkdir函数创建了一个名为/dev/__properties__的目录,并设置了目录的权限为S_IRWXU | S_IXGRP | S_IXOTH,即目录的所有者可以读写执行,而组用户和其他用户只能执行(不能读写)
CreateSerializedPropertyInfo(); // 加载系统属性信息
ExportKernelBootProps(); // 通过ExportKernelBootProps函数,将内核的变量导出为init进程内部变量以及当前所需的系统属性。这样做可以让init进程和其他系统服务能够访问到内核启动时的各种参数和配置。
}
void StartPropertyService(int* epoll_socket) {
InitPropertySet("ro.property_service.version", "2");
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) { // 使用socketpair函数创建一个UNIX域套接字对(sockets[2])。这个套接字对用于在属性服务和init进程之间双向通信。AF_UNIX指定使用UNIX域套接字,SOCK_SEQPACKET确保消息以有序、可靠的方式传递,SOCK_CLOEXEC确保在exec调用时自动关闭套接字
PLOG(FATAL) << "Failed to socketpair() between property_service and init";
}
*epoll_socket = from_init_socket = sockets[0]; // 将sockets[0]分配给*epoll_socket(可能是供init进程使用以监视该套接字)和from_init_socket(在函数内部使用,表示从init进程接收消息的套接字)
init_socket = sockets[1]; // sockets[1]分配给init_socket,用于向init进程发送消息
StartSendingMessages();
// 调用CreateSocket函数尝试创建一个新的套接字,用于监听来自其他进程对系统属性的查询和设置请求。PROP_SERVICE_NAME可能是套接字的文件名或标识符,SOCK_STREAM指定使用流式套接字,SOCK_CLOEXEC和SOCK_NONBLOCK分别用于确保在exec调用时自动关闭套接字和使套接字为非阻塞模式。
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, {}); result.ok()) {
property_set_fd = *result; // 套接字创建成功,将返回的文件描述符存储在property_set_fd中
} else {
LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
listen(property_set_fd, 8); // 用listen函数让property_set_fd套接字进入监听状态,准备接受最多8个连接请求(backlog参数为8)
auto new_thread = std::thread{PropertyServiceThread}; // 创建一个新的线程new_thread,该线程将执行PropertyServiceThread函数
property_service_thread.swap(new_thread); // 使用std::swap将新线程与property_service_thread交换,这样property_service_thread就持有新创建的线程了
}
//PropertyServiceThread线程的主要职责是监听和处理来自 property_set_fd 和 init_socket 的事件。每当有系统属性设置请求到达 property_set_fd,或者 init 进程通过 init_socket 发送消息时,相应的处理函数就会被调用。
//这种设计使得属性服务能够高效地响应系统属性的变化,并与 init 进程进行通信。
static void PropertyServiceThread() {
Epoll epoll; // 使用 epoll 来高效地处理多个文件描述符(file descriptors)上的事件
if (auto result = epoll.Open(); !result.ok()) {
LOG(FATAL) << result.error();
}
// 通过 epoll.RegisterHandler 方法,将两个文件描述符(property_set_fd 和 init_socket)与相应的处理函数(handle_property_set_fd 和 HandleInitSocket)关联起来。
if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd);
!result.ok()) {
LOG(FATAL) << result.error();
}
if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) {
LOG(FATAL) << result.error();
}
while (true) {
auto pending_functions = epoll.Wait(std::nullopt); // 无限循环,使用 epoll.Wait(std::nullopt) 方法等待事件的发生,std::nullopt 表示没有指定超时时间,即无限期等待
if (!pending_functions.ok()) {
LOG(ERROR) << pending_functions.error();
} else {
// 等待成功,则遍历 pending_functions 中的所有处理函数,并执行它们。这些处理函数就是之前通过 RegisterHandler 方法注册的回调函数,用于处理具体的文件描述符事件
for (const auto& function : *pending_functions) {
(*function)();
}
}
}
}
//handle_property_set_fd函数建立socket连接,然后从socket中读取操作信息,根据不同的操作类型,调用HandlePropertySet做具体的操作
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC); // 使用accept4函数从property_set_fd(一个监听套接字)接受一个新的连接。accept4是accept的增强版,可以直接设置SOCK_CLOEXEC标志,确保在exec调用时关闭套接字
if (s == -1) {
return;
}
ucred cr;
socklen_t cr_size = sizeof(cr);
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { // 通过getsockopt和SO_PEERCRED选项获取连接对端的凭证(用户ID、组ID等),存储在ucred结构体cr中。
close(s);
PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
return;
}
SocketConnection socket(s, cr); // 使用接受到的套接字描述符s和对端凭证cr创建一个SocketConnection对象。
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
if (!socket.RecvUint32(&cmd, &timeout_ms)) { // 从socket连接中读取一个无符号32位整数cmd,它表示客户端请求的操作类型(如设置属性)
PLOG(ERROR) << "sys_prop: error while reading command from the socket";
socket.SendUint32(PROP_ERROR_READ_CMD);
return;
}
switch (cmd) { // 根据操作信息,执行对应处理,两者区别一个是以char形式读取,一个以String形式读取
case PROP_MSG_SETPROP:
uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error);//调用HandlePropertySet做具体的操作
break;
}
}
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr,
SocketConnection* socket, std::string* error) {
if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) { // 在CheckPermissions中调用CheckControlPropertyPerms,并进一步调用CheckControlPropertyPerms
return ret;
}
return PropertySet(name, value, error); //调用property_set进行属性设置
}
HandlePropertySet是最终的处理函数,以"ctl"开头的key就做一些Service的Start,Stop,Restart操作,其他的就是调用property_set进行属性设置,不管是前者还是后者,都要进行SELinux安全性检查,只有该进程有操作权限才能执行相应操作。