preface
This series of articles briefly introduces the starting process of Android system, which will not stick to the details of the source code, so as to let readers understand the general process. In addition, we need to remind you that you need to have some C/C + + foundation to read this chapter. In this process, there will be many important knowledge points. In this series, we will explain them one by one. In this article, we will learn the init process. In this series, we will analyze the startup process of Android system based on the Android 8.1 source code.
1 init process startup process
The init process is the first process in the user space of Android system. The process number is 1, which is a key step in the startup process of Android system. As the first process, it is given many extremely important work responsibilities, such as creating Zygote (incubator) and attribute service. The init process is composed of multiple source files, which are located in the source directory system/core/init.
1.1 introduction of init process
In order to explain the init process, we first need to understand the first few steps of the Android system startup process to introduce the init process.
1.2 entry function of init process
After the Linux kernel is loaded, it first looks for the init.rc File, and start the init process, and then view the entry function main of the init process. The code is as follows:
system/core/init/init.cpp
int main(int argc, char** argv) { if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (!strcmp(basename(argv[0]), "watchdogd")) { return watchdogd_main(argc, argv); } if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } add_environment("PATH", _PATH_DEFPATH); bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr); //Create a file and mount it if (is_first_stage) { boot_clock::time_point start_time = boot_clock::now(); // Clear the umask. umask(0); // Get the basic filesystem setup we need put together in the initramdisk // on / and then we'll let the rc file figure out the rest. // Create and mount the file directory required for startup mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); #define MAKE_STR(x) __STRING(x) mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)); // Don't expose the raw commandline to unprivileged processes. chmod("/proc/cmdline", 0440); gid_t groups[] = { AID_READPROC }; setgroups(arraysize(groups), groups); mount("sysfs", "/sys", "sysfs", 0, NULL); mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL); mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)); mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)); mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)); // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually // talk to the outside world... // Initialize the Kernel Log, so that the Kernel Log can be obtained from the outside world InitKernelLogging(argv); LOG(INFO) << "init first stage started!"; if (!DoFirstStageMount()) { LOG(ERROR) << "Failed to mount required partitions early ..."; panic(); } SetInitAvbVersionInRecovery(); // Set up SELinux, loading the SELinux policy. selinux_initialize(true); // We're in the kernel domain, so re-exec init to transition to the init domain now // that the SELinux policy has been loaded. if (selinux_android_restorecon("/init", 0) == -1) { PLOG(ERROR) << "restorecon failed"; security_failure(); } setenv("INIT_SECOND_STAGE", "true", 1); static constexpr uint32_t kNanosecondsPerMillisecond = 1e6; uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond; setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1); char* path = argv[0]; char* args[] = { path, nullptr }; execv(path, args); // execv() only returns if an error happened, in which case we // panic and never fall through this conditional. PLOG(ERROR) << "execv(\"" << path << "\") failed"; security_failure(); } // At this point we're in the second stage of init. InitKernelLogging(argv); LOG(INFO) << "init second stage started!"; // Set up a session keyring that all processes will have access to. It // will hold things like FBE encryption keys. No process should override // its session keyring. keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); // Indicate that booting is in progress to background fw loaders, etc. close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); // Initialize property service property_init(); // ....... 1 // If arguments are passed both on the command line and in DT, // properties set in DT always have priority over the command-line ones. process_kernel_dt(); process_kernel_cmdline(); // Propagate the kernel variables to internal variables // used by init as well as the current required properties. export_kernel_boot_props(); // Make the time that init started available for bootstat to log. property_set("ro.boottime.init", getenv("INIT_STARTED_AT")); property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK")); // Set libavb version for Framework-only OTA match in Treble build. const char* avb_version = getenv("INIT_AVB_VERSION"); if (avb_version) property_set("ro.boot.avb_version", avb_version); // Clean up our environment. unsetenv("INIT_SECOND_STAGE"); unsetenv("INIT_STARTED_AT"); unsetenv("INIT_SELINUX_TOOK"); unsetenv("INIT_AVB_VERSION"); // Now set up SELinux for second stage. selinux_initialize(false); selinux_restore_context(); // Create epoll handle epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (epoll_fd == -1) { PLOG(ERROR) << "epoll_create1 failed"; exit(1); } // It is used to set the subprocess signal processing function. If the subprocess (Zygote process) exits abnormally, the init process will call the function // Set signal processing function to process signal_handler_init(); // ...... 2 // Import default environment variables property_load_boot_defaults(); export_oem_lock_status(); // Start property service start_property_service(); // ...... 3 set_usb_controller(); const BuiltinFunctionMap function_map; Action::set_function_map(&function_map); ActionManager& am = ActionManager::GetInstance(); ServiceManager& sm = ServiceManager::GetInstance(); Parser& parser = Parser::GetInstance(); parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm)); parser.AddSectionParser("on", std::make_unique<ActionParser>(&am)); parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser)); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { // analysis init.rc configuration file parser.ParseConfig("/init.rc"); // ...... 4 parser.set_is_system_etc_init_loaded( parser.ParseConfig("/system/etc/init")); parser.set_is_vendor_etc_init_loaded( parser.ParseConfig("/vendor/etc/init")); parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init")); } else { parser.ParseConfig(bootscript); parser.set_is_system_etc_init_loaded(true); parser.set_is_vendor_etc_init_loaded(true); parser.set_is_odm_etc_init_loaded(true); } // Turning this on and letting the INFO logging be discarded adds 0.2s to // Nexus 9 boot time, so it's disabled by default. if (false) DumpState(); am.QueueEventTrigger("early-init"); // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); // ... so that we can start queuing up actions that require stuff from /dev. am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits"); am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict"); am.QueueBuiltinAction(keychord_init_action, "keychord_init"); am.QueueBuiltinAction(console_init_action, "console_init"); // Trigger all the boot actions to get us started. am.QueueEventTrigger("init"); // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random // wasn't ready immediately after wait_for_coldboot_done am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); // Don't mount filesystems or start core system services in charger mode. std::string bootmode = GetProperty("ro.bootmode", ""); if (bootmode == "charger") { am.QueueEventTrigger("charger"); } else { am.QueueEventTrigger("late-init"); } // Run all property triggers based on current state of the properties. am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers"); while (true) { // By default, sleep until something happens. int epoll_timeout_ms = -1; if (do_shutdown && !shutting_down) { do_shutdown = false; if (HandlePowerctlMessage(shutdown_command)) { shutting_down = true; } } if (!(waiting_for_prop || sm.IsWaitingForExec())) { // Traverse the execution function corresponding to the command carried in each action am.ExecuteOneCommand(); } if (!(waiting_for_prop || sm.IsWaitingForExec())) { if (!shutting_down) // Restart the dead process restart_processes(); // ...... 5 // If there's a process that needs restarting, wake up in time for that. if (process_needs_restart_at != 0) { epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000; if (epoll_timeout_ms < 0) epoll_timeout_ms = 0; } // If there's more work to do, wake up again immediately. if (am.HasMoreCommands()) epoll_timeout_ms = 0; } epoll_event ev; int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms)); if (nr == -1) { PLOG(ERROR) << "epoll_wait failed"; } else if (nr == 1) { ((void (*)()) ev.data.ptr)(); } } return 0; }
In the main function of init, we only need to pay attention to the main points. At the beginning, the umask will be cleaned up, and then the file directories needed for startup will be created and mounted. Among them, tmpfs, devpts, proc, sysfs and selinuxfs are mounted. These are the system runtime directories. These directories only exist when the system is running, and disappear when the system is stopped.
Call property at comment 1_ The init function initializes the property and calls start at note 3_ property_ The service function starts the property service, which will be explained later.
Call signal at comment 2_ handler_ The init function is used to set the subprocess signal processing function, which is defined in system/core/init/ signal_handler.cpp It is mainly used to prevent the sub processes out of the init process fork from becoming Zombie process (the zombie process is when the child process ends before the parent process, and the parent process does not recycle the child process, releasing the resources occupied by the child process, the child process will become a zombie process). In order to prevent the occurrence of the zombie process, the system will issue SIGCHLD when the child process is suspended and terminated, and signal_ handler_ The init function is used to receive SIGCHLD signals (only SIGCHLD signals terminated by the process are processed internally).
Suppose the subprocess Zygote of init process fork is terminated, signal_ handler_ Handle is called inside the init function_ The signal function finally finds out the Zygote process and removes all the information of the Zygote process, and then restarts the startup script of the Zygote service (for example init.zygote64.rc), about init.Zygote64 As we will see later in. Re, the Zygote process itself will be restarted in note 5. Here is just an example of Zygote process. The principle of other init process subprocesses is similar.
Note 4 for parsing init.rc Files, parsing init.rc The file of is system/core/init/init_parse.cpp File, let's look at init.rc What has been done in.
1.3 analysis init.rc
init.rc It is a very important configuration file. It is a script written by Android Init Language. It mainly contains five types of statements: Action, Commands, Services, Options and Import. init.rc The configuration code for is shown below.
system/core/rootdir/init. rc
on init sysclktz 0 # Mix device-specific information into the entropy pool copy /proc/cmdline /dev/urandom copy /default.prop /dev/urandom ... on boot # basic network init ifup lo hostname localhost domainname localdomain ...
Only a part of the code is intercepted here, where ා is the annotation symbol. on init and on boot are Action type statements. Their formats are:
on <trgger> [&& <trigger>]* <command> <command> <command> ...
Action starts with on, then triggers and commands are defined. Action statements are parsed by ActionParser.
In order to analyze how to create a Zygote process, first understand the Service type statements. Services are some programs that need to be restarted when the system starts or exits during initialization. The format is as follows:
service <name> <pathname> [ <argument> ]* <option> <option> ...
First, you need to define the name of the Service and specify the program path. Then you need to decorate the Service through option (which determines when and how the Service runs). The Service statement uses ServiceParser to parse
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server class main priority -20 user root group root readproc 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
The service is used to inform the init process to create a process named zygote. The path of the zygote process execution program is / system/bin/app_process64, the latter is to pass to app_ Parameters for process64. class main means that zygote's class name is main, which will be used later.
1.4 parsing Service type statements
bool ServiceParser::ParseSection(std::vector<std::string>&& args, const std::string& filename, int line, std::string* err) { if (args.size() < 3) { // Determine whether the service has name and program (executable program) *err = "services must have a name and a program"; return false; } const std::string& name = args[1]; if (!IsValidName(name)) { // Check if the name of the Service is valid *err = StringPrintf("invalid service name '%s'", name.c_str()); return false; } // In Service_ Find if there is a Service named name in Manager Service* old_service = service_manager_->FindServiceByName(name); if (old_service) { // Determine whether there is a duplicate Service *err = "ignored duplicate definition of service '" + name + "'"; return false; } std::vector<std::string> str_args(args.begin() + 2, args.end()); service_ = std::make_unique<Service>(name, str_args); // ... 1 return true; } bool ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) { return service_ ? service_->ParseLine(std::move(args), err) : false; }
void ServiceParser::EndSection() { if (service_) { service_manager_->AddService(std::move(service_)); } }
void ServiceManager::AddService(std::unique_ptr<Service> service) { services_.emplace_back(std::move(service)); // ... 1 }
1.5 init start Zygote
on nonencrypted class_start main class_start late_start
Of which class_start is a command, and the corresponding function is do_class_start. We know that main means Zygote, so class_start main is used to start Zygote. do_class_start function in builtins.cpp , as shown below.
system/core/init/builtins.cpp
static int do_class_start(const std::vector<std::string>& args) { /* Starting a class does not start services * which are explicitly disabled. They must * be started individually. */ ServiceManager::GetInstance(). ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); }); return 0; }
The ForEachServicelnClass function will traverse the Service list, find Zygote with class name as main, and execute the StartlfNotDisabled function, as shown below:
system/core/init/service.cpp
bool Service::StartIfNotDisabled() { if (!(flags_ & SVC_DISABLED)) { // ... 1 return Start(); } else { flags_ |= SVC_DISABLED_START; } return true; }
system/core/init/service.cpp
bool Service::Start() { // Starting a service removes it from the disabled or reset state and // immediately takes it out of the restarting state if it was in there. flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); // Running processes require no additional work --- if they're in the // process of exiting, we've ensured that they will immediately restart // on exit, unless they are ONESHOT. // Determine whether the Service is running. If so, return directly to avoid repeated startup. if (flags_ & SVC_RUNNING) { return false; } // Console environment required bool needs_console = (flags_ & SVC_CONSOLE); if (needs_console) { if (console_.empty()) { console_ = default_console; } // Make sure that open call succeeds to ensure a console driver is // properly registered for the device node int console_fd = open(console_.c_str(), O_RDWR | O_CLOEXEC); if (console_fd < 0) { PLOG(ERROR) << "service '" << name_ << "' couldn't open console '" << console_ << "'"; flags_ |= SVC_DISABLED; return false; } close(console_fd); } //Judge whether the corresponding execution file of the Service to be started exists. If not, the Service will not be started struct stat sb; if (stat(args_[0].c_str(), &sb) == -1) { PLOG(ERROR) << "cannot find '" << args_[0] << "', disabling '" << name_ << "'"; flags_ |= SVC_DISABLED; return false; } std::string scon; if (!seclabel_.empty()) { scon = seclabel_; } else { scon = ComputeContextFromExecutable(name_, args_[0]); if (scon == "") { return false; } } LOG(INFO) << "starting service '" << name_ << "'..."; //1 if the subprocess is not started, the fork function is called to create the subprocess pid_t pid = -1; if (namespace_flags_) { pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr); } else { pid = fork(); //Focus on creating subprocesses through fock } if (pid == 0) { //2 running in a subprocess umask(077); if (namespace_flags_ & CLONE_NEWPID) { // This will fork again to run an init process inside the PID // namespace. SetUpPidNamespace(name_); } for (const auto& ei : envvars_) { add_environment(ei.name.c_str(), ei.value.c_str()); } std::for_each(descriptors_.begin(), descriptors_.end(), std::bind(&DescriptorInfo::CreateAndPublish, std::placeholders::_1, scon)); // See if there were "writepid" instructions to write to files under /dev/cpuset/. auto cpuset_predicate = [](const std::string& path) { return StartsWith(path, "/dev/cpuset/"); }; auto iter = std::find_if(writepid_files_.begin(), writepid_files_.end(), cpuset_predicate); if (iter == writepid_files_.end()) { // There were no "writepid" instructions for cpusets, check if the system default // cpuset is specified to be used for the process. std::string default_cpuset = GetProperty("ro.cpuset.default", ""); if (!default_cpuset.empty()) { // Make sure the cpuset name starts and ends with '/'. // A single '/' means the 'root' cpuset. if (default_cpuset.front() != '/') { default_cpuset.insert(0, 1, '/'); } if (default_cpuset.back() != '/') { default_cpuset.push_back('/'); } writepid_files_.push_back( StringPrintf("/dev/cpuset%stasks", default_cpuset.c_str())); } } std::string pid_str = std::to_string(getpid()); for (const auto& file : writepid_files_) { if (!WriteStringToFile(pid_str, file)) { PLOG(ERROR) << "couldn't write " << pid_str << " to " << file; } } if (ioprio_class_ != IoSchedClass_NONE) { if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) { PLOG(ERROR) << "failed to set pid " << getpid() << " ioprio=" << ioprio_class_ << "," << ioprio_pri_; } } if (needs_console) { setsid(); OpenConsole(); } else { ZapStdio(); } // As requested, set our gid, supplemental gids, uid, context, and // priority. Aborts on failure. SetProcessAttributes(); if (!ExpandArgsAndExecve(args_)) { //3 key points: start sub process execution. PLOG(ERROR) << "cannot execve('" << args_[0] << "')"; } _exit(127); } if (pid < 0) { PLOG(ERROR) << "failed to fork for '" << name_ << "'"; pid_ = 0; return false; } if (oom_score_adjust_ != -1000) { std::string oom_str = std::to_string(oom_score_adjust_); std::string oom_file = StringPrintf("/proc/%d/oom_score_adj", pid); if (!WriteStringToFile(oom_str, oom_file)) { PLOG(ERROR) << "couldn't write oom_score_adj: " << strerror(errno); } } time_started_ = boot_clock::now(); pid_ = pid; flags_ |= SVC_RUNNING; process_cgroup_empty_ = false; errno = -createProcessGroup(uid_, pid_); if (errno != 0) { PLOG(ERROR) << "createProcessGroup(" << uid_ << ", " << pid_ << ") failed for service '" << name_ << "'"; } else { if (swappiness_ != -1) { if (!setProcessGroupSwappiness(uid_, pid_, swappiness_)) { PLOG(ERROR) << "setProcessGroupSwappiness failed"; } } if (soft_limit_in_bytes_ != -1) { if (!setProcessGroupSoftLimit(uid_, pid_, soft_limit_in_bytes_)) { PLOG(ERROR) << "setProcessGroupSoftLimit failed"; } } if (limit_in_bytes_ != -1) { if (!setProcessGroupLimit(uid_, pid_, limit_in_bytes_)) { PLOG(ERROR) << "setProcessGroupLimit failed"; } } } if ((flags_ & SVC_EXEC) != 0) { LOG(INFO) << "SVC_EXEC pid " << pid_ << " (uid " << uid_ << " gid " << gid_ << "+" << supp_gids_.size() << " context " << (!seclabel_.empty() ? seclabel_ : "default") << ") started; waiting..."; } NotifyStateChange("running"); return true; }
In the Start function, first determine whether the service has been run. If it has been run, it will not Start again, and return false directly. When the program executes to the annotation 1, it shows that the sub process has not been started, calling the fork function to create the child process, and returning the PID value. If pid=0 is in the annotation 2, it shows that the current code logic is executed in the sub process, and the annotation 3 is calling the ExpandArgsAndExecve function in the sub process, the Service sub process will be activated and enter the main function of the Service, if Service It's Zygote. As mentioned above, the execution path of Zygote is system/bin/app_process64, which will enter frameworks/base/cmds/app_ process/app _ Main. The main function of CPP, that is, the main function of Zygote, is as follows:
int main(int argc, char* const argv[]) { .... if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", args, zygote); // ... 1 } else if (className) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); } }
1.6 Attribute Service
// Initialize property service property_init(); // Start property service start_property_service();
These two lines of code are used to initialize the property service configuration and start the property service. First, let's learn the initialization and startup of property service configuration.
1. Property service initialization and startup
void property_init() { if (__system_property_area_init()) { LOG(ERROR) << "Failed to initialize property area"; exit(1); } }
Wei system_ property_ area_ The init function is used to initialize the attribute memory area. Next, look at start_ property_ Specific code of service function:
void start_property_service() { property_set("ro.property_service.version", "2"); property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, nullptr, sehandle); // ... 1 if (property_set_fd == -1) { PLOG(ERROR) << "start_property_service socket creation failed"; exit(1); } listen(property_set_fd, 8); // ... 2 register_epoll_handler(property_set_fd, handle_property_set_fd); // ... 3 }
Create a non blocking Socket at comment 1. Call the listen number pair property at comment 2_ set_ FD listens, so the created Socket becomes a server, that is, a property service. The second parameter of the listen function is set to 8, which means that the property service can provide services for up to eight users trying to set properties at the same time. The code at comment 3 will_ set_ FD is put into epoll, and epoll is used to monitor property_set_fd: when property_ set_ When data arrives in FD, the init process will call handle_property_set_fd function.
In the new Linux kernel, epoll is used to replace select. Epoll is the improved poll of Linux kernel for processing large batch of file descriptors. It is an enhanced version of select/poll of multiplexing I/0 interface under Linux. It can significantly improve the utilization of CPU resources of the system when there are only a few active programs in a large number of concurrent connections. The data type used to save events in epoll is red black tree, which is fast to find. The array used to save information in select is slow to find. Only when waiting for a small number of file descriptors, the efficiency of epoll and select will be about the same.
2. Service processing client request
From the above we know that when the property service receives a client request, it will call handle_property_set_fd function:
system/core/init/Property_service.cpp
static void handle_property_set_fd() { ... switch (cmd) { case PROP_MSG_SETPROP: { char prop_name[PROP_NAME_MAX]; char prop_value[PROP_VALUE_MAX]; // If the socket cannot read the property data, it will return directly. 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; handle_property_set(socket, prop_value, prop_value, true); // ... 1 break; } ... } }
Describe handle in note 1_ property_ set_ FD function calls handle_property_set function to process the client request. The code is as follows:
system/core/init/Property_service.cpp
static void handle_property_set(SocketConnection& socket, const std::string& name, const std::string& value, bool legacy_protocol) { ... // Start with "ctl.", indicating control attribute if (android::base::StartsWith(name, "ctl.")) { // ... 1 // Check client permissions if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) { // Set control properties handle_control_message(name.c_str() + 4, value.c_str()); // ... 2 if (!legacy_protocol) { socket.SendUint32(PROP_SUCCESS); } } else { LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4) << " service ctl [" << value << "]" << " uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid; if (!legacy_protocol) { socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE); } } } else { // Non control attribute, common attribute, // Check client permissions if (check_mac_perms(name, source_ctx, &cr)) { uint32_t result = property_set(name, value); // ... 3 if (!legacy_protocol) { socket.SendUint32(result); } } else { LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name; if (!legacy_protocol) { socket.SendUint32(PROP_ERROR_PERMISSION_DENIED); } } } freecon(source_ctx); }
As can be seen from the above code, system attributes can be divided into two types: one is common attribute, and the other is control attribute. Control attribute is used to execute some commands, such as boot animation. The property name in note 1 starts with "ctl.", which indicates that it is control property and then calls handle_. control_ Message function to modify the control properties. If it is a normal property, call the property at annotation 3_ Set function to modify common attributes as follows:
system/core/init/Property_service.cpp
uint32_t property_set(const std::string& name, const std::string& value) { if (name == "selinux.restorecon_recursive") { return PropertySetAsync(name, value, RestoreconRecursiveAsync); } return PropertySetImpl(name, value); // ... 1 }
In property_ In the set method, first determine whether the name of the attribute is“ selinux.restorecon_recursive ", if yes, use the PropertySetAsync function to complete the setting, if not, use the PropertySetImpl function to complete the setting, here we analyze the PropertySetImpl function to complete the property setting, the code is as follows:
system/core/init/Property_service.cpp
static uint32_t PropertySetImpl(const std::string& name, const std::string& value) { size_t valuelen = value.size(); // Determine whether the name of the attribute is legal if (!is_legal_property_name(name)) { LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: bad name"; return PROP_ERROR_INVALID_NAME; } if (valuelen >= PROP_VALUE_MAX) { LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: " << "value too long"; return PROP_ERROR_INVALID_VALUE; } // Find the attribute from the attribute store prop_info* pi = (prop_info*) __system_property_find(name.c_str()); // Judge whether the attribute exists if (pi != nullptr) { // ro.* properties are actually "write-once". // If the property name starts with "ro.", it means read-only property. It can't be modified and will be returned directly. if (android::base::StartsWith(name, "ro.")) { LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: " << "property already set"; return PROP_ERROR_READ_ONLY_PROPERTY; } // If the property exists, update the property value. __system_property_update(pi, value.c_str(), valuelen); } else { // If the property does not exist, add it int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen); if (rc < 0) { LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: " << "__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. // Process properties beginning with "persist." if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) { write_persistent_property(name.c_str(), value.c_str()); } property_changed(name, value); return PROP_SUCCESS; }