You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
intueventd_main(intargc, 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/* Prevent fire-and-forget children from becoming zombies. * If we should need to wait() for some children in the future * (as opposed to none right now), double-forking here instead * of ignoring SIGCHLD may be the better solution. */signal(SIGCHLD, SIG_IGN);//忽略子进程终止信号InitKernelLogging(argv); //初始化日志输出LOG(INFO) << "ueventd started!";
selinux_callbackcb;
cb.func_log=selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);//注册selinux相关的用于打印log的回调函数ueventd_parse_config_file("/ueventd.rc"); //解析.rc文件,这个后续再讲ueventd_parse_config_file("/vendor/ueventd.rc");
ueventd_parse_config_file("/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::stringhardware=android::base::GetProperty("ro.hardware", "");
ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());
device_init();//创建一个socket来接收uevent,再对内核启动时注册到/sys/下的驱动程序进行“冷插拔”处理,以创建对应的节点文件。pollfdufd;
ufd.events=POLLIN;
ufd.fd=get_device_fd();//获取device_init中创建出的socketwhile (true) {//开户无限循环,随时监听驱动ufd.revents=0;
intnr=poll(&ufd, 1, -1);//监听来自驱动的ueventif (nr <= 0) {
continue;
}
if (ufd.revents&POLLIN) {
handle_device_fd();//驱动程序进行“热插拔”处理,以创建对应的节点文件。
}
}
return0;
}
staticvoidinstall_reboot_signal_handlers() {
// 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.structsigactionaction;
memset(&action, 0, sizeof(action));
sigfillset(&action.sa_mask);//将所有信号加入至信号集action.sa_handler= [](int) {
// panic() reboots to bootloaderpanic(); //重启系统
};
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);
#endifsigaction(SIGSYS, &action, nullptr);
sigaction(SIGTRAP, &action, nullptr);
}
if (is_first_stage) {
...
// 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();//重启系统
}
...
}
voidInitLogging(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]);
}
constchar*tags=getenv("ANDROID_LOG_TAGS");//获取系统当前日志输出等级if (tags==nullptr) {
return;
}
std::vector<std::string>specs=Split(tags, " "); //将tags以空格拆分成数组for (size_ti=0; i<specs.size(); ++i) {
// "tag-pattern:[vdiwefs]"
std::stringspec(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
<< ")";
}
}
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;
}
for (auto mount_point : {"/system", "/vendor", "/odm"}) {
fstab_rec*fstab_rec=fs_mgr_get_entry_for_mount_point(device_tree_fstab_.get(), mount_point); //这里主要是把挂载的信息解析出来if (fstab_rec!=nullptr) {
mount_fstab_recs_.push_back(fstab_rec);//将挂载信息放入数组中存起来
}
}
}
四、启用SELinux安全策略
SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」
和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。
在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件
if (is_first_stage) {
...
//Avb即Android Verfied boot,功能包括Secure Boot, verfying boot 和 dm-verity, //原理都是对二进制文件进行签名,在系统启动时进行认证,确保系统运行的是合法的二进制镜像文件。//其中认证的范围涵盖:bootloader,boot.img,system.imgSetInitAvbVersionInRecovery();//在刷机模式下初始化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./* * 1.这句英文大概意思是,我们执行第一遍时是在kernel domain,所以要重新执行init文件,切换到init domain, * 这样SELinux policy才已经加载进来了 * 2.后面的security_failure函数会调用panic重启系统 */if (restorecon("/init") ==-1) { //restorecon命令用来恢复SELinux文件属性即恢复文件的安全上下文PLOG(ERROR) << "restorecon failed";
security_failure(); //失败则重启系统
}
...
}
4.1 selinux_initialize
定义在platform/system/core/init/init.cpp
staticvoidselinux_initialize(boolin_kernel_domain) {
Timert;
selinux_callbackcb;
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,第二阶段为falseLOG(INFO) << "Loading SELinux policy";
if (!selinux_load_policy()) { //加载selinux的安全策略panic();
}
boolkernel_enforcing= (security_getenforce() ==1); //获取当前kernel的工作模式boolis_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();
}
}
if (!write_file("/sys/fs/selinux/checkreqprot", "0")) {
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_ms()).c_str(), 1);
} else {
selinux_init_all_handles(); //第二阶段时初始化处理函数
}
}
intselinux_android_load_policy_from_fd(intfd, constchar*description)
{
intrc;
structstatsb;
void*map=NULL;
staticintload_successful=0;
/* * Since updating policy at runtime has been abolished * we just check whether a policy has been loaded before * and return if this is the case. * There is no point in reloading policy. */if (load_successful){
selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
return0;
}
set_selinuxmnt(SELINUXMNT); //SELINUXMNT的值为 /sys/fs/selinux if (fstat(fd, &sb) <0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n",
description, strerror(errno));
return-1;
}
/* * mmap 的作用是将一个文件或者其它对象映射进内存 */map=mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map==MAP_FAILED) {
selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n",
description, strerror(errno));
return-1;
}
rc=security_load_policy(map, sb.st_size);
if (rc<0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not load policy: %s\n",
strerror(errno));
munmap(map, sb.st_size);
return-1;
}
munmap(map, sb.st_size);
selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
load_successful=1;
return0;
}
if (is_first_stage) {
...
setenv("INIT_SECOND_STAGE", "true", 1);
static constexpr uint32_tkNanosecondsPerMillisecond=1e6;
uint64_tstart_ms=start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", StringPrintf("%"PRIu64, 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();
}
前言
上一篇中讲到,Linux系统执行完初始化操作最后会执行根目录下的init文件,init是一个可执行程序,
它的源码在platform/system/core/init/init.cpp。
之前我们讲过init进程是用户空间的第一个进程,我们熟悉的app应用程序都是以它为父进程的,
init进程入口函数是main函数,这个函数做的事情还是比较多的,主要分为三个部分
由于内容比较多,所以对于init的讲解,我分为三个章节来讲,本文只讲解第一阶段,第一阶段主要有以下内容
本文涉及到的文件
一、ueventd/watchdogd跳转及环境变量设置
1.1 ueventd_main
定义在platform/system/core/init/ueventd.cpp
Android根文件系统的映像中不存在“/dev”目录,该目录是init进程启动后动态创建的。
因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
ueventd通过两种方式创建设备节点文件。
第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。
1.2 watchdogd_main
定义在platform/system/core/init/watchdogd.cpp
"看门狗"本身是一个定时器电路,内部会不断的进行计时(或计数)操作,计算机系统和"看门狗"有两个引脚相连接,
正常运行时每隔一段时间就会通过其中一个引脚向"看门狗"发送信号,"看门狗"接收到信号后会将计时器清零并重新开始计时,
而一旦系统出现问题,进入死循环或任何阻塞状态,不能及时发送信号让"看门狗"的计时器清零,当计时结束时,
"看门狗"就会通过另一个引脚向系统发送“复位信号”,让系统重启
watchdogd_main主要是定时器作用,而DEV_NAME就是那个引脚
1.3 install_reboot_signal_handlers
定义在platform/system/core/init/init.cpp
这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
1.4 add_environment
定义在platform/system/core/init/init.cpp
这个函数主要作用是将一个键值对放到一个Char数组中,如果数组中有key就替换,没有就插入,跟Java中的Map差不多
二、 挂载文件系统并创建目录
2.1 mount
mount是用来挂载文件系统的,mount属于Linux系统调用
参数:
source:将要挂上的文件系统,通常是一个设备名。
target:文件系统所要挂载的目标目录。
filesystemtype:文件系统的类型,可以是"ext2","msdos","proc","ntfs","iso9660"。。。
mountflags:指定文件系统的读写访问标志,可能值有以下
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安全策略文件
2.2 mknod
mknod用于创建Linux中的设备文件
参数:
path:设备所在目录
mode:指定设备的类型和读写访问标志
可能的类型
dev 表示设备,由makedev(1, 9) 函数创建,9为主设备号、1为次设备号
2.3 其他命令
mkdir也是Linux系统调用,作用是创建目录,第一个参数是目录路径,第二个是读写权限
chmod用于修改文件/目录的读写权限
setgroups 用来将list 数组中所标明的组加入到目前进程的组设置中
这里我解释下文件的权限,也就是类似0755这种,要理解权限首先要明白「用户和组」的概念
Linux系统可以有多个用户,多个用户可以属于同一个组,用户和组的概念就像我们人和家庭一样,人属于家庭的一分子,用户属于一个组,我们一般在Linux终端输入ls -al之后会有如下结果
第一个foxleezh表示所有者,这里的foxleezh表示一个用户,类似foxleezh这个人
第二个foxleezh表示文件所有用户组,这里的foxleezh表示一个组,类似foxleezh这个家庭
然后我们来看下dwxr-xr-x,这个要分成四部分来理解,d表示目录(文件用 - 表示),wxr表示所有者权限,xr表示文件所有用户组的权限,x表示其他用户的权限
那么dwxr-xr-x还有种表示方法就是751,是不是感觉跟0755差不多了,那0755前面那个0表示什么意思呢?
0755前面的0跟suid和guid有关
三、 初始化日志输出、挂载分区设备
3.1 InitKernelLogging
定义在platform/system/core/init/log.cpp
InitKernelLogging首先是将标准输入输出重定向到"/sys/fs/selinux/null",然后调用InitLogging初始化log日志系统
3.2 InitLogging
定义在platform/system/core/base/logging.cpp
InitLogging主要工作是设置logger和aborter的处理函数,然后设置日志系统输出等级
3.3 KernelLogger
定义在platform/system/core/base/logging.cpp
在InitKernelLogging方法中有句调用
这句的作用就是将KernelLogger函数作为log日志的处理函数,KernelLogger主要作用就是将要输出的日志格式化之后写入到 /dev/kmsg 设备中
3.3 DoFirstStageMount
定义在platform/system/core/init/init_first_stage.cpp
主要作用是初始化特定设备并挂载
3.4 handle->DoFirstStageMount
定义在platform/system/core/init/init_first_stage.cpp
这里主要作用是去解析/proc/device-tree/firmware/android/fstab,然后得到"/system", "/vendor", "/odm"三个目录的挂载信息
四、启用SELinux安全策略
SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」
和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。
在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件
4.1 selinux_initialize
定义在platform/system/core/init/init.cpp
4.2 selinux_set_callback
定义在platform/external/selinux/libselinux/src/callbacks.c
主要就是根据不同的type设置回调函数,selinux_log,selinux_audit这些都是函数指针
4.3 selinux_load_policy
定义在platform/system/core/init/init.cpp
这里区分了两种情况,这两种情况只是区分从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取
,第二个是从 /sepolicy 读取,他们最终都是调用selinux_android_load_policy_from_fd方法
4.4 selinux_android_load_policy_from_fd
定义在platform/external/selinux/libselinux/src/android/android.c
这个函数主要作用是设置selinux_mnt 的值为/sys/fs/selinux ,然后调用security_load_policy
4.5 security_load_policy
定义在platform/external/selinux/libselinux/src/load_policy.c
这个函数主要作用就是写入data到/sys/fs/selinux,data其实就是之前找的那些策略文件,由此我们知道,看起来selinux_load_policy调用这么多代码,
其实只是将策略文件拷贝到 /sys/fs/selinux 目录下
4.6 security_setenforce
定义在platform/external/selinux/libselinux/src/setenforce.c
selinux有两种工作模式:
不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce 文件, 0表示permissive 1表示enforcing
五、开始第二阶段前的准备
这里主要就是设置一些变量如INIT_SECOND_STAGE,INIT_STARTED_AT,为第二阶段做准备,然后再次调用init的main函数,启动用户态的init进程
小结
init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略
The text was updated successfully, but these errors were encountered: