前言
这一篇文章需要介绍在init进程中第二阶段做的工作,其实两个阶段都有关于selinux的相关内容,这一部分相对比较复杂,我们会单独出一篇文章去介绍selinux的相关内容,所以这里就将其略过了。在第二阶段中,init进程所做的工作主要下面几个方面:
- 初始化属性服务
- 初始化子进程终止信号处理函数
- 对rc文件的解析,并且启动Zygote进程
初始化属性服务
所谓的属性服务就是系统或者一些系统应用会存储一些属性配置,一般都是键值对,属性服务就是用来给这些键值对提供存储和读取的。我们通过:
adb shell getprop
命令就可以查看当前设备的属性值。
int main(int argc, char** argv) {
...
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
...
}
...
//初始化属性服务
property_init();
...
return 0;
}
当某个进程A,通过property_set()修改属性值后,init进程会检查访问权限,当权限满足要求后,则更改相应的属性值,属性值一旦改变则会触发相应的触发器(即rc文件中的on开头的语句),在Android Shared Memmory(共享内存区域)中有一个_system_property_area_区域,里面记录着所有的属性值。对于进程A通过property_get()方法,获取的也是该共享内存区域的属性值。
property_init
void property_init() {
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();//从文件中加载属性值
if (__system_property_area_init()) {//创建共享内存
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
CreateSerializedPropertyInfo
void CreateSerializedPropertyInfo() {
auto property_infos = std::vector<PropertyInfoEntry>();
/**
* access()会检查是否可以读/写某一已存在的文件
* 返回值:若所有欲查核的权限都通过了检查则返回0 值,表示成功,只要有一权限被禁止则返回-1。
*/
if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
&property_infos)) {//从文件中加载property信息
return;
}
// Don't check for failure here, so we always have a sane list of properties.
// E.g. In case of recovery, the vendor partition will not have mounted and we
// still need the system / platform properties to function.
if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
&property_infos)) {
// Fallback to nonplat_* if vendor_* doesn't exist.
LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
&property_infos);
}
} else {
if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
return;
}
if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
// Fallback to nonplat_* if vendor_* doesn't exist.
LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
}
}
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;
}
constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
PLOG(ERROR) << "Unable to write serialized property infos to file";
}
selinux_android_restorecon(kPropertyInfosPath, 0);
}
CreateSerializedPropertyInfo的作用是读取一些文件,将读取的属性值存储在一个动态数组中。
LoadPropertyInfoFromFile
bool LoadPropertyInfoFromFile(const std::string& filename,
std::vector<PropertyInfoEntry>* property_infos) {
auto file_contents = std::string();
if (!ReadFileToString(filename, &file_contents)) {
PLOG(ERROR) << "Could not read properties from '" << filename << "'";
return false;
}
auto errors = std::vector<std::string>{};
ParsePropertyInfoFile(file_contents, property_infos, &errors);//解析Propertiy文件
// Individual parsing errors are reported but do not cause a failed boot, which is what
// returning false would do here.
for (const auto& error : errors) {
LOG(ERROR) << "Could not read line from '" << filename << "': " << error;
}
return true;
}
ParsePropertyInfoFile
void ParsePropertyInfoFile(const std::string& file_contents,
std::vector<PropertyInfoEntry>* property_infos,
std::vector<std::string>* errors) {
// Do not clear property_infos to allow this function to be called on multiple files, with
// their results concatenated.
errors->clear();
for (const auto& line : Split(file_contents, "\n")) {//逐行的解析property文件
auto trimmed_line = Trim(line);
if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
continue;
}
auto property_info_entry = PropertyInfoEntry{};
auto parse_error = std::string{};
if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
errors->emplace_back(parse_error);
continue;
}
property_infos->emplace_back(property_info_entry);
}
}
_system_property_area_init
int __system_property_area_init() {
bool fsetxattr_failed = false;
return system_properties.AreaInit(PROP_FILENAME, &fsetxattr_failed) && !fsetxattr_failed ? 0 : -1;
}
初始化属性内存区域
SystemProperties::AreaInit
bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
if (strlen(filename) > PROP_FILENAME_MAX) {
return false;
}
strcpy(property_filename_, filename);
contexts_ = new (contexts_data_) ContextsSerialized();
if (!contexts_->Initialize(true, property_filename_, fsetxattr_failed)) {
return false;
}
initialized_ = true;
return true;
}
ContextsSerialized::Initialize
bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
filename_ = filename;
if (!InitializeProperties()) {
return false;
}
if (writable) {
mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
bool open_failed = false;
if (fsetxattr_failed) {
*fsetxattr_failed = false;
}
for (size_t i = 0; i < num_context_nodes_; ++i) {
if (!context_nodes_[i].Open(true, fsetxattr_failed)) {
open_failed = true;
}
}
if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
FreeAndUnmap();
return false;
}
} else {
if (!MapSerialPropertyArea(false, nullptr)) {
FreeAndUnmap();
return false;
}
}
return true;
}
启动属性服务
int main(int argc, char** argv) {
...
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
...
}
...
//启动属性服务
start_property_service();
...
return 0;
}
其实这个属性服务是一个Socket服务,我们需要通过它来实现我们属性服务的设置,那为什么要启动一个Socket服务呢,因为不是所有的进程都能任意的修改这些系统的属性。Android将这一任务交给了init进程。而其他进程如果想修改属性服务需要通过Socket来通知init进程做处理。下面我们通过代码看看这个流程。
start_property_service
void start_property_service() {
selinux_callback cb;
cb.func_audit = SelinuxAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
property_set("ro.property_service.version", "2");
//创建socket,返回socket fd
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的连接数为8
/**
*注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd
*/
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
Socket 描述符 property_set_fd被创建后,放入了epoll中,用epoll来监听property_set_fd,当property_set_fd有数据到来时,init进程将调用handle_property_set_fd函数来进行处理。
epoll是Linux内核的可扩展I/O事件通知机制。也就是他能高效的监听文件描述符。提高CPU的利用率。
handle_property_set_fd
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) {
//设置prop属性
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;
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;
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;
}
}
HandlePropertySet
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr, std::string* error) {
if (!IsLegalPropertyName(name)) {
*error = "Illegal property name";
return PROP_ERROR_INVALID_NAME;
}
if (StartsWith(name, "ctl.")) {//设置控制属性
if (!CheckControlPropertyPerms(name, value, source_context, cr)) {
*error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
value.c_str());
return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
}
HandleControlMessage(name.c_str() + 4, value, cr.pid);
return PROP_SUCCESS;
}
const char* target_context = nullptr;
const char* type = nullptr;
property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);
if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) {
*error = "SELinux permission check failed";
return PROP_ERROR_PERMISSION_DENIED;
}
if (type == nullptr || !CheckType(type, value)) {
*error = StringPrintf("Property type check failed, value doesn't match expected type '%s'",
(type ?: "(null)"));
return PROP_ERROR_INVALID_VALUE;
}
// sys.powerctl is a special property that is used to make the device reboot. We want to log
// any process that sets this property to be able to accurately blame the cause of a shutdown.
if (name == "sys.powerctl") {
std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
std::string process_cmdline;
std::string process_log_string;
if (ReadFileToString(cmdline_path, &process_cmdline)) {
// Since cmdline is null deliminated, .c_str() conveniently gives us just the process
// path.
process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
}
LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
<< process_log_string;
}
if (name == "selinux.restorecon_recursive") {
return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
}
return PropertySet(name, value, error);//设置属性
}
PropertySet
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
size_t valuelen = value.size();
if (!IsLegalPropertyName(name)) {//判断属性名是否合法
*error = "Illegal property name";
return PROP_ERROR_INVALID_NAME;
}
if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
*error = "Property value too long";
return PROP_ERROR_INVALID_VALUE;
}
if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
*error = "Value is not a UTF8 encoded string";
return PROP_ERROR_INVALID_VALUE;
}
prop_info* pi = (prop_info*) __system_property_find(name.c_str());//判断当前属性是否已经存在
if (pi != nullptr) {
// ro.* properties are actually "write-once".
if (StartsWith(name, "ro.")) { //如果已经存在并且是只读的,则不允许再设置。
*error = "Read-only property was already set";
return PROP_ERROR_READ_ONLY_PROPERTY;
}
__system_property_update(pi, value.c_str(), valuelen);//如果已经存在并且是非只读的,则更新值
} else {
int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
if (rc < 0) {
*error = "__system_property_add failed";
return PROP_ERROR_SET_FAILED;
}
}
// Don't write properties to disk until after we have read all default
// properties to prevent them from being overwritten by default values.
if (persistent_properties_loaded && StartsWith(name, "persist.")) {//把persist.开头的属性,持久化到disk
WritePersistentProperty(name, value);
}
property_changed(name, value);
return PROP_SUCCESS;
}
各个前缀的解释:
- persist 前缀. 保存在 /data/property 目录, 重启后依然生效.在data解密后或者late-init阶段加载.
- ro 前缀. 只读属性, 类似C/CPP中的常量定义, 它能且只能被设置1次, 这类属性一般尽可能早设置. 依据前文的加载顺序, 在有两个相同的ro前缀property时, 前面加载的生效, 而非ro前缀的property, 后面加载的生效.
- ctl 前缀. 这是方便控制init中的服务而设立的 -- 通过把相关服务的名字设置为 ctl.start 和 ctl.stop的值, 就能方便地启动和停止指定服务. (命令行中的start和stop实际上就是通过该property来设置.) 这种能力是受selinux控制的具体的权限定义在 property.te, 有兴趣可以看下, 所有的property相关权限定义都在这个文件中.
系统属性
void property_init() {
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
- Android 的系统属性提供了一个可全局访问的配置设置仓库
- 各个文件的加载顺序是非常重要的, ro前缀的property早加载的生效, 而非ro前缀的property晚加载的生效.
- 上面我们看到, persist属性是写入 /data/property, 这也是为什么persist属性在重启后依然生效.
各个前缀的解释:
- persist 前缀. 保存在 /data/property/persistent_properties中, 重启后依然生效.在data解密后或者late-init阶段加载.
- ro 前缀. 只读属性, 类似C/CPP中的常量定义, 它能且只能被设置1次, 这类属性一般尽可能早设置. 依据前文的加载顺序, 在有两个相同的ro前缀property时, 前面加载的生效, 而非ro前缀的property, 后面加载的生效.
- ctl 前缀. 这是方便控制init中的服务而设立的 -- 通过把相关服务的名字设置为 ctl.start 和 ctl.stop的值, 就能方便地启动和停止指定服务. (命令行中的start和stop实际上就是通过该property来设置.) 这种能力是受selinux控制的具体的权限定义在 property.te, 有兴趣可以看下, 所有的property相关权限定义都在这个文件中.
解析init.rc文件
int main(int argc, char** argv) {
...
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
...
}
...
LoadBootScripts(am, sm);//解析init.rc文件
...
return 0;
}
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");//解析init.rc文件
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
} else {
parser.ParseConfig(bootscript);
}
}
Init.rc是一个Android Init Language 编写的配置文件。主要包含5种 Action、Service、Command、Options、Imports
Action
通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:
- on early-init; 在初始化早期阶段触发;
- on init; 在初始化阶段触发;
- on late-init; 在初始化晚期阶段触发;
- on boot/charger: 当系统启动/充电时触发
- on property:=: 当属性值满足条件时触发;
Service
服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。
例如 service ueventd /sbin/ueventd 进程名为ueventd 可执行文件的路径为/sbin/ueventd
Command
命令
- chmod 修改文件权限
- chown 修改文件拥有者
- class_start 启动属于同一个class的所有服务
- start 启动一个具体的服务
例如 chmod 0773 /data/misc/trace 修改权限
Options
Options是Service的可选项,与service配合使用
critical: 规定在4分钟内该service四次重启,则系统会重启并进入恢复模式
disabled: 不随class自动启动,只有根据service名才启动
Imports
Import 其他的rc文件
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
启动Zygote服务
在init.rc文件中import
/init.${ro.zygote}.rc
如果你是32位的系统,对应的是init.zygote32.rc文件
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
这个rc文件就是启动zygote进程的配置文件,具体zygote的启动将在下一篇文章介绍。
总结
init作为Android系统在用户空间的第一个进程所做的工作包括: 作为大多数守护进程的样板, init代码的执行流程完全遵循建立服务的经典模式: 初始化, 然后陷入一个循环中, 而且永远不退出.
关于我
- 公众号: CodingDev