這是一個連載的博文系列,我將持續爲你們提供儘量透徹的Android源碼分析 github連載地址html
上一篇中講了init進程的第一階段,咱們接着講第二階段,主要有如下內容java
本文涉及到的文件node
platform/system/core/init/init.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp
platform/system/core/init/property_service.cpp
複製代碼
1、建立進程會話密鑰並初始化屬性系統linux
第二階段一開始會有一個is_first_stage的判斷,因爲以前第一階段最後有設置INIT_SECOND_STAGE, 所以直接跳過一大段代碼。從keyctl開始纔是重點內容,咱們一一展開來看android
int main(int argc, char** argv) {
//一樣進行ueventd/watchdogd跳轉及環境變量設置
...
//以前準備工做時將INIT_SECOND_STAGE設置爲true,已經不爲nullptr,因此is_first_stage爲false
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
//is_first_stage爲false,直接跳過
if (is_first_stage) {
...
}
// At this point we're in the second stage of init.
InitKernelLogging(argv); //上一節有講,初始化日誌輸出
LOG(INFO) << "init second stage started!";
// Set up a session keyring that all processes will have access to. It
// will hold things like FBE encryption keys. No process should override
// its session keyring.
keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1); //初始化進程會話密鑰
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//建立 /dev/.booting 文件,就是個標記,表示booting進行中
property_init();//初始化屬性系統,並從指定文件讀取屬性
//接下來的一系列操做都是從各個文件讀取一些屬性,而後經過property_set設置系統屬性
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
/* * 1.這句英文的大概意思是,若是參數同時從命令行和DT傳過來,DT的優先級老是大於命令行的 * 2.DT即device-tree,中文意思是設備樹,這裏面記錄本身的硬件配置和系統運行參數,參考http://www.wowotech.net/linux_kenrel/why-dt.html */
process_kernel_dt();//處理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();//處理其餘的一些屬性
// Make the time that init started available for bootstat to log.
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
// Set libavb version for Framework-only OTA match in Treble build.
const char* avb_version = getenv("INIT_AVB_VERSION");
if (avb_version) property_set("ro.boot.avb_version", avb_version);
// Clean up our environment.
unsetenv("INIT_SECOND_STAGE"); //清空這些環境變量,由於以前都已經存入到系統屬性中去了
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
...
複製代碼
定義在platform/system/core/init/keyutils.hgit
keyctl將主要的工做交給__NR_keyctl這個系統調用,keyctl是Linux系統操縱內核的通信密鑰管理工具github
咱們分析下 keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1)數據庫
參考linux手冊數組
這裏並無拿返回值,估計就是爲了新建會話密鑰環了,從註釋Set up a session keyring也可看出緩存
static inline long keyctl(int cmd, ...) {
va_list va;
unsigned long arg2, arg3, arg4, arg5;
//va_start,va_arg,va_end是配合使用的,用於將可變參數從堆棧中讀取出來
va_start(va, cmd); //va_start是獲取第一個參數地址
arg2 = va_arg(va, unsigned long); //va_arg 遍歷參數
arg3 = va_arg(va, unsigned long);
arg4 = va_arg(va, unsigned long);
arg5 = va_arg(va, unsigned long);
va_end(va); //va_end 恢復堆棧
return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); //系統調用
}
複製代碼
定義在 platform/system/core/init/property_service.cpp
直接交給 __system_property_area_init 處理
void property_init() {
if (__system_property_area_init()) {
LOG(ERROR) << "Failed to initialize property area";
exit(1);
}
}
複製代碼
__system_property_area_init 定義在/bionic/libc/bionic/system_properties.cpp
看名字大概知道是用來初始化屬性系統區域的,應該是分門別類更準確些,首先清除緩存,這裏主要是清除幾個鏈表以及在內存中的映射,新建property_filename目錄,這個目錄的值爲 /dev/_properties_ 而後就是調用initialize_properties加載一些系統屬性的類別信息,最後將加載的鏈表寫入文件並映射到內存
int __system_property_area_init() {
free_and_unmap_contexts();//清除一些緩存
mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);//新建目錄property_filename,權限是rwx-x-x
if (!initialize_properties()) { //讀取一些文件,把鍵值信息存入到鏈表中
return -1;
}
bool open_failed = false;
bool fsetxattr_failed = false;
list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
if (!l->open(true, &fsetxattr_failed)) {
//將contexts鏈表中的數據寫入到property_filename目錄下文件中,每種context對應一個文件,並經過mmap映射進內存中
open_failed = true;
}
});
if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {//增長 properties_serial的映射,跟contexts中的同樣
free_and_unmap_contexts();//映射失敗清除緩存
return -1;
}
initialized = true;
return fsetxattr_failed ? -2 : 0;
}
複製代碼
定義在/bionic/libc/bionic/system_properties.cpp
交給 initialize_properties_from_file 處理,指定了一些文件路徑
static bool initialize_properties() {
// If we do find /property_contexts, then this is being
// run as part of the OTA updater on older release that had
// /property_contexts - b/34370523
if (initialize_properties_from_file("/property_contexts")) {
return true;
}
// Use property_contexts from /system & /vendor, fall back to those from /
if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
if (!initialize_properties_from_file("/system/etc/selinux/plat_property_contexts")) {
return false;
}
// Don't check for failure here, so we always have a sane list of properties.
// E.g. In case of recovery, the vendor partition will not have mounted and we
// still need the system / platform properties to function.
initialize_properties_from_file("/vendor/etc/selinux/nonplat_property_contexts");
} else {
if (!initialize_properties_from_file("/plat_property_contexts")) {
return false;
}
initialize_properties_from_file("/nonplat_property_contexts");
}
return true;
}
複製代碼
定義在/bionic/libc/bionic/system_properties.cpp
這個函數主要工做是解析屬性類別文件,對屬性作一下分類,具體就是一行行解析,過濾 # 開頭的、只讀到key的、從ctl.開頭的,而後將解析出來的鍵值對放到兩個鏈表中
prefixes鏈表存放key(實際上是一些key的前綴),contexts鏈表存放value(實際上是對應key應當屬於那些類別的信息),這樣的好處是將龐雜的屬性根據前綴分類,存儲到不一樣的context中, 查找和修改是很是高效的,相似map的作法
static bool initialize_properties_from_file(const char* filename) {
FILE* file = fopen(filename, "re");
if (!file) {
return false;
}
char* buffer = nullptr;
size_t line_len;
char* prop_prefix = nullptr;
char* context = nullptr;
while (getline(&buffer, &line_len, file) > 0) { //一行一行讀取,而後將結果放到buffer中
int items = read_spec_entries(buffer, 2, &prop_prefix, &context);
//將buffer的數據,按空格做爲區分,key賦值給prop_prefix,value賦值給context
if (items <= 0) { //沒有讀取到,好比 # 這種是註釋
continue;
}
if (items == 1) { //只讀取到key,釋放key的內存
free(prop_prefix);
continue;
}
/* * init uses ctl.* properties as an IPC mechanism and does not write them * to a property file, therefore we do not need to create property files * to store them. */
if (!strncmp(prop_prefix, "ctl.", 4)) { //以ctl.開頭忽略掉,由於這個不屬於屬性,主要用於IPC機制
free(prop_prefix);
free(context);
continue;
}
/* * C++中[ arg1,arg2,... ](T param, T param1,... ){ commond} 這個是lambda表達式,也能夠看做一個函數指針 * []中是引用外部參數 *()中是參數定義,這個跟普通方法的()同樣 * {}中是方法體 */
auto old_context =
list_find(contexts, [context](context_node* l) { return !strcmp(l->context(), context); });
// list_find主要是循環contexts這個鏈表,若是發現context的值在鏈表裏已經有,就將對應的鏈表結構context_node返回
if (old_context) {
list_add_after_len(&prefixes, prop_prefix, old_context);
//list_add_after_len 主要做用是將prop_prefix和old_context按順序放到prefixes鏈表裏
} else {
list_add(&contexts, context, nullptr);//將context的值放到contexts鏈表裏
list_add_after_len(&prefixes, prop_prefix, contexts);
}
free(prop_prefix); //釋放資源
free(context);
}
free(buffer);
fclose(file);
return true;
}
複製代碼
定義在/bionic/libc/bionic/system_properties.cpp
以前咱們看到有兩個重要的鏈表prefixs和contexts,frefixs存key(實際上是一些key的前綴),contexts存value(實際上是對應key應當屬於那些類別的信息),接下來咱們看下這兩個鏈表的結構
context_node中有三個比較重要的屬性context_、_pa和next,context_用來存類別信息,_pa是存具體key-value節點的,next是鏈表下一個節點
prefix_node中有三個重要屬性prefix,context和next,prefix用來存key,context用來存關聯的context_node,next是鏈表下一個節點
prop_area 這個在context_node裏引用,屬性data是具體key-value的數據庫,裏面是用 hybrid trie/binary tree(字典樹)這種結構存儲的,也就是一對多,我給張圖就明白了
prop_info 就是具體的key-value了,這個是從prop_area解析出來的
class context_node {
public:
/* * C++中構造函數後面接 :(冒號) 表示對屬性賦初始值 */
context_node(context_node* next, const char* context, prop_area* pa)
: next(next), context_(strdup(context)), pa_(pa), no_access_(false) {
lock_.init(false);
}
...
context_node* next;
private:
bool check_access();
void unmap();
Lock lock_;
char* context_;
prop_area* pa_;
bool no_access_;
};
struct prefix_node {
prefix_node(struct prefix_node* next, const char* prefix, context_node* context)
: prefix(strdup(prefix)), prefix_len(strlen(prefix)), context(context), next(next) {
}
~prefix_node() {
free(prefix);
}
char* prefix;
const size_t prefix_len;
context_node* context;
struct prefix_node* next;
};
class prop_area {
public:
prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) {
atomic_init(&serial_, 0);
memset(reserved_, 0, sizeof(reserved_));
// Allocate enough space for the root node.
bytes_used_ = sizeof(prop_bt);
}
....
private:
...
uint32_t bytes_used_;
atomic_uint_least32_t serial_;
uint32_t magic_;
uint32_t version_;
uint32_t reserved_[28];
char data_[0];
DISALLOW_COPY_AND_ASSIGN(prop_area);
};
struct prop_info {
atomic_uint_least32_t serial;
// we need to keep this buffer around because the property
// value can be modified whereas name is constant.
char value[PROP_VALUE_MAX];
char name[0];
prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen) {
memcpy(this->name, name, namelen);
this->name[namelen] = '\0';
atomic_init(&this->serial, valuelen << 24);
memcpy(this->value, value, valuelen);
this->value[valuelen] = '\0';
}
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(prop_info);
};
複製代碼
以前有個list_add函數,這個函數是一個模板函數,與Java中的泛型相似,List 和 Args至關於T和T1,這個函數主要做用就是調用T的構造函數, 把list,可變參數args做爲參數傳進去
template <typename List, typename... Args>
static inline void list_add(List** list, Args... args) {
*list = new List(*list, args...);
}
複製代碼
定義在platform/system/core/init/init.cpp
讀取DT(設備樹)的屬性信息,而後經過 property_set 設置系統屬性
static void process_kernel_dt() {
if (!is_android_dt_value_expected("compatible", "android,firmware")) {
//判斷 /proc/device-tree/firmware/android/compatible 文件中的值是否爲 android,firmware
return;
}
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(kAndroidDtDir.c_str()), closedir);
// kAndroidDtDir的值爲/proc/device-tree/firmware/android
if (!dir) return;
std::string dt_file;
struct dirent *dp;
while ((dp = readdir(dir.get())) != NULL) { //遍歷dir中的文件
if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
//跳過 compatible和name文件
continue;
}
std::string file_name = kAndroidDtDir + dp->d_name;
android::base::ReadFileToString(file_name, &dt_file); //讀取文件內容
std::replace(dt_file.begin(), dt_file.end(), ',', '.'); //替換 , 爲 .
std::string property_name = StringPrintf("ro.boot.%s", dp->d_name);
property_set(property_name.c_str(), dt_file.c_str()); // 將 ro.boot.文件名 做爲key,文件內容爲value,設置進屬性
}
}
複製代碼
定義在/bionic/libc/bionic/system_properties.cpp
property_set用的地方特別多,做用是設置系統屬性,具體就是經過遍歷以前的prefixs鏈表找到對應的context_node,而後經過context_node的_pa屬性找到對應key-value節點prop_info,能找到就更新value,找不到就設置新值, 另外就是調用property_changed方法觸發trigger,trigger後續講.rc解析時再詳細講,trigger能夠觸發一系列活動
uint32_t property_set(const std::string& name, const std::string& value) {
size_t valuelen = value.size();
if (!is_legal_property_name(name)) { //檢查key合法性,大概就是 xx.xx.xx 這種 ,xx只能是字母、數字、_、-、@
LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: bad name";
return PROP_ERROR_INVALID_NAME;
}
if (valuelen >= PROP_VALUE_MAX) {//不能超過最大長度 92
LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
<< "value too long";
return PROP_ERROR_INVALID_VALUE;
}
if (name == "selinux.restorecon_recursive" && valuelen > 0) { // 跳過selinux,不容許修改
if (restorecon(value.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) {
LOG(ERROR) << "Failed to restorecon_recursive " << value;
}
}
prop_info* pi = (prop_info*) __system_property_find(name.c_str()); //找到key對應節點
if (pi != nullptr) { //若是對應節點存在就更新
// ro.* properties are actually "write-once".
if (android::base::StartsWith(name, "ro.")) {
LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
<< "property already set";
return PROP_ERROR_READ_ONLY_PROPERTY;
}
__system_property_update(pi, value.c_str(), valuelen);
} else { //沒有對應節點就新建
int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
if (rc < 0) {
LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
<< "__system_property_add failed";
return PROP_ERROR_SET_FAILED;
}
}
// Don't write properties to disk until after we have read all default
// properties to prevent them from being overwritten by default values.
if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) {
//若是以persist開頭的,將值寫入文件
write_persistent_property(name.c_str(), value.c_str());
}
property_changed(name, value); //觸發trigger
return PROP_SUCCESS;
}
複製代碼
後續的一些函數或代碼都是直接或間接調用 property_set 設置系統屬性
static void process_kernel_cmdline() {
// The first pass does the common stuff, and finds if we are in qemu.
// The second pass is only necessary for qemu to export all kernel params
// as properties.
import_kernel_cmdline(false, import_kernel_nv);
if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
if (key.empty()) return;
if (for_emulator) {
// In the emulator, export any kernel option with the "ro.kernel." prefix.
property_set(StringPrintf("ro.kernel.%s", key.c_str()).c_str(), value.c_str());
return;
}
if (key == "qemu") {
strlcpy(qemu, value.c_str(), sizeof(qemu));
} else if (android::base::StartsWith(key, "androidboot.")) {
property_set(StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(), value.c_str());
}
}
複製代碼
static void export_kernel_boot_props() {
struct {
const char *src_prop;
const char *dst_prop;
const char *default_value;
} prop_map[] = {
{ "ro.boot.serialno", "ro.serialno", "", },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
{ "ro.boot.hardware", "ro.hardware", "unknown", },
{ "ro.boot.revision", "ro.revision", "0", },
};
for (size_t i = 0; i < arraysize(prop_map); i++) {
std::string value = GetProperty(prop_map[i].src_prop, "");
property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
}
}
複製代碼
// Now set up SELinux for second stage.
selinux_initialize(false); //第二階段初始化SELinux policy
selinux_restore_context();//恢復安全上下文
複製代碼
定義在platform/system/core/init/init.cpp
第二階段只是執行 selinux_init_all_handles
static void selinux_initialize(bool in_kernel_domain) {
... //和以前同樣設置回調函數
if (in_kernel_domain) {//第二階段跳過
...
} else {
selinux_init_all_handles();
}
}
複製代碼
定義在platform/system/core/init/init.cpp
這裏是建立SELinux的處理函數,selinux_android_file_context_handle和selinux_android_prop_context_handle內部實現差很少,其實就是傳遞不一樣的文件路徑給selabel_open
static void selinux_init_all_handles(void) {
sehandle = selinux_android_file_context_handle(); //建立context的處理函數
selinux_android_set_sehandle(sehandle);//將剛剛新建的處理賦值給fc_sehandle
sehandle_prop = selinux_android_prop_context_handle();//建立prop的處理函數
}
複製代碼
定義在platform/external/selinux/libselinux/src/label.c
首先建立一個selabel_handle結構體,而後根據backend的類型將處理函數映射給initfuncs數組中的值,將參數opts傳遞過去
這個opts只是包含一個簡單的路徑,好比 /system/etc/selinux/plat_file_contexts ,而initfuncs負責去解析它
struct selabel_handle *selabel_open(unsigned int backend, const struct selinux_opt *opts, unsigned nopts) {
struct selabel_handle *rec = NULL;
if (backend >= ARRAY_SIZE(initfuncs)) {
errno = EINVAL;
goto out;
}
if (!initfuncs[backend]) {
errno = ENOTSUP;
goto out;
}
rec = (struct selabel_handle *)malloc(sizeof(*rec));
if (!rec)
goto out;
memset(rec, 0, sizeof(*rec));
rec->backend = backend;
rec->validating = selabel_is_validate_set(opts, nopts);
rec->subs = NULL;
rec->dist_subs = NULL;
rec->digest = selabel_is_digest_set(opts, nopts, rec->digest);
if ((*initfuncs[backend])(rec, opts, nopts)) { //
selabel_close(rec);
rec = NULL;
}
out:
return rec;
}
複製代碼
定義在platform/external/selinux/libselinux/src/label.c
這些數組對應backend的6種可能的值
/* file contexts */
#define SELABEL_CTX_FILE 0
/* media contexts */
#define SELABEL_CTX_MEDIA 1
/* x contexts */
#define SELABEL_CTX_X 2
/* db objects */
#define SELABEL_CTX_DB 3
/* Android property service contexts */
#define SELABEL_CTX_ANDROID_PROP 4
/* Android service contexts */
#define SELABEL_CTX_ANDROID_SERVICE 5
複製代碼
initfuncs數組中每一項都對應一個init函數,init函數主要做用是解析傳進來的文件,這些傳進來的文件定義了哪些進程能夠訪問哪些文件,執行哪些操做 SELinux的內容比較多,因爲篇幅就暫時不深刻了 能夠參考老羅的SEAndroid安全機制框架分析
static selabel_initfunc initfuncs[] = {
&selabel_file_init,
CONFIG_MEDIA_BACKEND(selabel_media_init),
CONFIG_X_BACKEND(selabel_x_init),
CONFIG_DB_BACKEND(selabel_db_init),
CONFIG_ANDROID_BACKEND(selabel_property_init),
CONFIG_ANDROID_BACKEND(selabel_service_init),
};
複製代碼
定義在 platform/system/core/init/init.cpp
主要就是恢復這些文件的安全上下文,由於這些文件是在SELinux安全機制初始化前建立,因此須要從新恢復下安全性
static void selinux_restore_context() {
LOG(INFO) << "Running restorecon...";
restorecon("/dev");
restorecon("/dev/kmsg");
restorecon("/dev/socket");
restorecon("/dev/random");
restorecon("/dev/urandom");
restorecon("/dev/__properties__");
restorecon("/file_contexts.bin");
restorecon("/plat_file_contexts");
restorecon("/nonplat_file_contexts");
restorecon("/plat_property_contexts");
restorecon("/nonplat_property_contexts");
restorecon("/plat_seapp_contexts");
restorecon("/nonplat_seapp_contexts");
restorecon("/plat_service_contexts");
restorecon("/nonplat_service_contexts");
restorecon("/plat_hwservice_contexts");
restorecon("/nonplat_hwservice_contexts");
restorecon("/sepolicy");
restorecon("/vndservice_contexts");
restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
restorecon("/dev/device-mapper");
}
複製代碼
epoll_fd = epoll_create1(EPOLL_CLOEXEC);//建立epoll實例,並返回epoll的文件描述符
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
signal_handler_init();//主要是建立handler處理子進程終止信號,建立一個匿名socket並註冊到epoll進行監聽
複製代碼
EPOLL相似於POLL,是Linux中用來作事件觸發的,跟EventBus功能差很少
linux很長的時間都在使用select來作事件觸發,它是經過輪詢來處理的,輪詢的fd數目越多,天然耗時越多,對於大量的描述符處理,EPOLL更有優點
epoll_create1是epoll_create的升級版,能夠動態調整epoll實例中文件描述符的個數 EPOLL_CLOEXEC這個參數是爲文件描述符添加O_CLOEXEC屬性,參考http://blog.csdn.net/gqtcgq/article/details/48767691
定義在platform/system/core/init/signal_handler.cpp
這個函數主要的做用是註冊SIGCHLD信號的處理函數
init是一個守護進程,爲了防止init的子進程成爲殭屍進程(zombie process), 須要init在子進程在結束時獲取子進程的結束碼,經過結束碼將程序表中的子進程移除, 防止成爲殭屍進程的子進程佔用程序表的空間(程序表的空間達到上限時,系統就不能再啓動新的進程了,會引發嚴重的系統問題)
在linux當中,父進程是經過捕捉SIGCHLD信號來得知子進程運行結束的狀況,SIGCHLD信號會在子進程終止的時候發出,瞭解這些背景後,咱們來看看init進程如何處理這個信號
首先,調用socketpair,這個方法會返回一對文件描述符,這樣當一端寫入時,另外一端就能被通知到, socketpair兩端既能夠寫也能夠讀,這裏只是單向的讓s[0]寫,s[1]讀
而後,新建一個sigaction結構體,sa_handler是信號處理函數,指向SIGCHLD_handler, SIGCHLD_handler作的事情就是往s[0]裏寫個"1",這樣s1就會收到通知,SA_NOCLDSTOP表示只在子進程終止時處理, 由於子進程在暫停時也會發出SIGCHLD信號
sigaction(SIGCHLD, &act, 0) 這個是創建信號綁定關係,也就是說當監聽到SIGCHLD信號時,由act這個sigaction結構體處理
ReapAnyOutstandingChildren 這個後文講
最後,register_epoll_handler的做用就是註冊一個監聽,當signal_read_fd(以前的s[1])收到信號,觸發handle_signal
終上所述,signal_handler_init函數的做用就是,接收到SIGCHLD信號時觸發handle_signal
void signal_handler_init() {
// Create a signalling mechanism for SIGCHLD.
int s[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { //建立socket並返回文件描述符
PLOG(ERROR) << "socketpair failed";
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));
act.sa_handler = SIGCHLD_handler; //act處理函數
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, 0);
ServiceManager::GetInstance().ReapAnyOutstandingChildren();//具體處理子進程終止信號
register_epoll_handler(signal_read_fd, handle_signal);//註冊signal_read_fd到epoll中
}
void register_epoll_handler(int fd, void (*fn)() ) {
epoll_event ev;
ev.events = EPOLLIN; //監聽事件類型,EPOLLIN表示fd中有數據可讀
ev.data.ptr = reinterpret_cast<void*>(fn); //回調函數賦值給ptr
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { //註冊事件
PLOG(ERROR) << "epoll_ctl failed";
}
}
複製代碼
定義在platform/system/core/init/signal_handler.cpp
首先清空signal_read_fd中的數據,而後調用ReapAnyOutstandingChildren,以前在signal_handler_init中調用過一次, 它實際上是調用ReapOneProcess
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
read(signal_read_fd, buf, sizeof(buf));
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}
複製代碼
定義在platform/system/core/init/service.cpp
這是最終的處理函數了,這個函數先用waitpid找出掛掉進程的pid,而後根據pid找到對應Service,最後調用Service的Reap方法清除資源,根據進程對應的類型,決定是否重啓機器或重啓進程
bool ServiceManager::ReapOneProcess() {
int status;
pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
//用waitpid函數獲取狀態發生變化的子進程pid
//waitpid的標記爲WNOHANG,即非阻塞,返回爲正值就說明有進程掛掉了
if (pid == 0) {
return false;
} else if (pid == -1) {
PLOG(ERROR) << "waitpid failed";
return false;
}
Service* svc = FindServiceByPid(pid);//經過pid找到對應的Service
std::string name;
std::string wait_string;
if (svc) {
name = android::base::StringPrintf("Service '%s' (pid %d)",
svc->name().c_str(), pid);
if (svc->flags() & SVC_EXEC) {
wait_string =
android::base::StringPrintf(" waiting took %f seconds", exec_waiter_->duration_s());
}
} else {
name = android::base::StringPrintf("Untracked pid %d", pid);
}
if (WIFEXITED(status)) {
LOG(INFO) << name << " exited with status " << WEXITSTATUS(status) << wait_string;
} else if (WIFSIGNALED(status)) {
LOG(INFO) << name << " killed by signal " << WTERMSIG(status) << wait_string;
} else if (WIFSTOPPED(status)) {
LOG(INFO) << name << " stopped by signal " << WSTOPSIG(status) << wait_string;
} else {
LOG(INFO) << name << " state changed" << wait_string;
}
if (!svc) { //沒有找到,說明已經結束了
return true;
}
svc->Reap();//清除子進程相關的資源
if (svc->flags() & SVC_EXEC) {
exec_waiter_.reset();
}
if (svc->flags() & SVC_TEMPORARY) {
RemoveService(*svc);
}
return true;
}
複製代碼
property_load_boot_defaults();//從文件中加載一些屬性,讀取usb配置
export_oem_lock_status();//設置ro.boot.flash.locked 屬性
start_property_service();//開啓一個socket監聽系統屬性的設置
set_usb_controller();//設置sys.usb.controller 屬性
複製代碼
property_load_boot_defaults,export_oem_lock_status,set_usb_controller這三個函數都是調用property_set設置一些系統屬性
void property_load_boot_defaults() {
if (!load_properties_from_file("/system/etc/prop.default", NULL)) { //從文件中讀取屬性
// Try recovery path
if (!load_properties_from_file("/prop.default", NULL)) {
// Try legacy path
load_properties_from_file("/default.prop", NULL);
}
}
load_properties_from_file("/odm/default.prop", NULL);
load_properties_from_file("/vendor/default.prop", NULL);
update_sys_usb_config();
}
static void export_oem_lock_status() {
if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
return;
}
std::string value = GetProperty("ro.boot.verifiedbootstate", "");
if (!value.empty()) {
property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
}
}
static void set_usb_controller() {
std::unique_ptr<DIR, decltype(&closedir)>dir(opendir("/sys/class/udc"), closedir);
if (!dir) return;
dirent* dp;
while ((dp = readdir(dir.get())) != nullptr) {
if (dp->d_name[0] == '.') continue;
property_set("sys.usb.controller", dp->d_name);
break;
}
}
複製代碼
定義在platform/system/core/init/property_service.cpp
以前咱們看到經過property_set能夠輕鬆設置系統屬性,那幹嗎這裏還要啓動一個屬性服務呢?這裏其實涉及到一些權限的問題,不是全部進程均可以隨意修改任何的系統屬性, Android將屬性的設置統一交由init進程管理,其餘進程不能直接修改屬性,而只能通知init進程來修改,而在這過程當中,init進程能夠進行權限控制,咱們來看看這些是如何實現的
首先建立一個socket並返回文件描述符,而後設置最大併發數爲8,其餘進程能夠經過這個socket通知init進程修改系統屬性, 最後註冊epoll事件,也就是當監聽到property_set_fd改變時調用handle_property_set_fd
void start_property_service() {
property_set("ro.property_service.version", "2");
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);//建立socket用於通訊
if (property_set_fd == -1) {
PLOG(ERROR) << "start_property_service socket creation failed";
exit(1);
}
listen(property_set_fd, 8);//監聽property_set_fd,設置最大併發數爲8
register_epoll_handler(property_set_fd, handle_property_set_fd);//註冊epoll事件
}
複製代碼
定義在platform/system/core/init/property_service.cpp
這個函數主要做用是創建socket鏈接,而後從socket中讀取操做信息,根據不一樣的操做類型,調用handle_property_set作具體的操做
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);//等待客戶端鏈接
if (s == -1) {
return;
}
struct ucred cr;
socklen_t cr_size = sizeof(cr);
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {//獲取鏈接到此socket的進程的憑據
close(s);
PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
return;
}
SocketConnection socket(s, cr);// 創建socket鏈接
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
if (!socket.RecvUint32(&cmd, &timeout_ms)) { //讀取socket中的操做信息
PLOG(ERROR) << "sys_prop: error while reading command from the socket";
socket.SendUint32(PROP_ERROR_READ_CMD);
return;
}
switch (cmd) { //根據操做信息,執行對應處理,二者區別一個是以char形式讀取,一個以String形式讀取
case PROP_MSG_SETPROP: {
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
!socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
return;
}
prop_name[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
handle_property_set(socket, prop_value, prop_value, true);
break;
}
case PROP_MSG_SETPROP2: {
std::string name;
std::string value;
if (!socket.RecvString(&name, &timeout_ms) ||
!socket.RecvString(&value, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
socket.SendUint32(PROP_ERROR_READ_DATA);
return;
}
handle_property_set(socket, name, value, false);
break;
}
default:
LOG(ERROR) << "sys_prop: invalid command " << cmd;
socket.SendUint32(PROP_ERROR_INVALID_CMD);
break;
}
}
複製代碼
定義在platform/system/core/init/property_service.cpp
這就是最終的處理函數,以"ctl."開頭的key就作一些Service的Start,Stop,Restart操做,其餘的就是調用property_set進行屬性設置, 不論是前者仍是後者,都要進行SELinux安全性檢查,只有該進程有操做權限才能執行相應操做
static void handle_property_set(SocketConnection& socket, const std::string& name, const std::string& value, bool legacy_protocol) {
const char* cmd_name = legacy_protocol ? "PROP_MSG_SETPROP" : "PROP_MSG_SETPROP2";
if (!is_legal_property_name(name)) { //檢查key的合法性
LOG(ERROR) << "sys_prop(" << cmd_name << "): illegal property name \"" << name << "\"";
socket.SendUint32(PROP_ERROR_INVALID_NAME);
return;
}
struct ucred cr = socket.cred(); //獲取操做進程的憑證
char* source_ctx = nullptr;
getpeercon(socket.socket(), &source_ctx);
if (android::base::StartsWith(name, "ctl.")) { //若是以ctl.開頭,就執行Service的一些控制操做
if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {//SELinux安全檢查,有權限才進行操做
handle_control_message(name.c_str() + 4, value.c_str());
if (!legacy_protocol) {
socket.SendUint32(PROP_SUCCESS);
}
} else {
LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
<< " service ctl [" << value << "]"
<< " uid:" << cr.uid
<< " gid:" << cr.gid
<< " pid:" << cr.pid;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
}
}
} else { //其餘的屬性調用property_set進行設置
if (check_mac_perms(name, source_ctx, &cr)) {//SELinux安全檢查,有權限才進行操做
uint32_t result = property_set(name, value);
if (!legacy_protocol) {
socket.SendUint32(result);
}
} else {
LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
}
}
}
freecon(source_ctx);
}
複製代碼
小結
init進程第二階段主要工做是初始化屬性系統,解析SELinux的匹配規則,處理子進程終止信號,啓動系統屬性服務,能夠說每一項都很關鍵,若是說第一階段是爲屬性系統,SELinux作準備,那麼第二階段就是真正去把這些落實的,下一篇咱們將講解.rc文件的解析