AndroidT(13) init 进程 -- first stage init 的初始化 (二)

news/2024/7/11 1:02:03 标签: android, android init, framework, system, c++

1.概览

  第一阶段的 init 工作主要用于读取系统启动阶段需要的配置信息(例如 linux的bootconfig,cmdline等配置信息)、挂载文件系统、安装 kernel 中的模块驱动,最后就是启动第二阶段的 init 来进行 Android 系统相关的组件。第一阶段的 init 被编译为静态的可执行程序,位于 ramdisk 中。在 kernel 启动后该 init 应用程序则会被运行。

2.构建必要的目录及设备

  这个步骤用于环境的初始化,大概分为下面几类
    a)环境变量的设置
    b)必要文件及设备的创建
    c)对文件及设备的访问权限配置,这里只有传统的 DAC(Discretionary Access Control) 还没启动DAC(Mandatory Access Control).对 DAC 和 MAC 有疑问的可以参考之前的博客《Android R(11)HIDL服务的sepolicy(五)》。

3.设置 log 的输出位置

  我们知道 Android 有自己的 log 系统,但此时它还是不可用的。所以需要将 log 导入到 kernel log 中去,方便调试。

//system\core\init\first_stage_init.cpp
FirstStageMain
    ...
    SetStdioToDevNull(argv);
    // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
    // talk to the outside world...
    InitKernelLogging(argv);

4.kernel 驱动模块加载

4.1 所在位置

  走到 kernel 启动 init 进程这一阶段时,内存中的 ramdisk 内容则是由 vendor_boot ramdik 和 init_boot ramdisk 组成的,下面是官网给出的示意图,这部分工作则是在 bootloader 阶段完成的,在此不做赘述
在这里插入图片描述

  kernel 模块是位于 vendor_boot ramdik 的,所以有必要展示下 vendor_boot 的目录结构

├── bootconfig
├── dtb
├── vendor_ramdisk
|__ vendor-ramdisk-by-name

  vendor_ramdisk 的展开结构如下

├── acct
├── apex
├── config
├── data
├── data_mirror
├── debug_ramdisk
├── default.prop
├── dev
├── first_stage_ramdisk
│   └── fstab.s5e8835
├── init.recovery.s5e8835.rc
├── lib
│   └── modules
│       ├── first_module.ko
│       ├-- ...
│       ├── modules.alias
│       ├── modules.dep
│       ├── modules.load.recovery
│       ├── modules.softdep
│       └── last_module.ko
├── linkerconfig
├── metadata
├── mnt
├── odm
├── odm_dlkm
├── odm_file_contexts
├── odm_property_contexts
├── oem
├── plat_file_contexts
├── plat_property_contexts
├── plat_service_contexts
├── postinstall
├── proc
├── product_file_contexts
├── product_property_contexts
├── product_service_contexts
├── prop.default
├── res
│   └── images
│       ├── ...
│       └── stage_fill.png
├── sdcard
├── second_stage_resources
├── sepolicy
├── storage
├── sys
├── system
│   ├── bin
│   ├── ├── ...
│   │   └── ziptool
│   ├── etc
│   │   ├── cgroups.json
│   │   ├── init
│   │   │   ├── hw
│   │   │   │   └── init.rc
│   │   │   └── servicemanager.recovery.rc
│   │   ├── ld.config.txt
│   │   ├── mke2fs.conf
│   │   ├── recovery.fstab
│   │   ├── security
│   │   │   └── otacerts.zip
│   │   └── ueventd.rc
│   └── lib64
│       ├── ...
│       ├── android.hardware.health-V1-ndk.so
│       ├── hw
│       │   ├── ...
│       │   └── android.hardware.health@2.0-impl-default.so
│       ├── ...
│       └── libz.so
├── system_dlkm
├── system_ext_file_contexts
├── system_ext_property_contexts
├── system_ext_service_contexts
├── tmp
├── vendor
├── vendor_dlkm
├── vendor_file_contexts
├── vendor_property_contexts
└── vendor_service_contexts

4.2 模块目录统计策略

  kernel 中的模块加载则是在这一阶段进行的,下面是对应的代码

//system\core\init\first_stage_init.cpp
FirstStageMain
    //code 1
    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console, want_parallel, module_count))
    //code 2
    if (module_count > 0) {
        auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>(
                boot_clock::now() - module_start_time);
        setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1);
        LOG(INFO) << "Loaded " << module_count << " kernel modules took "
                  << module_elapse_time.count() << " ms";
    }

  code 1 中,LoadKernelModules 只支持对目录 /lib/modules 深度为 1 的模块的加载

//system\core\init\first_stage_init.cpp
#define MODULE_BASE_DIR "/lib/modules"
bool LoadKernelModules(bool recovery, bool want_console, bool want_parallel, int& modules_loaded)
    std::unique_ptr<DIR, decltype(&closedir)> base_dir(opendir(MODULE_BASE_DIR), closedir);
    std::vector<std::string> module_dirs;
    while ((entry = readdir(base_dir.get()))) {
        if (entry->d_type != DT_DIR) {
            continue;
        }
        sscanf(entry->d_name, "%d.%d", &dir_major, &dir_minor)
        module_dirs.emplace_back(entry->d_name);
    }

  可以看到,它只统计 /lib/modules 目录下的第一级目录,并记录在 module_dirs vector 中,不会递归的查找。

4.3 模块的加载策略

  先看下整体的代码逻辑

//system\core\init\first_stage_init.cpp
#define MODULE_BASE_DIR "/lib/modules"
FirstStageMain
    for (const auto& module_dir : module_dirs) {
        //code 1
        std::string dir_path = MODULE_BASE_DIR "/";
        dir_path.append(module_dir);
        //code 2
        Modprobe m({dir_path}, GetModuleLoadList(recovery, dir_path));
        //code 3
        bool retval = m.LoadListedModules(!want_console);
        //code 4
        modules_loaded = m.GetModuleCount();
    }

  可见整体的代码逻辑还是相当清晰的,遍历 module_dirs 中所有的目录,对每一个目录调用 Modprobe 工具类来加载。
  下面先看看工具类 Modprobe,它是独立的一个库 libmodprobe

//system\core\libmodprobe\Android.bp
cc_library_static {
    name: "libmodprobe",
    vendor_available: true,
    ramdisk_available: true,
    recovery_available: true,
    srcs: [
        "libmodprobe.cpp",
        "libmodprobe_ext.cpp",
    ],
    shared_libs: [
        "libbase",
    ],
    export_include_dirs: ["include/"],
}

  code 2 中 GetModuleLoadList 的定义如下

std::string GetModuleLoadList(bool recovery, const std::string& dir_path) {
    auto module_load_file = "modules.load";
    if (recovery) {
        struct stat fileStat;
        std::string recovery_load_path = dir_path + "/modules.load.recovery";
        if (!stat(recovery_load_path.c_str(), &fileStat)) {
            module_load_file = "modules.load.recovery";
        }
    }
    return module_load_file;
}

  如果是 recovery 模式那么就使用 modules.load.recovery 反之则是modules.load。所以对于正常启动此处会返回 modules.load。下面来看看它的构造过程

Modprobe m({dir_path}/*base_paths*/, "modules.load"/*load_file*/);
Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file, bool use_blocklist)
    for (const auto& base_path : base_paths) {
        //code 2-1
        auto alias_callback = std::bind(&Modprobe::ParseAliasCallback, this, _1);
        ParseCfg(base_path + "/modules.alias", alias_callback);
        ...
        //code 2-2
        auto dep_callback = std::bind(&Modprobe::ParseDepCallback, this, base_path, _1);
        ParseCfg(base_path + "/modules.dep", dep_callback);
        ...
        //code 2-3
        auto softdep_callback = std::bind(&Modprobe::ParseSoftdepCallback, this, _1);
        ParseCfg(base_path + "/modules.softdep", softdep_callback);
        ...
        //code 2-4
        auto load_callback = std::bind(&Modprobe::ParseLoadCallback, this, _1);
        ParseCfg(base_path + "/" + load_file, load_callback);
        ...
        //code 2-5
        auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
        ParseCfg(base_path + "/modules.options", options_callback);
        ...
        //code 2-6
        auto blocklist_callback = std::bind(&Modprobe::ParseBlocklistCallback, this, _1);
        ParseCfg(base_path + "/modules.blocklist", blocklist_callback);
    }

  Modprobe 构造方法中仅仅只是将各个文件的内容进行解析并存入对于的变量中去,供后续使用。下面则列出他们最终被存入的变量
  code 2-1

//\system\core\libmodprobe\libmodprobe.cpp
ParseAliasCallback
    this->module_aliases_.emplace_back(alias, module_name);

  code 2-2

ParseDepCallback
    deps.push_back(prefix + *arg);
    this->module_deps_[canonical_name] = deps;

  code 2-3

ParseSoftdepCallback
    while (it != args.end()) {
        if (state == "pre:") {
            this->module_pre_softdep_.emplace_back(module, token);
        } else {
            this->module_post_softdep_.emplace_back(module, token);
        }
    }

  code 2-4

ParseLoadCallback
    this->module_load_.emplace_back(canonical_name);

  code 2-5

ParseOptionsCallback
    auto [unused, inserted] = this->module_options_.emplace(canonical_name, options);

  code 2-6

ParseBlocklistCallback
    this->module_blocklist_.emplace(canonical_name);

  code 3 中,就会根据上面初始化的值进行 kernel 模块的加载了,其实现如下

//system\core\libmodprobe\libmodprobe.cpp
bool retval = m.LoadListedModules(!want_console);
    auto ret = true;
    for (const auto& module : module_load_) {
        if (!LoadWithAliases(module, true)) {
            if (IsBlocklisted(module)) continue;
            ret = false;
            if (strict) break;
        }
    }
    return ret;

  LoadWithAliases 里面涉及的模块则是来自于文件 /lib/modules/modules.alias。

//system\core\libmodprobe\libmodprobe.cpp
Modprobe::LoadWithAliases(const std::string& module_name, ...)
    std::set<std::string> modules_to_load = {canonical_name};
    for (const auto& [alias, aliased_module] : module_aliases_) {
        ...
        modules_to_load.emplace(aliased_module);
    }

    for (const auto& module : modules_to_load) {
        if (InsmodWithDeps(module, parameters))
            module_loaded = true;
    }

  将事先准备好的 module_aliases_ 内容过滤下再调用 InsmodWithDeps 进行安装。

//system\core\libmodprobe\libmodprobe.cpp
InsmodWithDeps
    auto dependencies = GetDependencies(module_name);
    for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep)
        LoadWithAliases(*dep, true)
    // load target module itself with args
    Insmod(dependencies[0], parameters)

  从上面代码也可以看出来,在安装模块前,则会先安装他们的依赖。最后再安装 当前 kernel 模块。

4.4 加载时间的统计

  加载时间在优化系统启动时间时还是很重要的,kernle 模块的加载可以通过搜索 如下 log 查看

kernel modules took

  它的实现如下

    if (module_count > 0) {
        auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>(
                boot_clock::now() - module_start_time);
        setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1);
        LOG(INFO) << "Loaded " << module_count << " kernel modules took "
                  << module_elapse_time.count() << " ms";
    }

  可以看到,此时间还被记录到环境变量中去了 kEnvInitModuleDurationMs

//system\core\init\first_stage_init.h
static constexpr char kEnvInitModuleDurationMs[] = "INIT_MODULE_DURATION_MS";

5.属性文件的处理

//system\core\init\second_stage_resources.h
constexpr const char kSecondStageRes[] = "/second_stage_resources";
constexpr const char kBootImageRamdiskProp[] = "/system/etc/ramdisk/build.prop";
//system\core\init\first_stage_init.cpp
FirstStageMain
    if (access(kBootImageRamdiskProp, F_OK) == 0) {
        //system\core\init\second_stage_resources.h
        std::string dest = GetRamdiskPropForSecondStage();
            //return /second_stage_resources/system/etc/ramdisk/build.prop
            return std::string(kSecondStageRes) + kBootImageRamdiskProp;
        std::string dir = android::base::Dirname(dest);
        fs::copy_file(kBootImageRamdiskProp, dest, ec)
    }

  对于 ramdisk 中的属性文件,最终也会反应到 Android 的属性服务中去的。但是 ramdisk 在 Android 启动后会被卸载掉,所以这里需要做下拷贝操作。
  它的路径如下

/second_stage_resources/system/etc/ramdisk/build.prop

6.是否允许root

//system\core\init\second_stage_resources.h
constexpr const char kDebugRamdiskProp[] = "/debug_ramdisk/adb_debug.prop";
//system\core\init\first_stage_init.cpp
FirstStageMain
    if (access("/force_debuggable", F_OK) == 0) {
        constexpr const char adb_debug_prop_src[] = "/adb_debug.prop";
        constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil";
        if (access(adb_debug_prop_src, F_OK) == 0 &&
            !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec))
        if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 &&
            !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec))
        // setenv for second-stage init to read above kDebugRamdisk* files.
        setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
    }

  所以只要 ramdisk 中存在 /force_debuggable 就代表着要开启调试模式,这也意味着adb是可用的,并且是支持 root 的。

7.切换根目录

FirstStageMain
    if (ForceNormalBoot(cmdline, bootconfig)) {
        mkdir("/first_stage_ramdisk", 0755);
        PrepareSwitchRoot();
        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
        // target directory to itself here.
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            PLOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
        SwitchRoot("/first_stage_ramdisk");
    }

  代码很简单,提出来是为了提示接下去根目录就变化了。

8.根据配置挂在第一阶段的文件系统

FirstStageMain
    //system\core\init\first_stage_mount.cpp
    DoFirstStageMount(!created_devices)
        //code 1
        auto fsm = FirstStageMount::Create();
        //code 2
        (*fsm)->DoCreateDevices()
        //code 3
        return (*fsm)->DoFirstStageMount();

  可以看到流程很清晰,下面来看看他们的具体实现。

8.1 code 1

//system\core\init\first_stage_mount.cpp
auto fsm = FirstStageMount::Create();
    auto fstab = ReadFirstStageFstab()
        //Fstab fstab;
        if (!ReadFstabFromDt(&fstab)) {
            if (ReadDefaultFstab(&fstab)) {
                fstab.erase(std::remove_if(fstab.begin(), fstab.end(),
                                        [](const auto& entry) {
                                            return !entry.fs_mgr_flags.first_stage_mount;
                                        }),
                            fstab.end());
            }
        }
        return fstab;
    return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));

  可以看出,挂在表如果在 Deivce tree 中获取到了,那么就不再调用 ReadDefaultFstab,两者是互斥的。我们使用 ReadDefaultFstab 来获取,所以下面来看看他的实现

//system\core\fs_mgr\fs_mgr_fstab.cpp
ReadDefaultFstab(Fstab* fstab)
    ReadFstabFromDt(fstab, false /* verbose */);
    default_fstab_path = GetFstabPath();
    Fstab default_fstab;
    ReadFstabFromFile(default_fstab_path, &default_fstab)
    for (auto&& entry : default_fstab) {
        fstab->emplace_back(std::move(entry));
    }
    return true;

  我们依然忽略掉从 Device tree 中获取的部分,看下查找 fs 文件的顺序

//system\core\fs_mgr\fs_mgr_fstab.cpp
GetFstabPath
    for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
        std::string suffix;
        //a
        if (!fs_mgr_get_boot_config(prop/*key*/, &suffix/*out_val*/)) continue;
        //b
        for (const char* prefix : {// late-boot/post-boot locations
                                   "/odm/etc/fstab.", "/vendor/etc/fstab.",
                                   // early boot locations
                                   "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.",
                                   "/fstab.", "/first_stage_ramdisk/fstab."}) {
            //c
            std::string fstab_path = prefix + suffix;
            if (access(fstab_path.c_str(), F_OK) == 0) {
                return fstab_path;
            }
        }
    }

  从上面的代码 c 处可以看到,路径由前缀和后缀构成。
  前缀的实现代码如下

//system\core\fs_mgr\fs_mgr_boot_config.cpp
fs_mgr_get_boot_config(const std::string& key, std::string* out_val)
    // next, check if we have "ro.boot" property already
    *out_val = android::base::GetProperty("ro.boot." + key, "");
    if (!out_val->empty()) {
        return true;
    }
    // next, check if we have the property in bootconfig
    if (fs_mgr_get_boot_config_from_bootconfig_source(key, out_val)) {
        return true;
    }

    // finally, fallback to kernel cmdline, properties may not be ready yet
    if (fs_mgr_get_boot_config_from_kernel_cmdline(key, out_val)) {
        return true;
    }

  可以看出,在每一个key中获取后缀的顺序如下并且任何一个获取到后就立即返回,以该值为准,属性值->bootconfig->kernel cmdline。
  一般来说,到 ro.boot.hardware 属性的获取就可以确定了,如下

flg1080:/ # getprop ro.boot.hardware
staf1080

  那么此时的后缀就为 staf1080。
  后缀确定后,也就很好确定获取文件系统挂在顺序了
    1) /odm/etc/fstab.staf1080
    2) /vendor/etc/fstab.staf1080
    3) /system/etc/fstab.staf1080
    4) /first_stage_ramdisk/system/etc/fstab.staf1080
    5) /fstab.staf1080
    6) /first_stage_ramdisk/fstab.staf1080
  注意了,上面的路径是按先后顺序只取一例,不做糅合的。
  最后 Create 返回的是 FirstStageMountVBootV2 数据结构,看看它的类图
在这里插入图片描述

  再看看它的构造

return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
    //system\core\init\first_stage_mount.cpp
    FirstStageMountVBootV2((Fstab fstab)
        ...
        for (const auto& entry : fstab_) {
            if (!entry.vbmeta_partition.empty()) {
                vbmeta_partitions_.emplace_back(entry.vbmeta_partition);
            }
        }
        ...

  构造方法中都是 vbmeta 的内容,这部分属于安全的内容,在此则不做赘述。

8.2 code 2

//system\core\init\first_stage_mount.cpp
(*fsm)->DoCreateDevices()
||
FirstStageMount::DoCreateDevices()
    InitDevices()
        GetSuperDeviceName(&devices);
        GetDmVerityDevices(&devices)
        InitRequiredDevices(std::move(devices))
            //system\core\init\block_dev_initializer.cpp
            return block_dev_init_.InitDevices(std::move(devices));
    CreateLogicalPartitions

  其中 InitDevices 做的也很简单,从 fstab 中获取到各个分区的设备信息,然后 polling 等待 uevnet信息,如果是对应的block设备事件则调用它的处理方法进行处理

//system\core\init\devices.cpp
HandleDevice
    if (action == "add" || (action == "change" && StartsWith(devpath, "/dev/block/dm-"))){
        ...
    }
    if (action == "remove") {
        ...
    }

  CreateLogicalPartitions最终调用 android::fs_mgr::CreateLogicalPartitions 进行处理

//system\core\fs_mgr\fs_mgr_dm_linear.cpp
CreateLogicalPartitions(const std::string& block_device)
    return CreateLogicalPartitions(*metadata.get(), block_device);
        ...

8.3 code 3

  最后开始工具 fs配置文件进行文件系统挂载了

return (*fsm)->DoFirstStageMount();
    //system\core\init\first_stage_mount.cpp
    return MountPartitions()
        TrySwitchSystemAsRoot
        for (auto current = fstab_.begin(); current != fstab_.end();) {
            MountPartition(current, false /* erase_same_mounts */, &end)
        }

        for (const auto& entry : fstab_) {
            if (entry.fs_type == "overlay") {
                fs_mgr_mount_overlayfs_fstab_entry(entry);
            }
        }

        // If we don't see /system or / in the fstab, then we need to create an root entry for
        // overlayfs.
        if (!GetEntryForMountPoint(&fstab_, "/system") && !GetEntryForMountPoint(&fstab_, "/")) {
            FstabEntry root_entry;
            if (GetRootEntry(&root_entry)) {
                fstab_.emplace_back(std::move(root_entry));
            }
        }

        MapScratchPartitionIfNeeded(&fstab_, init_devices);
        fs_mgr_overlayfs_mount_all(&fstab_);

9.启动下一阶段的 init

  在完成这一次的使命后,first init 也到了该退出的时候了。

const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));

  execv的关键点则是会使用 path 指向的可执行程序替换当前进程已在执行的程序,用人话讲就是 first init 被 /system/bin/init 替换了,下面就会执行 /system/bin/init 中的 main 入口方法了。
  所以上面代码等价于在 shell 中执行如下命令,并且它的 pid 保持不变及为 1

/system/bin/init selinux_setup

http://www.niftyadmin.cn/n/420897.html

相关文章

PLC模拟量超限报警功能块

模拟量偏差报警功能块请参看下面文章: 模拟量偏差报警功能块(SCL代码)_RXXW_Dor的博客-CSDN博客工业模拟量采集的相关基础知识,可以查看专栏的系列文章,这里不再赘述,常用链接如下:PLC模拟量采集算法数学基础(线性传感器)_plc傳感器數據轉化_RXXW_Dor的博客-CSDN博客。…

基于最近电平逼近的开环MMC逆变器MATLAB仿真模型

资源&#xff1a; 基于最近电平逼近的开环MMC逆变器MATLAB仿真N12资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87874091模型介绍&#xff1a; MATLAB21b版本 DC:12kV&#xff0c;N&#xff1d;12&#xff0c; 采用最近电平逼近调制&#xff0c;采用基…

C语言---形参所导致的段错误

前言 今天刷B站&#xff0c;无意之间看到一个宣称90%人都会错的嵌入式面试题。感兴趣就看了一下。卡了十多分钟才想明白&#xff0c;只是一个小知识点&#xff0c;但还是分享一下。 题目 #include <stdio.h> #include <stdlib.h> #include <string.h>void g…

算法提高-图论-floyd算法及其扩展应用

floyd算法及其扩展应用 floyd算法及其扩展应用AcWing 1125. 牛的旅行AcWing 343. 排序AcWing 344. 观光之旅AcWing 345. 牛站 floyd算法及其扩展应用 AcWing 1125. 牛的旅行 #include <iostream> #include <cstring> #include <cmath> #include <algori…

LVS+KeepAlived集群

LVSKeepAlived集群 一.KeepAlived的原理 1.1基于什么协议 KeepAlived基于VRRP热备份协议# VRRP协议号112# VRRP组播地址224.0.0.18# VRRP通告报文的TTL值必须是2551.2如何选举Master 1&#xff09;初始化时根据state判断master和backup。 2&#xff09;最终根据优先级决定m…

卑微小测试的一天----自动生成正交法测试用例

前言 工作过程中&#xff0c;我们接触到需求后第一要务是 熟悉需求并且输出测试用例&#xff0c;针对接口测试的入参测试&#xff0c;需要校验大量入参的组合场景&#xff0c;这时我们通常采用正交法来设计测试用例&#xff0c;在减少测试用例的数量时&#xff0c;同时保障测试…

【LeetCode】HOT 100(8)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

用饭店来形象比喻线程池的工作原理

一、线程池解决的问题&#xff1f; 使用线程池主要解决在程序中频繁创建和销毁线程导致的资源浪费&#xff0c;线程池可以维护一定量的线程来执行所需要的任务&#xff0c;维护的线程也可以重复使用。 二、用形象的饭店来解释工作原理 线程池就相当于一家饭店&#xff0c; 任…