一、init.rc
Android中利用rc文件配置一些需要提前執(zhí)行的操作,在系統(tǒng)啟動的時候解析并執(zhí)行,為啟動Android系統(tǒng)核心服務(wù)提供保障??蓞⒖迹?a target="_blank">http://androidxref.com/9.0.0_r3/xref/system/core/init/README.md
rc文件以行為單位,一行定義一個語句,使用#作為注釋。rc語法核心包括
- Action
- Service
- Command
- Options
其中需要注意的是Action和Service的名稱是唯一的。
rc文件是可以通過import語句來導(dǎo)入其他rc文件的,例如/init.rc就引入包含了其他文件
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
1.1、 Action
以on開頭,通過觸發(fā)器trigger來決定對應(yīng)的service什么時候執(zhí)行,執(zhí)行的時機(jī)有
- on early-init -> 初始化的早期觸發(fā)
- on init -> 初始化階段觸發(fā)
- on late-init -> 初始化后期觸發(fā)
- on boot/charger -> 系統(tǒng)啟動或者充電時觸發(fā)
- on property:<key>=<value> -> 滿足條件時觸發(fā)
定義語法
on <trigger> #觸發(fā)條件
<command> #觸發(fā)命令
<command1>#第二個觸發(fā)命令,可以執(zhí)行多個命令
1.2、Service
Service,顧名思義就是需要啟動的服務(wù),以service開頭,init啟動后會由init進(jìn)程啟動它作為子進(jìn)程。
定義:
service <name> <pathname> [<argument>]* #可以有多個參數(shù)
<option>
<option> #可以有多個option
例子
service servicemanager /system/bin/servicemanager
定義的就是名稱為servicemanager的服務(wù),對應(yīng)的執(zhí)行文件為system/bin/servicemanager,因此在啟動服務(wù)前需要判斷服務(wù)對應(yīng)的文件是否存在
1.3、Command
Command主要是一些常用的操作命令,例如:
- class_start<service_class_name> -> 啟動屬于同一個class的所有服務(wù)
- start<service_name> ->啟動指定的服務(wù)
- stop <service_name> ->停止正在運(yùn)行的服務(wù)
- setprop<name> <value> -> 設(shè)置系統(tǒng)屬性值
- symlink <target><sym_link> ->創(chuàng)建連接到target的sym_link符號連接
- write<path><string> -> 向path路徑下寫入字符串
- exec -> fork 并執(zhí)行,init進(jìn)程會被阻塞,知道執(zhí)行完畢
- export -> 設(shè)置環(huán)境變量
1.4、Options
Option是Service配合使用的可選操作項(xiàng)
- disable -> 不跟隨class自動啟動,只有跟隨service name才啟動
- oneshot -> service執(zhí)行完畢退出后不會再重啟
- user/group -> 設(shè)置service的用戶和群組,默認(rèn)是root
- class -> 設(shè)置所屬的類名,service綁定的class啟動或者退出時,service也會啟動或者退出,默認(rèn)是default
- onrestart ->服務(wù)重啟時執(zhí)行
- socket -> 創(chuàng)建名為/dev/socket/name的socket
- critical -> 在規(guī)定的時間內(nèi)service如果不斷重啟,系統(tǒng)會重啟進(jìn)入recovery模式
1.5 demo
#初始化早期執(zhí)行
on early-init
# Set init and its forked children's oom_adj.
# 往/proc/1/oom_score_adj中寫入 -1000
write /proc/1/oom_score_adj -1000
#啟動ueventd服務(wù)
start ueventd
#初始化時執(zhí)行
on init
mkdir /dev/stune
write /proc/sys/kernel/sched_tunable_scaling 0
#當(dāng)sys.boot_from_charger_mode為1時執(zhí)行
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init
#初始化后期執(zhí)行
on late-init
trigger early-fs
# 定義一個名為flash_recovery,執(zhí)行文件在/system/bin/install-recovery.sh的服務(wù)
# 該服務(wù)是oneshot的,執(zhí)行完成后不再重啟,且啟動或者退出動作和main服務(wù)一起
service flash_recovery /system/bin/install-recovery.sh
class main
oneshot
二、init.rc解析
在《Android啟動流程之一 init進(jìn)程啟動》中有提到,init進(jìn)程的main函數(shù)中有加載init.rc文件,對應(yīng)的執(zhí)行函數(shù)為:
2.1、LoadBootScripts
先判斷是否自定義了rc文件,如果沒有的話就讀取默認(rèn)的/init.rc
system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
//創(chuàng)建解析器
Parser parser = CreateParser(action_manager, service_list);
//先判斷是否自定義了rc文件,如果沒有的話就讀取默認(rèn)的/init.rc
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
//解析/init.rc
parser.ParseConfig("/init.rc");
//以下這些rc文件如果第一次解析失敗,就放到完一點(diǎn)的解析隊(duì)列中
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
} else {
//解析自定義的rc文件
parser.ParseConfig(bootscript);
}
}
這個過程中最主要的一個操作是,調(diào)用CreateParser方法創(chuàng)建了rc文件的解析器,有三種解析器,分別是:
- service解析器,用于解析service開頭的rc語句,對應(yīng)的類是service.cpp
- on 解析器,用于解析on 開頭的語句.對應(yīng)的類是action_parser.cpp
- import解析器,用于解析import開頭的語句,該語句定義了讓rc文件之間可以互相包含進(jìn)來.對應(yīng)的類是import_parser.cpp
類圖為:

Parser中主要有四個關(guān)鍵方法:
- ParseSection 該方法主要解析主要的指令,例如service,on,import等
- ParseLineSection 該方法主要是解析指令service,on后面跟隨的Command和options
- EndSection 解析每個命令配置完成后調(diào)用,這時解析完成的數(shù)據(jù)需要在這里進(jìn)行保存
- EndFile 解析完成一個文件時調(diào)用
2.2、ParseConfig & ParseData方法
ParseConfig會調(diào)用兩個參數(shù)的ParseConfig,這里會判斷傳入的path是文件還是目錄,如果是文件的話調(diào)用ParseConfigFile解析,如果是目錄的話調(diào)用ParseConfigDir遍歷目錄下的rc文件全部解析,這個步驟可以忽略不用太關(guān)注,最終都會調(diào)用到ParseData方法真正解析文件,整個解析過程會掃描配置文件,解析指令存放到鏈表中,在init進(jìn)程中執(zhí)行。
ParseData函數(shù)
system/core/init/parser.cpp
//第一個參數(shù):文件路徑,第二個參數(shù):文件內(nèi)容,第三個參數(shù):錯誤信息
void Parser::ParseData(const std::string& filename, const std::string& data, size_t* parse_errors) {
// TODO: Use a parser with const input and remove this copy
//開始之前為了安全考慮,先復(fù)制一份文件內(nèi)容進(jìn)行解析
std::vector<char> data_copy(data.begin(), data.end());
//在讀取到的數(shù)據(jù)最后添加一個‘\0’
data_copy.push_back('\0');
parse_state state;
//初始化從第0行
state.line = 0;
//開始指針指向0
state.ptr = &data_copy[0];
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
int section_start_line = -1;
//存放讀取到的配置文本
std::vector<std::string> args;
auto end_section = [&] {
if (section_parser == nullptr) return;
//調(diào)用EndSection方法保存讀取到的配置信息
if (auto result = section_parser->EndSection(); !result) {
(*parse_errors)++;
LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
}
section_parser = nullptr;
section_start_line = -1;
};
for (;;) {
switch (next_token(&state)) {
case T_EOF: //字符串解析結(jié)束
end_section();
return;
case T_NEWLINE://解析完一行,新開始一行的解析
//開始讀取新的一行,line加1
state.line++;
//如果始終沒有讀取到文本則進(jìn)入下一個循環(huán),讀取下一行
if (args.empty()) break;
// If we have a line matching a prefix we recognize, call its callback and unset any
// current section parsers. This is meant for /sys/ and /dev/ line entries for
// uevent.
//這里主要是針對ueventd.rc文件的處理,識別 /sys/ /dev/開頭的配置
for (const auto& [prefix, callback] : line_callbacks_) {
if (android::base::StartsWith(args[0], prefix)) {
end_section();
if (auto result = callback(std::move(args)); !result) {
(*parse_errors)++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
}
break;
}
}
if (section_parsers_.count(args[0])) {
//切換parser之前先確保上一次的解析完成
end_section();
//args[0]表示是section的開頭文本,也就是service,on,import三者之一
//依據(jù)這個來匹配到對應(yīng)的Parser
section_parser = section_parsers_[args[0]].get();
section_start_line = state.line;
//解析Action關(guān)鍵字行
if (auto result =
section_parser->ParseSection(std::move(args), filename, state.line);
!result) {
(*parse_errors)++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
section_parser = nullptr;
}
} else if (section_parser) {
//解析Action對應(yīng)的命令行
if (auto result = section_parser->ParseLineSection(std::move(args), state.line);
!result) {
(*parse_errors)++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
}
}
args.clear();
break;
case T_TEXT://解析到字符串
//讀取到文本就添加到args中,一行保存一次
//state是一個結(jié)構(gòu)體
//struct parse_state
//{
// char *ptr; // 要解析的字符串
// char *text; // 解析到的字符串,可以理解為返回一行的數(shù)據(jù)
// int line; // 解析到第行數(shù)
// int nexttoken; // 解析狀態(tài),有 T_EOF、T_NEWLINE、T_TEXT 三種
//};
//
//
args.emplace_back(state.text);
break;
}
}
}
ParseData函數(shù)中我們需要注意以下幾點(diǎn)
- arg[0] 是每一行讀取到的第一個字符串,解析過程中會依據(jù)這個字符串來動態(tài)切換解析器,字符串和解析器的對應(yīng)關(guān)系為:service -> ServiceParser, on -> ActionParser,import -> ImportParser
- parse_state 是一個結(jié)構(gòu)體
struct parse_state{
char *ptr; //要解析的字符串
char *text;//解析到的字符串,一般為一行字符串
int line; //解析到第幾行
int nexttoken; //解析狀態(tài),有 T_EOF、T_NEWLINE、T_TEXT 三種
}
- nexttoken,解析狀態(tài),T_EOF 表示字符串解析結(jié)束,T_NEWLINE 表示解析完一行的數(shù)據(jù),T_TEXT 表示解析到一個單詞
2.3、ActionParser
上面有提供Parser的類圖大致結(jié)構(gòu),在解析過程中實(shí)際的解析會在對應(yīng)的Parser中進(jìn)行。ActionParser主要是解析以 on開頭的一系列命令,Action決定對應(yīng)的動作什么時候會執(zhí)行,解析完成后Action會被添加到存放Action的隊(duì)列中。Action的每個命令是按順序排隊(duì)執(zhí)行,每個命令依次進(jìn)入執(zhí)行狀態(tài)。
ActionParser實(shí)際上就是把配置解析為Action結(jié)構(gòu),對應(yīng)的Action類定義在system/core/init/action.h中
......
std::map<std::string, std::string> property_triggers_;
std::string event_trigger_;
std::vector<Command> commands_;
......
Commands存放Action中定義的一系列Command操作。
2.3.1 ParseSection
Result<Success> ActionParser::ParseSection(std::vector<std::string>&& args,
const std::string& filename, int line) {
//這里判斷Action是否有定義對應(yīng)的trigger觸發(fā)器,如果沒有的話就返回失敗
std::vector<std::string> triggers(args.begin() + 1, args.end());
if (triggers.size() < 1) {
return Error() << "Actions must have a trigger";
}
......
std::string event_trigger;
std::map<std::string, std::string> property_triggers;
//解析Action的trigger觸發(fā)器,提取event_trigger和property_triggers
if (auto result = ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers);
!result) {
return Error() << "ParseTriggers() failed: " << result.error();
}
//創(chuàng)建Action對象
auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
property_triggers);
action_ = std::move(action);
return Success();
}
這里傳遞給ParseTriggers方法的triggers是去除了命令關(guān)鍵字on之外的觸發(fā)器信息,ParseTriggers會判斷是否是屬性變化觸發(fā)的觸發(fā)器,如果是屬性變化的觸發(fā)器的話填充到property_triggers,否則填充到event_trigger。
Result<Success> ParseTriggers(const std::vector<std::string>& args, Subcontext* subcontext,
std::string* event_trigger,
std::map<std::string, std::string>* property_triggers) {
const static std::string prop_str("property:");
for (std::size_t i = 0; i < args.size(); ++i) {
if (args[i].empty()) {
return Error() << "empty trigger is not valid";
}
//trigger觸發(fā)器需要通過 && 連接
//例如:on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
if (i % 2) {
if (args[i] != "&&") {
return Error() << "&& is the only symbol allowed to concatenate actions";
} else {
continue;
}
}
//判斷是否是property:開頭的配置條件,Action中滿足條件時會被執(zhí)行,這里主要是屬性變化觸發(fā)器的解析
//這里會把屬性變化的觸發(fā)條件填充到property_triggers中
if (!args[i].compare(0, prop_str.length(), prop_str)) {
if (auto result = ParsePropertyTrigger(args[i], subcontext, property_triggers);
!result) {
return result;
}
} else {
if (!event_trigger->empty()) {
return Error() << "multiple event triggers are not allowed";
}
//如果不是屬性變化的觸發(fā)器,則填充到event_trigger中
*event_trigger = args[i];
}
}
return Success();
}
ParseTriggers做了兩件事情
- 觸發(fā)器語法判斷 && 連接符
- 觸發(fā)器依據(jù)是否是屬性變化區(qū)分不同觸發(fā)器提取到不同的數(shù)據(jù)結(jié)構(gòu)里面
2.3.2、ParseLineSection
解析完成命令關(guān)鍵字部分后就會解析對應(yīng)的Command子模塊,ParseLineSection直接調(diào)用了Action的AddCommand函數(shù)
Result<Success> Action::AddCommand(const std::vector<std::string>& args, int line) {
if (!function_map_) {
return Error() << "no function map available";
}
//通過args調(diào)用FindFunction查找對應(yīng)的處理命令,類似解析命令關(guān)鍵字行的做法
//利用args[0]進(jìn)行匹配,function_map在init進(jìn)程啟動的時候已經(jīng)初始化了。
auto function = function_map_->FindFunction(args);
if (!function) return Error() << function.error();
commands_.emplace_back(function->second, function->first, args, line);
return Success();
}
FindFunction函數(shù)在keyword_map.h中會通過args[0]以及具體的字符內(nèi)容匹配到最終的命令參數(shù),最后填充到commands_中
function_map_的內(nèi)容如下(system/core/init/builtins.cpp)
static const Map builtin_functions = {
{"bootchart", {1, 1, {false, do_bootchart}}},
{"chmod", {2, 2, {true, do_chmod}}},
{"chown", {2, 3, {true, do_chown}}},
{"class_reset", {1, 1, {false, do_class_reset}}},
{"class_restart", {1, 1, {false, do_class_restart}}},
{"class_start", {1, 1, {false, do_class_start}}},
{"class_stop", {1, 1, {false, do_class_stop}}},
{"copy", {2, 2, {true, do_copy}}},
{"domainname", {1, 1, {true, do_domainname}}},
{"enable", {1, 1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
{"exec_start", {1, 1, {false, do_exec_start}}},
{"export", {2, 2, {false, do_export}}},
{"hostname", {1, 1, {true, do_hostname}}},
{"ifup", {1, 1, {true, do_ifup}}},
{"init_user0", {0, 0, {false, do_init_user0}}},
{"insmod", {1, kMax, {true, do_insmod}}},
{"installkey", {1, 1, {false, do_installkey}}},
{"load_persist_props", {0, 0, {false, do_load_persist_props}}},
{"load_system_props", {0, 0, {false, do_load_system_props}}},
{"loglevel", {1, 1, {false, do_loglevel}}},
{"mkdir", {1, 4, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {1, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
{"umount", {1, 1, {false, do_umount}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"restart", {1, 1, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
{"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}},
{"rm", {1, 1, {true, do_rm}}},
{"rmdir", {1, 1, {true, do_rmdir}}},
{"setprop", {2, 2, {true, do_setprop}}},
{"setrlimit", {3, 3, {false, do_setrlimit}}},
{"start", {1, 1, {false, do_start}}},
{"stop", {1, 1, {false, do_stop}}},
{"swapon_all", {1, 1, {false, do_swapon_all}}},
{"symlink", {2, 2, {true, do_symlink}}},
{"sysclktz", {1, 1, {false, do_sysclktz}}},
{"trigger", {1, 1, {false, do_trigger}}},
{"verity_load_state", {0, 0, {false, do_verity_load_state}}},
{"verity_update_state", {0, 0, {false, do_verity_update_state}}},
{"wait", {1, 2, {true, do_wait}}},
{"wait_for_prop", {2, 2, {false, do_wait_for_prop}}},
{"write", {2, 2, {true, do_write}}},
#ifdef VENDOR_EDIT
{"reload_policy", {0, 0, {true, do_reload_policy}}},
{"md", {1, 1, {true, do_md}}},
{"copyall", {2, 2, {true, do_copyall}}},
#if OP_FEATURE_UPDATE_RESERVE == 1
{"mount_reserve", {3, kMax, {false, do_mount_reserve}}},
#endif
#endif
};
map對應(yīng)的各字段,從左到右
- 函數(shù)名
- 最小參數(shù)個數(shù)
- 最大參數(shù)個數(shù)
- 處理的函數(shù)地址
2.3.3、EndSection
這里主要是將最終解析出來的Action保存到ActionManager的actions_中
Result<Success> ActionParser::EndSection() {
if (action_ && action_->NumCommands() > 0) {
action_manager_->AddAction(std::move(action_));
}
return Success();
}
void ActionManager::AddAction(std::unique_ptr<Action> action) {
actions_.emplace_back(std::move(action));
}
2.4、ServiceParser
Service結(jié)構(gòu)比較復(fù)雜,感興趣的話可以閱讀system/core/init/service.h文件。但是主要還是定義了Service的一些操作函數(shù),rc文件中定義的一些命令和Options
2.4.1、ParseSection
Result<Success> ServiceParser::ParseSection(std::vector<std::string>&& args,
const std::string& filename, int line) {
//首先需要驗(yàn)證service配置是否正確,service配置必須包含
//服務(wù)名和啟動參數(shù)
if (args.size() < 3) {
return Error() << "services must have a name and a program";
}
const std::string& name = args[1];
//驗(yàn)證服務(wù)名是否合法
if (!IsValidName(name)) {
return Error() << "invalid service name '" << name << "'";
}
......
//服務(wù)啟動參數(shù)
std::vector<std::string> str_args(args.begin() + 2, args.end());
service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args);
return Success();
}
2.4.2、ParseLineSection
ParseLineSection主要是解析service命令快接下來Options的指令塊。
Result<Success> ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line) {
return service_ ? service_->ParseLine(std::move(args)) : Success();
}
Result<Success> Service::ParseLine(const std::vector<std::string>& args) {
static const OptionParserMap parser_map;
auto parser = parser_map.FindFunction(args);
if (!parser) return parser.error();
return std::invoke(*parser, this, args);
}
parser_map是固定的,內(nèi)容如下,參數(shù)分別代表:處理關(guān)鍵字、最小參數(shù)個數(shù)、最大參數(shù)個數(shù)、處理函數(shù)地址。:
static const Map option_parsers = {
{"capabilities",
{1, kMax, &Service::ParseCapabilities}},
{"class", {1, kMax, &Service::ParseClass}},
{"console", {0, 1, &Service::ParseConsole}},
{"critical", {0, 0, &Service::ParseCritical}},
{"disabled", {0, 0, &Service::ParseDisabled}},
{"enter_namespace",
{2, 2, &Service::ParseEnterNamespace}},
{"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}},
{"interface", {2, 2, &Service::ParseInterface}},
{"ioprio", {2, 2, &Service::ParseIoprio}},
{"priority", {1, 1, &Service::ParsePriority}},
{"keycodes", {1, kMax, &Service::ParseKeycodes}},
{"oneshot", {0, 0, &Service::ParseOneshot}},
{"onrestart", {1, kMax, &Service::ParseOnrestart}},
{"override", {0, 0, &Service::ParseOverride}},
{"oom_score_adjust",
{1, 1, &Service::ParseOomScoreAdjust}},
{"memcg.swappiness",
{1, 1, &Service::ParseMemcgSwappiness}},
{"memcg.soft_limit_in_bytes",
{1, 1, &Service::ParseMemcgSoftLimitInBytes}},
{"memcg.limit_in_bytes",
{1, 1, &Service::ParseMemcgLimitInBytes}},
{"namespace", {1, 2, &Service::ParseNamespace}},
{"rlimit", {3, 3, &Service::ParseProcessRlimit}},
{"seclabel", {1, 1, &Service::ParseSeclabel}},
{"setenv", {2, 2, &Service::ParseSetenv}},
{"shutdown", {1, 1, &Service::ParseShutdown}},
{"socket", {3, 6, &Service::ParseSocket}},
{"file", {2, 2, &Service::ParseFile}},
{"user", {1, 1, &Service::ParseUser}},
{"writepid", {1, kMax, &Service::ParseWritepid}},
};
3.4.3 EndSection
EndSection 將創(chuàng)建并填充完成的 Sevice 對象加入到 services_ 鏈表中,首先會先依據(jù)service name在鏈表中查找是否已經(jīng)有了,如果有的話需要先移除,再重新創(chuàng)建新的服務(wù)保存到鏈表中
Result<Success> ServiceParser::EndSection() {
if (service_) {
//先找到舊的service并移除
Service* old_service = service_list_->FindService(service_->name());
if (old_service) {
if (!service_->is_override()) {
return Error() << "ignored duplicate definition of service '" << service_->name()
<< "'";
}
service_list_->RemoveService(*old_service);
old_service = nullptr;
}
//添加service到鏈表中
service_list_->AddService(std::move(service_));
}
return Success();
}
三、總結(jié)
總結(jié)來看,init.rc文件會依據(jù)不同的命令切換不同的Parser進(jìn)行解析,解析過程中主要是執(zhí)行ParseSection,ParseLineSection和EndSection來進(jìn)行解析。此過程中會包含語法的檢查,數(shù)據(jù)儲存等。大致的過程如下:
