Android系統啟動流程 init行程
- 1. 背景
- 2. 介紹
- 2.1 概述
- 2.2 init行程入口
- 3. ueventd/watchdogd跳轉
- 3.1 ueventd_main
- 3.2 watchdogd_main
- 3.3 InstallRebootSignalHandlers
- 4. 環境變數設定及創建檔案系統目錄并掛載相關的檔案系統
- 4.1 clearenv
- 4.2 setenv
- 4.3 mount
- 4.4 mknod
- 5. 初始化日志輸出、掛載磁區設備
- 5.1 InitKernelLogging
- 5.1.1 InitLogging
- 5.1.2 KernelLogger
- 5.2 DoFirstStageMount
- 5.2.1 FirstStageMount::Create()
- 5.2.2 handle->DoFirstStageMount
- 6. 啟用SELinux安全策略
- 6.1 SelinuxSetupKernelLogging
- 6.2 SelinuxInitialize
- 6.2.1 LoadPolicy
- IsSplitPolicyDevice
- LoadSplitPolicy
- LoadMonolithicPolicy
- 6.2.2 checkreqprot
- 7. 開始第二階段前的準備
- 8. 小結
1. 背景
最近因作業需要,正在學習Android系統的啟動流程,我們目前維護的代碼主要是Android 9.0系統,因此結合代碼,對整個流程進行學習熟悉,
2. 介紹
2.1 概述
Linux系統執行完初始化操作最后會執行根目錄下的init檔案,init是一個可執行程式,它的原始碼在platform/system/core/init/init.cpp,init行程是用戶空間的第一個行程,我們熟悉的app應用程式都是以它為父行程的,init行程入口函式是main函式,這個函式做的事情還是比較多的,主要分為三個部分
- init行程第一階段
- init行程第二階段
- init.rc檔案決議
依據這三個部分將init行程分三篇文章描述,本文主要講解第一階段,第一階段主要有以下內容
- ueventd/watchdogd跳轉
- 環境變數設定及創建檔案系統目錄并掛載相關的檔案系統
- 初始化日志輸出、掛載磁區設備
- 啟用SELinux安全策略
- 開始第二階段前的準備
2.2 init行程入口
Android init行程的入口檔案在system/core/init/init.cpp中,
其中的main函式為主要介紹部分,
int main(int argc, char** argv) {
/*
* 1.strcmp是String的一個函式,比較字串,相等回傳0
* 2.C++中0也可以表示false
* 3.basename是C庫中的一個函式,得到特定的路徑中的最后一個'/'后面的內容,
* 比如/sdcard/miui_recovery/backup,得到的結果是backup
* 4.InitKernelLogging做了一些重定向來記錄一些系統log主要就是我們常用的dev/kmsg的輸出
*/
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
if (argc > 1 && !strcmp(argv[1], "subcontext")) {
InitKernelLogging(argv);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
if (REBOOT_BOOTLOADER_ON_PANIC) {
//初始化重啟系統的處理信號,內部通過sigaction 注冊信號,當監聽到該信號時重啟系統
InstallRebootSignalHandlers();
}
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);
//注冊環境變數PATH
// _PATH_DEFPATH 是定義在bionic/libc/include/paths.h中
clearenv();
setenv("PATH", _PATH_DEFPATH, 1);
// 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));
if constexpr (WORLD_WRITABLE_KMSG) {
mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
}
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
// Mount staging areas for devices managed by vold
// See storage config details at http://source.android.com/devices/storage/
mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000");
// /mnt/vendor is used to mount vendor-specific partitions that can not be
// part of the vendor partition, e.g. because they are mounted read-write.
mkdir("/mnt/vendor", 0755);
// 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(FATAL) << "Failed to mount required partitions early ...";
}
SetInitAvbVersionInRecovery();
// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
global_seccomp();
// Set up SELinux, loading the SELinux policy.
SelinuxSetupKernelLogging();
SelinuxInitialize();
// 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(FATAL) << "restorecon failed of /init failed";
}
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(FATAL) << "execv(\"" << path << "\") failed";
}
......
}
3. ueventd/watchdogd跳轉
在main函式剛開始,會根據引數argv去判斷是否需要啟動ueventd和
watchdogd,其中ueventd是為了維護/dev/下的一些設備節點,比如洗掉或者增加,watchdogd就是常說的看門狗,是為了監測系統,保證系統的穩定性,比如出現鎖死,卡死之類的情況能及時處理,關于watchdogd可自行百度了解,InitKernelLogging做了一些重定向來記錄一些系統log主要就是我們常用的dev/kmsg的輸出,關于REBOOT_BOOTLOADER_ON_PANIC這個是否定義是在init的mk檔案中定義的,一般只有調式版本才會用到這個,定義了一些信號,當init行程崩潰時去重啟bootloader,我們也更容易發現一些問題
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, &sysfs_permissions, nullptr));
parser.AddSingleLineParser("/dev/",
std::bind(ParsePermissionsLine, _1, 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");
auto boot_devices = fs_mgr_get_boot_devices();
return DeviceHandler(std::move(dev_permissions), std::move(sysfs_permissions),
std::move(subsystems), std::move(boot_devices), 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!";
SelinuxSetupKernelLogging();//注冊selinux相關的用于列印log
SelabelInitialize();
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
定義在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
定義在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);
}
// Calling DoReboot() or LOG(FATAL) is not a good option as this is a signal handler.
// RebootSystem uses syscall() which isn't actually async-signal-safe, but our only option
// and probably good enough given this is already an error case and only enabled for
// development builds.
RebootSystem(ANDROID_RB_RESTART2, "bootloader");//重啟系統
};
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);
}
4. 環境變數設定及創建檔案系統目錄并掛載相關的檔案系統
is_first_stage 值是通過獲取INIT_SECOND_STAGE是否指向空來判斷的,此時系統剛啟動,環境變數等尚未初始化,所以此時is_first_stage 的值是ture,會進入if陳述句,剛開始會通過umask來清空和初始化創建檔案的權限掩碼,通過clearenv來清慷訓境變數,之后就會去創建一些開機用到的系統檔案并將其掛載,
定義在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);//清空檔案權限
//注冊環境變數PATH
// _PATH_DEFPATH 是定義在bionic/libc/include/paths.h中
clearenv();
setenv("PATH", _PATH_DEFPATH, 1);
// 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));
if constexpr (WORLD_WRITABLE_KMSG) {
mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
}
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
// Mount staging areas for devices managed by vold
// See storage config details at http://source.android.com/devices/storage/
mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000");
// /mnt/vendor is used to mount vendor-specific partitions that can not be
// part of the vendor partition, e.g. because they are mounted read-write.
mkdir("/mnt/vendor", 0755);
......
}
......
}
4.1 clearenv
主要負責清慷訓境變數,
int clearenv() {
char** e = environ;
if (e != NULL) {
for (; *e; ++e) {
*e = NULL;
}
}
return 0;
}
4.2 setenv
定義在uboot\common\cmd_nvedit.c
這個函式主要作用是將一個鍵值對放到一個Char陣列中,如果陣列中有key就替換,沒有就插入
int setenv(const char *varname, const char *varvalue)
{
const char * const argv[4] = { "setenv", varname, varvalue, NULL };
/* before import into hashtable */
if (!(gd->flags & GD_FLG_ENV_READY))
return 1;
if (varvalue == NULL || varvalue[0] == '\0')
return _do_env_set(0, 2, (char * const *)argv);
else
return _do_env_set(0, 3, (char * const *)argv);
}
4.3 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.4 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. 初始化日志輸出、掛載磁區設備
定義在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(FATAL) << "Failed to mount required partitions early ...";
}
......
}
......
}
5.1 InitKernelLogging
定義在system\core\init\log.cpp
void InitKernelLogging(char* argv[]) {
// Make stdin/stdout/stderr all point to /dev/null.
int fd = open("/sys/fs/selinux/null", O_RDWR);//打開檔案
//若開啟失敗,則記錄log
if (fd == -1) {
int saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
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, InitAborter);//初始化log
}
這里需要說明的是,dup2函式的作用是用來復制一個檔案的描述符,
通常用來重定向行程的stdin、stdout和stderr,
它的函式原形是:
int dup2(int oldfd, int targetfd)
該函式執行后,targetfd將變成oldfd的復制品,
因此上述程序其實就是:創建出__null__設備后,將0、1、2系結到__null__設備上,
因此init行程呼叫InitKernelLogging函式后,通過標準的輸入輸出無法輸出資訊,
5.1.1 InitLogging
定義在system\core\base\logging.cpp
InitLogging主要作業是設定logger和aborter的處理函式,然后設定日志系統輸出等級
//此處設定的是KernelLogger
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) {
SetDefaultTag(basename(argv[0]));
}
const char* tags = getenv("ANDROID_LOG_TAGS");//獲取系統當前日志輸出等級
if (tags == nullptr) {
return;
}
//根據TAG決定最小記錄等級
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
定義在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;
//通過iovec將log發送到dev/kmsg
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;
}
//滿足上述條件時,就會呼叫FirstStageMount的DoFirstStageMount函式
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()
定義在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(INFO) << "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]);//將掛載資訊放入陣列中存起來
}
auto boot_devices = fs_mgr_get_boot_devices();
device_handler_ =
std::make_unique<DeviceHandler>(std::vector<Permissions>{}, std::vector<SysfsPermissions>{},
std::vector<Subsystem>{}, std::move(boot_devices), false);
}
std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
if (IsDtVbmetaCompatible()) {
return std::make_unique<FirstStageMountVBootV2>();
} else {
return std::make_unique<FirstStageMountVBootV1>();
}
}
5.2.2 handle->DoFirstStageMount
定義在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) {
......
//此處應該是初始化安全框架:Android Verified Boot
//AVB主要用于防止系統檔案本身被篡改,還包含了防止系統回滾的功能,
//以免有人試圖回滾系統并利用以前的漏洞
SetInitAvbVersionInRecovery();//在刷機模式下初始化avb的版本,不是刷機模式直接跳過
// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
// 此處決定是否使能全域seccomp; 如果沒有使能, zygote也會使能
// 讀取/proc/cmdline內容, 如果包含androidboot.seccomp=global則呼叫set_global_seccomp_filter
// set_global_seccomp_filter位于bionic/libc/seccomp/seccomp_policy.cpp
global_seccomp();
// Set up SELinux, loading the SELinux policy.
SelinuxSetupKernelLogging();// 將selinux日志重定向到內核日志輸出, 即/dev/kmsg
SelinuxInitialize();//加載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.
//按selinux policy要求,重新設定init檔案屬性
if (selinux_android_restorecon("/init", 0) == -1) {//restorecon命令用來恢復SELinux檔案屬性即恢復檔案的安全背景關系
PLOG(FATAL) << "restorecon failed of /init failed";
}
......
}
......
}
6.1 SelinuxSetupKernelLogging
設定log callback,可以呼叫selinux_log把log寫入到kmsg里面
定位在system\core\init\selinux.cpp
void SelinuxSetupKernelLogging() {
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
}
6.2 SelinuxInitialize
AVB相關的資訊目前還不太了解,不做深入分析,
此處,我們看看selinux_initialize相關的作業:
定位在system\core\init\selinux.cpp
void SelinuxInitialize() {
Timer t;
LOG(INFO) << "Loading SELinux policy";
if (!LoadPolicy()) {// 加載SELinux策略
LOG(FATAL) << "Unable to load SELinux policy";
}
bool kernel_enforcing = (security_getenforce() == 1);// 從kernel獲取SELinux的狀態,和getenforce的實作一樣
bool is_enforcing = IsEnforcing();// 從bootargs里面獲取
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {// 如果kernel里面的SELinux狀態和bootargs的不一致,要設定成bootargs里面傳過來的值
PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
}
}
if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {// 由內核強制執行檢查保護
LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
}
// init's first stage can't set properties, so pass the time to the second stage.
// 獲取SELinux策略加載時間并設定到設定環境變數INIT_SELINUX_TOOK
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}
6.2.1 LoadPolicy
從Android8.0之后,因為Project Treble,system和vendor策略分離,所以Android P上走的是LoadSplitPolicy
bool LoadPolicy() {
/*
* 加載SELinux策略
* 如果IsSplitPolicyDevice回傳true, 執行LoadSplitPolicy, 否則執行LoadMonolithicPolicy
*/
return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}
IsSplitPolicyDevice
IsSplitPolicyDevice 檢測/system/etc/selinux/plat_sepolicy.cil檔案是否存在
constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";
bool IsSplitPolicyDevice() {
return access(plat_policy_cil_file, R_OK) != -1;
}
LoadSplitPolicy
bool LoadSplitPolicy() {
// IMPLEMENTATION NOTE: Split policy consists of three CIL files:
// * platform -- policy needed due to logic contained in the system image,
// * non-platform -- policy needed due to logic contained in the vendor image,
// * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
// with newer versions of platform policy.
//
// secilc is invoked to compile the above three policy files into a single monolithic policy
// file. This file is then loaded into the kernel.
// Load precompiled policy from vendor image, if a matching policy is found there. The policy
// must match the platform policy on the system image.
/*
* 依次查找以下預編譯SELinux檔案
* "/vendor/etc/selinux/precompiled_sepolicy"
* "/odm/etc/selinux/precompiled_sepolicy"
*/
std::string precompiled_sepolicy_file;
if (FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {// 先找odm磁區,再找vendor磁區
unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));// open file
if (fd != -1) {
if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {// 加載到kernel
LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
return false;
}
return true;
}
}
// No suitable precompiled policy could be loaded
LOG(INFO) << "Compiling SELinux policy";
// Determine the highest policy language version supported by the kernel
set_selinuxmnt("/sys/fs/selinux");
// 將策略檔案寫入到 /sys/fs/selinux/load
int max_policy_version = security_policyvers();
if (max_policy_version == -1) {
PLOG(ERROR) << "Failed to determine highest policy version supported by kernel";
return false;
}
// We store the output of the compilation on /dev because this is the most convenient tmpfs
// storage mount available this early in the boot sequence.
char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));
if (compiled_sepolicy_fd < 0) {
PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;
return false;
}
// Determine which mapping file to include
std::string vend_plat_vers;
if (!GetVendorMappingVersion(&vend_plat_vers)) {
return false;
}
std::string mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");
// vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
// nonplat_sepolicy.cil.
std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
// For backward compatibility.
// TODO: remove this after no device is using nonplat_sepolicy.cil.
vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
plat_pub_versioned_cil_file.clear();
} else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
return false;
}
// odm_sepolicy.cil is default but optional.
std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");
if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
odm_policy_cil_file.clear();
}
const std::string version_as_string = std::to_string(max_policy_version);
// clang-format off
std::vector<const char*> compile_args {
"/system/bin/secilc",
plat_policy_cil_file,
"-m", "-M", "true", "-G", "-N",
// Target the highest policy language version supported by the kernel
"-c", version_as_string.c_str(),
mapping_file.c_str(),
"-o", compiled_sepolicy,
// We don't care about file_contexts output by the compiler
"-f", "/sys/fs/selinux/null", // /dev/null is not yet available
};
// clang-format on
if (!plat_pub_versioned_cil_file.empty()) {
compile_args.push_back(plat_pub_versioned_cil_file.c_str());
}
if (!vendor_policy_cil_file.empty()) {
compile_args.push_back(vendor_policy_cil_file.c_str());
}
if (!odm_policy_cil_file.empty()) {
compile_args.push_back(odm_policy_cil_file.c_str());
}
compile_args.push_back(nullptr);
if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
unlink(compiled_sepolicy);
return false;
}
unlink(compiled_sepolicy);
LOG(INFO) << "Loading compiled SELinux policy";
if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
return false;
}
return true;
}
LoadSplitPolicy 加載selinux 策略時,首先從預先編譯好的二進制檔案precompiled_sepolicy讀取
如果沒有找到的話,再用secilc命令編譯所有的cil,重新得到一個二進制策略檔案
FindPrecompiledSplitPolicy
這個函式會查找兩個地方,/odm/etc/selinux和/vendor/etc/selinux
/vendor/etc/selinux/precompiled_sepolicy
/odm/etc/selinux/precompiled_sepolicy
如果有odm磁區,precompiled_sepolicy會放在odm磁區,否則在vendor磁區,因此讀取的時候也是先找odm再找vendor
LoadMonolithicPolicy
從檔案/sepolicy加載策略
bool LoadMonolithicPolicy() {
LOG(VERBOSE) << "Loading SELinux policy from monolithic file";
if (selinux_android_load_policy() < 0) {
PLOG(ERROR) << "Failed to load monolithic SELinux policy";
return false;
}
return true;
}
6.2.2 checkreqprot
設定"checkreqprot"標記的初始值,
"0"表示由內核強制執行檢查保護(包括其中隱含的所有執行保護)
"1"表示由應用程式自己主動請求執行檢查保護
默認值由內核在編譯時確定,也可以在運行時通過/sys/fs/selinux/checkreqprot修改
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);//再次呼叫init的main函式,啟動用戶態的init行程
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(FATAL) << "execv(\"" << path << "\") failed";
// 內核態的行程不應該退出
}
......
}
可能大家不明白呼叫execv方法就重新執行main方法,下面給出該方法原型解釋,
函式原型
int execv(const char *progname, char *const argv[]); //#include <unistd.h>
功能介紹
execv會停止執行當前的行程,并且以progname應用行程替換被停止執行的行程,行程ID沒有改變,
引數:
progname: 被執行的應用程式,
argv: 傳遞給應用程式的引數串列, 注意,這個陣列的第一個引數應該是應用程式名字本身,并且最后一個引數應該為NULL,不參將多個引數合并為一個引數放入陣列,
回傳值:
如果應用程式正常執行完畢,那么execv是永遠不會回傳的;當execv在呼叫出錯了,此時它的回傳值應該是-1,具體的錯誤代碼可以通過全域變數errno查看,還可以通過stderr得到具體的錯誤描述字符,
第一階段結束時,會復位一些資訊,并設定一些環境變數,
最后啟動用戶態的用戶態的init行程,進入init階段二,
8. 小結
梳理源代碼真是讓人痛苦的一件事,龐大的代碼量很難完全梳理一遍,很多細致的函式了解功能及可,細致分析過于浪費時間,下一篇文章講解init行程第二階段,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/349666.html
標籤:其他
