安卓啓動流程(二) - Parser解析器

Parser是rc文件解析成執行邏輯的核心工具。
內部通過tokenizer分詞器對rc文件的字符流進行解析,轉換成單詞(參數)和對應的token令牌。根據token令牌,派分到不同的解析器實現進行的處理。
通過模板模式,Parser將實際的解析邏輯解耦到解析器模板的實現類中進行。


Parser定義

/system/core/init/parser.h

class Parser {
    public:
        using LineCallback = std::function<Result<Success>(std::vector<std::string>&&)>;
        Parser();
        bool ParseConfig(const std::string& path);
        void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
        void AddSingleLineParser(const std::string& prefix, LineCallback callback);

    private:
        std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
        std::vector<std::pair<std::string, LineCallback>> line_callbacks_;
};
  • bool ParseConfig(const std::string& path);
    解析指定的rc文件
    path可以是rc文件路徑,也可以是一個目錄路徑。

  • void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
    添加 SectionParser 解析器
    添加段落解析器,name爲一行命令中,首個參數的完全匹配值。

  • void AddSingleLineParser(const std::string& prefix, LineCallback callback);
    添加 LineCallback 解析器
    添加單行命令解析器。prefix爲一行命令中,首個參數的前序匹配值。


單行命令解析器模板:LineCallback
class Parser {
    public:
        using LineCallback = std::function<Result<Success>(std::vector<std::string>&&)>;
}

本質是一個函數。
解析過程中,對一行命令的首個參數進行匹配,當匹配成功時,馬上調用對應的單行命令解析器(即回調函數)。
從源碼上看,只有在ueventd進程解析ueventd.rc時,纔有該類型解析器的應用。

段落命令解析器模板:SectionParser
class SectionParser {
    public:
        virtual ~SectionParser() {}
        virtual Result<Success> ParseSection(std::vector<std::string>&& args,
                                             const std::string& filename, int line) = 0;
        virtual Result<Success> ParseLineSection(std::vector<std::string>&&, int) { return Success(); };
        virtual Result<Success> EndSection() { return Success(); };
        virtual void EndFile(){};
};

解析過程中,對一行命令的首個參數進行匹配,當匹配成功時,即認爲該行時一個命令段落的開始,並使用匹配到的解析器解析段落中的命令內容。

主要定義了幾個函數:

  • ParseSection()
    處理段落首行命令
    行命令處理時,匹配到段落解析器的關鍵字,則視爲一個段落的首行,段落的後續命令交由該段落解析器處理,並調用該解析器的ParseSection()函數。
  • ParseLineSection()
    處理段落內容命令
    對匹配不到關鍵字的行命令,視爲段落的內容,假如存在解析中的段落解析器,則調用該解析器的ParseLineSection()函數。

  • EndSection()
    處理段落結束
    段落結束時,假如存在解析中的段落解析器,則調用該解析器的EndSection()函數。

  • EndFile()
    處理文件結束
    文件解析結束時,調用所有已添加解析器中的EndFile()函數。


Parser實現

  • AddSectionParser / AddSingleLineParser

    void Parser::AddSectionParser(const std::string& name,   std::unique_ptr<SectionParser> parser) {
        section_parsers_[name] = std::move(parser);
    }
    
    void Parser::AddSingleLineParser(const std::string& prefix, LineCallback callback) {
        line_callbacks_.emplace_back(prefix, callback);
    }
    

    把關鍵字和解析器添加到對應的容器中。

  • ParseConfig

    Parser::ParseConfig(const std::string& path) {
        size_t parse_errors;
        return ParseConfig(path, &parse_errors);
    }
    
    bool Parser::ParseConfig(const std::string& path, size_t* parse_errors) {
        *parse_errors = 0;
        if (is_dir(path.c_str())) {
            return ParseConfigDir(path, parse_errors);
        }
        return ParseConfigFile(path, parse_errors);
    }
    

    判斷path是否文件夾,選擇直接解析文件,還是解析目錄下的所有rc文件。
    ParseConfigDir()的內部遍歷出的文件,最終同樣調用ParseConfigFile()進行解析。

  • ParseConfigFile

      bool Parser::ParseConfigFile(const std::string& path, size_t* parse_errors) {
        auto config_contents = ReadFile(path);
        if (!config_contents) {
            return false;
        }
        config_contents->push_back('\n'); 
        ParseData(path, *config_contents, parse_errors);
        for (const auto& [section_name, section_parser] : section_parsers_) {
            section_parser->EndFile();
        }
        return true;
    }
    

    通過ReadFile()函數,將文件數據讀取爲字符串數據。然後通過ParseData()函數執行解析和處理。
    文件解析完成時,調用所有添加的解析器的EndFile()函數。


ParseData() 核心解析函數

Parse解析器的核心解析函數。
通過tokenizer分詞器解析字符串獲取token令牌,根據token令牌的類型,執行處理邏輯,或派分事件到具體解析器實現進行處理。

解析過程相關變量:
void Parser::ParseData(const std::string& filename, const std::string& data, size_t* parse_errors) {
    std::vector<char> data_copy(data.begin(), data.end());
    data_copy.push_back('\0');
    ...
}
  • data_copy
    存放rc文件字符流的字符容器
    把傳入數據複製到名爲data_copy的vector<char>容器,並在末尾補充結束字符'\0'(NULL字符)。
struct parse_state
{
    char *ptr;        // 當前解析中字符的指針,即解析進度
    char *text;       // 當前檢出的單詞,即參數
    int line;         // 當前解析中的行號
    int nexttoken;    // 令牌緩存,用於性能優化
};

ParseData() {
    ...
    parse_state state;
    state.line = 0;
    state.ptr = &data_copy[0];
    state.nexttoken = 0;
    ...
}
  • state
    解析數據結構體
    創建parse_state結構體state並初始化。用於存放 解析過程的狀態 和 產生的臨時數據。
ParseData() {
    ...
    SectionParser* section_parser = nullptr;
    int section_start_line = -1;
    std::vector<std::string> args;
    ...
}
  • section_parser
    段落目標解析器
    當解析到新行時,假如新行首個參數命中解析器關鍵字 (即段落的首行),則把命中的解析器設置爲段落目標解析器 (即當前段落的解析器)
    沒有命中解析器關鍵字時,則嘗試使用段落目標解析器對行進行解析(即解析段落非首行數據,即命令數據)

  • section_start_line
    當前段落開始行號
    如果當前解析的行,是一個段落的首行,則記錄該行行號。

  • args
    當前行解析出的參數鏈表
    參數鏈表,存放一行命令解析出的所有參數。

ParseData() {
    ...
    auto end_section = [&] {
        if (section_parser == nullptr) return;
            if (auto result = section_parser->EndSection(); !result) {
                (*parse_errors)++;
            }
        section_parser = nullptr;
        section_start_line = -1;
    };
    ...
}
  • end_section
    Lambda表達式,用於結束段落解析和重置解析相關數據
    假如當前段落目標解析器不爲空,則調用其EndSection()函數,通知解析器該段落解析結束,並重置段落目標解析器的指針section_parser和當前段落開始行號section_start_line
解析過程邏輯階段:

tokenizer分詞器的實現分析:安卓啓動流程(三) - tokenizer分詞器

ParseData() {
    ...
    for (;;) {
        switch (next_token(&state)) {
            case T_EOF: ...
            case T_NEWLINE: ...
            case T_TEXT: ...
        }
    }
    ...
}

死循環,通過next_token(&state)函數獲取token令牌和檢出單詞參數。

  • case T_TEXT

    case T_TEXT:
        args.emplace_back(state.text);
        break;
    

    檢出了一個單詞
    args插入單詞,state.text指針指向單詞的首字符。

  • case T_EOF

    case T_EOF:
        end_section();
        return;
    

    表示rc文本解析結束
    調用end_section表達式,通知當前段落目標解析器該段落解析結束,並退出ParseData函數。

  • case T_NEWLINE

    case T_NEWLINE:
        // 當前解析的行號 +1
        state.line++;
    
        // 檢出的參數爲空,不執行處理
        if (args.empty()) break;
    
        // 該行是否匹配到單行命令解析器
        // 假如匹配到單行命令解析器,嘗試通過end_section函數結束當前段落解析, 
        // 然後調用對應的單行命令解析器
        for (const auto& [prefix, callback] : line_callbacks_) {
            if (android::base::StartsWith(args[0], prefix)) {
                // 嘗試通過end_section函數結束當前段落解析
                end_section();
                // 調用對應的單行命令解析器
                if (auto result = callback(std::move(args)); !result) {
                    (*parse_errors)++;
                }
                break;
            }
        }
        
        // 該行是否匹配到段落解析器
        if (section_parsers_.count(args[0])) {
            // 當匹配到解析器時, 嘗試通過end_section函數結束上一個段落解析
            end_section();
            // 記錄當前解析器
            section_parser = section_parsers_[args[0]].get();
            // 記錄段落行號
            section_start_line = state.line;
            // 調用解析器的ParseSection函數
            if (auto result = section_parser->ParseSection(std::move(args), filename, state.line); !result) {
                (*parse_errors)++;
                section_parser = nullptr;
            }
        } else if (section_parser) {
            // 假如存在段落目標解析器,則調用解析器的ParseLineSection函數
            if (auto result = section_parser->ParseLineSection(std::move(args), state.line); !result) {
                (*parse_errors)++;
            }
        }
    
        // 清空參數鏈表
        args.clear();
        break;
    }
    

    檢測到換行,該行結束,處理行命令
    該行參數已解析完成,在解析下一行前,處理該行的命令。


大致流程總結

  1. 使用ParseData()對路徑參數path下的所有rc文件,進行解析。
  2. ParseData()中命令的單位爲每一行的文本。一行命令中檢出的所有參數都會放入args鏈表,在下一行解析開始前,處理命令,並在命令處理完成時清空args鏈表。
  3. 命令處理開始時,先進行解析器匹配。當args鏈表數量大於0時,進入匹配階段,首先匹配單行命令解析器,然後匹配段落命令解析器。
  4. 匹配階段無法匹配到解析器時,則該行命令視爲段落命令,進入段落命令處理階段,嘗試使用已設置爲處理中的段落解析器進行處理,無法找到解析器,則跳過該行命令的處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章