eBPF 进阶: 内核新特性进展一览
Linux 内核在 2022 年主要发布了 5.16-5.19 以及 6.0 和 6.1 这几个版本,每个版本都为 eBPF 引入了大量的新特性。本文将对这些新特性进行一点简要的介绍,更详细的资料请参考对应的链接信息。总体而言,eBPF 在内核中依然是最活跃的模块之一,它的功能特性也还在高速发展中。某种意义上说,eBPF 正朝着一个完备的内核态可编程接口快速进化。
- eBPF 进阶: 内核新特性进展一览
- BPF kfuncs
- Bloom Filter Map:5.16
- Compile Once – Run Everywhere:Linux 5.17
- bpf_loop() 辅助函数:5.17
- BPF_LINK_TYPE_KPROBE_MULTI:5.18
- 动态指针和类型指针:5.19
- USDT:5.19
- bpf panic:6.1
- BPF 内存分配器、链表:6.1
- user ring buffer 6.1
BPF kfuncs
BPF子系统暴露了内核内部算法和数据结构的许多方面;这自然导致了对在内核变化时保持接口稳定性的关注。长期以来,BPF对用户空间不提供接口稳定性保证的立场似乎一直有点问题;过去,内核开发者发现他们不得不维护那些不打算稳定的接口。现在BPF社区开始考虑至少为它的一些接口提供明确的稳定性承诺可能意味着什么。
BPF允许由用户空间加载的程序被附加到大量钩子中的任何一个,并在内核中运行–在子系统的验证器得出这些程序不会损害系统的结论之后。一个程序将获得由它所连接的钩子提供给它的内核数据结构的访问权。在某些情况下,程序可以直接修改这些数据,从而直接影响内核的运行;在其他情况下,内核将对BPF程序返回的值采取行动,例如,允许或不允许某项操作。
还有两种机制,内核可以通过它们使BPF程序获得额外的功能。帮助函数(或 “helpers”)是为提供给BPF程序而编写的特殊函数;它们从扩展BPF时代开始就存在了。被称为kfuncs的机制比较新;它允许任何内核函数被提供给BPF,可能会有一些限制。Kfuncs更简单、更灵活;如果它们首先被实现,那么似乎不太可能有人会在后来添加帮助器。也就是说,kfuncs有一个重要的限制,即它们只能被JIT编译的BPF代码访问,所以它们在缺乏JIT支持的架构上是不可用的(这个列表目前包括32位Arm和RISC-V,尽管增加这两种支持的补丁正在开发中). 每个kfunc都为BPF程序提供了一些有用的功能,但几乎每个kfunc都暴露了内核内部工作方式的某些方面。
- Reconsidering BPF ABI stability: https://mp.weixin.qq.com/s/wYDSXuwVgmGw-wmFgBNJcA
- Documentation/bpf: Add a description of “stable kfuncs” https://www.spinics.net/lists/kernel/msg4676660.html
Bloom Filter Map:5.16
布隆过滤器是一种节省空间的概率数据结构,用于快速测试一个元素是否存在于一个集合中。在布隆过滤器中,假阳性是可能的,而假阴性则不可能。
这个补丁集包括布隆过滤器中可配置数量的哈希值和条目的基准测试。这些基准大致表明,平均而言,使用3个哈希函数是最理想的选择之一。当比较hashmap查找中使用3个哈希值的bloom filter和不使用bloom filter的hashmap查找时,使用bloom filter的查找对于5万个条目大约快15%,对于10万个条目快25%,对于5万个条目快180%,对于1百万个条目快200%。
- BPF: Implement bloom filter map https://lwn.net/Articles/868024/
Compile Once – Run Everywhere:Linux 5.17
Linux 5.17 为 eBPF 添加了一次编译到处执行(Compile Once – Run Everywhere,简称 CO-RE),大大简化了 eBPF 程序处理多版本内核兼容时的复杂性以及循环逻辑的处理。
eBPF 的一次编译到处执行(简称 CO-RE)项目借助了 BPF 类型格式(BPF Type Format, 简称 BTF)提供的调试信息,再通过下面的四个步骤,使得 eBPF 程序可以适配不同版本的内核:
- 第一,在 bpftool 工具中提供了从 BTF 生成头文件的工具,从而摆脱了对内核头文件的依赖。
- 第二,通过对 BPF 代码中的访问偏移量进行重写,解决了不同内核版本中数据结构偏移量不同的问题。
- 第三,在 libbpf 中预定义不同内核版本中数据结构的修改,解决了不同内核中数据结构不兼容的问题。
- 第四,在 libbpf 中提供一系列的内核特性探测库函数,解决了 eBPF 程序在不同内核内版本中需要执行不同行为的问题。比如,你可以用 bpf_core_type_exists() 和bpf_core_field_exists() 分别检查内核数据类型和成员变量是否存在,也可以用类似 extern int LINUX_KERNEL_VERSION __kconfig 的方式查询内核的配置选项。
采用这些方法之后,CO-RE 就使得 eBPF 程序可以在开发环境编译完成之后,分发到不同版本内核的机器中运行,并且也不再需要目标机器安装各种开发工具和内核头文件。所以,Linux 内核社区更推荐所有开发者使用 CO-RE 和 libbpf 来构建 eBPF 程序。实际上,如果你看过 BCC 的源代码,你会发现 BCC 已经把很多工具都迁移到了 CO-RE。
- eBPF多内核版本兼容详解: https://time.geekbang.org/column/article/534577
- BPF CO-RE reference guide:https://nakryiko.com/posts/bpf-core-reference-guide/
bpf_loop() 辅助函数:5.17
扩展的BPF虚拟机的主要特征之一是内置于内核的验证器,它确保所有BPF程序都能安全运行。不过,BPF开发者常常认为验证器有点喜忧参半;虽然它能在很多问题发生之前抓住它们,但它也很难让人满意。将其与一个善意但受规则约束且挑剔的官僚机构相提并论并不是完全错的。Joanne Koong提出的 bpf_loop() 建议是为了让一种类型的循环结构更容易取悦BPF的官僚们。
简而言之,这就是Koong的补丁的目的。它增加了一个新的辅助函数,可以从BPF代码中调用。
long bpf_loop(u32 iterations, long (*loop_fn)(u32 index, void *ctx),void *ctx, u64 flags);
对 bpf_loop() 的调用将导致对 loop_fn() 的迭代调用,迭代数和传入的 ctx 作为参数。flags 值目前未使用,必须为零。loop_fn() 通常将返回0;返回值为1将立即结束迭代。不允许有其他的返回值。
不像 bpf_for_each_map_elem() 受限于 bpf map 大小,bpf_loop() 的循环次数高达 1<<23 = 8388608(超过 8 百万)次;大大地扩展了 bpf_loop() 的应用场景。不过,bpf_loop() 并没有受限于 BPF 指令数(1 百万条),这是因为循环发生在 bpf_loop() 帮助函数内部。
- A different approach to BPF loops:https://lwn.net/Articles/877062/
- eBPF Talk: 实战经验之 loop:https://mp.weixin.qq.com/s/neOVsMNVWFbwpTSek-_YsA
BPF_LINK_TYPE_KPROBE_MULTI:5.18
这个补丁集增加了新的链接类型BPF_TRACE_KPROBE_MULTI,它通过Masami制作的fprobe API [1]来连接kprobe程序。fprobe API允许一次在多个函数上附加探针,速度非常快,因为它工作在ftrace之上。另一方面,它将探测点限制在函数入口或返回。
- bpf: Add kprobe multi link:https://lwn.net/Articles/885811/
动态指针和类型指针:5.19
BPF程序中的所有内存访问都使用验证器进行安全性静态检查,验证器在允许程序运行之前对其进行全面分析。虽然这允许 BPF 程序在内核空间中安全运行,但它限制了该程序如何使用指针。直到最近,一个这样的限制是在 BPF 程序中被指针引用的内存区域的大小必须在加载 BPF 程序时静态知道。Joanne Koong 最近设置的一个补丁集增强了 BPF,以支持使用指向动态大小的内存区域的指针加载程序。
Koong 的补丁集增加了对访问 BPF 程序中动态大小的内存区域的支持,其中包含一个名为 dynptrs 的新特性。dynptrs 背后的主要思想是将指向动态大小的数据区域的指针与验证器和一些 BPF 辅助函数使用的元数据相关联,以确保对该区域的访问是有效的。Koong 的补丁集在一个新定义的类型中创建了这种关联,称为struct bpf_dynptr。这种结构对 BPF 程序是不透明的。
- https://mp.weixin.qq.com/s/rz4pd41Y-Cet5YVSAKmCRw
USDT:5.19
静态跟踪点(tracepoint),在用户空间也被称为 USDT(用户静态定义的跟踪)探针(应用程序中感兴趣的特定位置),跟踪器可以在此处挂载检查代码执行和数据。它们由开发人员在源代码中明确定义,通常在编译时用 “–enable-trace” 等标志启用。静态跟踪点的优势在于它们不会经常变化:开发人员通常会保持稳定的静态跟踪 ABI,所以跟踪工具在不同的应用程序版本之间工作,这很有用,例如当升级 PostgreSQL 安装并遇到性能降低时。
- eBPF 概述:第 5 部分:跟踪用户进程:https://www.ebpf.top/post/ebpf-overview-part-5/
- Using user-space tracepoints with BPF:https://lwn.net/Articles/753601/
bpf panic:6.1
BPF子系统的关键卖点之一是加载BPF程序是安全的:BPF验证器在允许加载之前确保该程序不会伤害内核。随着更多的功能被提供给BPF程序,这种保证也许会失去一些力量,但即便如此,看到 Artem Savkov 的这个提议加入了一个明确设计为使系统崩溃的BPF助手,可能会让人有点吃惊。如果这个补丁集以类似于目前的形式被合并,它将是一个新时代的预兆,即至少在某些情况下,BPF程序被允许公开地进行破坏。
正如 Savkov 所指出的,BPF 的主要用例之一是内核调试,这项任务也经常因为存在一个适时的崩溃转储而得到帮助。通过使内核的panic() 函数对BPF程序可用,Savkov试图将这两者结合起来,允许BPF程序在检测到表明开发人员正在寻找的问题的条件时导致崩溃,并创建崩溃转储。Savkov似乎不是唯一想要这种能力的人;Jiri Olsa指出,他也收到了关于这种功能的请求。
- The BPF panic function: https://lwn.net/Articles/901284/
BPF 内存分配器、链表:6.1
本系列介绍了用户定义的BPF对象在程序中的 BTF 类型。这允许BPF程序分配他们自己的对象,建立他们的自己的对象层次,并使用由BPF 运行时提供的基本构件来灵活地建立自己的数据结构。
然后,我们介绍了对单一所有权BPF链表的支持。它可以放在BPF映射或分配的对象中,并把这些被分配的对象作为元素。它作为一个侵入性的集合工作。这样做的目的是为了在将来使分配的对象成为多个数据结构的一部分。
这个补丁和未来补丁的最终目标是允许人们在 BPF C 中做一些有限的内核式编程,并允许程序员灵活地从基本的构建块中灵活地构建自己的复杂数据结构。
关键的区别在于,这种程序是经过验证的,是安全的,保存系统的运行时完整性,并被证明是没有错误的
具体的功能包含
- 分配对象
- bpf_obj_new, bpf_obj_drop 来分配和释放对象
- 单一所有权的BPF链表
- 在 BPF maps 中支持它们
- 在分配的对象中支持它们
- 全局自旋锁。
- 在被分配对象中的自旋锁。
参考:https://lwn.net/Articles/914833/
user ring buffer 6.1
这个补丁集定义了一个新的映射类型,BPF_MAP_TYPE_USER_RINGBUF,它在一个环形缓冲器上提供了单用户空间生产者/单内核消费者的语义。 除了新的映射类型外,还增加了一个名为bpf_user_ringbuf_drain()的辅助函数,它允许BPF程序指定一个具有如下签名的回调,样本由辅助函数发布到该回调。
void(struct bpf_dynptr *dynptr, void *context)。
然后程序可以使用bpf_dynptr_read()或bpf_dynptr_data()辅助函数来安全地从dynptr中读取样本。目前没有可用的辅助函数来确定样本的大小,但是如果需要的话,可以很容易地添加一个。
libbpf 也添加了一些对应的 API:
struct ring_buffer_user *
ring_buffer_user__new(int map_fd,const struct ring_buffer_user_opts *opts);
void ring_buffer_user__free(struct ring_buffer_user *rb);
void *ring_buffer_user__reserve(struct ring_buffer_user *rb,uint32_t size);
void *ring_buffer_user__poll(struct ring_buffer_user *rb, uint32_t size,int timeout_ms);
void ring_buffer_user__discard(struct ring_buffer_user *rb, void *sample);
void ring_buffer_user__submit(struct ring_buffer_user *rb, void *sample);
- bpf: Add user-space-publisher ring buffer map type: https://lwn.net/Articles/907056/
- 本文由 eunomia-bpf 团队完成,我们正在探索 eBPF 和 WebAssembly 相互结合的工具链和运行时: https://github.com/eunomia-bpf/wasm-bpf
- 以及在 Wasm 和 eBPF 之上,尝试构建一些有趣的应用场景:
相关文章:
eBPF 进阶: 内核新特性进展一览
Linux 内核在 2022 年主要发布了 5.16-5.19 以及 6.0 和 6.1 这几个版本,每个版本都为 eBPF 引入了大量的新特性。本文将对这些新特性进行一点简要的介绍,更详细的资料请参考对应的链接信息。总体而言,eBPF 在内核中依然是最活跃的模块之一&a…...
2.输入子系统学习-multi-touch-protocol-2023.02
Documentation/input/multi-touch-protocol.txt(百度翻译) Multi-touch (MT) Protocol ------------------------- Copyright (C) 2009-2010 Henrik Rydberg <rydbergeuromail.se> 一、Introduction ------------ In order to utilize t…...
【靶机】vulnhub靶机pylington
靶机下载地址 Pylington: 1 ~ VulnHub kali ip:192.168.174.128 靶机ip:192.168.174.146 arp-scan -l发现靶机ip是192.168.174.146 进行靶机的端口扫描,这里使用的是nmap的gui 可以发现开放了21和80端口,80端口扫描到了robot…...
【大数据】大数据学习路线
职位选择 首先明确一点:大数据涉及的知识面广度还是有的,需要学习的组件繁多,想要每一项精通几乎不可能,所以企业在招聘的时候会进行细分,基于某个方向进行招聘,比如关键字,数据仓库工程师、数…...
【Python爬虫案例教学】采集某网站壁纸,实现壁纸自由
前言 (。・∀・)ノ゙嗨 大家好,这里是小圆 现在开始每天都给大家 分享些关于python爬虫的案例教学 从最简单的开始 — 采集图片壁纸 今天就来扒拉这个优质的壁纸网站~ 网址 👇 顺便瞧一眼 这里的…...
波卡2022年第四季度报告
本文将介绍Messari最新发布的波卡Polkadot 2022年第四季度报告内容。 1 Messari已经发布关于波卡Polkadot最新的报告:显示了2022年第四季度的日活账户增加了64%,新用户增长49%。 2 Messari指出,波卡中继链在2022第四季度的环比增长令人印象…...
第一章:初始化react项目+antd+less
初始化react项目 我们首先使用react脚手架创建一个项目 Ant Design less creact-react-app中文文档 creact-react-app demo生产环境打包运行 当我们执行了 npm run build 打包后直接访问index.html 看效果白屏 这时候就需要安装一个serve包 npm install -g serve当我们安…...
图的基本概念
1、图的概念 G(V,E) 图G由节点集合VV(G)和边集合EE(G)组成,其中V为非空有限集合。 集合V中的节点(node)用红色标出,通过集合E中黑色的边(edge)连接。 G的边:E中的每个顶点对&#x…...
MySQL必会四大函数-窗口函数
在了解窗口函数之前,我们必须了解聚合函数。常见的聚合函数,包括 AVG、COUNT、MAX、MIN、SUM 以及 GROUP_CONCAT,常和GROUP BY 函数一起使用。聚合函数的作用就是对一组数据行进行汇总计算,并且返回单个分析结果。 窗口函数和聚合…...
各CCF期刊点评网站/学术论坛的信息汇总及个人评价
CCF中文期刊投稿选择之篇章一:各CCF期刊点评网站/学术论坛的信息汇总及个人评价中文科技期刊A类(EI检索)中文期刊投稿点评网站整理1.小木虫学术论坛2. Letpub3. Justscience4. 发表记5. 会伴(Conference Partner)6. ijouranl7. 掌桥科研这是以…...
深度解析 JavaScript 严格模式:利弊长远的考量
前言 ECMAScript 5首次引入严格模式的概念。严格模式用于选择以更严格的条件检查JavaScript代码错误,可以应用到全局,也可以应用到函数内部。 严格模式的好处是可以提早发现错误,因此可以捕获某些 ECMAScript 问题导致的编程错误。 理解严格…...
Vue.js 循环语句
Vue.js 循环语句 在Vue开发中,for循环是我们最常遇见的场景之一,我们知道常见的遍历方式有for循环,for of、forEach、for in.虽然在开发过程中,这几种方式基本上可以满足我们大多数的场景,但是你真的知道他们之间的区…...
家政服务小程序实战教程12-详情页
我们的家政服务小程序已经完成了首页和分类展示页面的开发,接下来就需要开发详情页了。在详情页里我们展示我们的各项服务内容,让用户可以了解每项家政服务可以提供的内容。 低码开发不像传统开发,如果开发详情页需要考虑每个字段的类型&…...
十四、平衡二叉树
1、看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一棵二叉排序树(BST),并分析问题所在。 上面二叉排序树存在问题分析: 左子树全部为空,从形式上看&…...
AC/DC 基础
一、概念: AC转换成DC的基本方法有变压器方式和开关方式,如下图1、2所示;整流的基本方法有全波整流和半波整流,如下图3所示。 图1 变压器方式 图2 开关方式 图3 整流方式 二、转换方式 1、变压器方式 变压器方式首先需要通过变压…...
集成电路相关书籍
注:从此开始,文中提到的书籍都会在公众号对应文章末尾给出链接,不需要在微信后台获取,当然还是可以通过在微信后台回复相关书名获取对应的电子书。 在后台看到很多人回复集成电路相关的一些书籍,所以本文就提供一些书籍…...
前端开发之防抖与节流
前端开发中我们经常会通过监听某些事件来完成项目需求 1.通过监听 scroll 事件,检测滚动位置,根据滚动位置显示返回顶部按钮 2.通过监听 resize 事件,对某些自适应页面调整DOM的渲染(通过CSS实现的自适应不再此范围内)…...
大公司如何用A/B测试解决增长问题?
摘要:上线六年,字节跳动的短视频产品——抖音已成为许多人记录美好生活的平台。除了抖音,字节跳动旗下还同时运营着数十款产品,从资讯、游戏,到房产、教育等横跨多个领域。在产品迭代速度和创新能力的快速发展下&#…...
【Airplay_BCT】Bonjour API架构
Bonjour API 架构 OS X 和 iOS 为 Bonjour 服务应用程序提供了多层应用程序编程接口 (API): Foundation 框架中的 NSNetService 和 NSNetServiceBrowser 类; CFNetServices,Core Services 中 CFNetwork 框架的一部分; Java 的 DN…...
为什么sleeping的会话会造成阻塞(2)
背景客户反馈系统突然从11:10开始运行非常缓慢,在SQL专家云中看到大量的产生阻塞的活动会话,KILL掉阻塞的源头马上又出现新的源头,实在没有办法只能重启应用程序断开所有数据库连接才解决,请我们协助分析根本的原因。现象登录SQL专…...
从矩阵中提取对角线元素;将一维数组转换为对角线矩阵:np.diag()函数
【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】从矩阵中提取对角线元素将一维数组转换为对角线矩阵np.diag()函数选择题下列说法错误的是?import numpy as npmyarray1 np.array([1,2,3])print("【显示】myarray1")print(myarray1…...
JavaSE学习day7_02 封装和构造方法
4. 封装 面向对象的三大特征: 封装、继承、多态 封装:对象代表什么,就得封装对应的数据,并提供数据对应的行为。 比如人画圆:”画“这个行为应该封装在圆这个类,为什么?因为”画“圆要知道圆…...
2022年FIT2CLOUD飞致云开源成绩单
2023年2月15日,中国领先的开源软件公司FIT2CLOUD飞致云发布《2022年开源成绩单》,盘点公司2022年全年在开源软件产品与社区运营方面的表现。目前,飞致云旗下的核心开源软件组合包括JumpServer开源堡垒机、DataEase开源数据可视化分析平台、Me…...
【Python】asyncio使用注意事项
目录协程的定义协程的运行多个协程运行关于loop.close()回调事件循环协程的定义 需要使用 async def 语句 协程可以做哪些事: 1、等待一个future结果 2、等待另一个协程(产生一个结果或引发一个异常) 3、产生一个结果给正在等它的协程 4、引发一个异常给正在等它的协程 …...
成都链安受邀参加第五届CCF中国区块链技术大会
2月10-12日,由中国计算机学会主办的,2023年国内首场大型区块链学术会议—第五届CCF中国区块链技术大会在无锡市成功举办,成都链安作为区块链安全头部企业受邀参加此次大会。大会上,成都链安创始人&CTO郭文生教授与锡东新城商务…...
验证码识别--封装版
前面我们说过了数字英文的验证码识别操作,本章我们对其进行完善一下,结合selenium来实际操作操作。import osimport timedef coding_path(path):Base_Path os.path.abspath(os.path.dirname(os.path.abspath(__file__)) /..)Base_image os.path.join(…...
创建Wails项目
项目生成 现在 CLI 已安装,您可以使用 wails init 命令生成一个新项目。 选择您最喜欢的框架: SvelteReactVuePreactLitVanilla 使用 JavaScript 生成一个 Vue 项目: wails init -n myproject -t vue如果您更愿意使用 TypeScript: wails init -…...
深度解析UG二次开发装配的部件事件、部件原型和部件实例
做UG二次开发快一年了,每次遇到装配的问题涉及到部件事件、部件原型和部件实例还是一头雾水,什么是实例,什么是原型这些专业术语等等。 针对这个问题,今天专门写了一篇特辑,结合装配实例深度剖析装配过程中的的所有参数…...
Linux安装elasticsearch-head
elasticsearch-head 是一款专门针对于 elasticsearch 的客户端工具,用来展示数据。 elasticsearch-head 是基于 JavaScript 语言编写的,可以使用 Nodejs 下的包管理器 npm 部署。 1 安装Nodejs nodejs下载地址: https://nodejs.org/en/dow…...
MySQL InnoDB表的碎片量化和整理(data free能否用来衡量碎片?)
网络上有很多MySQL表碎片整理的问题,大多数是通过demo一个表然后参考data free来进行碎片整理,这种方式对myisam引擎或者其他引擎可能有效(本人没有做详细的测试).对Innodb引擎是不是准确的,或者data free是不是可以参…...
建设网站我们重中之重-用户体验/24小时人工在线客服
目录连续子数组的最大和1 题目描述2 解题(java)2.1 动态规划解析2.2 空间复杂度降低2.3 Java代码3 复杂性分析回文子串1 题目描述2 解题(Java)2.1 动态规划法2.2 中心扩展法最短无序连续子数组1 题目描述2 解题(Java&a…...
关于配色的网站推荐/百度业务员联系电话
mysql插入数据后获得主键 针对自增主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键,比如MySQL, <insert id"add" parameterType"vo.Category" useGeneratedKeys"true" key…...
重庆网站建设注意事项/快排seo
接上一篇 WPF多进程UI探索(Like Chrome) 找到了相对较靠谱的跨进程传递WPFUI的方法,本篇将对WPF多进程UI框架进行设计。 功能性需求 一个主进程作为宿主,承载多个子进程的UI每个子进程相互独立,互不影响主进程和子进程…...
网站没有地图怎么做的/5118关键词挖掘工具
网站地址:https://srm.dongfang.com/bid_detail.screen 东方电气采购的页面看似很友好,实际上并不好爬取 在观察网页的审查元素之后发现,1处的网页响应只是单纯的一些js代码,并没有我们想要的数据信息,因此很明显该网页…...
dede手机网站跳转/网页设计制作教程
很久很久以前我是个.net程序媛,几年前也做过一些php的项目,不过我大部分时间都是一个前端设计师(工程师)。本来我想自己写api,但是一上手,才发现,忘了。。。99%忘了,孕傻害死人。算了呢,就用小程…...
做网站推广员/营业推广的方式
题意: 有N个女生想跟自己的一个或者多个男生做在一起。然后要你算出最后能够匹配出多少对。 解题思路: 这道题是明显的二分匹配题目。有个强大的算法:匈牙利算法,确实很凶,这算法挺牛叉。 这道题属于单边匹配。 算法的…...