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

Linux连接跟踪Conntrack:原理、应用与内核实现

从我们日常使用的手机 APP,到企业复杂的网络架构,连接跟踪(conntrack)都在背后默默发挥着关键作用。它就像是网络世界的 “大管家”,帮助众多网络应用维持着高效、稳定的运行。以 Kubernetes Service 为例,在容器编排的复杂环境中,Kubernetes 需要将外部的网络请求准确无误地转发到对应的容器服务上。这一过程中,连接跟踪就像是导航仪,它记录着每个服务的连接状态,确保请求能够被正确路由,让各个容器服务之间的通信有条不紊地进行 。

再看 Docker network,当我们在容器中运行各种应用时,容器之间以及容器与外部网络之间的通信管理十分重要。连接跟踪能够精准地识别和跟踪这些连接,让容器里的应用如同在一个有序的网络社区中,彼此可以顺畅交流,互不干扰。还有 iptables 主机防火墙,它守护着主机网络的安全。

连接跟踪则为它提供了关键信息,帮助防火墙判断哪些连接是合法的,哪些可能存在风险,从而对网络流量进行有效的过滤和控制,保障主机的网络安全。可以说,连接跟踪就像网络世界的基石,虽然平时不太容易被我们察觉,但一旦缺失,许多网络应用都将陷入混乱。接下来,让我们深入了解连接跟踪的原理、应用以及它在 Linux 内核中的实现机制,揭开它神秘的面纱。

一、连接跟踪(Conntrack)概述

顾名思义,连接跟踪是保存连接状态的一种机制。为什么要保存连接状态呢? 举个例子,当你通过浏览器访问一个网站(连接网站的80端口)时,预期会收到服务器发送的源端口为80的报文回应,防火墙自然应该放行这些回应报文。那是不是所有源端口为80端口的报文都应该放行呢?显然不是,我们只应该放行源IP为服务器地址,源端口为80的报文,而应该阻止源地址不符的报文,即使它的源端口也是80。总结一下这种情况就是,我们只应该让主动发起的连接产生的双向报文通过

图片

另一个例子是NAT。我们可以使用iptables配置nat表进行地址或者端口转换的规则。如果每一个报文都去查询规则,这样效率太低了,因为同一个连接的转换方式是不变的!连接跟踪提供了一种缓存解决方案:当一条连接的第一个数据包通过时查询nat表时,连接跟踪将转换方法保存下来,后续的报文只需要根据连接跟踪里保存的转换方法就可以了。

1.1连接跟踪发生在哪里?

连接跟踪需要拿到报文的第一手资料,因此它们的入口是以高优先级存在于LOCAL_OUT(本机发送)和PRE_ROUTING(报文接收)这两个链。

既然有入口,自然就有出口。连接跟踪采用的方案是在入口记录,在出口确认(confirm)。以IPv4为例:

图片

当连接的第一个skb通过入口时,连接跟踪会将连接跟踪信息保存在skb->nfctinfo,而在出口处,连接跟踪会从skb上取下连接跟踪信息,保存在自己的hash表中。当然,如果这个数据包在中途其他HOOK点被丢弃了,也就不存在最后的confirm过程了。

1.2连接跟踪信息是什么?

连接跟踪信息会在入口处进行计算,保存在skb上,信息具体包括tuple信息(地址、端口、协议号等)、扩展信息以及各协议的私有信息。

图片

  • tuple信息包括发送和接收两个方向,对TCP和UDP来说,是IP加Port;对ICMP来说是IP加Type和Code,等等;

  • 扩展信息比较复杂,本文暂时略过;

  • 各协议的私有信息,比如对TCP就是序号、重传次数、缩放因子等。

报文的连接跟踪状态

途径Netfilter框架的每一个报文总是会在入口处(PRE ROUTING或者LOCAL OUT)被赋予一个连接跟踪状态。这个状态存储在skb->nfctinfo,有以下常见的取值:

  • IP_CT_ESTABLISHED:这是一个属于已经建立连接的报文,Netfilter目击过两个方向都互通过报文了

  • IP_CT_RELATED:这个状态的报文所处的连接与另一个IP_CT_ESTABLISHED状态的连接是有联系的。比如典型的ftp,ftp-data的连接就是ftp-control派生出来的,它就是RELATED状态

  • IP_CT_NEW:这是连接的第一个包,常见的就是TCP中的SYN包,UDP、ICMP中第一个包,

  • IP_CT_ESTABLISHED + IP_CT_IS_REPLY:与IP_CT_ESTABLISHED类似,但是是在回复方向

  • IP_CT_RELATED + IP_CT_IS_REPLY:与IP_CT_RELATED类似,但是是在回复方向

二、连接跟踪(Conntrack)原理剖析

2.1 底层工作机制

连接跟踪的底层工作机制,就像是一场精心编排的 “数据包处理交响乐”,每一个环节都紧密相扣,有条不紊。

当数据包进入网络节点时,连接跟踪系统就像一个敏锐的 “拦截者”,迅速对其进行拦截。它会仔细分析数据包的头部信息,从中提取出关键的五元组信息,即源 IP 地址、目的 IP 地址、源端口、目的端口和协议类型。这些信息就像是数据包的 “身份密码”,连接跟踪系统凭借它们来判断这个数据包是否属于一个已有的连接,或者是否需要建立一个新的连接。

一旦识别出数据包的 “身份”,连接跟踪系统就会开始建立连接追踪记录。如果这个数据包是一个新连接的开始,比如 TCP 协议中的 SYN 包,连接跟踪系统会在连接跟踪表中创建一个新的记录项。这个记录项就像是一个新的 “档案”,记录着这个连接的初始状态和相关信息。在这个 “档案” 里,会包含连接的五元组信息、创建时间、当前状态等内容,为后续对这个连接的跟踪和管理提供了基础 。

随着数据的传输,连接跟踪系统会持续关注连接的状态变化。当接收到属于已有连接的数据包时,它会像一个严谨的 “档案管理员”,及时更新连接记录中的各种统计信息,比如收发包数、字节数等。同时,它还会根据数据包的类型和内容,更新连接的状态。例如,在 TCP 连接中,当接收到 SYN + ACK 包时,连接状态就会从 “SYN_SENT” 更新为 “ESTABLISHED”,标志着连接已经成功建立 。

当连接结束时,无论是正常的结束,还是因为超时、错误等原因导致的异常结束,连接跟踪系统都会及时删除连接记录。它会从连接跟踪表中移除对应的记录项,释放相关的系统资源,就像清理不再使用的 “档案” 一样,确保连接跟踪表的高效运行,为新的连接记录腾出空间 。

2.2与 Netfilter 的渊源

在 Linux 内核的网络世界里,Netfilter 就像是一个强大的 “交通枢纽”,而连接跟踪则是这个 “交通枢纽” 中不可或缺的一部分。Netfilter 是 Linux 内核中一个对数据包进行控制、修改和过滤的框架,它在内核协议栈中精心设置了若干 hook 点,这些 hook 点就像是交通要道上的 “检查站”,所有流经的数据包都必须在这里接受检查和处理 。

连接跟踪正是借助 Netfilter 的 hook 点来实现其功能的。当数据包到达 Netfilter 设置的 hook 点时,连接跟踪模块就会被触发。它会对数据包进行分析和处理,提取出连接相关的信息,并将这些信息记录到连接跟踪表中。在 NF_INET_PRE_ROUTING 这个 hook 点,连接跟踪模块会对进入系统的数据包进行检查,判断是否需要建立新的连接跟踪记录;而在 NF_INET_POST_ROUTING 这个 hook 点,它会对离开系统的数据包进行处理,更新连接的状态信息 。

不过,随着技术的不断发展,云原生网络方案 Cilium 带来了一种全新的思路。Cilium 在 1.7.4 + 版本中,基于 BPF hook 实现了一套独立的连接跟踪和 NAT 机制。BPF hook 就像是 Cilium 自己搭建的 “检查站”,它能够实现与 Netfilter 中 hook 机制类似的数据包拦截功能 。

在这个基础上,Cilium 构建了一套全新的连接跟踪和 NAT 系统,这使得它即便在卸载 Netfilter 的情况下,也能正常支持 Kubernetes 的 ClusterIP、NodePort、ExternalIPs 和 LoadBalancer 等功能 。由于这套连接跟踪机制是独立于 Netfilter 的,它的连接跟踪和 NAT 信息不会存储在内核中 Netfilter 的连接跟踪表和 NAT 表中,而是有自己独立的存储和管理方式,这也为网络管理带来了更多的灵活性和创新性 。

三、连接跟踪(Conntrack)的多元应用

3.1 在 NAT 中的关键角色

NAT,全称 Network Address Translation,即网络地址转换,它就像是网络世界里的 “地址翻译官”。在 IPv4 地址资源日益紧张的今天,NAT 技术应运而生,它允许一个机构或网络以一个公用 IP 地址出现在 Internet 上 。简单来说,它能够把内部私有网络地址翻译成合法网络 IP 地址 。

在一个企业内部网络中,众多员工的电脑都分配有私有 IP 地址,如 192.168.1.x 段的地址。当这些电脑需要访问互联网时,NAT 设备就会发挥作用。它会将数据包中的源 IP 地址(私有 IP)转换为一个合法的公网 IP 地址,然后将数据包发送出去。当外部服务器返回响应数据包时,NAT 设备又能根据之前的转换记录,把目的 IP 地址(公网 IP)转换回对应的私有 IP 地址,确保数据包能准确无误地回到员工的电脑上 。

在这个过程中,连接跟踪起着至关重要的辅助作用。当NAT设备进行地址转换时,连接跟踪系统会记录下每个连接的相关信息,包括源IP、目的 IP、源端口、目的端口以及协议类型等 。这些信息就像是一个个 “连接档案”,当后续的数据包到来时,NAT设备可以根据连接跟踪表中的记录,快速准确地进行地址转换,确保通信的顺畅 。而且,对于那些经过NAT转换的连接,连接跟踪系统能够识别出回复报文,自动完成反向转换,无需额外添加规则,大大提高了网络通信的效率 。

3.2 助力状态包过滤

状态包过滤,是一种比传统包过滤更为智能和高效的网络安全技术 。传统包过滤只基于每个数据包的源和目的地址以及端口号进行检查,对每个数据包独立处理,就像是一个只看表面信息的 “检查员” 。而状态包过滤则像是一个经验丰富的 “侦探”,它会根据网络连接的状态,对数据包进行过滤和管理 。

状态包过滤器能够检测网络连接的状态,比如TCP连接的三次握手过程,它都了如指掌 。当一个TCP SYN包到达时,状态包过滤器会知道这是一个新连接的开始;当后续的SYN + ACK 包和 ACK包到来时,它能根据之前的记录,判断这些数据包是否属于同一个连接 。通过这种方式,状态包过滤器可以判断是否允许特定的数据包通过网络,从而有效地防范会话劫持、拒绝服务攻击等网络安全威胁 。

连接跟踪为状态包过滤提供了关键的连接状态信息。连接跟踪系统会维护一个连接状态表,记录着每个连接的详细信息,包括连接的建立、数据传输以及关闭等各个阶段 。状态包过滤器在处理数据包时,会参考连接跟踪表中的信息,判断该数据包是否属于一个已建立的合法连接 。如果是,就允许数据包通过;如果不是,就会根据安全策略进行处理,比如丢弃数据包 。在一个企业网络中,连接跟踪表记录着内部员工与外部服务器建立的 HTTP 连接信息。当外部服务器返回的 HTTP 响应数据包到达时,状态包过滤器会根据连接跟踪表,确认该数据包属于合法连接,从而允许其进入企业内部网络 。

3.3 应用层网关扩展

在网络协议的世界里,大多数协议在 IP 层和传输层头部进行转换处理就可以正常工作,但有些应用层协议比较特殊,它们在协议数据报文中包含了地址信息 。为了让这些应用也能顺利完成 NAT 转换,就需要连接跟踪通过扩展组件来跟踪应用层协议,这就是应用层网关(ALG)技术 。

以 FTP 协议为例,它在数据传输过程中会包含地址和端口信息。当一个 FTP 客户端连接到 FTP 服务器时,首先会建立控制连接,用于传输命令和响应 。在这个过程中,连接跟踪系统会记录下控制连接的相关信息 。当客户端需要传输数据时,会通过 PORT 或 PASV 命令来建立数据连接 。这些命令中包含了客户端或服务器的数据传输地址和端口信息 。

连接跟踪的扩展组件会对这些信息进行分析和处理,确保在 NAT 环境下,数据连接能够正确建立 。如果客户端使用 PORT 命令,连接跟踪组件会根据 NAT 转换规则,修改命令中的地址信息,使其与转换后的公网地址一致 。这样,FTP 服务器就能正确地与客户端建立数据连接,实现文件的传输 。

四、内核中连接跟踪的实现

4.1 模块初始化流程

在 Linux 内核中,连接跟踪的模块初始化是一个严谨且关键的过程,就像是搭建一座大厦前的奠基工作,为后续的连接跟踪功能奠定了坚实的基础。这一过程主要由 nf_conntrack_init_init_net () 函数和 nf_conntrack_init_net () 函数协同完成 。

图片

这张图乍一看比较乱,但是这张图非常好的说明了数据流在内核中的过程;在图中的灰色框标记了conntrack的位置,这个是连接跟踪的起始点,其实在netfilter的所有HOOK点都可以更改流(flow)跟踪的信息。

nf_conntrack_init_init_net () 函数首先会根据系统内存的大小来确定连接跟踪表的关键参数。它会给全局变量 nf_conntrack_htable_size 赋值,这个值指定了存放连接跟踪条目的哈希表的大小 。同时,它还会计算出系统可以创建的连接跟踪条目的最大数量,并赋值给 nf_conntrack_max 。这两个参数就像是连接跟踪表的 “容量规划师”,合理地规划了连接跟踪表的存储能力 。接着,该函数会为 nf_conn 结构申请 slab cache,这个缓存就像是一个专门为 nf_conn 结构打造的 “仓库”,用于高效地存储和管理 nf_conn 结构 。

每个连接的状态都由一个 nf_conn 结构体实例来描述,每个连接又分为 original 和 reply 两个方向,每个方向都用一个元组(tuple)表示,tuple 中包含了这个方向上数据包的关键信息,如源 IP、目的 IP、源 port、目的 port 等 。随后,它会给全局的 nf_ct_l3protos [] 数组赋上默认值,这个数组中的每个元素都被赋值为 nf_conntrack_l3proto_generic,这是一个不区分 L3 协议的处理函数,后续的初始化会根据不同的 L3 协议为其赋上相应的值 。最后,它还会给全局的 hash 表 nf_ct_helper_hash 分配一个页大小的空间,这个 hash 表用于存放 helper 类型的 conntrack extension 。

nf_conntrack_init_net () 函数则主要负责初始化 net->ct 成员 。net 是本地 CPU 的网络命名空间,在单 CPU 系统中就是全局变量 init_net 。它会将 net->ct.count 计数器设置为 0,就像是给连接跟踪的 “计数器” 清零,准备开始记录连接数量 。然后初始化 unconfirmed 链表和 dying 链表,这两个链表就像是连接跟踪的 “临时存放区”,分别用于存放未确认的连接和即将死亡的连接 。

接着,它会给 net->ct.stat 分配空间并清零,net->ct.stat 用于统计连接跟踪的相关数据,就像是一个 “数据统计员” 。之后,它会初始化 conntrack hash table,根据之前计算好的 nf_conntrack_htable_size 来分配相应的内存空间 。最后,它还会初始化 net->ct.expect_hash 及缓存,并在 /proc 中创建相应文件,完成一系列的初始化工作 。

4.2 连接跟踪表的数据结构

连接跟踪表的数据结构是连接跟踪功能实现的核心部分,它就像是一个精心设计的 “信息仓库”,高效地存储和管理着网络连接的各种信息 。在这个 “仓库” 中,nf_conn 结构体是描述连接状态的关键 “单元” 。每个 nf_conn 结构体实例都对应着一个网络连接,它包含了连接的各种详细信息,如连接的两个方向(original 和 reply)的元组信息,这些元组信息就像是连接的 “身份标识”,包含了源 IP、目的 IP、源端口、目的端口和协议类型等关键内容 。

同时,nf_conn 结构体还记录了连接的状态,比如是新建连接(NEW)、已建立连接(ESTABLISHED)还是其他状态,这些状态信息就像是连接的 “状态标签”,方便系统对连接进行管理和处理 。此外,它还包含了一些与连接相关的统计信息,如收发包数、字节数等,这些统计信息就像是连接的 “运行数据记录”,有助于系统了解连接的运行情况 。

连接跟踪表采用哈希表(hash table)来实现,这是一种高效的数据结构,能够快速地定位和查找连接信息 。哈希表就像是一个大型的 “索引库”,每个连接的元组信息通过特定的哈希函数计算出一个哈希值,这个哈希值就像是连接在 “索引库” 中的 “索引编号”,通过这个编号可以快速地找到对应的连接记录 。

在哈希表中,每个哈希桶(bucket)是一个链表,当多个连接的哈希值相同时,这些连接的记录就会以链表的形式存储在同一个哈希桶中 。这种设计既利用了哈希表的快速查找特性,又解决了哈希冲突的问题 。在一个高并发的网络环境中,可能会有大量的连接同时存在,哈希表的这种设计能够保证系统在处理这些连接时,依然能够快速地进行查找和匹配操作,大大提高了连接跟踪的效率 。

(1)重要结构体

  • struct nf_hook_ops {}: 在HOOK点上注册的连接跟踪信息,通过nf_register_hooks()注册

  • struct nf_conntrack_tuple {}: 连接跟踪的基本元素,表示特定方向的流。

  • struct nf_conn {}:连接跟踪条目,定义一个 flow。

在nf_conn中有重要成员:如ct_general、status、master、tuplehash、timeout等。

struct nf_conn {struct nf_conntrack ct_general;spinlock_t	lock;u16		cpu;/* XXX should I move this to the tail ? - Y.K *//* These are my tuples; original and reply */struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];/* Have we seen traffic both ways yet? (bitset) */unsigned long status;/* Timer function; drops refcnt when it goes off. */struct timer_list timeout;possible_net_t ct_net;/* all members below initialized via memset */u8 __nfct_init_offset[0];/* If we were expected by an expectation, this will be it */struct nf_conn *master;
#if defined(CONFIG_NF_CONNTRACK_MARK)u_int32_t mark;
#endif
#ifdef CONFIG_NF_CONNTRACK_SECMARKu_int32_t secmark;
#endif/* Extensions */struct nf_ct_ext *ext;/* Storage reserved for other modules, must be the last member */union nf_conntrack_proto proto;
};

(2)重要函数

  • hash_conntrack_raw():根据 tuple 计算出一个 32 位的哈希值(hash key)。

  • nf_conntrack_in():连接跟踪模块的核心,包进入连接跟踪的地方。在此函数中包含下边的步骤:resolve_normal_ct() -> nf_ct_timeout_lookup()在resolve_normal_ct() 中会计算元组的散列值,进行匹配,没有就创建nf_conntrack_tuple_hash,将其加入未确认tuplehash列表中,已经创建则判断状态是否超时。

  • nf_conntrack_confirm():确认前面通过 nf_conntrack_in() 创建的新连接(是否被丢弃),将元组从未确认tuplehash列表中删除。

  • nf_ct_get(skb, ctinfo):获取连接跟踪数据,没有建立返回null

具体细节可以在内核代码中查看代码路径官方网站:https://elixir.bootlin.com/linux/v4.4.155/source/net/netfilter

4.3 连接查找与匹配策略

当一个数据包到达时,连接查找的流程就像是在一个庞大的图书馆中查找一本特定的书籍 。首先,系统会从数据包中提取出关键的元组信息,这些元组信息就像是书籍的 “关键词” 。然后,根据这些元组信息,通过哈希函数计算出一个哈希值,这个哈希值就像是图书馆中的 “书架编号” 。系统会根据这个哈希值快速定位到哈希表中的相应哈希桶,就像是找到了对应的书架 。

如果哈希桶中只有一个连接记录,那么就可以直接找到对应的连接,这就像是在书架上只有一本书时,直接就能拿到想要的书 。但如果哈希桶中存在多个连接记录(即发生了哈希冲突),系统就会沿着链表依次查找,对比每个连接记录的元组信息与数据包的元组信息是否匹配,这就像是在书架上有很多本书时,需要逐一查看书籍的内容来找到目标书籍 。

匹配策略的实现主要基于数据包的元组信息 。系统会严格对比数据包的源 IP、目的 IP、源端口、目的端口和协议类型等元组信息与连接跟踪表中记录的元组信息 。只有当这些信息完全一致时,才认为找到了匹配的连接 。在一个企业网络中,当内部员工的电脑向外部服务器发送数据包时,系统会根据数据包的元组信息在连接跟踪表中进行查找和匹配 。如果找到了匹配的连接,就说明这是一个已建立连接的后续数据包,系统会根据连接的状态进行相应的处理,比如更新连接的统计信息等 。如果没有找到匹配的连接,就说明这可能是一个新的连接请求,系统会按照新连接的处理流程进行处理,比如创建新的连接跟踪记录 。

4.4 连接生命周期管理

连接的生命周期管理就像是一场精心安排的 “旅程”,涵盖了连接的创建、确认、更新和删除等多个重要阶段,每个阶段都有其特定的内核处理逻辑和状态转换 。

当一个新的连接请求到来时,比如 TCP 协议中的 SYN 包到达,内核会创建一个新的连接跟踪记录 。内核会从数据包中提取出五元组信息,然后为这个连接分配一个 nf_conn 结构体实例 。在这个结构体中,会初始化连接的两个方向的元组信息,将连接状态设置为初始状态,比如 TCP 连接的 SYN_SENT 状态 。同时,还会将这个连接的原始方向的 tuple_hash 添加到 unconfirmed 链表中,等待进一步的确认 。

当连接收到对方的确认信息时,比如 TCP 协议中的 SYN + ACK 包到达,内核会对连接进行确认操作 。它会将连接从 unconfirmed 链表中移除,并将其添加到连接跟踪表的哈希表中,标志着连接已被确认 。同时,会更新连接的状态,将 TCP 连接的状态更新为 ESTABLISHED,表明连接已成功建立 。

在连接的数据传输过程中,内核会持续关注连接的状态变化,并及时更新连接的相关信息 。当接收到属于已有连接的数据包时,内核会更新连接记录中的收发包数、字节数等统计信息 。如果连接的状态发生了变化,比如 TCP 连接进入了 FIN_WAIT_1 状态,内核也会及时更新连接的状态信息 。

当连接结束时,无论是正常结束还是异常结束,内核都会执行删除操作 。对于正常结束的连接,比如 TCP 连接完成了四次挥手过程,内核会将连接从连接跟踪表的哈希表中移除,并释放 nf_conn 结构体实例所占用的内存空间 。对于异常结束的连接,比如连接超时,内核也会同样进行删除操作,确保连接跟踪表的高效运行,为新的连接记录腾出空间 。

http://www.dtcms.com/a/279481.html

相关文章:

  • OSPF高级特性之GR
  • echarts应用到swiper 轮播图中,每次鼠标一点击图表所在slide,图表就会消失
  • LSV负载均衡
  • PostgreSQL ExecInitIndexScan 函数解析
  • k8s-高级调度(二)
  • 如何使用Cisco DevNet提供的免费ACI学习实验室(Learning Labs)?(Grok3 回答)
  • PostgreSQL 16 Administration Cookbook 读书笔记:第6章 Security
  • DLL 文件 OSError: [WinError 1401] 应用程序无法启动问题解决
  • 七、深度学习——RNN
  • HTTPS 协议原理
  • ZYNQ双核通信终极指南:FreeRTOS移植+OpenAMP双核通信+固化实战
  • 一文明白AI、AIGC、LLM、GPT、Agent、workFlow、MCP、RAG概念与关系
  • 浏览器防录屏是怎样提高视频安全性?
  • 现有医疗AI记忆、规划与工具使用的创新路径分析
  • 【Linux网络】多路转接poll、epoll
  • vue3 JavaScript 获取 el-table 单元格 赋红色外框
  • mac上用datagrip连接es
  • MFC/C++语言怎么比较CString类型最后一个字符
  • K8S的平台核心架构思想[面向抽象编程]
  • LVS(Linux Virtual Server)集群技术详解
  • linux 内核: 访问当前进程的 task_struct
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 架构搭建
  • C++-linux 6.makefile和cmake
  • 深入掌握Performance面板与LCP/FCP指标优化指南
  • 学习笔记——农作物遥感识别与大范围农作物类别制图的若干关键问题
  • 计算两个经纬度之间的距离(JavaScript 实现)
  • HashMap的长度为什么要是2的n次幂以及HashMap的继承关系(元码解析)
  • 前缀和题目:使数组互补的最少操作次数
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十四课——图像二值化的FPGA实现
  • 如何集成光栅传感器到FPGA+ARM系统中?