Framework学习之旅:init 进程启动过程

概述

init 进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动流程中一个关键的步骤,它有很多重要的职责,比如创建Zygote(孵化器)和属性服务等。它的生命周期贯穿整个Linux内核运行的始终。Android中所有其它的进程共同的鼻祖均为init进程。

init进程启动流程分析

Android Q(10.0)的init入口函数由原先的init.cpp调整到了main.cpp。接下来就从main函数开始:

system/core/init/main.cpp

// 第一个参数argc表示参数个数,第二个参数是参数列表
int main(int argc, char** argv) {
    
     //当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1表示true,也就执行ueventd_main
     //ueventd主要是负责设备节点的创建、权限设定等一些列工作
    if (!strcmp(basename(argv[0]), "ueventd")) {
         return ueventd_main(argc, argv);
    }
    
     //当传入的参数个数大于1时,执行下面的几个操作
    if (argc > 1) {
   
       //参数为subcontext,初始化日志系统,
      if (!strcmp(argv[1], "subcontext")) {
          android::base::InitLogging(argv,&android::base::KernelLogger);
          const BuiltinFunctionMap function_map;
          return SubcontextMain(argc, argv, &function_map);
        }
        
       //参数为“selinux_setup”,启动Selinux安全策略
      if (!strcmp(argv[1], "selinux_setup")) {
         return SetupSelinux(argv);
        }  
        
       //参数为“second_stage”,启动init进程第二阶段
       if (!strcmp(argv[1], "second_stage")) {
        return SecondStageMain(argc, argv);
        }
    }
    
    // 默认启动init进程第一阶段
    return FirstStageMain(argc, argv);
}

main() 方法执行顺序如下:

  • ueventd_main init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件。
  • FirstStageMain 启动第一阶段
  • SetupSelinux 加载selinux规则,并设置并设置selinux日志,完成SELinux相关工作
  • SecondStageMain 启动第二阶段

接下来,继续查看ueventd_main() 函数

platform/system/core/init/ueventd.cpp

int ueventd_main(int argc, char** argv) {
    //设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
    umask(000);
    
    //初始化内核日志,位于节点/dev/kmsg, 此时logd、logcat进程 还没有起来,
    //采用kernel的log系统,打开的设备节点/dev/kmsg,那么可通过cat/dev/kmsg来获取内核log。
    android::base::InitLogging(argv, &android::base::KernelLogger);
    
    //注册selinux相关的用于打印log的回调函数 
    SelinuxSetupKernelLogging(); 
    SelabelInitialize();
    
    //解析xml,根据不同SOC厂商获取不同的hardware rc文件
    auto ueventd_configuration = ParseConfig({"/ueventd.rc", "/vendor/ueventd.rc","/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
    
    //冷启动
    if (access(COLDBOOT_DONE, F_OK) != 0) { 
        ColdBoot cold_boot(uevent_listener, uevent_handlers);
        cold_boot.Run();
    }
    
    for (auto& uevent_handler : uevent_handlers) {
        uevent_handler->ColdbootDone(); 
    }
    
    //忽略子进程终止信号 
    signal(SIGCHLD, SIG_IGN);
    
    //在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级
    while (waitpid(-1, nullptr, WNOHANG) > 0) { }
    
    //监听来自驱动的uevent,进行“热插拔”处理
    uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) { 
       for (auto& uevent_handler : uevent_handlers) {
             //热启动,创建设备 
             uevent_handler->HandleUevent(uevent);
        }
        
        return ListenerAction::kContinue; 
    }); 
    
    return 0;
}

Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的。因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。

ueventd通过两种方式创建设备节点文件:

  • “冷插拔”(Cold Plug)

即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。

  • 应“热插拔”(Hot Plug)

即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点
文件。

init 进程启动第一阶段

init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略。

platform\system\core\init\first_stage_init.cpp

int FirstStageMain(int argc, char** argv) {
    
    //init crash时重启引导加载程序
    //这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    
    //清空文件权限
    umask(0);
    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    
    //在RAM内存上获取基本的文件系统,剩余的被rc文件所用
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
    
    #define MAKE_STR(x) __STRING(x)
    CHECKCALL(mount("proc", "/proc", "proc", 0,"hidepid=2,gid=" MAKE_STR(AID_READPROC)));
    
    #undef MAKE_STR
    // 非特权应用不能使用Andrlid cmdline
    CHECKCALL(chmod("/proc/cmdline", 0440));
    gid_t groups[] = {AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs",0, NULL));
    
    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600,makedev(1, 11)));
    if constexpr (WORLD_WRITABLE_KMSG) {
       CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622,makedev(1, 11)));
    }
    
    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666,makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666,makedev(1, 9)));
    
    //这对于日志包装器是必需的,它在ueventd运行之前被调用
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666,makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1,3)));
    
    //在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,只需要在第二阶段通过rc文件解析来加载。
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC |MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=1000"));
    
    //创建可供读写的vendor目录
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are
    mounted read-write.CHECKCALL(mkdir("/mnt/product", 0755));
    
    // 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,
    // 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC |MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));
    
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC |MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));
 
   // /debug_ramdisk is used to preserve additional files from
    the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs",MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=0"));
    
    #undef CHECKCALL
   //把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
   SetStdioToDevNull(argv);
   //在/dev目录下挂载好 tmpfs 以及 kmsg 
   //这样就可以初始化 /kernel Log 系统,供用户打印log
   InitKernelLogging(argv);
   
   struct stat new_root_info;
   if (stat("/", &new_root_info) != 0) {
      PLOG(ERROR) << "Could not stat("/"), not freeingramdisk";
      old_root_dir.reset();
   }
   
   if (old_root_dir && old_root_info.st_dev !=new_root_info.st_dev) {
     FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
   }
   
   SetInitAvbVersionInRecovery();
   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);
   
   //启动init进程,传入参数selinux_steup
   // 执行命令: /system/bin/init selinux_setup

   const char* path = "/system/bin/init";
   const char* args[] = {path, "selinux_setup", nullptr};
   execv(path, const_cast<char**>(args));
   PLOG(FATAL) << "execv("" << path << "") failed";
 
   return 1;
    
}

第一阶段完成以下内容:

  1. 创建文件系统目录并挂载相关的文件系统
  2. 屏蔽标准的输入输出/初始化内核log系统

加载SELinux规则

SELinux是Linux的一个扩张强制访问控制安全模块。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。

初始化selinux,加载SELinux规则,配置SELinux相关log输出,并启动第二阶段。

platform\system\core\init\selinux.cpp

// 初始化selinux
int SetupSelinux(char** argv) {
  
   //初始化Kernel日志
   InitKernelLogging(argv);
  
   // Debug版本init crash时重启引导加载程序
   if (REBOOT_BOOTLOADER_ON_PANIC) {
    InstallRebootSignalHandlers();
   }
   
   //注册回调,用来设置需要写入kmsg的selinux日志
   SelinuxSetupKernelLogging();
   
   //加载SELinux规则
   SelinuxInitialize();
   
   // 在内核域中,希望转换到init域。在其xattrs中存储selabel的文件系统(如ext4)不需要显式restorecon,但其他文件系统需要。尤其是对于ramdisk,如对于a/b设备的恢复映像,这是必需要做的一步其实就是当前在内核域中,在加载Seliux后,需要重新执行init切换到C空间的用户态。
   if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
     PLOG(FATAL) << "restorecon failed of /system/bin/initfailed";
   }
   
   //准备启动init进程,传入参数second_stage
   const char* path = "/system/bin/init";
   const char* args[] = {path, "second_stage", nullptr};
   execv(path, const_cast<char**>(args));
   
   // 执行 /system/bin/init second_stage, 进入第二阶段
   PLOG(FATAL) << "execv("" << path << "") failed";
   
   return 1;
}

// 加载SELinux规则
void SelinuxInitialize() {
   
    LOG(INFO) << "Loading SELinux policy";
    if (!LoadPolicy()) {
      LOG(FATAL) << "Unable to load SELinux policy";
    } 
    
    //获取当前Kernel的工作模式
    bool kernel_enforcing = (security_getenforce() == 1);
    
    //获取工作模式的配置 
    bool is_enforcing = IsEnforcing();
    
    //如果当前的工作模式与配置的不同,就将当前的工作模式改掉
    if (kernel_enforcing != is_enforcing) { 
       if (security_setenforce(is_enforcing)) { 
          PLOG(FATAL) << "security_setenforce
          (" << (is_enforcing ? "true" : "false") << ") failed"; 
       } 
        
    }
    
    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {
        LOG(FATAL) << "Unable to write to/sys/fs/selinux/checkreqprot: " << result.error(); 
    }
    
}

// 加载SELinux规则:这里区分了两种情况,这两种情况只是区分从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取,
// 第二个是从 /sepolicy读取,他们最终都是调用selinux_android_load_policy_from_fd()方法
bool LoadPolicy() {
 return IsSplitPolicyDevice() ? LoadSplitPolicy():LoadMonolithicPolicy();
}

SELinux有两种工作模式

  • permissive

所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志,一般eng模式用。

  • enforcing

所有操作都会进行权限检查。一般user和user-debug模式用,不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce文件,0表示permissive, 1表示enforcing。

init进程启动第二阶段

init进程启动第二阶段需要重点分析 SecondStageMain()方法。

int SecondStageMain(int argc, char* argv) {
    
   // 01. 创建进程会话密钥并初始化属性系统 
   keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
   
   //创建 /dev/.booting 文件,就是个标记,表示booting进行中 
   close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
   
   // 初始化属性系统,并从指定文件读取属性 
   property_init();
   
   //02. 进行SELinux第二阶段并恢复一些文件安全上下文 SelinuxRestoreContext();
   
   // 03. 新建epoll并初始化子进程终止信号处理函数
   Epoll epoll; 
   if (auto result = epoll.Open(); !result) {
      PLOG(FATAL) << result.error(); 
   }
   
   InstallSignalFdHandler(&epoll);
   
   //04. 设置其他系统属性并开启系统属性服务 
   StartPropertyService(&epoll);
   
   // 05 解析init.rc等文件,建立rc文件的action 、 service,启动其他进程 
   ActionManager& am = ActionManager::GetInstance();
   ServiceList& sm = ServiceList::GetInstance(); 
   LoadBootScripts(am, sm);
   
}
小结:

在这里插入图片描述
第二阶段主要内容:

1.创建进程会话密钥并初始化属性系统
2.进行SELinux第二阶段并恢复一些文件安全上下文
3.新建epoll并初始化子进程终止信号处理函数。
4.启动匹配属性的服务端, 详细查看第六节-属性服务
5.解析init.rc等文件,建立rc文件的action 、service,启动其他进程

信号处理

init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie
process),需要init在子进程在结束时获取子进程的结束码。通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。

InstallSignalFdHandler

在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出。我们来看看init进程如何处理这个信号。

首先,新建一个sigaction结构体,sa_handler是信号处理函数,指向内核指定的函数指针SIG_DFL和Android9.0及之前的版本不同,这里不再通过socket的读写句柄进行接收信号,改成了内核的信号处理函数SIG_DFL。

然后,sigaction(SIGCHLD,&act,nullptr)这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理。

最后,RegisterHandler的作用就是signal_read_fd(之前的s[1])收到信号,触发handle_signal。

终上所述,InstallSignalFdHandler函数的作用就是,接收到SIGCHLD信号时触发HandleSignalFd进行信号处理。

在这里插入图片描述

platform/system/core/init.cpp

// 初始化子进程终止信号处理过程
static void InstallSignalFdHandler(Epoll* epoll) {

    // SA_NOCLDSTOP使init进程只有在其子进程终止时才会受到SIGCHLD信号
    const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags
    = SA_NOCLDSTOP };
    sigaction(SIGCHLD, &act, nullptr);   
    
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    
    if (!IsRebootCapable()) {
     // 如果init不具有 CAP_SYS_BOOT的能力,则它此时正值容器中运行
     // 在这种场景下,接收SIGTERM 将会导致系统关闭
     sigaddset(&mask, SIGTERM);
    }
 
    if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
     PLOG(FATAL) << "failed to block signals";
    }
    
    // 注册处理程序以解除对子进程中的信号的阻止
    const int result = pthread_atfork(nullptr, nullptr,&UnblockSignals);
    if (result != 0) {
       LOG(FATAL) << "Failed to register a fork handler: " <<strerror(result);
    }
    
    //创建信号句柄
    signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
    if (signal_fd == -1) {
       PLOG(FATAL) << "failed to create signalfd";
    }
    
    //信号注册,当signal_fd收到信号时,触发HandleSignalFd
    // 分析1、分析2
    if (auto result = epoll->RegisterHandler(signal_fd,HandleSignalFd); !result) {
       LOG(FATAL) << result.error();
    }
    
}
分析1:RegisterHandler
/platform/system/core/epoll.cpp

// 监控SIGCHLD信号,调用 ReapAnyOutstandingChildren来 终止出现问题的子进程
Result Epoll::RegisterHandler(int fd, std::function<void()>
handler, uint32_t events) {
    if (!events) {
       return Error() << "Must specify events";
    }
    
    auto [it, inserted] = epoll_handlers.emplace(fd,std::move(handler));
    
    if (!inserted) {
        return Error() << "Cannot specify two epoll handlers for agiven FD";
    }
    epoll_event ev;
    ev.events = events;
    
    ev.data.ptr = reinterpret_cast<void*>(&it->second);
    // 将fd的可读事件加入到epoll_fd的监听队列中
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        Result result = ErrnoError() << "epoll_ctl failed to add fd";
        epoll_handlers.erase(fd);
        return result;
    }
    
    return {};
    
}
分析2:HandleSignalFd
// 监控SIGCHLD信号,调用 ReapAnyOutstandingChildren来终止出现问题的子进程
static void HandleSignalFd() {

    signalfd_siginfo siginfo;
    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd,&siginfo, sizeof(siginfo)));
    
    if (bytes_read != sizeof(siginfo)) {
       PLOG(ERROR) << "Failed to read siginfo from signal_fd";
       return;
    }
    
    //监控SIGCHLD信号
    switch (siginfo.ssi_signo) {
       case SIGCHLD:
            ReapAnyOutstandingChildren();// 分析3
            break;
       case SIGTERM:
            HandleSigtermSignal(siginfo);
            break;
       default:
            PLOG(ERROR) << "signal_fd: received unexpected signal" << siginfo.ssi_signo;
       break;
 }
}
分析3:ReapOneProcess

ReapOneProcess是最终的处理函数了,这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程。

/platform/system/core/sigchld_handle.cpp


void ReapAnyOutstandingChildren() {
    while (ReapOneProcess()) {
    }
}

static bool ReapOneProcess() {
    
    siginfo_t siginfo = {};
    //用waitpid函数获取状态发生变化的子进程pid
    //waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了
    if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED| WNOHANG | WNOWAIT)) != 0) {
       PLOG(ERROR) << "waitid failed";
       return false;
    }
    
    auto pid = siginfo.si_pid;
    if (pid == 0) 
       return false;
    // 当我们知道当前有一个僵尸pid,我们使用scopeguard来清楚该 pid   
    auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
    
    std::string name; 
    std::string wait_string; 
    Service* service = nullptr;
    
    if (SubcontextChildReap(pid)) { 
       name = "Subcontext"; 
    } else {
       service = ServiceList::GetInstance().FindService(pid, &Service::pid);
       
       if (service) {
           name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
           
           if (service->flags() & SVC_EXEC) {
               auto exec_duration = boot_clock::now() - service->time_started();
               auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds >(exec_duration).count();
               wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
           }else if (service->flags() & SVC_ONESHOT) {
               auto exec_duration = boot_clock::now() - service->time_started();
               auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds >(exec_duration) .count();
               wait_string = StringPrintf(" oneshot service took %f seconds in background",exec_duration_ms / 1000.0f);
           }
       }else { 
           name = StringPrintf("Untracked pid %d", pid);
        }
    }
    
    if (siginfo.si_code == CLD_EXITED) { 
       LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string; 
    }else {
       LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string; 
    }
    
    //没有找到service,说明已经结束了,退出
    if (!service) return true;
    
    //清除子进程相关的资源
    service->Reap(siginfo);
    
    if (service->flags() & SVC_TEMPORARY) {
        //移除该service
        ServiceList::GetInstance().RemoveService(*service);
    }
    
    return true;
}

小结

信号处理主要工作:
1.初始化信号signal句柄
2.循环处理子进程
3.注册epoll句柄
4.处理子进程终止

属性服务

Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看具体的流程是什么。

property_init

初始化属性系统,并从指定文件读取属性,并进行SELinux注册,进行属性权限控制
清除缓存。这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为/dev/properties然后就是调用CreateSerializedPropertyInfo加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存。

platform/system/core/property_service.cpp

void property_init() {

    //设置SELinux回调,进行权限控制 
    selinux_callback cb; 
    cb.func_audit = PropertyAuditCallback; selinux_set_callback(SELINUX_CB_AUDIT, cb);
    
    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"; 
    }
}
StartPropertyService

启动属性服务:首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性,最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd。

platform/system/core/init.cpp

void StartPropertyService(Epoll* epoll) {
   property_set("ro.property_service.version", "2");
   
   //建立socket连接
   if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, {})) {
      property_set_fd = *result; 
   } else { 
      PLOG(FATAL) << "start_property_service socket creation failed: " << result.error(); 
   } 
   
   // 最大监听8个并发 
   listen(property_set_fd, 8);
   
   // 注册property_set_fd,当收到句柄改变时,通过handle_property_set_fd来处理
   if (auto result = epoll- >RegisterHandler(property_set_fd, handle_property_set_fd); !result) { 
      PLOG(FATAL) << result.error(); 
   }
} 
handle_property_set_fd

建立socket连接,然后从socket中读取操作信息,根据不同的操作类型,调用HandlePropertySet做具体的操作。HandlePropertySet是最终的处理函数,以"ctl"开头的key就做一些Service的Start,Stop,Restart操作,其他的就是调用property_set进行属性设置,不管是前者还是后者,都要进行SELinux安全性检查,只有该进程有操作权限才能执行相应操作。

platform/system/core/property_service.cpp

static void handle_property_set_fd() {
    // 等待客户端连接 
    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC); 
    if (s == -1) { 
       return; 
    }
    
    ucred cr;
    socklen_t cr_size = sizeof(cr);
    
    // 获取连接到此socket的进程的凭据
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s); 
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED"; 
        return;
    }
    
    // 建立socket连接 
    SocketConnection socket(s, cr); 
    uint32_t timeout_ms = kDefaultSocketTimeout;
    
    uint32_t cmd = 0;
    // 读取socket中的操作信息
    if (!socket.RecvUint32(&cmd, &timeout_ms)) { 
      PLOG(ERROR) << "sys_prop: error while reading command from the socket"; 
      socket.SendUint32(PROP_ERROR_READ_CMD); 
      return;
    }
    
    // 根据操作信息,执行对应处理,两者区别一个是以char形式读取, 一个以String形式读取
    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;
              std::string source_context; 
              if (!socket.GetSourceContext(&source_context)) {        
                 PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed"; 
                 return; 
              }
              
              const auto& cr = socket.cred(); 
              std::string error; 
              uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, &error); 
              if (result != PROP_SUCCESS) { 
                 LOG(ERROR) << "Unable to set property '" << prop_name << "' 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; 
              }
              std::string source_context; 
              if (!socket.GetSourceContext(&source_context)) {   
                 PLOG(ERROR) << "Unable to set property '" << name << "': getpeercon() failed";
                 socket.SendUint32(PROP_ERROR_PERMISSION_DENIED); 
                 return; 
              }
              
              const auto& cr = socket.cred(); 
              std::string error; 
              uint32_t result = HandlePropertySet(name, value, source_context, cr, &error); 
              if (result != PROP_SUCCESS) { 
                 LOG(ERROR) << "Unable to set property '" << name << "' 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;
    }
}

第三阶段init.rc

当属性服务建立完成后,init的自身功能基本就告一段落,接下来需要来启动其他的进程。其他进程都是一个二进制文件,我们可以直接通过exec的命令方式来启动,例如 ./system/bin/initsecond_stage,来启动init进程的第二阶段。但是Android系统有那么多的Native进程,如果都通过传exec在代码中一个个的来执行进程,那无疑是一个灾难性的设计。

在这个基础上Android推出了一个init.rc的机制,即类似通过读取配置文件的方式,来启动不同的进程。接下来我们就来看看init.rc是如何工作的。

init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本。

init.rc主要包含五种类型语句:Action、Command、Service、Option、Import。

Action

Action表示了一组命令(commands)组成。Action包括一个触发器,决定了何时运行这个动作。

通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:

  • on early-init

在初始化早期阶段触发

  • on init

在初始化阶段触发

  • on late-init

在初始化晚期阶段触发

  • on boot/charger

当系统启动/充电时触发

  • on property:=

当属性值满足条件时触发

Command

command是action的命令列表中的命令,或者是service中的选项onrestart的参数命令,命令将在所属事件发生时被一个个地执行。

常用的命令:

  • class_start <service_class_name>

启动属于同一个class的所有服务

  • class_stop <service_class_name>

停止指定类的服务

  • start <service_name>

启动指定的服务,若已启动则跳过

  • stop <service_name>

停止正在运行的服务

  • setprop

设置属性值

  • mkdir

创建指定目录

  • symlink <sym_link>

创建连接到的<sym_link>符号链接

  • write

向文件path中写入字符串

  • exec

fork并执行,会阻塞init进程直到程序完毕

  • exprot

设定环境变量

  • loglevel

设置log级别

  • hostname

设置主机名

  • import

导入一个额外的init配置文件

Service

服务Service,以service开头,由init进程启动。一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。

Options

Options是Service的可选项,与service配合使用

可选项命令:

  • disabled

不随class自动启动,只有根据service名才启动

  • oneshot

service退出后不再重启

  • user/group

设置执行服务的用户/用户组,默认都是root

  • class

设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default

  • onrestart

当服务重启时执行相应命令

  • socket

创建名为/dev/socket/的socket

  • critical

在规定时间内该service不断重启,则系统会重启并进入恢复模式

  • default

意味着disabled=false,oneshot=false,critical=false

import

用来导入其他的rc文件

init.rc 解析过程

1. LoadBootScripts

如果没有特殊配置ro.boot.init_rc,则解析./init.rc把/system/etc/init、/product/etc/init、/product_services/etc/init、/odm/etc/init、/vendor/etc/init 这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里的rc文件。

platform\system\core\init\init.cpp

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"); 
       
       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("/product_services/etc/init")){
         late_import_paths.emplace_back("/product_services/et c/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);
   }
    
}

Android7.0后,init.rc进行了拆分,每个服务都有自己的rc文件,他们基本上都被加载到/system/etc/init,/vendor/etc/init,/odm/etc/init等目录,等init.rc解析完成后,会来解析这些目录中的rc文件,用来执行相关的动作。

platform\system\core\init\init.cpp

// 创建Parser解析对象,例如service、on、import对象
Parser CreateParser(ActionManager& action_manager,ServiceList&service_list) {
    Parser parser;
    parser.AddSectionParser( "service", std::make_unique<ServiceParser> (&service_list, subcontexts, std::nullopt));
    parser.AddSectionParser("on",std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import",std::make_unique<ImportParser>(&parser));
    return parser;
}

2.Zygote启动
从Android 5.0的版本开始,Android支持64位的编译,因此zygote本身也支持32位和64位。通过属性ro.zygote来控制不同版本的zygote进程启动。

在init.rc的import段如下代码:

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

从上面的import /init.${ro.zygote}.rc,可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件。

init.rc位于/system/core/rootdir下,在这个路径下还包括四个关于zygote的rc文件:分别是init.zygote32.rc、init.zygote32_64.rc、init.zygote64.rc、init.zygote64_32.rc,由硬件决定调用哪个文件。

这里拿64位处理器为例,init.zygote64.rc的代码如下所示:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    # class是一个option,指定zygote服务的类型为main
    class main
    priority -20
    user root
    group root readproc reserved_disk
    # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    # onrestart是一个option,说明在zygote重启时需要执行的command
    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

service zygote :init.zygote64.rc 中定义了一个zygote服务, init进程就是通过这个service名称来创建zygote进程。

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 

zygote这个服务,通过执行进行/system/bin/app_process64 并
传入4个参数进行运行:

  • -Xzygote

该参数将作为虚拟机启动时所需的参数

  • /system/bin

/system/bin 代表虚拟机程序所在目录

  • –zygote

指明以ZygoteInit.java类中的main函数作为虚拟机执行入口

  • –start-system-server

告诉Zygote进程启动systemServer进程

init 进程启动总结

  1. 创建和挂载启动所需的文件目录
  2. 初始化和启动属性服务
  3. 解析init.rc配置文件并启动启动Zygote服务
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值