編輯:Android開發教程
======================================================== ========================================================
= 【原創文章】:參考部分博客內容,學習之余進行了大量的篩減細化分析 = = 【特殊申明】:避諱抄襲侵權之嫌疑,特此說明,歡迎轉載! =
======================================================== ========================================================
*************************************************************************** ***************************************************************************
* Android源碼下載:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/ * * 源碼編譯可參考【牛肉面大神之作】:http://blog.csdn.net/cjpx00008/article/details/60474883 *
*************************************************************************** ***************************************************************************
【開篇說明】
在【Android啟示錄】中,提到了主要的分析對象和分享內容,拋開Android內核級的知識點,學習Android第一步便是“init”,作為天字第一號進程,代碼羞澀難懂,但是也極其重要,熟悉init的原理對後面Zygote -- SystemServer -- 核心服務等一些列源碼的研究是有很大作用的,所以既然說研究Android源碼,就先拿init “庖丁解牛”!
【正文開始】
Init進程,它是一個由內核啟動的用戶級進程,當Linux內核啟動之後,運行的第一個進程是init,這個進程是一個守護進程,確切的說,它是Linux系統中用戶控件的第一個進程,所以它的進程號是1。它的生命周期貫穿整個linux 內核運行的始終, linux中所有其它的進程的共同始祖均為init進程,可以通過“adb shell ps | grep init”查看進程號。
Android init進程的入口文件在system/core/init/init.cpp中,由於init是命令行程序,所以分析init.cpp首先應從main函數開始:
int main(int argc, char** argv) { // 入口函數main if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (!strcmp(basename(argv[0]), "watchdogd")) { return watchdogd_main(argc, argv); } // Clear the umask. umask(0); // 清除屏蔽字(file mode creation mask),保證新建的目錄的訪問權限不受屏蔽字影響。 add_environment("PATH", _PATH_DEFPATH); bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0); // 判斷是否是系統啟動的第一階段,只有啟動參數中有--second-stage才為第二階段
// 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.
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); // 掛載tmpfs文件系統
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL); // 掛載devpts文件系統
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)); // 掛載proc文件系統
mount("sysfs", "/sys", "sysfs", 0, NULL); // 掛載sysfs文件系統
}
以上代碼主要做的工作就是:【創建文件系統目錄並掛載相關的文件系統】
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */ // We must have some place other than / to create the device nodes for // kmsg and null, otherwise we won't be able to remount / read-only // later on. Now that tmpfs is mounted on /dev, we can actually talk // to the outside world. open_devnull_stdio(); // 屏蔽標准的輸入輸出 --> 定義在system/core/init/Util.cpp中 // init進程通過klog_init函數,提供輸出log信息的設備 --> 定義在system/core/libcutils/Klog.c中 klog_init(); // 對klog進行初始化 klog_set_level(KLOG_NOTICE_LEVEL); // NOTICE level
繼續分析源碼,接下來要做的就是初始化屬性域:
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */
/* 03. 初始化屬性域 */ NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage"); if (!is_first_stage) { // 引入SELinux機制後,通過is_first_stage區分init運行狀態 // Indicate that booting is in progress to background fw loaders, etc. close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); /* 檢測/dev/.booting文件是否可讀寫、創建等*/
property_init(); // 初始化屬性域 --> 定義於system/core/init/Property_service.cpp // If arguments are passed both on the command line and in DT, // properties set in DT always have priority over the command-line ones. process_kernel_dt(); process_kernel_cmdline(); // 處理內核命令行 // Propagate the kernel variables to internal variables // used by init as well as the current required properties. export_kernel_boot_props(); }
看一下property_init方法:位於system/core/init/Property_service.cpp中
void property_init() { if (__system_property_area_init()) { // 調用此函數初始化屬性域 ERROR("Failed to initialize property area\n"); exit(1); } }
繼續分析main函數:
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */ /* 03. 初始化屬性域 */
/* 04. 完成SELinux相關工作 */ // Set up SELinux, including loading the SELinux policy if we're in the kernel domain. selinux_initialize(is_first_stage); // 調用selinux_initialize啟動SELinux
詳細看一下selinux_initialize()函數:
static void selinux_initialize(bool in_kernel_domain) { // 區分內核態和用戶態 Timer t; //使用Timer計時,計算selinux初始化耗時 selinux_callback cb; cb.func_log = selinux_klog_callback; // 用於打印Log的回調函數 selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; // 用於檢查權限的回調函數 selinux_set_callback(SELINUX_CB_AUDIT, cb); if (in_kernel_domain) { // 內核態處理流程,第一階段in_kernel_domain為true INFO("Loading SELinux policy...\n"); // 該行log打印不出,INFO級別 // 用於加載sepolicy文件。該函數最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略配置文件 if (selinux_android_load_policy() < 0) { ERROR("failed to load policy: %s\n", strerror(errno)); security_failure(); } bool kernel_enforcing = (security_getenforce() == 1); // 內核中讀取的信息 bool is_enforcing = selinux_is_enforcing(); // 命令行中得到的信息 if (kernel_enforcing != is_enforcing) { // 用於設置selinux的工作模式。selinux有兩種工作模式: // 1、”permissive”,所有的操作都被允許(即沒有MAC),但是如果違反權限的話,會記錄日志 // 2、”enforcing”,所有操作都會進行權限檢查。在一般的終端中,應該工作於enforing模式 if (security_setenforce(is_enforcing)) { //設置selinux的模式,是開還是關 ERROR("security_setenforce(%s) failed: %s\n", is_enforcing ? "true" : "false", strerror(errno)); security_failure(); // 將重啟進入recovery mode } } if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) { security_failure(); } NOTICE("(Initializing SELinux %s took %.2fs.)\n", is_enforcing ? "enforcing" : "non-enforcing", t.duration()); //輸出selinux的模式,與初始化耗時
} else {
selinux_init_all_handles(); //如果啟動第二階段,調用該函數
}
}
回到main函數中繼續分析:
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */ /* 03. 初始化屬性域 */ /* 04. 完成SELinux相關工作 */
/* 05. 重新設置屬性 */ // If we're in the kernel domain, re-exec init to transition to the init domain now // that the SELinux policy has been loaded. if (is_first_stage) { if (restorecon("/init") == -1) { // 按selinux policy要求,重新設置init文件屬性
ERROR("restorecon failed: %s\n", strerror(errno)); security_failure(); } char* path = argv[0]; char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; //設置參數--second-stage
if (execv(path, args) == -1) { // 執行init進程,重新進入main函數 ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno)); security_failure(); } } // These directories were necessarily created before initial policy load // and therefore need their security context restored to the proper value. // This must happen before /dev is populated by ueventd. NOTICE("Running restorecon...\n"); restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon("/property_contexts"); restorecon_recursive("/sys"); epoll_fd = epoll_create1(EPOLL_CLOEXEC); // 調用epoll_create1創建epoll句柄 if (epoll_fd == -1) { ERROR("epoll_create1 failed: %s\n", strerror(errno)); exit(1); }
接著往下分析:
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */ /* 03. 初始化屬性域 */ /* 04. 完成SELinux相關工作 */· /* 05. 重新設置屬性 */ /* 06. 創建epoll句柄 */
/* 07. 裝載子進程信號處理器 */ signal_handler_init(); // 裝載子進程信號處理器
Note:init是一個守護進程,為了防止init的子進程成為僵屍進程(zombie process),需要init在子進程結束時獲取子進程的結束碼,通過結束碼將程序表中的子進程移除,防止成為僵屍進程的子進程占用程序表的空間(程序表的空間達到上限時,系統就不能再啟動新的進程了,會引起嚴重的系統問題)。
細化signal_handler_init()函數:
void signal_handler_init() { // 函數定位於:system/core/init/Singal_handler.cpp // 在linux當中,父進程是通過捕捉SIGCHLD信號來得知子進程運行結束的情況 // Create a signalling mechanism for SIGCHLD. int s[2]; // 利用socketpair創建出已經連接的兩個socket,分別作為信號的讀、寫端 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { ERROR("socketpair failed: %s\n", strerror(errno)); exit(1); } signal_write_fd = s[0]; signal_read_fd = s[1]; // Write to signal_write_fd if we catch SIGCHLD. struct sigaction act; memset(&act, 0, sizeof(act)); // 信號處理器為SIGCHLD_handler,其被存在sigaction結構體中,負責處理SIGCHLD消息 act.sa_handler = SIGCHLD_handler; // 信號處理器:SIGCHLD_handler act.sa_flags = SA_NOCLDSTOP; // 僅當進程終止時才接受SIGCHLD信號 // 調用信號安裝函數sigaction,將監聽的信號及對應的信號處理器注冊到內核中 sigaction(SIGCHLD, &act, 0); // 相對於6.0的代碼,進一步作了封裝,用於終止出現問題的子進程 ServiceManager::GetInstance().ReapAnyOutstandingChildren(); register_epoll_handler(signal_read_fd, handle_signal); // 定義在system/core/init/Init.cpp }
Linux進程通過互相發送接收消息來實現進程間的通信,這些消息被稱為“信號”。每個進程在處理其它進程發送的信號時都要注冊處理者,處理者被稱為信號處理器。
注意到sigaction結構體的sa_flags為SA_NOCLDSTOP。由於系統默認在子進程暫停時也會發送信號SIGCHLD,init需要忽略子進程在暫停時發出的SIGCHLD信號,因此將act.sa_flags 置為SA_NOCLDSTOP,該標志位表示僅當進程終止時才接受SIGCHLD信號。
觀察SIGCHLD_handler具體工作:
static void SIGCHLD_handler(int) { /* init進程是所有進程的父進程,當其子進程終止產生SIGCHLD信號時,SIGCHLD_handler對signal_write_fd執行寫操作,由於socketpair的綁定關系,這將觸發信號對應的signal_read_fd收到數據。*/ if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) { ERROR("write(signal_write_fd) failed: %s\n", strerror(errno)); } }
在裝在信號監聽器的最後,有如下函數:register_epoll_handler(signal_read_fd, handle_signal);
void register_epoll_handler(int fd, void (*fn)()) { // 回到init.cpp中 epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = reinterpret_cast<void*>(fn); // epoll_fd增加一個監聽對象fd,fd上有數據到來時,調用fn處理 // 當epoll句柄監聽到signal_read_fd中有數據可讀時,將調用handle_signal進行處理。 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { ERROR("epoll_ctl failed: %s\n", strerror(errno)); } }
【小結】
當init進程調用signal_handler_init後,一旦收到子進程終止帶來的SIGCHLD消息後,將利用信號處理者SIGCHLD_handler向signal_write_fd寫入信息; epoll句柄監聽到signal_read_fd收消息後,將調用handle_signal進行處理。
查看handle_signal函數:
static void handle_signal() { // --> 位於system/core/init/signal_handler.cpp中 // Clear outstanding requests. char buf[32]; read(signal_read_fd, buf, sizeof(buf)); ServiceManager::GetInstance().ReapAnyOutstandingChildren(); }
從代碼中可以看出,handle_signal只是清空signal_read_fd中的數據,然後調用ServiceManager::GetInstance().ReapAnyOutstandingChildren()。
繼續分析:
// 定義於system/core/init/service.cpp中,是一個單例對象。 ServiceManager::ServiceManager() { // 默認private屬性 } ServiceManager& ServiceManager::GetInstance() { static ServiceManager instance; return instance; } void ServiceManager::ReapAnyOutstandingChildren() { while (ReapOneProcess()) { // 實際調用了ReapOneProcess函數 } }
接下來看下ReapOneProcess這個函數:
bool ServiceManager::ReapOneProcess() { int status; //用waitpid函數獲取狀態發生變化的子進程pid //waitpid的標記為WNOHANG,即非阻塞,返回為正值就說明有進程掛掉了 pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG)); if (pid == 0) { return false; } else if (pid == -1) { ERROR("waitpid failed: %s\n", strerror(errno)); return false; } // 利用FindServiceByPid函數,找到pid對應的服務。 // FindServiceByPid主要通過輪詢解析init.rc生成的service_list,找到pid與參數一直的svc Service* svc = FindServiceByPid(pid); std::string name; if (svc) { name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name().c_str(), pid); } else { name = android::base::StringPrintf("Untracked pid %d", pid); } if (WIFEXITED(status)) { NOTICE("%s exited with status %d\n", name.c_str(), WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { NOTICE("%s killed by signal %d\n", name.c_str(), WTERMSIG(status)); // 輸出服務結束原因 } else if (WIFSTOPPED(status)) { NOTICE("%s stopped by signal %d\n", name.c_str(), WSTOPSIG(status)); } else { NOTICE("%s state changed", name.c_str()); } if (!svc) { return true; } if (svc->Reap()) { // 結束服務,相對於6.0作了進一步的封裝,重啟一些子進程,不做具體分析 waiting_for_exec = false; RemoveService(*svc); // 移除服務對應的信息 } return true; }
繼續分析main()函數:
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */ /* 03. 初始化屬性域 */ /* 04. 完成SELinux相關工作 */· /* 05. 重新設置屬性 */ /* 06. 創建epoll句柄 */ /* 07. 裝載子進程信號處理器 */
/* 08. 啟動匹配屬性的服務端*/ property_load_boot_defaults(); // 進程調用property_load_boot_defaults進行默認屬性配置相關的工作 export_oem_lock_status(); std::string bootmode = property_get("ro.bootmode"); // 獲取啟動模式 if (strncmp(bootmode.c_str(), "ffbm", 4) == 0){ property_set("ro.logdumpd","0"); }else{ property_set("ro.logdumpd","1"); } start_property_service();
看下property_load_boot_defaults()函數:位於system/core/init/Property_service.cpp中
// property_load_boot_defaults實際上就是調用load_properties_from_file解析配置文件 /* 09. 設置默認系統屬性 */ // 然後根據解析的結果,設置系統屬性 void property_load_boot_defaults() { load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL); }
接著繼續分析main:
int main(int argc, char** argv) { /* 01. 創建文件系統目錄並掛載相關的文件系統 */ /* 02. 屏蔽標准的輸入輸出/初始化內核log系統 */ /* 03. 初始化屬性域 */ /* 04. 完成SELinux相關工作 */· /* 05. 重新設置屬性 */ /* 06. 創建epoll句柄 */ /* 07. 裝載子進程信號處理器 */ /* 08. 設置默認系統屬性 */ /* 09. 啟動配置屬性的服務端 */
/* 10. 匹配命令和函數之間的對應關系 */
const BuiltinFunctionMap function_map; // system/core/init/builtins.cpp Action::set_function_map(&function_map); // 在Action中保存function_map對象,記錄了命令與函數之間的對應關系
【結尾】
由於init涉及的知識點是相當多,代碼之間的邏輯也是極其復雜,我在看別人的博客過程中,最反感一篇博客要看很久,往往因為瑣事而放棄堅持(確切的說,隨手把網頁關掉了),所以我就分章節分析,盡量少源碼多講解。
接下來,在Android啟動篇 — init原理(二)中將詳細分析init.rc的解析過程。
Android原生的VideoView的樣式真心弱爆了,但是擁有巨大用戶量的網易新聞客戶端居然使用的就是這個,真心服了網易的產品經理。目前主流的視頻網站,國內的如優酷、土
如果不使用系統自帶的TitleBar(即Activity被設置@android:style/Theme.NoTitleBar),那就需要自己來寫進度條了,這裡封裝了一個自
從這一篇開始我們將看看Java 5之後給我們添加的新的對線程操作的API,首先看看api文檔:java.util.concurrent包含許多線程安全、測試良好、高性能的
Android下面使用命令行截圖。因為工作調試用的機器,沒法連接USB,所以用不了一般的截圖方法,後來查了一下,Android4.0以後都內置了截圖命令。可以使用下面命令