基於9.0源碼解析,代碼的路徑在第一行註釋代碼。本文爲平時筆記的整理,不免有錯誤,歡迎你們指正,若有錯誤給您帶來困惑和損失,也請見諒。html
init進程是Android系統的第一個用戶進程,init進程由Linux內核啓動,入口爲/system/core/init/init.cpp中的main方法。它的執行主要包括兩個部分: first_stage和second_stage.這兩個階段作了不少的初始化工做,接下來的兩篇文章就探究一下這個過程。linux
/* system/core/init/init.cpp */
/**
* main函數:第一個參數傳入參數的個數,包括可執行程序的名稱。第二個參數爲一個字符串數組,
* 爲具體的傳入參數,其中args[0]爲可執行程序的名稱。
*/
int main(int argc, char** argv) {
/**
* 1.basename函數:當傳入一個路徑名參數時,將返回最後一個「/」後面的字符串,若是最後一個「/」爲空,則返回倒數第二個「/」和倒數第一個"/"以前的字符串。例如:
* "/data/data/com/xray",結果返回爲xray.
* 2.strcmp函數:比較傳入的兩個字符串,若是返回值爲0則相等,非0則爲不等。可是在c語言中0爲false,非0爲true.
*/
if (!strcmp(basename(argv[0]), "ueventd")) {//當argv[0]爲ueventd,也就是程序名爲ueventd,則進入ueventd_main,
//ueventd爲init進程建立的子進程,用於建立設備文件,同時接收uevent事件
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {//和ueventd同樣,watchdogd(看門狗)進程的入口,用於程序出問題時重啓系統。
return watchdogd_main(argc, argv);
}
if (argc > 1 && !strcmp(argv[1], "subcontext")) {//subcontext是android9新出現的,它的出現就是爲了隔離system和vendor
InitKernelLogging(argv); //
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
if (REBOOT_BOOTLOADER_ON_PANIC) { //初始化重啓信號的監聽,當監聽到重啓信號時,重啓系統
InstallRebootSignalHandlers();
}
...
return 0;
}
複製代碼
/* /system/core/rootdir/init.rc */
on early-init
...
start ueventd
## Daemon processes to be run by init.
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
複製代碼
從rc文件咱們知道ueventd進程依賴於/sbin/ueventd 這個二進制文件,可是它的代碼實如今哪裏了,在Android.mk文件中,發現了端倪,/sbin/ueventd只是init這個二進制文件的軟連接,因此ueventd的真正入口就是咱們上面的/system/core/init/init.cpp的main方法。android
/* /system/core/init/Android.mk */
# Create symlinks.
LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd; \
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/watchdogd
複製代碼
下面看看ueventd的實現數組
/* /system/core/init/ueventd.cpp */
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);
InitKernelLogging(argv);
LOG(INFO) << "ueventd started!";
SelinuxSetupKernelLogging();
SelabelInitialize();
DeviceHandler device_handler = CreateDeviceHandler();//解析rc文件
UeventListener uevent_listener;
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, device_handler);
cold_boot.Run();//遍歷/sys目錄, 將"add"寫入uevent文件, 根據uevent文件建立設備節點
}
// 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_listener.Poll([&device_handler](const Uevent& uevent) {//熱插拔,獲取內核uevent事件,動態的建立設備節點 HandleFirmwareEvent(uevent); device_handler.HandleDeviceEvent(uevent); return ListenerAction::kContinue; }); return 0; } 複製代碼
ueventd的啓動分爲兩個部分:安全
和ueventd同樣, watchdogd也是init的另外一面. 這時, 它被用做硬件watchdog定時器(timer) (/dev/watchdog,若是有的話)在用戶態中的接口. 設置一個超時((timeout)變量, 每隔一段時 間就發送一個keepalive信號(一個""字節)。若是超過了超時變量規定的時間,watchdogd 還沒能及時發送keepalive信號,硬件watchdog定時器就會發出一箇中斷,要求內核重啓,下面看看其實現:bash
/* system/core/init/watchdogd.cpp */
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);
if (fd == -1) {
PLOG(ERROR) << "Failed to open " << DEV_NAME;
return 1;
}
int timeout = interval + margin;
int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);//ioctl 是設備驅動程序中對設備的I/O通道進行管理的函數,
//這一個參數就是open函數返回的文件描述符,
//第二個參數是用戶程序對設備的控制命令(這裏是設置超時時間),
第三個參數是超時的值
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);//寫入「」字符串,告訴watchdog定時器(/dev/watchdog)一切正常,若是系統發生故障了,
//watchdogd 還沒能及時發送這個""字符串,硬件watchdog定時器就會發出一箇中斷,要求內核重啓
sleep(interval);
}
}
## subcontext
init 進程具備幾乎不受限制的權限,並可以使用系統分區和verdor分區(供應商分區)中的輸入腳本在啓動過程當中初始化系統。該訪問權限會致使 Treble 系統/供應商拆分中出現巨大漏洞,由於供應商腳本可能會指示 init 訪問不屬於穩定系統-供應商 ABI(應用二進制接口)的文件、屬性等。
供應商 init 已設計爲使用單獨的安全加強型 Linux (SELinux) 域 vendor_init,以利用供應商專屬權限來運行 /vendor 中的命令,從而填補此漏洞. 因此若是想要在init中運行腳本須要將其添加到vendor中
複製代碼
當內部經過sigaction註冊信號,當監聽到信號時,重啓bootloader.數據結構
/* /system/core/init/init.cpp */
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. //當init crashes時重啓bootloader 而不是內核panic這種默認的行爲。由於這樣能防止因爲錯誤配置致使的循環啓動 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);
}
複製代碼
init進程的執行主要涉及到兩個階段first stage和second stage,下面看看first stageapp
int main(int argc, char** argv) {
...
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);// 這個函數的做用是設置用戶建立文件和目錄時的默認權限,若是umask爲0,用戶建立的目錄權限就是:目錄在各位上的最大權限減去umaks值
//目錄的最大權限是666(由於目錄不須要執行權限),由於umask爲0,因此這裏建立的目錄的默認的權限就是666,若是umask值爲0022,那麼目錄的默認權限就是644
clearenv();
setenv("PATH", _PATH_DEFPATH, 1);//設置環境變量/sbin:/system/sbin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
// 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");//將臨時文件系統tmpfs掛載到/dev目錄 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/kmsg 輸出內核日誌
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...
//tmpfs已經掛載到/dev 而且有了/dev/kmsg文件,這個時候就能夠初始化內核日誌系統了,經過/dev/kmsg.就能夠將內核日誌輸出到外界了
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
/**
* 初始化驗證啓動,它會確保全部已經執行的代碼均來自可信來源,以防止受到攻擊和損壞。它能夠創建一條從受硬件保護的
*信任根到引導加載程序,再到啓動分區和其已驗證分區(包括system、 vendor)的完整信任鏈,在設備啓動的過程當中,都會在
* 進入下一個階段前驗證下一個階段的完整行和真實性。
*/
SetInitAvbVersionInRecovery();
// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
//爲了防止一些惡意的程序直接的調用system call, Android 新出了seccomp,它的做用就是過濾一些System call,防止應用層的app直接調用。
global_seccomp();
// Set up SELinux, loading the SELinux policy.
SelinuxSetupKernelLogging();
SelinuxInitialize();//啓動SELinux,加載SELinux策略
// 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);//這裏的path其實就是/system/bin/init 至關於再次調用main方法,執行second stage邏輯 // execv() only returns if an error happened, in which case we // panic and never fall through this conditional. PLOG(FATAL) << "execv(\"" << path << "\") failed"; } ... return 0; } 複製代碼
文件系統 | 描述 |
---|---|
tmpfs | 種虛擬內存文件系統,它會將全部的文件存儲在虛擬內存中,若是你將tmpfs文件系統卸載後,那麼其下的全部的內容將不復存在,斷電後內容也不復存在 |
devpts | 爲僞終端提供了一個標準接口,它的標準掛接點是/dev/ pts。只要pty的主複合設備/dev/ptmx被打開,就會在/dev/pts下動態的建立一個新的pty設備文件 |
proc | 一個很是重要的虛擬文件系統,它能夠看做是內核內部數據結構的接口,經過它咱們能夠得到系統的信息,同時也可以在運行時修改特定的內核參數 |
sysfs | 與proc文件系統相似,也是一個不佔有任何磁盤空間的虛擬文件系統。它一般被掛接在/sys目錄下。sysfs文件系統是Linux2.6內核引入的,它把鏈接在系統上的設備和總線組織成爲一個分級的文件,使得它們能夠在用戶空間存取 |
InitKernelLogging的做用是將標準輸入,標準輸出,標準錯誤重定向到/sys/fs/selinux/nulldom
/* /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);
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 fd1, int fd2) 將fd1複製到fd2,也就是i標準輸入,標準輸出,標準錯誤,都指向了/sys/fs/selinux/null
*/
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) close(fd);
android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
}
複製代碼
咱們看看最後一行代碼,這行代碼的路徑在system/core/base/logging.cpp, 它主要作的事情是設置日誌級別等。咱們着重看看第二個參數android::base::KernelLogger,它的代碼實現以下:socket
/* system/core/base/logging.cpp */
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(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
"Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
if (klog_fd == -1) return;
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中 } 複製代碼
因此咱們想查看內核日誌的話能夠經過:
cat /dev/kmsg
複製代碼
另外還有兩種查看內核日誌方法:
cat /proc/kmsg
複製代碼
dmesg
複製代碼
上面把init中第一階段的工做介紹完了,若是把每一個細節都弄明白確實很困難,有時一個c函數就的研究半天,可是我相信這個是值得的。下一篇文中會開始第二個階段,包括rc文件的解析,SELinux相關的問題(這個問題在Android P中很是的讓人頭疼)等。好了,這篇就就寫到這裏,咱們下篇見。