diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp index 3cd0252ff..e259fe580 100644 --- a/init/first_stage_init.cpp +++ b/init/first_stage_init.cpp @@ -123,7 +123,7 @@ std::string GetModuleLoadList(bool recovery, const std::string& dir_path) { } #define MODULE_BASE_DIR "/lib/modules" -bool LoadKernelModules(bool recovery, bool want_console, int& modules_loaded) { +bool LoadKernelModules(bool recovery, bool want_console, bool want_parallel, int& modules_loaded) { struct utsname uts; if (uname(&uts)) { LOG(FATAL) << "Failed to get kernel version."; @@ -172,7 +172,8 @@ bool LoadKernelModules(bool recovery, bool want_console, int& modules_loaded) { } Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(recovery, MODULE_BASE_DIR)); - bool retval = m.LoadListedModules(!want_console); + bool retval = (want_parallel) ? m.LoadModulesParallel(std::thread::hardware_concurrency()) + : m.LoadListedModules(!want_console); modules_loaded = m.GetModuleCount(); if (modules_loaded > 0) { return retval; @@ -285,11 +286,13 @@ int FirstStageMain(int argc, char** argv) { } auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0; + auto want_parallel = + bootconfig.find("androidboot.load_modules_parallel = \"true\"") != std::string::npos; boot_clock::time_point module_start_time = boot_clock::now(); int module_count = 0; if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console, - module_count)) { + want_parallel, module_count)) { if (want_console != FirstStageConsoleParam::DISABLED) { LOG(ERROR) << "Failed to load kernel modules, starting console"; } else { diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h index c934860ec..5d79d6ab6 100644 --- a/libmodprobe/include/modprobe/modprobe.h +++ b/libmodprobe/include/modprobe/modprobe.h @@ -16,17 +16,21 @@ #pragma once +#include #include #include #include #include #include +#include + class Modprobe { public: Modprobe(const std::vector&, const std::string load_file = "modules.load", bool use_blocklist = true); + bool LoadModulesParallel(int num_threads); bool LoadListedModules(bool strict = true); bool LoadWithAliases(const std::string& module_name, bool strict, const std::string& parameters = ""); @@ -66,7 +70,9 @@ class Modprobe { std::vector module_load_; std::unordered_map module_options_; std::set module_blocklist_; + std::mutex module_loaded_lock_; std::unordered_set module_loaded_; + std::unordered_set module_loaded_paths_; int module_count_ = 0; bool blocklist_enabled = false; }; diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp index 1a9d3642c..3054d2b43 100644 --- a/libmodprobe/libmodprobe.cpp +++ b/libmodprobe/libmodprobe.cpp @@ -21,8 +21,10 @@ #include #include +#include #include #include +#include #include #include @@ -437,6 +439,97 @@ bool Modprobe::IsBlocklisted(const std::string& module_name) { return module_blocklist_.count(canonical_name) > 0; } +// Another option to load kernel modules. load in independent modules in parallel +// and then load modules which only have soft dependency, third update dependency list of other +// remaining modules, repeat these steps until all modules are loaded. +bool Modprobe::LoadModulesParallel(int num_threads) { + bool ret = true; + std::map> mod_with_deps; + std::map> mod_with_softdeps; + + // Get dependencies + for (const auto& module : module_load_) { + auto dependencies = GetDependencies(MakeCanonical(module)); + + for (auto dep = dependencies.rbegin(); dep != dependencies.rend(); dep++) { + mod_with_deps[module].emplace(*dep); + } + } + + // Get soft dependencies + for (const auto& [it_mod, it_softdep] : module_pre_softdep_) { + mod_with_softdeps[MakeCanonical(it_mod)].emplace(it_softdep); + } + + // Get soft post dependencies + for (const auto& [it_mod, it_softdep] : module_post_softdep_) { + mod_with_softdeps[MakeCanonical(it_mod)].emplace(it_softdep); + } + + while (!mod_with_deps.empty()) { + std::vector threads; + std::vector mods_path_to_load; + std::vector mods_with_softdep_to_load; + std::mutex vector_lock; + + // Find independent modules and modules only having soft dependencies + for (const auto& [it_mod, it_dep] : mod_with_deps) { + if (it_dep.size() == 1 && mod_with_softdeps[it_mod].empty()) { + mods_path_to_load.emplace_back(*(it_dep.begin())); + } else if (it_dep.size() == 1) { + mods_with_softdep_to_load.emplace_back(it_mod); + } + } + + // Load independent modules in parallel + auto thread_function = [&] { + std::unique_lock lk(vector_lock); + while (!mods_path_to_load.empty()) { + auto mod_path_to_load = std::move(mods_path_to_load.back()); + mods_path_to_load.pop_back(); + + lk.unlock(); + ret &= Insmod(mod_path_to_load, ""); + lk.lock(); + } + }; + + std::generate_n(std::back_inserter(threads), num_threads, + [&] { return std::thread(thread_function); }); + + // Wait for the threads. + for (auto& thread : threads) { + thread.join(); + } + + // Since we cannot assure if these soft dependencies tree are overlap, + // we loaded these modules one by one. + for (auto dep = mods_with_softdep_to_load.rbegin(); dep != mods_with_softdep_to_load.rend(); + dep++) { + ret &= LoadWithAliases(*dep, true); + } + + std::lock_guard guard(module_loaded_lock_); + // Remove loaded module form mod_with_deps and soft dependencies of other modules + for (const auto& module_loaded : module_loaded_) { + mod_with_deps.erase(module_loaded); + + for (auto& [mod, softdeps] : mod_with_softdeps) { + softdeps.erase(module_loaded); + } + } + + // Remove loaded module form dependencies of other modules which are not loaded yet + for (const auto& module_loaded_path : module_loaded_paths_) { + for (auto& [mod, deps] : mod_with_deps) { + deps.erase(module_loaded_path); + } + } + } + + return ret; +} + bool Modprobe::LoadListedModules(bool strict) { auto ret = true; for (const auto& module : module_load_) { diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp index fb1f5e73e..94a1dc4e9 100644 --- a/libmodprobe/libmodprobe_ext.cpp +++ b/libmodprobe/libmodprobe_ext.cpp @@ -54,6 +54,8 @@ bool Modprobe::Insmod(const std::string& path_name, const std::string& parameter if (ret != 0) { if (errno == EEXIST) { // Module already loaded + std::lock_guard guard(module_loaded_lock_); + module_loaded_paths_.emplace(path_name); module_loaded_.emplace(canonical_name); return true; } @@ -62,6 +64,8 @@ bool Modprobe::Insmod(const std::string& path_name, const std::string& parameter } LOG(INFO) << "Loaded kernel module " << path_name; + std::lock_guard guard(module_loaded_lock_); + module_loaded_paths_.emplace(path_name); module_loaded_.emplace(canonical_name); module_count_++; return true; @@ -74,6 +78,7 @@ bool Modprobe::Rmmod(const std::string& module_name) { PLOG(ERROR) << "Failed to remove module '" << module_name << "'"; return false; } + std::lock_guard guard(module_loaded_lock_); module_loaded_.erase(canonical_name); return true; } diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp index f960b6139..f92a2b669 100644 --- a/libmodprobe/libmodprobe_test.cpp +++ b/libmodprobe/libmodprobe_test.cpp @@ -188,10 +188,11 @@ TEST(libmodprobe, Test) { EXPECT_TRUE(modules_loaded == expected_after_remove); - m = Modprobe({dir.path}); - EXPECT_FALSE(m.LoadWithAliases("test4", true)); - while (modules_loaded.size() > 0) EXPECT_TRUE(m.Remove(modules_loaded.front())); - EXPECT_TRUE(m.LoadListedModules()); + Modprobe m2({dir.path}); + + EXPECT_FALSE(m2.LoadWithAliases("test4", true)); + while (modules_loaded.size() > 0) EXPECT_TRUE(m2.Remove(modules_loaded.front())); + EXPECT_TRUE(m2.LoadListedModules()); GTEST_LOG_(INFO) << "Expected modules loaded after enabling blocklist (in order):"; for (auto i = expected_modules_blocklist_enabled.begin();