golang的垃圾回收详解
golang的垃圾回收详解
一、三色标记法
作为一门现代化的语言,golang与java一样,都在语言中内置了垃圾回收的功能,不需要程序员自己去回收堆内存。而垃圾回收中,最重要的两个部分就是垃圾检测算法以及垃圾回收算法。垃圾检测算法决定哪些对象是垃圾需要被回收,主要有引用计数法和三色标记法。垃圾回收算法决定如何回收内存,主要有标记清除,标记复制,标记压缩等。由于,引用计数法有循环引用的问题,故大部分的语言都是使用三色标记法来检测垃圾的。
三色标记法需要从一些对象出发进行分析,这些对象是必然不能被回收的,如栈对象(栈是程序自动回收的,不归垃圾回收管理),全局变量等,它们会被记录起来,放到一个列表中,这个列表在java中就被称为GC ROOTS,golang也有类似的定义。三色标记法从GC ROOTS出发,通过层层引用,GC ROOTS可以间接引用到的对象(对象可达),就不是垃圾,而GC ROOTS无法间接引用到的对象(对象不可达),就是我们需要回收的垃圾,而使用算法分析对象可不可达的过程,也被称为可达性分析。
三色标记法将对象分为三种颜色,黑色,灰色以及白色。
- 黑色,黑色代表着对象是GC ROOTS可达的,即该对象是有用的,不能被回收。而且该对象引用的对象都已经被标记,一般标记的过程就是将该对象所有引用的对象加入灰色对象的队列。
- 灰色,灰色代表该对象是GC ROOTS可达的,并且该对象引用的对象还没有被全部标记,一旦该对象引用的对象被全部标记,灰色就会变为黑色。
- 白色,白色代表该对象是GC ROOTS不可达的或者当前还没有标记到该对象,一般GC(垃圾回收)开始时,GC ROOTS中的对象会全部标记为黑色,GC ROOTS引用的对象标记为灰色,其它的对象则是白色,当GC的标记过程结束以后,剩下的白色对象就是要清除的垃圾。


二、并发垃圾回收
在垃圾回收的过程中,有一部分操作是必须要停止所有的用户线程,这被称为STW(stop the world)。STW时间的长短,是衡量一个垃圾回收算法好坏的一个重要因素。在Golang早期的时候,Golang的垃圾回收是串行的,所以STW时间特别长,达到几百毫秒,在后续的更新中,Golang垃圾回收进行了多次优化。Golang1.8后,STW停顿时间低于1ms。
Golang垃圾回收一般分为2个阶段,标记和清除。而在Golang早期的时候, 标记和清除都要STW,并且标记和清除都是单线程执行。
首先,标记需要扫描整个内存的对象,这也就意味着,内存越大,标记的时间越久,而STW的时间也会越久。其次,单线程只能使用单个cpu,无法最大化的使用多核服务器上的cpu资源。所以在Golang后面的优化中,就改用了多线程来清理垃圾。同时,清理阶段垃圾回收线程可以和用户线程一起并行执行,使STW时间降低了一些。
但是,标记阶段仍然会耗时几百毫秒,对于正常的程序来说,仍然是较难接受的。故必须要对标记进行优化,减少标记阶段的STW时间。golang的思路时,通过STW进行初始标记,然后退出STW状态,通过多个goroutine进行并发标记,标记完之后进入STW,进行再次标记以校准对象的状态,标记完成后,进行清理阶段的准备。最后退出STW状态,恢复用户goroutine,并启动多个垃圾清理协程进行垃圾清理。
之所以需要再次STW进行最终标记,是因为并发标记时,如果用户协程和标记协程对同一个变量进行操作,会产生浮动垃圾(是需要处理的垃圾,但是标记算法误认为它不是垃圾)或者错误标记把有用的对象标记为垃圾。前者只是少回收一点垃圾,但是后者会导致用户的数据丢失,导致程序运行出错。
三、并发垃圾回收导致的问题
如上图所示,当标记协程标记完C后,C已经变为黑色对象,而黑色对象是不会再次扫描的。然后用户断开了A对象对C对象的链接,所以C对象其实已经是垃圾了,可以被回收。但是由于C对象是黑色对象,所以本轮GC是不会回收C对象的,C对象就变成了浮动垃圾。当然,等到下一次GC,系统仍然可以正常回收C对象。
如上图所示,当标记协程扫描完B后,B已经变为黑色对象,此时D还没有被扫描,所以是灰色对象。然后用户让B对象引用H对象,并断开了D对象对H对象的链接。但是由于B对象是黑色对象,所以本轮GC不会被再次扫描,而只被B对象引用的H对象,仍然是白色对象,在清理阶段,就会被当场垃圾清除掉,导致程序出现问题。
浮动垃圾的问题其实并不算严重,因为下次GC仍然可以正常回收,所以该问题可以不解决。但是错误标记对象导致对象被错误清理是不能接受的。要想解决错误标记的问题,就要让三色标记法满足三色不变式。
四、三色不变式
三色不变式分为两种,强三色不变式和弱三色不变式。只要三色标记法满足其中一个,就不会出现错误标记的问题。
1.强三色不变式
强三色不变式指的是,在三色标记法进行标记时,不允许黑色对象引用白色对象。
1.弱三色不变式
弱三色不变式指的是,在三色标记法进行标记时,允许黑色对象引用白色对象,但是白色对象必须存在其他灰色对象对它的引用,或者有灰色对象对该白色对象是可达的。
五、插入写屏障
要想在三色标记法中实现三色不变式,就必须要加入一些额外的操作。在golang中,主要是依靠写屏障来实现三色不变式。写屏障,就是在对变量进行赋值的时候编译器自动插入额外的操作,如下图所示。比如说,在把白色对象赋值给黑色对象时,自动把白色对象变为灰色对象,就可以满足强三色不变式。
但是我们知道,大多数的情况下,我们都是操作栈上的变量,如果将所有的变量的赋值都加入写屏障,那么程序运行就会变慢。正常的赋值指令,翻译成汇编就是一条指令,如果加入写屏障维护三色不变式,那可能就要多加十几条汇编指令,导致程序运行GC时,将会耗费很多性能在写屏障的。所以,目前在golang实现的各个版本的写屏障,都是只对堆中变量使用写屏障,对栈中的变量则是不使用屏障。但是,如果只对堆中的变量使用写屏障,并发标记的时候毫无疑问会出问题,所以在写屏障的基础上优化出了两种解决方案,插入写屏障(incremental update)和删除写屏障(snapshot-at-the-beginning)。
在golang v1.5中,golang使用的是插入写屏障(incremental update)。插入写屏障满足的是强三色不变式,在实际算法落地的过程中,插入写屏障实现的是,不管是黑色对象还是白色对象引用其它对象,都会把被引用的对象从白色对象变为灰色对象,如果被引用的对象是灰色或者黑色则不处理。插入写屏障GC的时候,步骤如下:
- 首先进行STW,开启GC,做完前置操作后退出STW。
- 然后并行将GC ROOTS中的对象标记为黑色,并将其引用的对象标记为灰色。
- 每当对象A引用一个对象X的时候,golang会调用IsStackAddr函数去判断对象A的地址是否是在栈上。如果地址位于堆中,golang就会将值标记为灰色,如果是地址位于栈中, 则不做处理。
- 等GC协程完成整个内存的标记。
- 再次进入STW,重新标记并扫描栈上的对象的所有可达的对象,防止栈上对象的可达对象被错误标记。
- 在STW中完成清理阶段的前置准备。
- 退出STW,开始并行清理垃圾。
六、删除写屏障
与插入写屏障不一样的地方在于,插入写屏障在GC开始时,并不需要把所有的栈上的对象标黑再开始。而删除写屏障需要在第一次STW需要将栈上的对象全部变为黑色对象才能开始,但是删除写屏障不需要在标记阶段结束时,再次去扫描栈上的对象。删除写屏障满足的是弱三色不变式。删除屏障GC的时候,步骤如下:
- 首先进行STW,开启GC。
- 将栈上的对象里面的对象全部标记为黑色,并将其引用的对象标记为灰色,然后退出STW。
- 然后并行将GC ROOTS中的对象标记为黑色,并将其引用的对象标记为灰色。
- 每次当用户改变一个对象X引用的对象时,golang会调用IsStackAddr函数去判断对象X的地址是否是在栈上。如果地址位于堆中,golang就会将对象X原来引用的对象标记为灰色,如果是地址位于栈中, 则不做处理。
- 等GC协程完成整个内存的标记。
- 再次进入STW,开始进行清理阶段的前置准备。
- 退出STW,开始并行清理垃圾。
删除写屏障的缺点在于:
- 初始STW时,需要将栈上的对象全部标记为黑色,并将其引用的对象标记为灰色,导致初始的STW时间变长。
- 所有对象断开的被引用对象都会标记为灰色对象,这就意味着,这些对象中的垃圾也能存活到下一次GC开始前。所以删除写屏障的浮动垃圾会多一些。
五、混合写屏障
在golang v1.8中,综合了插入写屏障和删除写屏障,创造了一种新的写屏障,这种写屏障就被称为混合写屏障(hybrid write barrier)。混合写屏障在测试中显示,可以将STW的时间控制在50us以下。混合写屏障解决了插入写屏障扫描内存完成后需要额外重新标记栈区对象,并対栈区对象及其可达的对象进行可达性分析的缺点,也解决了删除写屏障需要在标记开始时,就必须把栈上的对象标记为黑色对象,并将其引用的对象标记为灰色的缺点。减少了GC过程中的STW时间。混合写屏障GC的工作流程如下:
- 首先进行STW,开启GC,做完前置操作后退出STW。
- GC开始后新分配的对象会被直接标记黑色。
- 然后并行将GC ROOTS中的对象标记为黑色,并将其引用的对象标记为灰色。注意在这个过程中,stack的标记是有先有后的,每个goroutine都有自己的stack,每当GC协程需要标记某个goroutine的stack时,就会停止goroutine的运行,然后在标记它对应的stack,最后在恢复goroutine。GC协程运行的过程中,除了当前正在被扫描的stack对应的goroutine不能运行以外,其它的goroutine都可以在其它的Processor上运行。
- 每当对象A引用一个对象X的时候,golang会调用IsStackAddr函数去判断对象A的地址是否是在栈上。如果是地址位于栈中, 则不做处理。如果地址位于堆中,golang就会将对象A原来引用的对象标记为灰色,如果当前调用它的goroutine的stack还没被扫描,就会将对象A新引用的对象标记为灰色。
- 等GC协程完成整个内存的标记。
- 再次进入STW,在STW中完成清理阶段的前置准备。
- 退出STW,开始并行清理垃圾。
六、混合写屏障的思考
混合写屏障的伪代码如下,其中slot是操作的变量的地址,*slot是该变量原来指向的对象的地址,ptr是将要赋值给slot的对象的地址。
writePointer(slot, ptr):shade(*slot)if current stack is grey:shade(ptr)*slot = ptr
从代码中可以看到,混合写屏障对于每一个对象都会使用删除写屏障,而插入写屏障则只有在操作的goroutine的栈未被完全扫描之前才会启用。这也就意味着goroutine对应的栈一旦被扫描,GC认为只需要删除写屏障就能保证不会出现错误标记的情况了。
首先,我们思考一下,为什么删除写屏障需要在最开始的时候将栈标记完毕。这是因为,如果删除写屏障的开始的时候没有将栈标记完毕,就会引发两个问题。
- 当把栈对象引用的白色对象赋值给黑色的堆对象,然后断开栈对象与白色对象的引用。由于不对栈对象使用删除写屏障,导致白色对象只有黑色对象引用,白色对象会被错误回收。
- 当把栈对象引用的白色对象赋值给黑色的栈对象,然后断开栈对象与白色对象的引用。由于不对栈对象使用删除写屏障,导致白色对象只有黑色对象引用,白色对象会被错误回收。
所以,在第一次STW的时候就将整个栈区扫描,标记完毕,删除写屏障就不会出现上述问题。而混合写屏障没有扫描标记整个栈区这个环节,同样也会面临同样的问题。
首先,我们分析一下第一个问题,这种情况下,只有对象的引用从栈区对象中,被复制到堆区对象才会有问题。因为堆区复制引用到栈区,或者堆区复制引用到堆区,当原来的堆对象断开引用都会触发混合写屏障,导致引用的对象被标记为灰色,而栈区复制引用到栈区,则可以看下面关于第二个问题的分析。问题1解决的方案就是伪代码中的
if current stack is grey:shade(ptr)
当对象的引用从栈区对象中,被复制到堆区对象时。如果该goroutine的栈区的对象还没被标记,则被复制的对象就会被标记为灰色对象,必然不会被错误回收。如果该goroutine的栈区的对象已经被全部标记,则复制的对象必然已经被标记为灰色或者黑色,也必然不会被回收,这就解决了问题1。
然后再看下第2个问题,在golang语言,一个goroutine正常是无法访问到另一个goroutine的栈的,除非两种特殊的操作 —— channel和启动goroutine。所以在GC开启的情况下进行这两种操作的时候,就需要对栈对象启用混合写屏障来保证三色标记法能正确标记对象。在Proposal: Eliminate STW stack re-scanning中的Channel operations and go statements就对这个问题进行了解释。如果两个对象是同一个goroutine栈中的对象,则因为在GC协程标记某个栈时,对应的goroutine必定被挂起,所以当某个goroutine运行时,整个栈要么已经被标记为黑色,要么所有对象是白色的,不会出现这个goroutine的栈中一半栈对象是白色,而另一半栈对象是黑色。当栈对象互相复制自己的引用给同一个栈区的对象时,如果src栈对象和dst栈对象都是白色对象,那等下自然会被正常标记。如果都是黑色,那它们复制的引用对应的对象必然已被标记为灰色或者黑色,也不需要去特殊处理。
七、抢占试调度
在golang早期的版本中(golang v1.0),使用的是协作试抢占。这就意味着,但GC需要STW的时候,需要等用户程序执行到safe-points(比如调用系统函数,读写channel,time.Sleep() ),但是如果用户程序一直没有执行到safe-points,那么GC永远不能完成STW。比如下面 这段代码
package mainimport ("fmt"
)func main() {for {}
}
在golang v1.4之后,golang变为了抢占试调度。golang在启动后,会启动一个线程运行 /src/runtime/proc.go中的sysmon函数,一旦某个goroutine运行超过10ms,sysmon就会向Goroutine对应的Processor(GMP模型里的P)发送SIGURG,然后在一系列的处理后,将超时的goroutine停止,这样类似GC这样的STW操作就能正常完成,或者其它的goroutine就有机会在该Processor上运行。
七、官方源码注释和文章
- Proposal: Eliminate STW stack re-scanning
- mgc.go注释
- mbarrier.go注释
- Proposal: Non-cooperative goroutine preemption
相关文章:

golang的垃圾回收详解
golang的垃圾回收详解 一、三色标记法 作为一门现代化的语言,golang与java一样,都在语言中内置了垃圾回收的功能,不需要程序员自己去回收堆内存。而垃圾回收中,最重要的两个部分就是垃圾检测算法以及垃圾回收算法。垃圾检测算法决…...

线上负载过高排查(top/vmstat/ifstat/free/df)
目录 一、五大命令 二、故障排查步骤 1、top命令找出CPU占比最高的 2、ps -ef 或者 jps -l进一步定位 3、ps -mp位到具体线程或者代码 4、jstack精准定位到错误的地方 本文通过学习:周阳老师-尚硅谷Java大厂面试题第二季 总结的LinuxJDK命令操作相关的笔记 一…...

Java的注解(Annotation)
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。例如JUnit单元测试中的Test方法,可以使得方法直接运行。JUnit单元测试Test单元测试是针…...
信息系统项目管理师:配置管理
配置管理指的是在一个系统或软件中对配置项的管理,包括对配置项的定义、存储、跟踪和修改等一系列活动。配置项可以是硬件设备、软件组件、系统设置、网络配置等,配置管理旨在确保在不同时间点或环境下系统或软件的配置项的正确性和一致性。通过配置管理…...
web餐饮开源程序
简介 一款专门针对餐饮行业而开发桌面应用程序 技术 借助Panuon.UI.Silver控件库,开发的一款餐饮软件。 运行环境:.NETFramework,Versionv4.8。 运行数据库:MySql。 ORM框架:SqlSugar。 第三方插件:Panuon.UI.Silv…...

28个案例问题分析---027---单表的11个Update接口--MyBatis
一:背景介绍 项目开发中。我们使用的是MyBatis,在MyBatis的xml文件里,两个表的更新功能,写了足足11个更新接口,毫无复用的思想 这种方式可以正常的实现功能,但是没有复用,无论是从时间上还是维…...

大数据开发治理平台 DataWorks
序言学习下阿里DataWorks的设计理念以及要做的事情cuiyaonan2000163.com参考文档:https://www.aliyun.com/product/bigdata/idehttps://help.aliyun.com/document_detail/73015.htmlhttps://help.aliyun.com/document_detail/324149.html ----数据治理LaunchDataWorks基于阿里云…...

Xshell的下载、使用、配置【ssh、telnet、串口】
目录 一、概述 二、Xshell的使用 2.1 Xshell使用ssh协议远程连接Linux主机或服务器 2.2 Xshell使用telnet协议远程连接Linux开发板 2.3 Xshell使用SERIAL协议远程连接Linux开发板 三、Xshell常用配置 3.1 配置默认会话属性 一、概述 Xshell是由NetSarang公司开发的强大…...

C++回顾(七)—— 面向对象模型
7.1 静态成员变量和静态成员函数 7.1.1 静态成员变量 关键字 static 可以用于说明一个类的成员;静态成员提供了一个同类对象的共享机制;把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static …...

开源监控服务uptime-kuma
好久没写文章了,刚好最近用了一个开源的监控服务,感觉蛮有意思的,记录一下 (一)安装 uptime-kuma安装方式有几种,这里当然是选择大家都爱的docker,一条命令搞定 docker run -d --restartalways -p 3001:…...
JavaScript混淆技术:了解其核心原理和常用手段
当今互联网时代,JavaScript已经成为了web前端开发的重点技术之一。其中,JavaScript代码的安全性问题一直是关注的焦点。为了保护JavaScript代码的安全性,很多人对其进行加密处理,众所周知,对于单纯的加密算法ÿ…...

大型医院云HIS系统:采用前后端分离架构,前端由Angular语言、JavaScript开发;后端使用Java语言开发 融合B/S版电子病历系统
一套医院云his系统源码 采用前后端分离架构,前端由Angular语言、JavaScript开发;后端使用Java语言开发。融合B/S版电子病历系统,支持电子病历四级,HIS与电子病历系统均拥有自主知识产权。 文末卡片获取联系! 基于云计…...

SAP UI5 Upload/Download file through NetWeaver Gateway
1、创建 SEGW对象 2、创建Entity Type 要把Media 标识打上 3、 激活对象然后到DPC Class的扩展对象里面重定义 /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_STREAM /IWBEP/IF_MGW_APPL_SRV_RUNTIME~UPDATE_STREAM METHOD /iwbep/if_m…...

opencv校正图像
目录1、前言2、例程2.1、代码2.2、效果口罩说明书网页3、按步骤分析转灰度图降噪 Canny边缘检测膨胀(可视具体情况省略)轮廓检索选取角度1、前言 我们用相机拍照时,会因为角度问题造成拍歪,会影响图像的识别,这时就需…...
JavaScript:函数与箭头函数的区别
ref 1. 定义 函数 function getName() {}箭头函数 const getName () > {}2. 命名 函数分为匿名、具名。 function getName() {} let getName function () {}箭头函数只有匿名。 const getName () > {}3. 构造函数 箭头函数都是匿名函数,所以不能作为构造…...
八股文(四)
目录 一、 Vue2的双向数据绑定原理 二、 vue2数据绑定缺点是什么?vue3是怎么解决的? (1)因为vue2.0 object.defineProperty只能劫持对象属性 (2)Proxy是直接代理对象 (3)proxy不…...

XSS挑战赛(xsslabs)1~10关通关解析
简介 XSS挑战赛,里面包含了各种XSS的防御方式和绕过方式,好好掌握里面的绕过细节,有助于我们更好的去发现XSS漏洞以及XSS的防御。本文更多的是分享解析的细节,不是一个标准的答案,希望大家在渗透的时候有更多的思维。…...

什么是以太网供电POE
POE指的是以太网供电,就是一根网线在传输网络的同时还传输设备所需的电源。我们最常见的就是通过POE交换机连接网络摄像头,网络摄像头无需的电源适配器,仅靠一根网线就能实现电源和网络的传输。POE供电一般可以到100米。POE包含两个部分&…...

【JUC2022】第七章 AQS、ReentrantReadWriteLock 和 StampedLock
【JUC2022】第七章 AQS 文章目录【JUC2022】第七章 AQS一、AQS1.概述2.同步器3.抽象的4.队列式二、ReentrantReadWriteLock1.概述2.案例3.存在的问题三、StampedLock1.概述2.案例3.存在的问题一、AQS 1.概述 AQS(AbstractQueueSynchronizer,抽象的队列式同步器)&am…...

Spark 磁盘作用
Spark 磁盘作用磁盘作用性能价值失败重试ReuseExchangeSpark 导航 磁盘作用 临时文件、中间文件、缓存数据,都会存储到 spark.local.dir 中 在 Shuffle Map 时, 当内存空间不足,就会溢出临时文件存储到磁盘上溢出的临时文件一起做归并计算…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...

Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
使用 ROS1-Noetic 和 mavros v1.20.1, 携带经纬度海拔的话题主要有三个: /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码,来分析他们的发布过程。发现前两个话题都对应了同一…...