Android 8.0 系统启动流程之init进程--第一阶段(四)
1、概述 上一篇中讲到,Linux系统执行完初始化操作最后会执行根目录下的init文件,init是一个可执行程序,它的源码在platform/system/core/init/init.cpp。init进程是用户空间的第一个进程,我们熟悉的app应用程序都是以它为父进程的,init进程入口函数是main函数,这个函数做的事情还是比较多的,主要分为三个部分init进程第一阶段ini...
1、概述
上一篇中讲到,Linux系统执行完初始化操作最后会执行根目录下的init文件,init是一个可执行程序,它的源码在platform/system/core/init/init.cpp。init进程是用户空间的第一个进程,我们熟悉的app应用程序都是以它为父进程的,init进程入口函数是main函数,这个函数做的事情还是比较多的,主要分为三个部分
- init进程第一阶段
- init进程第二阶段
- init.rc文件解析
由于内容比较多,所以对于init进程的分析,将分为三个章节,本文只讲解第一阶段,第一阶段主要有以下内容
- ueventd/watchdogd跳转及环境变量设置
- 挂载文件系统并创建目录
- 初始化日志输出、挂载分区设备
- 启用SELinux安全策略
- 开始第二阶段前的准备
2、init进程入口
定义在system/core/init/init.cpp中
int main(int argc, char** argv) {
/*
* 1.strcmp是String的一个函数,比较字符串,相等返回0
* 2.C++中0也可以表示false
* 3.basename是C库中的一个函数,得到特定的路径中的最后一个'/'后面的内容,
* 比如/sdcard/miui_recovery/backup,得到的结果是backup
*/
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) {
//初始化重启系统的处理信号,内部通过sigaction 注册信号,当监听到该信号时重启系统
InstallRebootSignalHandlers();
}
//注册环境变量PATH
// _PATH_DEFPATH 是定义在bionic/libc/include/paths.h中
add_environment("PATH", _PATH_DEFPATH);
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
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.
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...
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);//重新执行main方法,进入第二阶段
// 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();
}
......
}
3、ueventd/watchdogd跳转及环境变量设置
3.1 ueventd_main
定义在system/core/init/ueventd.cpp
Android根文件系统的映像中不存在“/dev”目录,该目录是init进程启动后动态创建的。
因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。ueventd通过两种方式创建设备节点文件。
第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。
DeviceHandler CreateDeviceHandler() {
Parser parser;
std::vector<Subsystem> subsystems;
parser.AddSectionParser("subsystem", std::make_unique<SubsystemParser>(&subsystems));
using namespace std::placeholders;
std::vector<SysfsPermissions> sysfs_permissions;
std::vector<Permissions> dev_permissions;
parser.AddSingleLineParser(
"/sys/", std::bind(ParsePermissionsLine, _1, _2, &sysfs_permissions, nullptr));
parser.AddSingleLineParser("/dev/",
std::bind(ParsePermissionsLine, _1, _2, nullptr, &dev_permissions));
parser.ParseConfig("/ueventd.rc");//解析.rc文件,这个后续再讲
parser.ParseConfig("/vendor/ueventd.rc");
parser.ParseConfig("/odm/ueventd.rc");
/*
* keep the current product name base configuration so
* we remain backwards compatible and allow it to override
* everything
* TODO: cleanup platform ueventd.rc to remove vendor specific
* device node entries (b/34968103)
*/
std::string hardware = android::base::GetProperty("ro.hardware", "");
parser.ParseConfig("/ueventd." + hardware + ".rc");
return DeviceHandler(std::move(dev_permissions), std::move(sysfs_permissions),
std::move(subsystems), true);
}
int ueventd_main(int argc, char** argv) {
/*
* init sets the umask to 077 for forked processes. We need to
* create files with exact permissions, without modification by
* the umask.
*/
umask(000);//设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
InitKernelLogging(argv);//初始化日志输出
LOG(INFO) << "ueventd started!";
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);//注册selinux相关的用于打印log的回调函数
DeviceHandler device_handler = CreateDeviceHandler();
UeventListener uevent_listener;
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, device_handler);
cold_boot.Run();//冷启动
}
// We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.
signal(SIGCHLD, SIG_IGN);//忽略子进程终止信号
// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
// for SIGCHLD above.
while (waitpid(-1, nullptr, WNOHANG) > 0) {
}
//监听来自驱动的uevent,进行“热插拔”处理
uevent_listener.Poll([&device_handler](const Uevent& uevent) {
HandleFirmwareEvent(uevent);
device_handler.HandleDeviceEvent(uevent);
return ListenerAction::kContinue;
});
return 0;
}
3.2 watchdogd_main
定义在platform/system/core/init/watchdogd.cpp
“看门狗”本身是一个定时器电路,内部会不断的进行计时(或计数)操作,计算机系统和”看门狗”有两个引脚相连接,正常运行时每隔一段时间就会通过其中一个引脚向”看门狗”发送信号,”看门狗”接收到信号后会将计时器清零并重新开始计时,而一旦系统出现问题,进入死循环或任何阻塞状态,不能及时发送信号让”看门狗”的计时器清零,当计时结束时,”看门狗”就会通过另一个引脚向系统发送“复位信号”,让系统重启。
watchdogd_main主要是定时器作用,而DEV_NAME就是那个引脚
int watchdogd_main(int argc, char **argv) {
InitKernelLogging(argv);
int interval = 10;
if (argc >= 2) interval = atoi(argv[1]);//atoi作用是将字符串转变为数值
int margin = 10;
if (argc >= 3) margin = atoi(argv[2]);
LOG(INFO) << "watchdogd started (interval " << interval << ", margin " << margin << ")!";
int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC);//打开文件 /dev/watchdog
if (fd == -1) {
PLOG(ERROR) << "Failed to open " << DEV_NAME;
return 1;
}
int timeout = interval + margin;
//ioctl是设备驱动程序中对设备的I/O通道进行管理的函数,WDIOC_SETTIMEOUT是设置超时时间
int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
if (ret) {
PLOG(ERROR) << "Failed to set timeout to " << timeout;
ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
if (ret) {
PLOG(ERROR) << "Failed to get timeout";
} else {
if (timeout > margin) {
interval = timeout - margin;
} else {
interval = 1;
}
LOG(WARNING) << "Adjusted interval to timeout returned by driver: "
<< "timeout " << timeout
<< ", interval " << interval
<< ", margin " << margin;
}
}
while (true) {//每间隔一定时间往文件中写入一个空字符,这就是看门狗的关键了
write(fd, "", 1);
sleep(interval);
}
}
3.3 InstallRebootSignalHandlers
定义在platform/system/core/init/init.cpp
这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
static void InstallRebootSignalHandlers() {
// Instead of panic'ing the kernel as is the default behavior when init crashes,
// we prefer to reboot to bootloader on development builds, as this will prevent
// boot looping bad configurations and allow both developers and test farms to easily
// recover.
struct sigaction action;
memset(&action, 0, sizeof(action));
sigfillset(&action.sa_mask);//将所有信号加入至信号集
action.sa_handler = [](int signal) {
// These signal handlers are also caught for processes forked from init, however we do not
// want them to trigger reboot, so we directly call _exit() for children processes here.
if (getpid() != 1) {
_exit(signal);
}
// panic() reboots to bootloader
panic();//重启系统
};
action.sa_flags = SA_RESTART;
sigaction(SIGABRT, &action, nullptr);
sigaction(SIGBUS, &action, nullptr);
sigaction(SIGFPE, &action, nullptr);
sigaction(SIGILL, &action, nullptr);
sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
sigaction(SIGSTKFLT, &action, nullptr);
#endif
sigaction(SIGSYS, &action, nullptr);
sigaction(SIGTRAP, &action, nullptr);
}
3.4 add_environment
定义在platform/system/core/init/init.cpp
这个函数主要作用是将一个键值对放到一个Char数组中,如果数组中有key就替换,没有就插入
/* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val)
{
size_t n;
size_t key_len = strlen(key);
/* The last environment entry is reserved to terminate the list */
for (n = 0; n < (arraysize(ENV) - 1); n++) {
/* Delete any existing entry for this key */
if (ENV[n] != NULL) {
//C++中strcspn用于返回字符所在下标,相当于String的indexof
size_t entry_key_len = strcspn(ENV[n], "=");
//如果key相同,删除对应数据
if ((entry_key_len == key_len) && (strncmp(ENV[n], key, entry_key_len) == 0)) {
free((char*)ENV[n]);
ENV[n] = NULL;
}
}
/* Add entry if a free slot is available */
//如果没有对应key,则插入数据
if (ENV[n] == NULL) {
char* entry;
asprintf(&entry, "%s=%s", key, val);
ENV[n] = entry;
return 0;
}
}
LOG(ERROR) << "No env. room to store: '" << key << "':'" << val << "'";
return -1;
}
4、 挂载文件系统并创建目录
定义在platform/system/core/init/init.cpp
int main(int argc, char** argv) {
......
//查看是否有环境变量INIT_SECOND_STAGE
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
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.
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));
......
}
......
}
4.1 mount
mount是用来挂载文件系统的,mount属于Linux系统调用,函数原型如下:
int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);
参数:
source:将要挂上的文件系统,通常是一个设备名。
target:文件系统所要挂载的目标目录。
filesystemtype:文件系统的类型,可以是"ext2","msdos","proc","ntfs","iso9660"。。。
mountflags:指定文件系统的读写访问标志,可能值有以下
参数 含义
MS_BIND 执行bind挂载,使文件或者子目录树在文件系统内的另一个点上可视。
MS_DIRSYNC 同步目录的更新。
MS_MANDLOCK 允许在文件上执行强制锁。
MS_MOVE 移动子目录树。
MS_NOATIME 不要更新文件上的访问时间。
MS_NODEV 不允许访问设备文件。
MS_NODIRATIME 不允许更新目录上的访问时间。
MS_NOEXEC 不允许在挂上的文件系统上执行程序。
MS_NOSUID 执行程序时,不遵照set-user-ID和set-group-ID位。
MS_RDONLY 指定文件系统为只读。
MS_REMOUNT 重新加载文件系统。这允许你改变现存文件系统的mountflag和数据,而无需使用先卸载,再挂上文件系统的方式。
MS_SYNCHRONOUS 同步文件的更新。
MNT_FORCE 强制卸载,即使文件系统处于忙状态。
MNT_EXPIRE 将挂载点标记为过时。
data:文件系统特有的参数
在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs,selinuxfs这5类文件系统。
tmpfs是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。
由于tmpfs是驻留在RAM的,因此它的内容是不持久的。断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。
devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取
selinuxfs也是虚拟文件系统,通常挂载在/sys/fs/selinux目录下,用来存放SELinux安全策略文件
4.2 mknod
mknod用于创建Linux中的设备文件,函数原型如下:
int mknod(const char* path, mode_t mode, dev_t dev) {
}
参数:
path:设备所在目录
mode:指定设备的类型和读写访问标志,可能的类型
参数 含义
S_IFMT type of file ,文件类型掩码
S_IFREG regular 普通文件
S_IFBLK block special 块设备文件
S_IFDIR directory 目录文件
S_IFCHR character special 字符设备文件
S_IFIFO fifo 管道文件
S_IFNAM special named file 特殊文件
S_IFLNK symbolic link 链接文件
dev: 表示设备,由makedev(1, 9) 函数创建,9为主设备号、1为次设备号
5、 初始化日志输出、挂载分区设备
定义在platform/system/core/init/init.cpp
int main(int argc, char** argv) {
......
if (is_first_stage) {
......
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
if (!DoFirstStageMount()) {
LOG(ERROR) << "Failed to mount required partitions early ...";
panic();//重启系统
}
......
}
......
}
5.1 InitKernelLogging
定义在platform/system/core/init/log.cpp
InitKernelLogging首先是将标准输入输出重定向到”/sys/fs/selinux/null”,然后调用InitLogging初始化log日志系统
void InitKernelLogging(char* argv[]) {
// Make stdin/stdout/stderr all point to /dev/null.
int fd = open("/sys/fs/selinux/null", O_RDWR);//打开文件
if (fd == -1) {
int saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger);
errno = saved_errno;
PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";
}
/*
* dup2(int old_fd, int new_fd) 的作用是复制文件描述符,将old复制到new,下文中将
* 0、1、2绑定到null设备上,通过标准的输入输出无法输出信息
*/
dup2(fd, 0);//重定向标准输入stdin
dup2(fd, 1);//重定向标准输出stdout
dup2(fd, 2);//重定向标准错误stderr
if (fd > 2) close(fd);
android::base::InitLogging(argv, &android::base::KernelLogger);//初始化log
}
5.1.1 InitLogging
定义在platform/system/core/base/logging.cpp
InitLogging主要工作是设置logger和aborter的处理函数,然后设置日志系统输出等级
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
/*
* C++中foo(std::forward<T>(arg))表示将arg按原本的左值或右值,传递给foo方法,
* LogFunction& 这种表示是左值,LogFunction&&这种表示是右值
*/
SetLogger(std::forward<LogFunction>(logger));//设置logger处理函数
SetAborter(std::forward<AbortFunction>(aborter));//设置aborter处理函数
if (gInitialized) {
return;
}
gInitialized = true;
// Stash the command line for later use. We can use /proc/self/cmdline on
// Linux to recover this, but we don't have that luxury on the Mac/Windows,
// and there are a couple of argv[0] variants that are commonly used.
if (argv != nullptr) {
std::lock_guard<std::mutex> lock(LoggingLock());
ProgramInvocationName() = basename(argv[0]);
}
const char* tags = getenv("ANDROID_LOG_TAGS");//获取系统当前日志输出等级
if (tags == nullptr) {
return;
}
std::vector<std::string> specs = Split(tags, " ");//将tags以空格拆分成数组
for (size_t i = 0; i < specs.size(); ++i) {
// "tag-pattern:[vdiwefs]"
std::string spec(specs[i]);
if (spec.size() == 3 && StartsWith(spec, "*:")) {//如果字符数为3且以*:开头
//那么根据第三个字符来设置日志输出等级(比如*:d,就是DEBUG级别)
switch (spec[2]) {
case 'v':
gMinimumLogSeverity = VERBOSE;
continue;
case 'd':
gMinimumLogSeverity = DEBUG;
continue;
case 'i':
gMinimumLogSeverity = INFO;
continue;
case 'w':
gMinimumLogSeverity = WARNING;
continue;
case 'e':
gMinimumLogSeverity = ERROR;
continue;
case 'f':
gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
continue;
// liblog will even suppress FATAL if you say 's' for silent, but that's
// crazy!
case 's':
gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
continue;
}
}
LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
<< ")";
}
}
5.1.2 KernelLogger
定义在platform/system/core/base/logging.cpp
在InitKernelLogging方法中有句调用android::base::InitLogging(argv, &android::base::KernelLogger);这句的作用就是将KernelLogger函数作为log日志的处理函数,KernelLogger主要作用就是将要输出的日志格式化之后写入到 /dev/kmsg 设备中
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
const char* tag, const char*, unsigned int, const char* msg) {
// clang-format off
static constexpr int kLogSeverityToKernelLogLevel[] = {
[android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log
// level)
[android::base::DEBUG] = 7, // KERN_DEBUG
[android::base::INFO] = 6, // KERN_INFO
[android::base::WARNING] = 4, // KERN_WARNING
[android::base::ERROR] = 3, // KERN_ERROR
[android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT
[android::base::FATAL] = 2, // KERN_CRIT
};
// clang-format on
//static_assert是编译断言,如果第一个参数为true,那么编译就不通过,这里是判断kLogSeverityToKernelLogLevel数组个数不能大于7
static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
"Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
//打开 /dev/kmsg 文件
static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
if (klog_fd == -1) return;
//根据传入的日志等级得到Linux的日志等级,也就是kLogSeverityToKernelLogLevel对应下标的映射
int level = kLogSeverityToKernelLogLevel[severity];
// The kernel's printk buffer is only 1024 bytes.
// TODO: should we automatically break up long lines into multiple lines?
// Or we could log but with something like "..." at the end?
char buf[1024];
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);//格式化日志输出
if (size > sizeof(buf)) {
size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
level, tag, size);
}
iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = size;
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));//将日志写入到 /dev/kmsg 中
}
5.2 DoFirstStageMount
定义在platform/system/core/init/init_first_stage.cpp
主要作用是初始化特定设备并挂载
bool DoFirstStageMount() {
// Skips first stage mount if we're in recovery mode.
if (IsRecoveryMode()) {//如果是刷机模式,直接跳过挂载
LOG(INFO) << "First stage mount skipped (recovery mode)";
return true;
}
// Firstly checks if device tree fstab entries are compatible.
//如果fstab/compatible的值不是android,fstab,直接跳过挂载
if (!is_android_dt_value_expected("fstab/compatible", "android,fstab")) {
LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
return true;
}
std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
if (!handle) {
LOG(ERROR) << "Failed to create FirstStageMount";
return false;
}
return handle->DoFirstStageMount();//主要是初始化特定设备并挂载
}
5.2.1 FirstStageMount::Create()
定义在platform/system/core/init/init_first_stage.cpp
FirstStageMount::FirstStageMount()
: need_dm_verity_(false), device_tree_fstab_(fs_mgr_read_fstab_dt(), fs_mgr_free_fstab) {
if (!device_tree_fstab_) {
LOG(ERROR) << "Failed to read fstab from device tree";
return;
}
// Stores device_tree_fstab_->recs[] into mount_fstab_recs_ (vector<fstab_rec*>)
// for easier manipulation later, e.g., range-base for loop.
for (int i = 0; i < device_tree_fstab_->num_entries; i++) {
mount_fstab_recs_.push_back(&device_tree_fstab_->recs[i]);//将挂载信息放入数组中存起来
}
}
std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
if (IsDtVbmetaCompatible()) {
return std::make_unique<FirstStageMountVBootV2>();
} else {
return std::make_unique<FirstStageMountVBootV1>();
}
}
5.2.1 handle->DoFirstStageMount
定义在platform/system/core/init/init_first_stage.cpp
bool FirstStageMount::DoFirstStageMount() {
// Nothing to mount.
if (mount_fstab_recs_.empty()) return true;
if (!InitDevices()) return false;
if (!MountPartitions()) return false;
return true;
}
6、启用SELinux安全策略
SELinux是 Linux的一个扩张强制访问控制安全模块。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件
int main(int argc, char** argv) {
......
if (is_first_stage) {
......
SetInitAvbVersionInRecovery();//在刷机模式下初始化avb的版本,不是刷机模式直接跳过
// Set up SELinux, loading the SELinux policy.
selinux_initialize(true);//加载SELinux policy,也就是安全策略,
// 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) {//restorecon命令用来恢复SELinux文件属性即恢复文件的安全上下文
PLOG(ERROR) << "restorecon failed";
security_failure();//失败则重启系统
}
......
}
......
}
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);//设置selinux的日志输出处理函数
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);//设置selinux的记录权限检测的处理函数
if (in_kernel_domain) {//这里是分了两个阶段,第一阶段in_kernel_domain为true,第二阶段为false
LOG(INFO) << "Loading SELinux policy";
if (!selinux_load_policy()) {//加载selinux的安全策略
panic();
}
bool kernel_enforcing = (security_getenforce() == 1);//获取当前kernel的工作模式
bool is_enforcing = selinux_is_enforcing();//获取工作模式的配置
if (kernel_enforcing != is_enforcing) {//如果当前的工作模式与配置的不同,就将当前的工作模式改掉
if (security_setenforce(is_enforcing)) {
PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
security_failure();
}
}
std::string err;
if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
LOG(ERROR) << err;
security_failure();
}
// init's first stage can't set properties, so pass the time to the second stage.
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
} else {
selinux_init_all_handles();//第二阶段时初始化处理函数
}
}
7、开始第二阶段前的准备
这里主要就是设置一些变量如INIT_SECOND_STAGE,INIT_STARTED_AT,为第二阶段做准备,然后再次调用init的main函数,启动用户态的init进程
int main(int argc, char** argv) {
......
if (is_first_stage) {
......
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);//重新执行main方法,进入第二阶段
......
}
......
}
可能大家不明白调用execv方法就重新执行main方法,下面给出该方法原型解释。
函数原型
int execv(const char *progname, char *const argv[]); //#include <unistd.h>
功能介绍
execv会停止执行当前的进程,并且以progname应用进程替换被停止执行的进程,进程ID没有改变。
参数:
progname: 被执行的应用程序。
argv: 传递给应用程序的参数列表, 注意,这个数组的第一个参数应该是应用程序名字本身,并且最后一个参数应该为NULL,不参将多个参数合并为一个参数放入数组。
返回值:
如果应用程序正常执行完毕,那么execv是永远不会返回的;当execv在调用出错了,此时它的返回值应该是-1,具体的错误代码可以通过全局变量errno查看,还可以通过stderr得到具体的错误描述字符。
8、小结
init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略
下一篇我将讲解init进程第二阶段,未完待续。。。
更多推荐
所有评论(0)