init进程属性服务

143 阅读5分钟

在开发和调试过程中常用到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安全性检查,只有该进程有操作权限才能执行相应操作。