Android13-系统服务大管家-ServiceManager进程-启动篇
文章目录
- 关注 ServiceMager 原因
- ServerManager需要掌握的知识
- 资料参考
- ServiceManager 进程启动
- 启动脚本
- 涉及到的相关源码文件
- 源码跟踪
- ServiceManager脚本启动位置
- ServiceManager关联脚本
- Native层源码分析
- main.cpp
- 流程
- 打开驱动 initWithDriver
- init
- make
- ProcessState 构造方法
- open_driver(driver)
- 注册成Binder服务 addService
- 注册成Binder服务 becomeContextManager
- 轮询
- 解析命令,执行命令
- 总结
关注 ServiceMager 原因
-
理解实现的原理
-
Framework层核心内容Service,绝绝大部分离不开ServiceManager的,framework层的各种Service
都和ServiceManager打交道的。应用层调用系统Service也是和ServiceManager 关联的, -
方便后面理解各种Service源码
-
Binder 机制了解 怎么可能不去熟悉ServiceManager
ServerManager需要掌握的知识
- ServiceManager的启动
- ServiceManager的注册
- ServiceManager的获取
资料参考
ServiceManager(Native)启动分析
系统服务大管家-ServiceManager
启动ServiceManager
ServiceManager 进程启动
我们需要从两方面分析,Native层和framework层,本章我们作为 ServiceManager 源码的开篇,仅关注启动相关源码分析
为什么把ServiceManager 启动单独拿出来:方便分析,一步一个脚印理解。
启动脚本
涉及到的相关源码文件
为了方便理解,暂不用实际平台项目作为参考,直接用谷歌一套来check 源码的开篇,仅关注启动相关源码分析
部分路径和平台源码路径不一致很正常,在实际开发中以实际各个OEM厂商源码 位置为准。
在线源码XrefAndroid:
init.rc
servicemanager
/bootable/recovery/etc/init.rc
/frameworks/native/cmds/servicemanager/servicemanager.rc
源码跟踪
ServiceManager脚本启动位置
ServiceManager是在rc脚本中启动的:
/bootable/recovery/etc/init.rc 中# Start essential servicesstart servicemanager
ServiceManager关联脚本
servicemanager服务定义在frameworks\native\cmds\servicemanager\servicemanager.rc。
可以看到,servicemanger是一个Linux程序,它在设备中的存储路径是/system/bin/servicemanager,源码路径则是/frameworks/native/cmds/servicemanager。
service servicemanager /system/bin/servicemanagerclass core animation #表示这是一个核心服务,系统启动时优先运行user system # 以 system 用户身份运 group system readproccritical # 标记为关键服务,如果退出会触发系统重启onrestart restart apexd # 如果servicemanager重启 则 apexd也会重启onrestart restart audioserver # 如果servicemanager重启 则 audioserveronrestart restart gatekeeperd # 如果servicemanager重启 则 audioserveronrestart class_restart --only-enabled main onrestart class_restart --only-enabled halonrestart class_restart --only-enabled early_haltask_profiles ServiceCapacityLowshutdown critical
简单介绍下这个文件脚本:
满足条件后执行程序 /system/bin/servicemanager,去Android 下面查看,果然有。
Native层源码分析
上面已经分析了servicemanager.rc 脚本了,我们看看该模块下的编译文件Android.bp
cc_binary {
name: "servicemanager",
defaults: ["servicemanager_defaults"],
init_rc: ["servicemanager.rc"],
srcs: ["main.cpp"],
}
这里就定位到了资源文件 main.cpp
main.cpp
int main(int argc, char** argv) {#ifdef __ANDROID_RECOVERY__android::base::InitLogging(argv, android::base::KernelLogger);#endifif (argc > 2) {LOG(FATAL) << "usage: " << argv[0] << " [binder driver]";}const char* driver = argc == 2 ? argv[1] : "/dev/binder"; //检查输入参数,如果参数为空,则driver为"/dev/binder"sp<ProcessState> ps = ProcessState::initWithDriver(driver); //创建ProcessState对象(进程唯一),并进行open、ioctl、mmap操作态ps->setThreadPoolMaxThreadCount(0); // 设置Binder线程池的最大线程数为0(表示使用默认设置)ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>()); //创建自己这个对象,构造自己时构造Access对象,这个是用于权限检测//先注册自己作为服务 if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {LOG(ERROR) << "Could not self register servicemanager";}//保存ServiceManager作为BBinder对象到IPCThreadState实例中IPCThreadState::self()->setTheContextObject(manager);//向驱动注册自己成为全局唯一的ContextManager,全局只有一个ServiceManagerps->becomeContextManager();sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/); //获取一个LooperBinderCallback::setupTo(looper); //将binder fd添加到Looper中监听,当驱动有事件时,回调handleEvent()处理ClientCallbackCallback::setupTo(looper, manager); //这个是用于告知客户端当前服务端有多少个客户端绑定的回调监听//循环等待事件到来while(true) { //阻塞等待event的到来,然后进行ioctl和驱动交互获取数据 looper->pollAll(-1);}// should not be reachedreturn EXIT_FAILURE;}
流程
上面的main方,可以分析Service启动流程的,我们先给出流程图,下一步一步一步分析说明
上面列举了main方法,给出了流程图,下面具体分析核心方法,具体在启动时候做了哪些动作
打开驱动 initWithDriver
initWithDriver
/frameworks/native/libs/binder/ProcessState.cppsp<ProcessState> ProcessState::initWithDriver(const char* driver){return init(driver, true /*requireDefault*/);}
init
sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault){................std::call_once(gProcessOnce, [&](){if (access(driver, R_OK) == -1) {ALOGE("Binder driver %s is unavailable. Using /dev/binder instead.", driver);driver = "/dev/binder";}// we must install these before instantiating the gProcess object,// otherwise this would race with creating it, and there could be the// possibility of an invalid gProcess object forked by another thread// before these are installedint ret = pthread_atfork(ProcessState::onFork, ProcessState::parentPostFork,ProcessState::childPostFork);LOG_ALWAYS_FATAL_IF(ret != 0, "pthread_atfork error %s", strerror(ret));std::lock_guard<std::mutex> l(gProcessMutex);gProcess = sp<ProcessState>::make(driver);});......verifyNotForked(gProcess->mForked);return gProcess;}
这里注意核心 内容 方法 make, 就是通过智能指针创建了ProcessState对象,走一遍构造方法。
Sp 关联的强指针-若指针参考
make
先看下make 方法位置和定义,在StrongPointer.h 里面
StrongPointer.h
// TODO: Ideally we should find a way to increment the reference count before running the// constructor, so that generating an sp<> to this in the constructor is no longer dangerous.template <typename T>template <typename... Args>sp<T> sp<T>::make(Args&&... args) {T* t = new T(std::forward<Args>(args)...);sp<T> result;result.m_ptr = t;t->incStrong(t); // bypass check_not_on_stack for heap allocationreturn result;}
所以 m sp::make(driver) 回去执行 ProcessState 构造方法
ProcessState 构造方法
ProcessState::ProcessState(const char* driver): mDriverName(String8(driver)),mDriverFD(-1),mVMStart(MAP_FAILED),mThreadCountLock(PTHREAD_MUTEX_INITIALIZER),mThreadCountDecrement(PTHREAD_COND_INITIALIZER),mExecutingThreadsCount(0),mWaitingForThreads(0),mMaxThreads(DEFAULT_MAX_BINDER_THREADS),mStarvationStartTimeMs(0),mForked(false),mThreadPoolStarted(false),mThreadPoolSeq(1),mCallRestriction(CallRestriction::NONE) {base::Result<int> opened = open_driver(driver);if (opened.ok()) {// mmap the binder, providing a chunk of virtual address space to receive transactions.mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,opened.value(), 0);if (mVMStart == MAP_FAILED) {close(opened.value());// *sigh*opened = base::Error()<< "Using " << driver << " failed: unable to mmap transaction memory.";mDriverName.clear();}}#ifdef __ANDROID__LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating: %s",driver, opened.error().message().c_str());#endifif (opened.ok()) {mDriverFD = opened.value();}}
open_driver(driver)
这里不就是打开驱动的逻辑嘛
static base::Result<int> open_driver(const char* driver) {int fd = open(driver, O_RDWR | O_CLOEXEC);if (fd < 0) {return base::ErrnoError() << "Opening '" << driver << "' failed";}int vers = 0;status_t result = ioctl(fd, BINDER_VERSION, &vers);if (result == -1) {close(fd);return base::ErrnoError() << "Binder ioctl to obtain version failed";}if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {close(fd);return base::Error() << "Binder driver protocol(" << vers<< ") does not match user space protocol("<< BINDER_CURRENT_PROTOCOL_VERSION<< ")! ioctl() return value: " << result;}size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);if (result == -1) {ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));}uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);if (result == -1) {ALOGE_IF(ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::ONEWAY_SPAM_DETECTION),"Binder ioctl to enable oneway spam detection failed: %s", strerror(errno));}return fd;}
在打开驱动时候进行了 ioctl 控制操作
注册成Binder服务 addService
ServiceManager.cpp
将ServiceManager这个服务保存中mNameToService中,回调服务onRegistration()方法。其实ServiceManager也是一个服务,用来管理其他服务,在其他服务启动注册前就已经就绪了。
Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority) {auto ctx = mAccess->getCallingContext();if (multiuser_get_app_id(ctx.uid) >= AID_APP) {return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services");}if (!mAccess->canAdd(ctx, name)) {return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");}if (binder == nullptr) {return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Null binder");}if (!isValidServiceName(name)) {LOG(ERROR) << "Invalid service name: " << name;return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name");}#ifndef VENDORSERVICEMANAGERif (!meetsDeclarationRequirements(binder, name)) {// already loggedreturn Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "VINTF declaration error");}#endif // !VENDORSERVICEMANAGER// implicitly unlinked when the binder is removedif (binder->remoteBinder() != nullptr &&binder->linkToDeath(sp<ServiceManager>::fromExisting(this)) != OK) {LOG(ERROR) << "Could not linkToDeath when adding " << name;return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "linkToDeath failure");}// Overwrite the old service if it existsmNameToService[name] = Service {.binder = binder,.allowIsolated = allowIsolated,.dumpPriority = dumpPriority,.debugPid = ctx.debugPid,};auto it = mNameToRegistrationCallback.find(name);if (it != mNameToRegistrationCallback.end()) {for (const sp<IServiceCallback>& cb : it->second) {mNameToService[name].guaranteeClient = true;// permission checked in registerForNotificationscb->onRegistration(name, binder);}}return Status::ok();}
注册成Binder服务 becomeContextManager
其实就是通过ioctl 给系统发送了一条指令 BINDER_SET_CONTEXT_MGR_EXT,驱动被认定这个进程是ServiceManager.
android binder驱动层-BINDER_SET_CONTEXT_MGR_EXT
bool ProcessState::becomeContextManager(){AutoMutex _l(mLock);flat_binder_object obj {.flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,};int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);// fallback to original methodif (result != 0) {android_errorWriteLog(0x534e4554, "121035042");int unused = 0;result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused);}if (result == -1) {ALOGE("Binder ioctl to become context manager failed: %s\n", strerror(errno));}return result == 0;}
轮询
开启了死循环,通过Looper不停的pull,回调给相应的LooperCallback
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);BinderCallback::setupTo(looper);
ClientCallbackCallback::setupTo(looper, manager);while(true) {looper->pollAll(-1);
}setupTo 方法:轮询读取命令
static sp<BinderCallback> setupTo(const sp<Looper>& looper) {sp<BinderCallback> cb = sp<BinderCallback>::make();int binder_fd = -1;IPCThreadState::self()->setupPolling(&binder_fd);LOG_ALWAYS_FATAL_IF(binder_fd < 0, "Failed to setupPolling: %d", binder_fd);int ret = looper->addFd(binder_fd,Looper::POLL_CALLBACK,Looper::EVENT_INPUT,cb,nullptr /*data*/);LOG_ALWAYS_FATAL_IF(ret != 1, "Failed to add binder FD to Looper");return cb;}int handleEvent(int /* fd */, int /* events */, void* /* data */) override {IPCThreadState::self()->handlePolledCommands();return 1; // Continue receiving callbacks.}};
解析命令,执行命令
上面轮训中看到 处理指令 handleEvent,最终回调方法是handlePolledCommands
status_t IPCThreadState::handlePolledCommands(){status_t result;do {result = getAndExecuteCommand();} while (mIn.dataPosition() < mIn.dataSize());processPendingDerefs();flushCommands();return result;}
总结
- 了解ServiceManager 进程的启动,脚本和Native基本知识
- 通过ServiceManager 的启动,为后续ServieManager的注册、获取、Binder和Service的交互打一个小基础
相关文章:

Android13-系统服务大管家-ServiceManager进程-启动篇
文章目录 关注 ServiceMager 原因ServerManager需要掌握的知识资料参考ServiceManager 进程启动启动脚本涉及到的相关源码文件源码跟踪ServiceManager脚本启动位置ServiceManager关联脚本 Native层源码分析main.cpp流程打开驱动 initWithDriverinitmakeProcessState 构造方法op…...

论文笔记:Rethinking Graph Neural Networks for Anomaly Detection
目录 摘要 “右移”现象 beta分布及其小波 实验 《Rethinking Graph Neural Networks for Anomaly Detection》,这是一篇关于图(graph)上异常节点诊断的论文。 论文出处:ICML 2022 论文地址:Rethinking Graph Ne…...

vue知识补充
1.列的样式 第一种:一列一列的写 <div class"house-detail"><div class"static-container"><form-item-static label"业主姓名">{{ baseData.mainOwnerName }}</form-item-static><form-item-static la…...

pushgateway指标聚合问题
一 问题现象 一个job有多个实例推送指标,但是从pushgateway上看这个job的instance字段,只显示一个实例的ip,而不是多个实例。导致在grafana上无法正常根据ip查看监控。 应用的prometheus的配置 management:metrics:tags:application: ${spr…...

使用docker搭建FastDFS文件服务
1.拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/fastdfs:latest2.使用docker镜像构建tracker容器(跟踪服务器,起到调度的作用) docker run -dti --networkhost --name tracker -v /data/fdfs/tracker:/var/fdfs -…...

【R语言】数据分析
一、描述性统计量 借助R语言内置的airquality数据集进行简单地演示: 1、集中趋势:均值和中位数 head(airquality) # 求集中趋势 mean(airquality$Ozone, na.rmT) # 求均值 median(airquality$Ozone, na.rmT) # 求中位数 2、众数 众数(mod…...

蓝桥杯C语言组:图论问题
蓝桥杯C语言组图论问题研究 摘要 图论是计算机科学中的一个重要分支,在蓝桥杯C语言组竞赛中,图论问题频繁出现,对参赛选手的算法设计和编程能力提出了较高要求。本文系统地介绍了图论的基本概念、常见算法及其在蓝桥杯C语言组中的应用&#…...

jmeter 性能测试Linux 常用的安装
把软件安装包全部都放在/data/soft目录下 一、 Java 环境安装 1. 把JDK的安装包上传到/data/soft/目录下 2. 解压jdk安装包,重命名jdk 3. 配置环境变量 JAVA_HOME [root@MiWiFi-RA72-srv soft]# vim /etc/profile export JAVA_HOME=/data/soft/jdk1.8 export PATH=…...
19 角度操作模块(angle.rs)
angle.rs代码定义了一个泛型结构体 Angle,用于表示一个角度,其中角度以弧度为单位存储。这个结构体提供了许多特性,包括复制、克隆、默认实现、调试输出、部分相等性比较、哈希等。此外,它还根据编译时的特性(features…...
前端高级面试题及其答案
以下是一些前端高级面试题及其答案: 一、JavaScript相关 事件循环(Event Loop)机制 答案: JavaScript的事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。它包含宏任务(macrotask)队列&…...
【ORACLE】这个‘‘和null不等价的场景,deepseek你怎么看?
【ORACLE】一处’和null不等价的场景–to_char(number,varchar2) 背景 最近在做一个国产数据库替代项目,要求将ORACLE迁移到一个openGauss系数据库,迁移后,执行一个存储过程时,发现国产库的执行结果和ORACLE不一致, …...

使用Python实现PDF与SVG相互转换
目录 使用工具 使用Python将SVG转换为PDF 使用Python将SVG添加到现有PDF中 使用Python将PDF转换为SVG 使用Python将PDF的特定页面转换为SVG SVG(可缩放矢量图形)和PDF(便携式文档格式)是两种常见且广泛使用的文件格式。SVG是…...
ComfyUI 安装教程:macOS 和 Linux 统一步骤
本教程将详细介绍如何在 macOS 和 Linux 上安装 ComfyUI。我们将从 安装 Anaconda 开始,到安装 PyTorch 和 ComfyUI,最后提供一些常见问题的解决方法。 macOS和linux安装步骤很相似 可以按照1️⃣安装anaconda2️⃣安装python3️⃣torch4️⃣comfyui Co…...

360手机刷机 360手机解Bootloader 360手机ROOT
360手机刷机 360手机解Bootloader 360手机ROOT 问:360手机已停产,现在和以后,能刷机吗? 答:360手机,是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…...

t113-qt
修改QT配置: # # qmake configuration for building with arm-linux-gnueabi-g ## MAKEFILE_GENERATOR UNIX # CONFIG incremental # QMAKE_INCREMENTAL_STYLE sublib# include(../common/linux.conf) # include(../common/gcc-base-unix.conf) # inc…...

【真一键部署脚本】——一键部署deepseek
目录 deepseek一键部署脚本说明 0 必要前提 1 使用方法 1.1 使用默认安装配置 1.1 .1 使用其它ds模型 1.2 使用自定义安装 2 附录:deepseek模型手动下载 3 脚本下载地址 deepseek一键部署脚本说明 0 必要前提 linux环境 python>3.10 1 使用方法 1.1 …...

【AI 语音】实时语音交互优化全解析:从 RTC 技术到双讲处理
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...

pytest-xdist 进行多进程并发测试
在自动化测试中,运行时间过长往往是令人头疼的问题。你是否遇到过执行 Pytest 测试用例时,整个测试流程缓慢得让人抓狂?别担心,pytest-xdist 正是解决这一问题的利器!它支持多进程并发执行,能够显著加快测试…...
【Android】版本和API对应关系表
目录 版本和API对应关系表 不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵! 版本和API对应关系表 版本名版本号名称APIAndroid 1616.0W36Android 1515.0V35Android 1414.0U34Android 1…...
通过acme生成与续签ssl证书,并部署到nginx
通过acme生成与续签ssl证书,并部署到nginx 介绍 官方介绍: acme.sh 实现了 acme 协议,可以从 ZeroSSL,Lets Encrypt 等 CA 生成免费的证书。 安装 acme.sh 1. curl方式 curl https://get.acme.sh | sh -s emailmyexample.com…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...