当前位置: 首页 > news >正文

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》&#xff0c;这是一篇关于图&#xff08;graph&#xff09;上异常节点诊断的论文。 论文出处&#xff1a;ICML 2022 论文地址&#xff1a;Rethinking Graph Ne…...

vue知识补充

1.列的样式 第一种&#xff1a;一列一列的写 <div class"house-detail"><div class"static-container"><form-item-static label"业主姓名">{{ baseData.mainOwnerName }}</form-item-static><form-item-static la…...

pushgateway指标聚合问题

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

使用docker搭建FastDFS文件服务

1.拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/fastdfs:latest2.使用docker镜像构建tracker容器&#xff08;跟踪服务器&#xff0c;起到调度的作用&#xff09; docker run -dti --networkhost --name tracker -v /data/fdfs/tracker:/var/fdfs -…...

【R语言】数据分析

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

蓝桥杯C语言组:图论问题

蓝桥杯C语言组图论问题研究 摘要 图论是计算机科学中的一个重要分支&#xff0c;在蓝桥杯C语言组竞赛中&#xff0c;图论问题频繁出现&#xff0c;对参赛选手的算法设计和编程能力提出了较高要求。本文系统地介绍了图论的基本概念、常见算法及其在蓝桥杯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&#xff0c;用于表示一个角度&#xff0c;其中角度以弧度为单位存储。这个结构体提供了许多特性&#xff0c;包括复制、克隆、默认实现、调试输出、部分相等性比较、哈希等。此外&#xff0c;它还根据编译时的特性&#xff08;features…...

前端高级面试题及其答案

以下是一些前端高级面试题及其答案&#xff1a; 一、JavaScript相关 事件循环&#xff08;Event Loop&#xff09;机制 答案&#xff1a; JavaScript的事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。它包含宏任务&#xff08;macrotask&#xff09;队列&…...

【ORACLE】这个‘‘和null不等价的场景,deepseek你怎么看?

【ORACLE】一处’和null不等价的场景–to_char(number,varchar2) 背景 最近在做一个国产数据库替代项目&#xff0c;要求将ORACLE迁移到一个openGauss系数据库&#xff0c;迁移后&#xff0c;执行一个存储过程时&#xff0c;发现国产库的执行结果和ORACLE不一致&#xff0c; …...

使用Python实现PDF与SVG相互转换

目录 使用工具 使用Python将SVG转换为PDF 使用Python将SVG添加到现有PDF中 使用Python将PDF转换为SVG 使用Python将PDF的特定页面转换为SVG SVG&#xff08;可缩放矢量图形&#xff09;和PDF&#xff08;便携式文档格式&#xff09;是两种常见且广泛使用的文件格式。SVG是…...

ComfyUI 安装教程:macOS 和 Linux 统一步骤

本教程将详细介绍如何在 macOS 和 Linux 上安装 ComfyUI。我们将从 安装 Anaconda 开始&#xff0c;到安装 PyTorch 和 ComfyUI&#xff0c;最后提供一些常见问题的解决方法。 macOS和linux安装步骤很相似 可以按照1️⃣安装anaconda2️⃣安装python3️⃣torch4️⃣comfyui Co…...

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问&#xff1a;360手机已停产&#xff0c;现在和以后&#xff0c;能刷机吗&#xff1f; 答&#xff1a;360手机&#xff0c;是肯定能刷机的 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 附录&#xff1a;deepseek模型手动下载 3 脚本下载地址 deepseek一键部署脚本说明 0 必要前提 linux环境 python>3.10 1 使用方法 1.1 …...

【AI 语音】实时语音交互优化全解析:从 RTC 技术到双讲处理

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…...

pytest-xdist 进行多进程并发测试

在自动化测试中&#xff0c;运行时间过长往往是令人头疼的问题。你是否遇到过执行 Pytest 测试用例时&#xff0c;整个测试流程缓慢得让人抓狂&#xff1f;别担心&#xff0c;pytest-xdist 正是解决这一问题的利器&#xff01;它支持多进程并发执行&#xff0c;能够显著加快测试…...

【Android】版本和API对应关系表

目录 版本和API对应关系表 不积跬步&#xff0c;无以至千里&#xff1b;不积小流&#xff0c;无以成江海。要沉下心来&#xff0c;诗和远方的路费真的很贵&#xff01; 版本和API对应关系表 版本名版本号名称APIAndroid 1616.0W36Android 1515.0V35Android 1414.0U34Android 1…...

通过acme生成与续签ssl证书,并部署到nginx

通过acme生成与续签ssl证书&#xff0c;并部署到nginx 介绍 官方介绍&#xff1a; acme.sh 实现了 acme 协议&#xff0c;可以从 ZeroSSL&#xff0c;Lets Encrypt 等 CA 生成免费的证书。 安装 acme.sh 1. curl方式 curl https://get.acme.sh | sh -s emailmyexample.com…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...