本篇文章主要讲解第五部分(ACL作为服务与内部机制扩展)的前4小节,其他部分请阅读专栏其他篇幅
目录
第一部分:ACL插件的作用和意义
- [第1章:从生活中的"门禁系统"说起------ACL 插件整体认知](#第1章:从生活中的"门禁系统"说起——ACL 插件整体认知)
- 1.1 先不管 VPP:现代 ACL 系统应该会做哪些事?
- 1.2 再看 VPP:ACL 插件具体提供了哪些能力?
- 1.3 ACL 插件在 VPP 里的大致位置:插在哪些弧上?
- 1.4 ACL 与其他模块的关系:谁先谁后、谁配合谁?
- 1.5 常见场景:ACL 插件在工程中的几种典型用法
第二部分:ACL插件的整体架构
-
- 2.1 ACL插件的文件组织结构
- 2.2 各文件的功能和职责
- 2.3 模块间的依赖关系
- 2.4 模块与外部系统的关系
- 2.5 文件组织的设计原则
- 2.6 总结:文件组织的"设计哲学"
-
- 3.1
acl_rule_t:一条 IP ACL 规则长什么样? - 3.2
acl_list_t:一张 IP ACL 表(很多规则 + 一个标签) - 3.3
macip_acl_rule_t:一条 MAC+IP 绑定规则 - 3.4
macip_acl_list_t:一张 MACIP ACL 表 - 3.5
acl_main_t:ACL 插件的全局大脑 - 3.6 会话相关结构体:Flow-aware ACL 的骨架
- 3.7 Hash 查找相关结构体:高性能匹配的"索引卡片"
- 3.8 Lookup Context 与对外导出方法:ACL 作为服务的"接口面"
- 3.9 数据结构之间的关系小结
- 3.1
第三部分:ACL插件的初始化和控制平面
-
第4章:模块初始化------ACL插件是如何"开机启动"的?
- 4.1 插件注册:告诉VPP"我是谁"
- 4.2 初始化函数注册:告诉VPP"什么时候叫我"
- 4.3 初始化主函数:acl_init 的完整流程
- 4.4 初始化流程总结:从"冷启动"到"就绪"
- 4.5 关键概念深入理解
- 4.6 初始化完成后的状态
- 4.7 本章小结
-
- 5.1 从API消息到规则存储:完整的流程概览
- 5.2 API消息处理:接收"入职申请"
- 5.3 核心函数:acl_add_list 的完整实现
- 5.4 ACL规则删除:如何"办理离职手续"
- 5.5 规则替换机制:add_replace 的语义
- 5.6 规则验证详解:为什么需要这么多检查?
- 5.7 规则转换详解:API格式 vs 内部格式
- 5.8 通知机制:为什么需要通知相关系统?
- 5.9 完整流程总结:从API到存储
- 5.10 本章小结
-
- 6.1 接口ACL绑定的概念:什么是"绑定"?
- 6.2 从API消息到绑定完成:完整的流程概览
- 6.3 API消息处理:接收"安装门禁申请"
- 6.4 核心函数:acl_interface_add_del_inout_acl
- 6.5 核心函数:acl_interface_set_inout_acl_list(完整实现)
- 6.6 Policy Epoch机制:什么是"策略纪元"?
- 6.7 Feature Arc启用/禁用:如何"打开/关闭门禁"?
- 6.8 多ACL串联处理:如何"安装多个门禁系统"?
- 6.9 反向索引的维护:为什么需要"反向查找"?
- 6.10 完整流程总结:从API到Feature Arc启用
- 6.11 关键概念深入理解
- 6.12 本章小结
-
[第7章:MACIP ACL管理------如何"绑定IP和MAC地址"?](#第7章:MACIP ACL管理——如何"绑定IP和MAC地址"?)
- 7.1 MACIP ACL的概念:什么是"MAC+IP绑定"?
- 7.2 MACIP ACL的用途:为什么需要"MAC+IP绑定"?
- 7.3 从API消息到MACIP ACL创建:完整的流程概览
- 7.4 API消息处理:接收"建立身份验证系统申请"
- 7.5 核心函数:macip_acl_add_list 的完整实现
- 7.6 L2 Classify表构建:macip_create_classify_tables 的核心逻辑
- 7.7 MACIP ACL接口绑定:如何"安装身份验证系统"?
- 7.8 MACIP ACL删除:如何"销毁身份验证系统"?
- 7.9 API消息处理:macip_acl_interface_add_del
- 7.10 MACIP ACL vs IP ACL:关键区别总结
- 7.11 完整流程总结:从API到Classify表应用
- 7.12 本章小结
第四部分:数据平面ACL处理
-
第8章:数据平面ACL匹配流程------数据包如何"过安检"?
- 8.1 数据平面ACL匹配的概念:什么是"数据平面"?
- 8.2 数据平面节点:ACL插件如何"插入"到数据包处理流程?
- 8.3 数据包处理流程:从节点调用到ACL匹配
- 8.4 5-tuple提取:如何"读取旅客信息"?
- 8.5 ACL匹配逻辑:如何"检查旅客是否符合规则"?
- 8.6 多ACL匹配:如何"检查多个安检规则表"?
- 8.7 会话管理:如何"记录常客信息"?
- 8.8 完整流程总结:从数据包到达到ACL匹配完成
- 8.9 性能优化技术
- 8.10 本章小结
-
[第9章:Flow-aware ACL详细实现------如何"记录和管理常客信息"?](#第9章:Flow-aware ACL详细实现——如何"记录和管理常客信息"?)
- 9.1 Flow-aware ACL的概念:什么是"有状态ACL"?
- 9.2 会话结构体:如何"存储常客信息"?
- 9.3 会话创建:如何"登记新常客"?
- 9.4 会话查找:如何"查找常客信息"?
- 9.5 会话跟踪:如何"更新常客访问记录"?
- 9.6 会话超时类型:如何"判断常客类型"?
- 9.7 会话超时计算:如何"计算常客过期时间"?
- 9.8 会话链表管理:如何"管理待检查列表"?
- 9.9 反向会话:如何"匹配返回流量"?
- 9.10 会话处理:如何"处理已有会话的数据包"?
- 9.11 会话删除:如何"注销常客信息"?
- 9.12 会话清理:如何"清理过期常客"?
- 9.13 会话清理进程:如何"定期清理过期常客"?
- 9.14 Policy Epoch机制:如何"检测过期会话"?
- 9.15 会话链表删除:如何"从待检查列表中删除常客"?
- 9.16 会话创建条件:如何"判断是否可以创建新会话"?
- 9.17 完整流程总结:从数据包到会话管理完成
- 9.18 本章小结
-
第10章:Hash匹配引擎------如何"用字典快速查找规则"?
- 10.1 Hash匹配引擎的概念:什么是"字典查找"?
- 10.2 数据结构:如何"组织规则信息"?
- 10.3 Hash表构建:如何"建立字典索引"?
- 10.4 掩码类型分配:如何"给规则分配分类"?
- 10.5 Hash表条目激活:如何"将规则添加到字典"?
- 10.6 TupleMerge算法:如何"合并相似分类"?
- 10.7 Hash匹配流程:如何"使用字典查找规则"?
- 10.8 本章小结
第五部分:ACL作为服务与内部机制扩展(ACL-as-a-service)
-
第11章:ACL-as-a-service------如何"让其他插件使用ACL引擎"?
- 11.1 ACL-as-a-service的概念:什么是"ACL作为服务"?
- 11.2 数据结构:如何"组织用户和上下文信息"?
- 11.3 用户模块注册:如何"注册为ACL服务用户"?
- 11.4 Lookup Context创建:如何"创建安检规则集合"?
- 11.5 ACL列表设置:如何"为规则集合添加规则"?
- 11.6 Lookup Context释放:如何"销毁安检规则集合"?
- 11.7 ACL锁定和解锁:如何"追踪ACL的使用情况"?
- 11.8 ACL应用和取消应用:如何"将规则添加到Hash表"?
- 11.9 ACL变更通知:如何"通知上下文ACL规则变化"?
- 11.10 方法导出和初始化:如何"让外部插件调用ACL方法"?
- 11.11 5-tuple填充和匹配:如何"使用ACL匹配引擎"?
- 11.12 完整使用流程:从注册到匹配
- 11.13 本章小结
-
第12章:会话表管理------如何"建立和管理常客数据库"?
- 12.1 会话表管理的概念:什么是"会话表管理"?
- 12.2 会话表初始化:如何"建立常客数据库"?
- 12.3 Per-worker数据结构:如何"为每个安检通道建立档案系统"?
- 12.4 会话表容量管理:如何"管理数据库容量"?
- 12.5 会话表性能优化:如何"优化数据库性能"?
- 12.6 会话表监控和调试:如何"监控和调试数据库"?
- 12.7 会话表清理机制:如何"定期清理过期记录"?
- 12.8 会话表容量管理:如何"管理数据库容量"?
- 12.9 会话表性能优化:如何"优化数据库性能"?
- 12.10 会话表监控和调试:如何"监控和调试数据库"?
- 12.11 会话表CLI命令:如何"使用命令行管理数据库"?
- 12.12 会话表初始化时机:何时"建立数据库"?
- 12.13 会话表容量限制:如何"防止数据库溢出"?
- 12.14 会话表性能调优:如何"优化数据库性能"?
- 12.15 会话表监控指标:如何"监控数据库健康状态"?
- 12.16 本章小结
-
第13章:ACL插件性能优化------如何"让安检更快更高效"?
- 13.1 性能优化的概念:什么是"性能优化"?
- 13.2 批量处理优化:如何"一次处理多个数据包"?
- 13.3 预取优化:如何"提前准备好数据"?
- 13.4 流水线处理:如何"同时处理多个数据包"?
- 13.5 缓存优化:如何"优化内存访问"?
- 13.6 分支预测优化:如何"帮助CPU预测分支"?
- 13.7 性能调优建议:如何"优化ACL插件性能"?
- 13.8 性能监控:如何"监控ACL插件性能"?
- 13.9 本章小结
-
第14章:ACL插件调试工具------如何"诊断和排查问题"?
- 14.1 调试工具的概念:什么是"调试工具"?
- 14.2 Trace功能:如何"跟踪数据包处理过程"?
- 14.3 ELOG功能:如何"记录系统事件"?
- 14.4 Show命令:如何"显示系统状态"?
- 14.5 调试宏:如何"输出调试信息"?
- 14.6 故障排查方法:如何"诊断和解决问题"?
- 14.7 本章小结
-
第15章:ACL插件高级功能------如何"处理非IP数据包"?
- 15.1 高级功能的概念:什么是"非IP数据包处理"?
- 15.2 Ethertype白名单:如何"管理特殊旅客白名单"?
- 15.3 非IP数据包处理节点:如何"检查特殊旅客"?
- 15.4 节点注册和Feature Arc集成:如何"注册特殊旅客检查点"?
- 15.5 Trace格式化:如何"格式化检查记录"?
- 15.6 本章小结
-
[第16章:MACIP ACL数据面处理------如何"检查MAC+IP绑定"?](#第16章:MACIP ACL数据面处理——如何"检查MAC+IP绑定"?)
- 16.1 MACIP ACL数据面处理的概念:什么是"MAC+IP绑定检查"?
- 16.2 MACIP ACL数据结构:如何"存储MAC+IP绑定规则"?
- 16.3 匹配类型管理:如何"组织MAC+IP绑定规则"?
- 16.4 L2 Classify表构建:如何"建立快速查找表"?
- 16.5 接口应用:如何"将MACIP ACL应用到接口"?
- 16.6 MACIP ACL在L2输入弧中的位置:如何"集成到数据平面"?
- 16.7 本章小结
-
第17章:Hash查找引擎------如何"用字典快速查找规则"?
- 17.1 Hash查找的必要性:为什么需要"字典查找"?
- 17.2 掩码类型(Mask Type)概念:什么是"字典分类"?
- 17.3 掩码类型池管理:如何"管理字典分类"?
- 17.4 ACL规则到Hash条目的转换:如何"将规则转换为字典条目"?
- 17.5 Hash表构建:如何"建立字典"?
- 17.6 Hash查找流程:如何"使用字典查找规则"?
- 17.7 本章小结
-
[第19章:Lookup Context机制------ACL作为服务的核心设计](#第19章:Lookup Context机制——ACL作为服务的核心设计)
- 19.1 ACL-as-a-Service的概念:为什么需要Lookup Context?
- 19.2 用户模块注册:acl_lookup_context_user_t
- 19.3 Lookup Context创建:acl_lookup_context_t
- 19.4 Context与ACL的绑定:set_acl_vec_for_context
- 19.5 多插件复用ACL引擎:exports.h和exported_types.h
- 19.6 Lookup Context的释放:put_lookup_context_index
- 19.7 ACL变更通知机制:notify_acl_change
- 19.8 调试和展示:show_lookup_context和show_lookup_user
- 19.9 本章小结
-
第20章:ACL插件方法导出------让其他插件也能"用上这套门禁系统"
- 20.1
acl_plugin_methods_t:ACL服务"菜单"的结构 - 20.2 方法注册和导出:ACL插件如何填充方法表?
- 20.3 外部插件如何使用这些方法?(调用流程示意)
- 20.4 内联匹配函数:
acl_plugin_fill_5tuple_inline与acl_plugin_match_5tuple_inline - 20.5 本章小结
- 20.1
第六部分:多核和性能优化
-
第21章:多核会话管理------如何让多个"安检员"协同工作?
- 21.1 Per-worker 数据结构:为每个"安检员"准备独立的办公桌
- 21.2 会话表分布策略:如何决定旅客档案放在哪个安检通道?
- 21.3 线程间同步机制:如何让多个"安检员"协同工作而不冲突?
- 21.4 本章小结
-
第22章:性能优化技术------如何让"安检系统"快如闪电?
- 22.1 批量处理优化:如何"批量安检"提高效率?
- 22.2 预取(Prefetch)优化:如何"提前准备材料"减少等待时间?
- 22.3 缓存行对齐:如何避免"伪共享"导致的性能问题?
- 22.4 分支预测优化:如何帮助CPU"猜对"程序执行路径?
- 22.5 向量化处理:如何一次处理多个数据包?
- 22.6 本章小结
第七部分:可观测性和调试
-
第23章:错误处理、日志记录与调试追踪机制------如何"记录安检日志和排查问题"?
- 23.1 错误处理机制:如何"标记和处理安检异常"?
- 23.2 日志记录机制:如何"记录安检工作日志"?
- 23.3 数据包追踪机制:如何"回放特定旅客的安检过程"?
- 23.4 计数器统计机制:如何"统计安检工作数据"?
- 23.5 事件日志追踪(ELOG):如何"记录详细的操作日志"?
- 23.6 本章小结
-
[第24章:日志与 Trace 实战------如何"从外部视角"排查 ACL 问题?](#第24章:日志与 Trace 实战——如何"从外部视角"排查 ACL 问题?)
- 24.1 总体排障思路:从"外症状"到"内原因"
- 24.2 ACL 插件 CLI 命令总览:有什么"观察窗口"?
- 24.3
show acl-plugin sessions:看清"常客"会话情况 - 24.4
set acl-plugin ...:开启事件 Trace 和调试开关 - 24.5 VPP 通用 Packet Trace:抓一条"问题包"的全链路
- 24.6 本章小结
-
第25章:CLI和API接口------如何"指挥"ACL插件做事?
- 25.1 ACL相关CLI命令
- 25.2 MACIP ACL相关CLI命令
- 25.3 ACL API消息处理
- 25.4 API消息格式和编码
- 25.5 本章小结
第八部分:综合案例和最佳实践
-
- 26.1 边界防火墙配置
- 26.2 内部安全区隔离
- 26.3 机房接入控制
- 26.4 多ACL串联配置
- 26.5 有状态ACL配置
- 26.6 本章总结
-
- 27.1 Hash匹配启用建议
- 27.2 TupleMerge参数调优
- 27.3 会话超时配置建议
- 27.4 多核配置优化
- 27.5 规则组织最佳实践
- 27.6 本章总结
-
- 28.1 常见问题诊断
- 28.2 规则匹配问题排查
- 28.3 会话表问题排查
- 28.4 性能问题排查
- 28.5 调试工具使用
- 28.6 本章总结
-
- 29.1 ACL插件的关键特点
- 29.2 在VPP数据包转发中的作用
- 29.3 性能优化要点
- 29.4 与其他模块的关系
- 29.5 最佳实践和注意事项
- 29.6 知识体系总结
- 29.7 本章总结
第11章:ACL-as-a-service------如何"让其他插件使用ACL引擎"?
生活类比:想象一下,你是一个"安检服务提供商",不仅为自己的机场提供安检服务,还向其他机场(其他插件)提供安检服务。
- 传统方式:每个机场都要自己建立安检系统(每个插件都要自己实现ACL匹配)
- ACL-as-a-service:其他机场可以"租用"你的安检服务(其他插件可以使用ACL插件的匹配引擎)
这一章,我们就跟着ACL插件的代码,看看它是如何"让其他插件使用ACL引擎"的。每一步我都会详细解释,确保你完全理解。
11.1 ACL-as-a-service的概念:什么是"ACL作为服务"?
11.1.1 什么是ACL-as-a-service?
ACL-as-a-service(ACL作为服务):一种机制,允许其他VPP插件使用ACL插件的匹配引擎,而无需自己实现ACL匹配逻辑。
关键概念详解:
-
传统方式的问题:
- 问题:每个需要ACL匹配的插件都要自己实现匹配逻辑
- 缺点:代码重复、维护困难、性能不一致
- 类比:就像"每个机场都要自己建立安检系统"(重复建设,浪费资源)
-
ACL-as-a-service的优势:
- 代码复用:多个插件共享同一个ACL匹配引擎
- 统一优化:ACL插件的性能优化对所有用户都有效
- 易于维护:只需要维护一个ACL匹配引擎
- 类比:就像"多个机场共享一个安检服务提供商"(统一标准,易于维护)
-
Lookup Context(查找上下文):一个抽象的ACL匹配上下文,不绑定到特定接口
- 作用:将多个ACL规则组织在一起,用于first-match查找
- 优势:可以用于接口ACL、ACL-based forwarding等多种场景
- 类比:就像"一个安检规则集合"(可以用于不同的机场)
-
用户模块(User Module):使用ACL插件的其他插件
- 注册:在使用ACL插件之前,需要先注册为用户模块
- 标识:每个用户模块有一个唯一的ID
- 类比:就像"租用安检服务的机场"(需要先注册,获得唯一标识)
11.1.2 为什么需要ACL-as-a-service?
使用场景:
-
ACL-based Forwarding:基于ACL规则的转发
- 场景:根据ACL规则匹配结果决定数据包的转发路径
- 需求:需要ACL匹配,但不绑定到接口
- 类比:就像"根据安检结果决定旅客的登机口"(不绑定到特定接口)
-
Policy-based Routing:基于策略的路由
- 场景:根据ACL规则匹配结果选择不同的路由表
- 需求:需要ACL匹配,但用于路由决策
- 类比:就像"根据安检结果选择不同的航班"(用于路由决策)
-
多插件复用:多个插件都需要ACL匹配
- 场景:多个插件都需要使用ACL匹配功能
- 需求:避免重复实现,统一使用ACL插件的匹配引擎
- 类比:就像"多个机场共享一个安检服务提供商"(避免重复建设)
优势总结:
| 特性 | 传统方式 | ACL-as-a-service |
|---|---|---|
| 代码复用 | 每个插件都要实现 | 共享ACL插件的匹配引擎 |
| 性能优化 | 各自优化,不一致 | 统一优化,所有用户受益 |
| 维护成本 | 高(多个实现) | 低(一个实现) |
| 灵活性 | 低(绑定到接口) | 高(不绑定到接口) |
类比:
- 传统方式:就像"每个机场都要自己建立安检系统"(重复建设,浪费资源)
- ACL-as-a-service:就像"多个机场共享一个安检服务提供商"(统一标准,易于维护)
11.2 数据结构:如何"组织用户和上下文信息"?
11.2.1 用户模块结构体:acl_lookup_context_user_t
acl_lookup_context_user_t 用于存储使用ACL插件的用户模块信息:
c
//19:28:src/plugins/acl/lookup_context.h
typedef struct {
/* A name of the portion of the code using the ACL infra */
char *user_module_name; // 用户模块名称
// user_module_name:使用ACL基础设施的代码部分的名称(如"acl-based-forwarding")
// 为什么需要?用于标识用户模块,便于调试和追踪
// 类比:就像"机场名称"(用于标识租用安检服务的机场)
/* text label for the first u32 user value assigned to context */
char *val1_label; // 第一个用户值的标签
// val1_label:分配给上下文的第一个u32用户值的文本标签(用于调试)
// 为什么需要?用于描述用户值的含义,便于调试
// 类比:就像"第一个用户值的标签"(如"转发表ID")
/* text label for the second u32 user value assigned to context */
char *val2_label; // 第二个用户值的标签
// val2_label:分配给上下文的第二个u32用户值的文本标签(用于调试)
// 为什么需要?用于描述用户值的含义,便于调试
// 类比:就像"第二个用户值的标签"(如"策略ID")
/* vector of lookup contexts of this user */
u32 *lookup_contexts; // 此用户的Lookup Context索引列表
// lookup_contexts:此用户创建的所有Lookup Context的索引列表
// 为什么需要?用于追踪用户创建的所有上下文,便于管理和清理
// 类比:就像"此机场的所有安检规则集合列表"(用于追踪和管理)
} acl_lookup_context_user_t;
结构体字段总结:
- 用户模块名称:用于标识用户模块(如"acl-based-forwarding")
- 用户值标签:用于描述用户值的含义(便于调试)
- Lookup Context列表:此用户创建的所有上下文的索引列表
类比:就像"机场的注册信息":
- 机场名称:用于标识机场(用户模块名称)
- 标签:用于描述用户值的含义(如"转发表ID"、"策略ID")
- 安检规则集合列表:此机场创建的所有安检规则集合(Lookup Context列表)
11.2.2 Lookup Context结构体:acl_lookup_context_t
acl_lookup_context_t 用于存储一个Lookup Context的信息:
c
//30:39:src/plugins/acl/lookup_context.h
typedef struct {
/* vector of acl #s within this context */
u32 *acl_indices; // ACL索引列表
// acl_indices:此上下文中要查找的ACL索引列表(按优先级排序)
// 为什么需要?用于存储要在此上下文中查找的ACL列表
// 类比:就像"安检规则集合"(包含多个安检规则,按优先级排序)
/* index of corresponding acl_lookup_context_user_t */
u32 context_user_id; // 对应的用户模块ID
// context_user_id:创建此上下文的用户模块ID
// 为什么需要?用于追踪上下文的创建者,便于管理和调试
// 类比:就像"创建此安检规则集合的机场ID"(用于追踪创建者)
/* per-instance user value 1 */
u32 user_val1; // 用户值1
// user_val1:用户指定的第一个u32值(用于区分同一用户的不同上下文实例)
// 为什么需要?用于区分同一用户模块的不同上下文实例
// 类比:就像"第一个用户值"(如"转发表ID",用于区分不同的转发表)
/* per-instance user value 2 */
u32 user_val2; // 用户值2
// user_val2:用户指定的第二个u32值(用于区分同一用户的不同上下文实例)
// 为什么需要?用于区分同一用户模块的不同上下文实例
// 类比:就像"第二个用户值"(如"策略ID",用于区分不同的策略)
} acl_lookup_context_t;
结构体字段总结:
- ACL索引列表:此上下文中要查找的ACL列表(按优先级排序)
- 用户模块ID:创建此上下文的用户模块ID
- 用户值:用户指定的两个u32值(用于区分不同的上下文实例)
类比:就像"一个安检规则集合":
- 规则列表:包含多个安检规则,按优先级排序(ACL索引列表)
- 创建者:创建此规则集合的机场(用户模块ID)
- 标识信息:用于区分不同实例的信息(如"转发表ID"、"策略ID")
11.2.3 ACL插件方法结构体:acl_plugin_methods_t
acl_plugin_methods_t 用于存储ACL插件导出的方法(函数指针):
c
//87:91:src/plugins/acl/exported_types.h
#define _(name) acl_plugin_ ## name ## _fn_t name;
typedef struct {
void *p_acl_main; /* a local copy of a pointer to acl_main */ // ACL主结构体指针
// p_acl_main:指向acl_main的指针(用于访问ACL插件的全局状态)
// 为什么需要?用于在外部插件中访问ACL插件的全局状态
// 类比:就像"安检服务提供商的'总部地址'"(用于访问全局状态)
foreach_acl_plugin_exported_method_name // 导出的方法列表(通过宏展开)
// 包括:acl_exists、register_user_module、get_lookup_context_index等
// 为什么需要?用于外部插件调用ACL插件的方法
// 类比:就像"安检服务提供商的'服务接口列表'"(用于调用服务)
} acl_plugin_methods_t;
#undef _
导出的方法列表 (通过foreach_acl_plugin_exported_method_name宏展开):
c
//78:85:src/plugins/acl/exported_types.h
#define foreach_acl_plugin_exported_method_name \
_(acl_exists) // 检查ACL是否存在
_(register_user_module) // 注册用户模块
_(get_lookup_context_index) // 获取Lookup Context索引
_(put_lookup_context_index) // 释放Lookup Context索引
_(set_acl_vec_for_context) // 为上下文设置ACL列表
_(fill_5tuple) // 填充5-tuple
_(match_5tuple) // 匹配5-tuple
结构体字段总结:
- ACL主结构体指针:指向acl_main的指针(用于访问全局状态)
- 导出的方法:ACL插件导出的所有方法(函数指针)
类比:就像"安检服务提供商的'服务接口'":
- 总部地址:用于访问全局状态(ACL主结构体指针)
- 服务接口列表:提供的所有服务(导出的方法)
11.3 用户模块注册:如何"注册为ACL服务用户"?
11.3.1 用户模块注册函数:acl_plugin_register_user_module
acl_plugin_register_user_module 用于注册用户模块,获取用户模块ID:
c
//77:88:src/plugins/acl/lookup_context.c
static u32 acl_plugin_register_user_module (char *user_module_name, char *val1_label, char *val2_label)
{
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
/*
* Because folks like to call this early on,
* use the global heap, so as to avoid
* initializing the main ACL heap before
* they start using ACLs.
*/
// 注释:因为人们喜欢在早期调用此函数,所以使用全局堆,以避免在开始使用ACL之前初始化主ACL堆。
// 为什么需要?允许在ACL插件完全初始化之前注册用户模块
// 类比:就像"允许在安检服务提供商完全启动之前注册机场"(提前注册)
u32 user_id = get_acl_user_id(am, user_module_name, val1_label, val2_label); // 获取或创建用户模块ID
// get_acl_user_id:获取或创建用户模块ID(我们下面详细讲解)
// 类比:就像"获取或创建'机场ID'"
return user_id; // 返回用户模块ID
// 类比:就像"返回'机场ID'"
}
函数总结:
- 获取ACL主结构体:获取ACL插件的全局状态
- 获取或创建用户模块ID :调用
get_acl_user_id获取或创建用户模块ID - 返回用户模块ID:返回用户模块ID(用于后续创建Lookup Context)
类比:就像"注册机场":
- 联系总部:联系"安检服务提供商"的"总部"
- 获取或创建ID:获取或创建"机场ID"
- 返回ID:返回"机场ID"(用于后续创建安检规则集合)
11.3.2 获取用户模块ID:get_acl_user_id
get_acl_user_id 用于获取或创建用户模块ID:
c
//36:52:src/plugins/acl/lookup_context.c
static u32 get_acl_user_id(acl_main_t *am, char *user_module_name, char *val1_label, char *val2_label)
{
acl_lookup_context_user_t *auser; // 用户模块指针(临时变量)
// 类比:就像"机场信息指针"
// ========== 第一部分:查找现有用户模块 ==========
pool_foreach (auser, am->acl_users) // 遍历所有用户模块
{
if (0 == strcmp(auser->user_module_name, user_module_name)) // 如果用户模块名称匹配
{
return (auser - am->acl_users); // 返回现有用户模块ID
// auser - am->acl_users:计算指针差值,得到用户模块ID
// 为什么需要?如果用户模块已存在,直接返回其ID(避免重复创建)
// 类比:就像"如果机场已注册,直接返回'机场ID'"
}
}
// ========== 第二部分:创建新用户模块 ==========
pool_get(am->acl_users, auser); // 从pool中分配用户模块内存
// pool_get:从pool中分配内存
// acl_users:用户模块pool(存储所有用户模块)
// 类比:就像"从'机场信息池'中分配新的'机场信息'"
auser->user_module_name = user_module_name; // 设置用户模块名称
// 类比:就像"设置'机场名称'"
auser->val1_label = val1_label; // 设置第一个用户值标签
// 类比:就像"设置'第一个用户值标签'"
auser->val2_label = val2_label; // 设置第二个用户值标签
// 类比:就像"设置'第二个用户值标签'"
return (auser - am->acl_users); // 返回新用户模块ID
// 类比:就像"返回新'机场ID'"
}
函数总结:
- 查找现有用户模块:遍历所有用户模块,如果名称匹配,返回现有ID
- 创建新用户模块:如果未找到,创建新的用户模块,设置名称和标签
- 返回用户模块ID:返回用户模块ID(新创建或已存在的)
类比:就像"获取或创建机场ID":
- 查找现有机场:遍历所有已注册的机场,如果名称匹配,返回现有ID
- 创建新机场:如果未找到,创建新的机场信息,设置名称和标签
- 返回机场ID:返回机场ID(新创建或已存在的)
11.4 Lookup Context创建:如何"创建安检规则集合"?
11.4.1 Lookup Context创建函数:acl_plugin_get_lookup_context_index
acl_plugin_get_lookup_context_index 用于创建新的Lookup Context,返回上下文索引:
c
//98:122:src/plugins/acl/lookup_context.c
static int acl_plugin_get_lookup_context_index (u32 acl_user_id, u32 val1, u32 val2)
{
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
acl_lookup_context_t *acontext; // Lookup Context指针(临时变量)
// 类比:就像"安检规则集合指针"
// ========== 第一部分:验证用户模块ID ==========
if (!acl_user_id_valid(am, acl_user_id)) // 如果用户模块ID无效
{
return VNET_API_ERROR_INVALID_REGISTRATION; // 返回错误码(无效注册)
// acl_user_id_valid:检查用户模块ID是否有效(我们下面详细讲解)
// VNET_API_ERROR_INVALID_REGISTRATION:无效注册错误码
// 为什么需要?确保用户模块已注册
// 类比:就像"如果'机场ID'无效,返回错误"(确保机场已注册)
}
/*
* The lookup context index allocation is
* an operation done within the global heap,
* so no heap switching necessary.
*/
// 注释:Lookup Context索引分配是在全局堆中进行的操作,所以不需要切换堆。
// 为什么需要?允许在ACL插件完全初始化之前创建上下文
// 类比:就像"允许在安检服务提供商完全启动之前创建规则集合"(提前创建)
// ========== 第二部分:分配Lookup Context内存 ==========
pool_get(am->acl_lookup_contexts, acontext); // 从pool中分配Lookup Context内存
// pool_get:从pool中分配内存
// acl_lookup_contexts:Lookup Context pool(存储所有上下文)
// 类比:就像"从'安检规则集合池'中分配新的'安检规则集合'"
// ========== 第三部分:初始化Lookup Context ==========
acontext->acl_indices = 0; // 初始化ACL索引列表为空
// 类比:就像"初始化'规则列表'为空"
acontext->context_user_id = acl_user_id; // 设置用户模块ID
// 类比:就像"设置'创建此规则集合的机场ID'"
acontext->user_val1 = val1; // 设置用户值1
// 类比:就像"设置'第一个用户值'"(如"转发表ID")
acontext->user_val2 = val2; // 设置用户值2
// 类比:就像"设置'第二个用户值'"(如"策略ID")
// ========== 第四部分:将上下文添加到用户模块的上下文列表 ==========
u32 new_context_id = acontext - am->acl_lookup_contexts; // 计算新上下文ID
// acontext - am->acl_lookup_contexts:计算指针差值,得到上下文ID
// 类比:就像"计算新'规则集合ID'"
vec_add1(am->acl_users[acl_user_id].lookup_contexts, new_context_id); // 将上下文ID添加到用户模块的上下文列表
// vec_add1:向向量添加一个元素
// lookup_contexts:用户模块的上下文列表
// 为什么需要?用于追踪用户创建的所有上下文,便于管理和清理
// 类比:就像"将'规则集合ID'添加到'机场的规则集合列表'"
return new_context_id; // 返回新上下文ID
// 类比:就像"返回新'规则集合ID'"
}
函数总结:
- 验证用户模块ID:确保用户模块已注册
- 分配内存:从pool中分配Lookup Context内存
- 初始化上下文:设置ACL索引列表、用户模块ID、用户值
- 添加到用户列表:将上下文ID添加到用户模块的上下文列表
- 返回上下文ID:返回新创建的上下文ID
类比:就像"创建安检规则集合":
- 验证机场:确保机场已注册
- 分配空间:从"规则集合池"中分配新的"规则集合"
- 初始化集合:设置规则列表、创建者、标识信息
- 添加到列表:将"规则集合ID"添加到"机场的规则集合列表"
- 返回ID:返回新创建的"规则集合ID"
11.4.2 用户模块ID验证:acl_user_id_valid
acl_user_id_valid 用于验证用户模块ID是否有效:
c
//54:61:src/plugins/acl/lookup_context.c
static int acl_user_id_valid(acl_main_t *am, u32 acl_user_id)
{
if (pool_is_free_index (am->acl_users, acl_user_id)) // 如果用户模块索引在pool中是空闲的
{
return 0; // 返回0(无效)
// pool_is_free_index:检查pool中的索引是否空闲
// 为什么需要?确保用户模块存在
// 类比:就像"如果'机场ID'对应的'机场信息'不存在,返回无效"
}
return 1; // 返回1(有效)
// 类比:就像"如果'机场信息'存在,返回有效"
}
函数总结:
- 检查pool索引:检查用户模块索引在pool中是否空闲
- 返回结果:如果空闲,返回0(无效);否则返回1(有效)
类比:就像"验证机场ID":
- 检查信息:检查"机场ID"对应的"机场信息"是否存在
- 返回结果:如果不存在,返回无效;否则返回有效
11.5 ACL列表设置:如何"为规则集合添加规则"?
11.5.1 ACL列表设置函数:acl_plugin_set_acl_vec_for_context
acl_plugin_set_acl_vec_for_context 用于为Lookup Context设置ACL列表:
c
//216:267:src/plugins/acl/lookup_context.c
static int acl_plugin_set_acl_vec_for_context (u32 lc_index, u32 *acl_list)
{
int rv = 0; // 返回值(初始化为0,表示成功)
// 类比:就像"初始化'操作结果'为成功"
uword *seen_acl_bitmap = 0; // 已见ACL位图(临时变量,用于检测重复ACL)
// seen_acl_bitmap:用于记录已见过的ACL索引(检测重复)
// 为什么需要?确保ACL列表中不包含重复的ACL
// 类比:就像"已见规则位图"(用于检测重复规则)
u32 *pacln = 0; // ACL索引指针(临时变量,用于遍历)
// 类比:就像"规则索引指针"
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
acl_lookup_context_t *acontext; // Lookup Context指针(临时变量)
// 类比:就像"安检规则集合指针"
// ========== 第一部分:打印调试信息 ==========
if (am->trace_acl) // 如果启用ACL Trace
{
u32 i;
elog_acl_cond_trace_X1(am, (1), "LOOKUP-CONTEXT: set-acl-list lc_index %d", "i4", lc_index); // 打印Trace信息
// elog_acl_cond_trace_X1:打印Trace信息(1个整数参数)
// 类比:就像"记录'设置规则列表'的Trace信息"
for(i=0; i<vec_len(acl_list); i++) // 遍历ACL列表
{
elog_acl_cond_trace_X2(am, (1), " acl-list[%d]: %d", "i4i4", i, acl_list[i]); // 打印每个ACL索引
// 类比:就像"记录每个规则的Trace信息"
}
}
// ========== 第二部分:验证Lookup Context索引 ==========
if (!acl_lc_index_valid(am, lc_index)) // 如果Lookup Context索引无效
{
clib_warning("BUG: lc_index %d is not valid", lc_index); // 打印警告
// acl_lc_index_valid:检查Lookup Context索引是否有效(我们下面详细讲解)
// 类比:就像"如果'规则集合ID'无效,报错"
return -1; // 返回错误(-1)
// 类比:就像"返回错误"
}
// ========== 第三部分:验证ACL列表 ==========
vec_foreach (pacln, acl_list) // 遍历ACL列表
{
if (pool_is_free_index (am->acls, *pacln)) // 如果ACL索引在pool中是空闲的(ACL不存在)
{
/* ACL is not defined. Can not apply */
// 注释:ACL未定义。无法应用。
clib_warning ("ERROR: ACL %d not defined", *pacln); // 打印警告
// 类比:就像"如果规则不存在,报错"
rv = VNET_API_ERROR_NO_SUCH_ENTRY; // 设置错误码(条目不存在)
// VNET_API_ERROR_NO_SUCH_ENTRY:条目不存在错误码
// 类比:就像"设置错误码为'条目不存在'"
goto done; // 跳转到done标签(退出函数)
// 类比:就像"跳转到'完成'标签"
}
if (clib_bitmap_get (seen_acl_bitmap, *pacln)) // 如果ACL已在位图中(重复ACL)
{
/* ACL being applied twice within the list. error. */
// 注释:ACL在列表中应用了两次。错误。
clib_warning ("ERROR: ACL %d being applied twice", *pacln); // 打印警告
// 类比:就像"如果规则重复,报错"
rv = VNET_API_ERROR_ENTRY_ALREADY_EXISTS; // 设置错误码(条目已存在)
// VNET_API_ERROR_ENTRY_ALREADY_EXISTS:条目已存在错误码
// 类比:就像"设置错误码为'条目已存在'"
goto done; // 跳转到done标签(退出函数)
// 类比:就像"跳转到'完成'标签"
}
seen_acl_bitmap = clib_bitmap_set (seen_acl_bitmap, *pacln, 1); // 将ACL索引设置到位图中
// clib_bitmap_set:设置位图中的位
// 为什么需要?用于检测重复ACL
// 类比:就像"将规则索引设置到'已见规则位图'中"
}
// ========== 第四部分:获取Lookup Context并保存旧ACL列表 ==========
acontext = pool_elt_at_index(am->acl_lookup_contexts, lc_index); // 获取Lookup Context指针
// pool_elt_at_index:从pool中获取指定索引的元素
// 类比:就像"获取'规则集合'指针"
u32 *old_acl_vector = acontext->acl_indices; // 保存旧ACL列表指针
// 类比:就像"保存'旧规则列表'指针"
acontext->acl_indices = vec_dup(acl_list); // 复制新ACL列表到上下文
// vec_dup:复制向量
// 为什么需要?创建新ACL列表的副本,避免修改原始列表
// 类比:就像"复制新规则列表到'规则集合'"
// ========== 第五部分:取消应用旧ACL列表 ==========
unapply_acl_vec(lc_index, old_acl_vector); // 取消应用旧ACL列表
// unapply_acl_vec:取消应用ACL列表(从Hash表中移除)
// 作用:从Hash表中移除旧ACL列表的规则
// 类比:就像"从'字典索引系统'中移除'旧规则'"
unlock_acl_vec(lc_index, old_acl_vector); // 解锁旧ACL列表
// unlock_acl_vec:解锁ACL列表(从反向索引中移除)
// 作用:从反向索引中移除旧ACL列表的引用
// 类比:就像"从'反向索引'中移除'旧规则'的引用"
// ========== 第六部分:锁定和应用新ACL列表 ==========
lock_acl_vec(lc_index, acontext->acl_indices); // 锁定新ACL列表
// lock_acl_vec:锁定ACL列表(添加到反向索引)
// 作用:将新ACL列表添加到反向索引(用于追踪ACL的使用情况)
// 类比:就像"将'新规则'添加到'反向索引'"
apply_acl_vec(lc_index, acontext->acl_indices); // 应用新ACL列表
// apply_acl_vec:应用ACL列表(添加到Hash表)
// 作用:将新ACL列表添加到Hash表(用于快速查找)
// 类比:就像"将'新规则'添加到'字典索引系统'"
// ========== 第七部分:释放旧ACL列表内存 ==========
vec_free(old_acl_vector); // 释放旧ACL列表内存
// vec_free:释放向量内存
// 类比:就像"释放'旧规则列表'内存"
done: // done标签(用于错误处理)
clib_bitmap_free (seen_acl_bitmap); // 释放已见ACL位图内存
// clib_bitmap_free:释放位图内存
// 类比:就像"释放'已见规则位图'内存"
return rv; // 返回返回值
// 类比:就像"返回'操作结果'"
}
函数总结:
这个函数完成了以下工作:
- 打印调试信息:如果启用Trace,打印设置ACL列表的信息
- 验证Lookup Context索引:确保上下文索引有效
- 验证ACL列表:检查ACL是否存在、是否重复
- 保存旧ACL列表:保存旧的ACL列表指针
- 取消应用旧ACL列表:从Hash表和反向索引中移除旧ACL列表
- 锁定和应用新ACL列表:将新ACL列表添加到反向索引和Hash表
- 释放内存:释放旧ACL列表和位图的内存
类比:就像完整的"为规则集合添加规则"流程:
- 记录信息:记录"设置规则列表"的信息
- 验证集合:确保"规则集合"存在
- 验证规则:检查规则是否存在、是否重复
- 保存旧列表:保存"旧规则列表"
- 移除旧规则:从"字典索引系统"和"反向索引"中移除"旧规则"
- 添加新规则:将"新规则"添加到"反向索引"和"字典索引系统"
- 释放内存:释放"旧规则列表"和"已见规则位图"的内存
11.6 Lookup Context释放:如何"销毁安检规则集合"?
11.6.1 Lookup Context释放函数:acl_plugin_put_lookup_context_index
acl_plugin_put_lookup_context_index 用于释放Lookup Context,销毁相关的数据结构:
c
//190:210:src/plugins/acl/lookup_context.c
static void acl_plugin_put_lookup_context_index (u32 lc_index)
{
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
elog_acl_cond_trace_X1(am, (am->trace_acl), "LOOKUP-CONTEXT: put-context lc_index %d", "i4", lc_index); // 打印Trace信息
// elog_acl_cond_trace_X1:打印Trace信息(1个整数参数)
// 类比:就像"记录'释放规则集合'的Trace信息"
// ========== 第一部分:验证Lookup Context索引 ==========
if (!acl_lc_index_valid(am, lc_index)) // 如果Lookup Context索引无效
{
clib_warning("BUG: lc_index %d is not valid", lc_index); // 打印警告
// acl_lc_index_valid:检查Lookup Context索引是否有效(我们上面已经讲解过)
// 类比:就像"如果'规则集合ID'无效,报错"
return; // 返回(不继续执行)
// 类比:就像"返回,不继续销毁"
}
// ========== 第二部分:获取Lookup Context ==========
acl_lookup_context_t *acontext = pool_elt_at_index(am->acl_lookup_contexts, lc_index); // 获取Lookup Context指针
// pool_elt_at_index:从pool中获取指定索引的元素
// 类比:就像"获取'规则集合'指针"
// ========== 第三部分:从用户模块的上下文列表中移除 ==========
u32 index = vec_search(am->acl_users[acontext->context_user_id].lookup_contexts, lc_index); // 在用户模块的上下文列表中搜索
// vec_search:在向量中搜索元素(返回索引,如果未找到返回~0)
// 类比:就像"在'机场的规则集合列表'中搜索'规则集合ID'"
ASSERT(index != ~0); // 断言索引不为无效值(应该找到)
// 为什么需要?确保上下文在用户模块的列表中(数据一致性检查)
// 类比:就像"确保'规则集合'在'机场的规则集合列表'中"
vec_del1(am->acl_users[acontext->context_user_id].lookup_contexts, index); // 从用户模块的上下文列表中删除
// vec_del1:从向量中删除一个元素
// 类比:就像"从'机场的规则集合列表'中删除'规则集合ID'"
// ========== 第四部分:取消应用ACL列表 ==========
unapply_acl_vec(lc_index, acontext->acl_indices); // 取消应用ACL列表
// unapply_acl_vec:取消应用ACL列表(从Hash表中移除)
// 作用:从Hash表中移除ACL列表的规则
// 类比:就像"从'字典索引系统'中移除'规则'"
// ========== 第五部分:解锁ACL列表 ==========
unlock_acl_vec(lc_index, acontext->acl_indices); // 解锁ACL列表
// unlock_acl_vec:解锁ACL列表(从反向索引中移除)
// 作用:从反向索引中移除ACL列表的引用
// 类比:就像"从'反向索引'中移除'规则'的引用"
// ========== 第六部分:释放ACL列表内存 ==========
vec_free(acontext->acl_indices); // 释放ACL列表内存
// vec_free:释放向量内存
// 类比:就像"释放'规则列表'内存"
// ========== 第七部分:释放Lookup Context内存 ==========
pool_put(am->acl_lookup_contexts, acontext); // 将Lookup Context归还给pool
// pool_put:将元素归还给pool(标记为可用)
// 作用:释放Lookup Context占用的内存,标记为可重用
// 类比:就像"将'规则集合'归还给'规则集合池',标记为可用"
}
函数总结:
这个函数完成了以下工作:
- 打印调试信息:如果启用Trace,打印释放上下文的信息
- 验证Lookup Context索引:确保上下文索引有效
- 获取Lookup Context:从pool中获取上下文指针
- 从用户列表移除:从用户模块的上下文列表中移除上下文ID
- 取消应用ACL列表:从Hash表中移除ACL列表的规则
- 解锁ACL列表:从反向索引中移除ACL列表的引用
- 释放内存:释放ACL列表和上下文的内存
类比:就像完整的"销毁安检规则集合"流程:
- 记录信息:记录"释放规则集合"的信息
- 验证集合:确保"规则集合"存在
- 获取集合:获取"规则集合"指针
- 从列表移除:从"机场的规则集合列表"中移除"规则集合ID"
- 移除规则:从"字典索引系统"中移除"规则"
- 移除引用:从"反向索引"中移除"规则"的引用
- 释放内存:释放"规则列表"和"规则集合"的内存
11.7 ACL锁定和解锁:如何"追踪ACL的使用情况"?
11.7.1 ACL锁定函数:lock_acl
lock_acl 用于锁定ACL(将ACL添加到反向索引):
c
//125:130:src/plugins/acl/lookup_context.c
static void
lock_acl(acl_main_t *am, u32 acl, u32 lc_index)
{
vec_validate(am->lc_index_vec_by_acl, acl); // 确保反向索引向量有足够的空间
// lc_index_vec_by_acl:按ACL索引的Lookup Context索引向量数组(反向索引)
// vec_validate:确保向量有足够的空间(如果不够,自动扩展)
// 为什么需要?用于追踪每个ACL被哪些Lookup Context使用
// 类比:就像"确保'规则使用列表数组'有足够的空间"
elog_acl_cond_trace_X2(am, (am->trace_acl), "lock acl %d in lc_index %d", "i4i4", acl, lc_index); // 打印Trace信息
// elog_acl_cond_trace_X2:打印Trace信息(2个整数参数)
// 类比:就像"记录'锁定规则'的Trace信息"
vec_add1(am->lc_index_vec_by_acl[acl], lc_index); // 将Lookup Context索引添加到反向索引
// vec_add1:向向量添加一个元素
// 作用:记录此ACL被此Lookup Context使用
// 类比:就像"将'规则集合ID'添加到'规则使用列表'"
}
函数总结:
- 确保空间:确保反向索引向量有足够的空间
- 打印Trace:如果启用Trace,打印锁定ACL的信息
- 添加到反向索引:将Lookup Context索引添加到反向索引
为什么需要反向索引?
- 问题:当ACL规则变化时,需要通知所有使用此ACL的Lookup Context
- 解决方案 :使用反向索引(
lc_index_vec_by_acl)记录每个ACL被哪些Lookup Context使用 - 类比:就像"规则使用列表"(记录每个规则被哪些规则集合使用)
11.7.2 ACL解锁函数:unlock_acl
unlock_acl 用于解锁ACL(从反向索引中移除ACL):
c
//143:152:src/plugins/acl/lookup_context.c
static void
unlock_acl(acl_main_t *am, u32 acl, u32 lc_index)
{
vec_validate(am->lc_index_vec_by_acl, acl); // 确保反向索引向量有足够的空间
// 类比:就像"确保'规则使用列表数组'有足够的空间"
elog_acl_cond_trace_X2(am, (am->trace_acl), "unlock acl %d in lc_index %d", "i4i4", acl, lc_index); // 打印Trace信息
// 类比:就像"记录'解锁规则'的Trace信息"
u32 index = vec_search(am->lc_index_vec_by_acl[acl], lc_index); // 在反向索引中搜索Lookup Context索引
// vec_search:在向量中搜索元素(返回索引,如果未找到返回~0)
// 类比:就像"在'规则使用列表'中搜索'规则集合ID'"
if (index != ~0) // 如果找到
{
vec_del1(am->lc_index_vec_by_acl[acl], index); // 从反向索引中删除
// vec_del1:从向量中删除一个元素
// 类比:就像"从'规则使用列表'中删除'规则集合ID'"
}
else // 如果未找到
{
clib_warning("BUG: can not unlock acl %d lc_index %d", acl, lc_index); // 打印警告
// 为什么需要?数据一致性检查(应该找到但未找到,说明数据不一致)
// 类比:就像"如果未找到,报错"(数据不一致)
}
}
函数总结:
- 确保空间:确保反向索引向量有足够的空间
- 打印Trace:如果启用Trace,打印解锁ACL的信息
- 搜索并删除:在反向索引中搜索Lookup Context索引,如果找到则删除
- 错误检查:如果未找到,打印警告(数据一致性检查)
类比:就像"从规则使用列表中移除规则集合ID":
- 确保空间:确保"规则使用列表数组"有足够的空间
- 记录信息:记录"解锁规则"的信息
- 搜索并删除:在"规则使用列表"中搜索"规则集合ID",如果找到则删除
- 错误检查:如果未找到,报错(数据不一致)
11.8 ACL应用和取消应用:如何"将规则添加到Hash表"?
11.8.1 ACL应用函数:apply_acl_vec
apply_acl_vec 用于应用ACL列表(将ACL列表添加到Hash表):
c
//164:172:src/plugins/acl/lookup_context.c
static void
apply_acl_vec(u32 lc_index, u32 *acls)
{
int i; // 循环变量(临时变量)
// 类比:就像"循环变量"
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
for(i=0; i<vec_len(acls); i++) // 遍历ACL列表
{
hash_acl_apply(am, lc_index, acls[i], i); // 应用单个ACL
// hash_acl_apply:应用单个ACL(添加到Hash表)
// 参数:am(ACL插件主结构体)、lc_index(Lookup Context索引)、acls[i](ACL索引)、i(ACL位置)
// 作用:将ACL添加到Hash表,构建Hash匹配结构
// 类比:就像"将单个规则添加到'字典索引系统'"
}
}
函数总结:
- 遍历ACL列表:逐个遍历ACL列表中的每个ACL
- 应用单个ACL :调用
hash_acl_apply将每个ACL添加到Hash表
类比:就像"将规则列表添加到字典索引系统":
- 遍历规则:逐个遍历规则列表中的每个规则
- 添加规则:将每个规则添加到"字典索引系统"
11.8.2 ACL取消应用函数:unapply_acl_vec
unapply_acl_vec 用于取消应用ACL列表(从Hash表中移除ACL列表):
c
//175:184:src/plugins/acl/lookup_context.c
static void
unapply_acl_vec(u32 lc_index, u32 *acls)
{
int i; // 循环变量(临时变量)
// 类比:就像"循环变量"
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
if (vec_len(acls) == 0) // 如果ACL列表为空
{
return; // 返回(无需处理)
// 类比:就像"如果规则列表为空,返回"
}
for(i=vec_len(acls); i > 0; i--) // 从后往前遍历ACL列表(逆序)
{
hash_acl_unapply(am, lc_index, acls[i-1]); // 取消应用单个ACL
// hash_acl_unapply:取消应用单个ACL(从Hash表中移除)
// 参数:am(ACL插件主结构体)、lc_index(Lookup Context索引)、acls[i-1](ACL索引)
// 为什么从后往前?因为Hash表构建时是从前往后,取消应用时需要逆序
// 类比:就像"从后往前移除规则"(逆序移除)
}
}
函数总结:
- 检查空列表:如果ACL列表为空,直接返回
- 逆序遍历:从后往前遍历ACL列表(逆序)
- 取消应用单个ACL :调用
hash_acl_unapply从Hash表中移除每个ACL
为什么需要逆序?
- 原因:Hash表构建时是从前往后,取消应用时需要逆序,以保持数据一致性
- 类比:就像"拆房子时要从上往下拆"(逆序操作)
11.9 ACL变更通知:如何"通知上下文ACL规则变化"?
11.9.1 ACL变更通知函数:acl_plugin_lookup_context_notify_acl_change
acl_plugin_lookup_context_notify_acl_change 用于通知Lookup Context系统ACL规则发生了变化:
c
//270:283:src/plugins/acl/lookup_context.c
void acl_plugin_lookup_context_notify_acl_change(u32 acl_num)
{
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
if (acl_plugin_acl_exists(acl_num)) // 如果ACL存在
{
if (hash_acl_exists(am, acl_num)) // 如果Hash ACL存在
{
/* this is a modification, clean up the older entries */
// 注释:这是修改,清理旧条目
hash_acl_delete(am, acl_num); // 删除Hash ACL
// hash_acl_delete:删除Hash ACL(从Hash表中移除旧规则)
// 作用:清理旧的Hash表条目
// 类比:就像"从'字典索引系统'中删除'旧规则'"
}
hash_acl_add(am, acl_num); // 添加Hash ACL
// hash_acl_add:添加Hash ACL(重新构建Hash表)
// 作用:重新构建Hash表,包含新的规则
// 类比:就像"将'新规则'添加到'字典索引系统'"
}
else // 如果ACL不存在(已删除)
{
/* this is a deletion notification */
// 注释:这是删除通知
hash_acl_delete(am, acl_num); // 删除Hash ACL
// hash_acl_delete:删除Hash ACL(从Hash表中移除规则)
// 作用:从Hash表中移除已删除的ACL
// 类比:就像"从'字典索引系统'中删除'已删除的规则'"
}
}
函数总结:
- 检查ACL是否存在:检查ACL是否存在
- 处理修改:如果ACL存在且Hash ACL存在,删除旧Hash ACL,添加新Hash ACL
- 处理删除:如果ACL不存在,删除Hash ACL
为什么需要通知机制?
- 问题:当ACL规则变化时,使用此ACL的Lookup Context需要更新Hash表
- 解决方案 :通过反向索引(
lc_index_vec_by_acl)找到所有使用此ACL的Lookup Context,然后重新构建Hash表 - 类比:就像"当规则变化时,通知所有使用此规则的规则集合更新"
11.10 方法导出和初始化:如何"让外部插件调用ACL方法"?
11.10.1 方法表初始化函数:acl_plugin_methods_vtable_init
acl_plugin_methods_vtable_init 用于初始化ACL插件的方法表(函数指针表):
c
//362:370:src/plugins/acl/lookup_context.c
__clib_export clib_error_t *
acl_plugin_methods_vtable_init(acl_plugin_methods_t *m)
{
m->p_acl_main = &acl_main; // 设置ACL主结构体指针
// p_acl_main:指向acl_main的指针(用于访问ACL插件的全局状态)
// 类比:就像"设置'总部地址'"
#define _(name) m->name = acl_plugin_ ## name; // 宏定义:设置方法指针
// 作用:将ACL插件的内部函数指针赋值给方法表
// 类比:就像"设置'服务接口'指针"
foreach_acl_plugin_exported_method_name // 遍历所有导出的方法名称(通过宏展开)
// 包括:acl_exists、register_user_module、get_lookup_context_index等
// 类比:就像"遍历所有'服务接口'名称"
#undef _
return 0; // 返回0(成功)
// 类比:就像"返回'成功'"
}
函数总结:
- 设置ACL主结构体指针 :将
&acl_main赋值给方法表的p_acl_main字段 - 设置方法指针:通过宏展开,将所有导出的方法指针赋值给方法表
- 返回成功:返回0表示成功
宏展开过程:
cpp
// foreach_acl_plugin_exported_method_name 展开为:
_(acl_exists)
_(register_user_module)
_(get_lookup_context_index)
_(put_lookup_context_index)
_(set_acl_vec_for_context)
_(fill_5tuple)
_(match_5tuple)
// 然后 _(name) 展开为:
m->acl_exists = acl_plugin_acl_exists;
m->register_user_module = acl_plugin_register_user_module;
m->get_lookup_context_index = acl_plugin_get_lookup_context_index;
// ... 等等
类比:就像"初始化服务接口表":
- 设置总部地址:设置"总部地址"到"服务接口表"
- 设置接口指针:将所有"服务接口"的指针设置到"服务接口表"
- 返回成功:返回"成功"
11.10.2 外部插件初始化函数:acl_plugin_exports_init
acl_plugin_exports_init 用于外部插件初始化ACL插件的方法表:
c
//39:45:src/plugins/acl/public_inlines.h
static inline clib_error_t * acl_plugin_exports_init (acl_plugin_methods_t *m)
{
acl_plugin_methods_vtable_init_fn_t mvi; // 方法表初始化函数指针(临时变量)
// acl_plugin_methods_vtable_init_fn_t:方法表初始化函数指针类型
// 类比:就像"服务接口表初始化函数指针"
LOAD_SYMBOL_FROM_PLUGIN_TO("acl_plugin.so", acl_plugin_methods_vtable_init, mvi); // 从插件中加载符号
// LOAD_SYMBOL_FROM_PLUGIN_TO:从插件中加载符号(函数指针)
// 参数:"acl_plugin.so"(插件文件名)、acl_plugin_methods_vtable_init(符号名)、mvi(输出函数指针)
// 作用:从ACL插件中加载方法表初始化函数
// 类比:就像"从'安检服务提供商'加载'服务接口表初始化函数'"
return (mvi(m)); // 调用方法表初始化函数
// mvi(m):调用方法表初始化函数,传入方法表指针
// 作用:初始化方法表,填充所有函数指针
// 类比:就像"调用'服务接口表初始化函数',初始化'服务接口表'"
}
函数总结:
- 加载符号:从ACL插件中加载方法表初始化函数
- 调用初始化函数:调用方法表初始化函数,填充所有函数指针
为什么需要动态加载?
- 原因:外部插件和ACL插件是独立的动态库,需要通过动态加载来获取函数指针
- 类比:就像"从'安检服务提供商'动态加载'服务接口'"
11.11 5-tuple填充和匹配:如何"使用ACL匹配引擎"?
11.11.1 5-tuple填充函数:acl_plugin_fill_5tuple
acl_plugin_fill_5tuple 用于从数据包中提取5-tuple:
c
//288:292:src/plugins/acl/lookup_context.c
static void acl_plugin_fill_5tuple (u32 lc_index, vlib_buffer_t * b0, int is_ip6, int is_input,
int is_l2_path, fa_5tuple_opaque_t * p5tuple_pkt)
{
acl_plugin_fill_5tuple_inline(&acl_main, lc_index, b0, is_ip6, is_input, is_l2_path, p5tuple_pkt); // 调用内联版本
// acl_plugin_fill_5tuple_inline:内联版本的5-tuple填充函数
// 作用:从数据包中提取5-tuple(源IP、目的IP、协议、源端口、目的端口)
// 类比:就像"从'数据包'中提取'旅客信息'"
}
函数总结:
- 调用内联版本:调用内联版本的5-tuple填充函数
为什么有内联和非内联版本?
- 内联版本:编译到调用者代码中,性能更好,但需要包含ACL插件的头文件
- 非内联版本:通过函数指针调用,灵活性更好,但性能稍差
- 类比:就像"直接调用"和"通过接口调用"的区别
11.11.2 5-tuple匹配函数:acl_plugin_match_5tuple
acl_plugin_match_5tuple 用于匹配5-tuple:
c
//294:303:src/plugins/acl/lookup_context.c
static int acl_plugin_match_5tuple (u32 lc_index,
fa_5tuple_opaque_t * pkt_5tuple,
int is_ip6, u8 * r_action,
u32 * r_acl_pos_p,
u32 * r_acl_match_p,
u32 * r_rule_match_p,
u32 * trace_bitmap)
{
return acl_plugin_match_5tuple_inline (&acl_main, lc_index, pkt_5tuple, is_ip6, r_action, r_acl_pos_p, r_acl_match_p, r_rule_match_p, trace_bitmap); // 调用内联版本
// acl_plugin_match_5tuple_inline:内联版本的5-tuple匹配函数
// 作用:在Lookup Context中匹配5-tuple,返回匹配结果
// 类比:就像"在'规则集合'中匹配'旅客信息',返回匹配结果"
}
函数总结:
- 调用内联版本:调用内联版本的5-tuple匹配函数
参数说明:
lc_index:Lookup Context索引pkt_5tuple:数据包的5-tuple(不透明类型)is_ip6:是否IPv6(0=IPv4,1=IPv6)r_action:输出参数,匹配的动作(permit/deny)r_acl_pos_p:输出参数,匹配的ACL位置r_acl_match_p:输出参数,匹配的ACL索引r_rule_match_p:输出参数,匹配的规则索引trace_bitmap:输出参数,Trace位图
返回值:1表示匹配成功,0表示未匹配
11.12 完整使用流程:从注册到匹配
让我们用一个完整的例子来总结整个流程:
场景:一个外部插件(如ACL-based forwarding)需要使用ACL插件的匹配引擎
步骤1:初始化方法表
cpp
acl_plugin_methods_t acl_plugin;
acl_plugin_exports_init(&acl_plugin); // 初始化方法表
步骤2:注册用户模块
cpp
u32 user_id = acl_plugin.register_user_module(
"acl-based-forwarding", // 用户模块名称
"table_id", // 第一个用户值标签
"policy_id" // 第二个用户值标签
);
步骤3:创建Lookup Context
cpp
u32 lc_index = acl_plugin.get_lookup_context_index(
user_id, // 用户模块ID
10, // 第一个用户值(转发表ID)
1 // 第二个用户值(策略ID)
);
步骤4:设置ACL列表
cpp
u32 acl_list[] = {0, 1, 2}; // ACL索引列表
acl_plugin.set_acl_vec_for_context(lc_index, acl_list);
步骤5:匹配数据包
cpp
fa_5tuple_opaque_t pkt_5tuple;
acl_plugin.fill_5tuple(lc_index, buffer, is_ip6, is_input, is_l2_path, &pkt_5tuple);
u8 action;
u32 acl_pos, acl_match, rule_match;
if (acl_plugin.match_5tuple(lc_index, &pkt_5tuple, is_ip6, &action,
&acl_pos, &acl_match, &rule_match, NULL)) {
// 匹配成功,处理动作
}
步骤6:释放Lookup Context
cpp
acl_plugin.put_lookup_context_index(lc_index);
类比:就像完整的"租用安检服务"流程:
- 初始化服务接口:初始化"服务接口表"
- 注册机场:注册为"机场",获得"机场ID"
- 创建规则集合:创建"安检规则集合",获得"规则集合ID"
- 添加规则:为"规则集合"添加规则
- 使用服务:使用"安检服务"匹配"旅客信息"
- 释放资源:释放"规则集合"资源
11.13 本章小结
通过这一章的详细讲解,我们了解了:
- ACL-as-a-service的概念:什么是ACL作为服务,为什么需要它
- 数据结构:用户模块结构体、Lookup Context结构体、方法表结构体
- 用户模块注册:如何注册为用户模块,获取用户模块ID
- Lookup Context创建:如何创建Lookup Context,获取上下文索引
- ACL列表设置:如何为Lookup Context设置ACL列表
- Lookup Context释放:如何释放Lookup Context,销毁相关数据结构
- ACL锁定和解锁:如何追踪ACL的使用情况(反向索引)
- ACL应用和取消应用:如何将ACL添加到Hash表或从Hash表中移除
- ACL变更通知:如何通知Lookup Context系统ACL规则变化
- 方法导出和初始化:如何让外部插件调用ACL方法
- 5-tuple填充和匹配:如何使用ACL匹配引擎
核心要点:
- ACL-as-a-service允许其他插件使用ACL插件的匹配引擎,避免重复实现
- Lookup Context是一个抽象的ACL匹配上下文,不绑定到特定接口
- 反向索引用于追踪ACL的使用情况,当ACL变化时通知所有使用此ACL的Lookup Context
- 方法表通过函数指针实现,允许外部插件动态调用ACL插件的方法
- 完整流程:注册用户模块 → 创建Lookup Context → 设置ACL列表 → 匹配数据包 → 释放资源
下一步:在下一章,我们会看到会话表管理的详细实现,包括会话表初始化、Per-worker数据结构、容量管理等。
第12章:会话表管理------如何"建立和管理常客数据库"?
生活类比:想象一下,你是一个"常客数据库管理员",需要建立一个"常客数据库系统"。
- 数据库设计:需要设计数据库结构(会话表结构)
- 索引系统:需要建立索引系统(Hash表)
- 容量管理:需要管理数据库容量(最大会话数)
- 性能优化:需要优化数据库性能(Per-worker数据)
这一章,我们就跟着ACL插件的代码,看看它是如何"建立和管理常客数据库"的。每一步我都会详细解释,确保你完全理解。
12.1 会话表管理的概念:什么是"会话表管理"?
12.1.1 什么是会话表管理?
会话表管理(Session Table Management):Flow-aware ACL的核心机制,用于管理所有网络连接的会话状态信息。
关键概念详解:
-
会话表(Session Table):存储所有会话的Hash表
- IPv4会话表 :使用
clib_bihash_16_8_t(16字节key,8字节value) - IPv6会话表 :使用
clib_bihash_40_8_t(40字节key,8字节value) - 类比:就像"常客数据库"(存储所有常客信息,通过身份证号快速查找)
- IPv4会话表 :使用
-
Per-worker数据结构:每个Worker线程独立的数据结构
- 会话Pool:每个Worker线程有独立的会话内存池
- 超时链表:每个Worker线程有独立的超时链表
- 为什么需要?:避免线程间竞争,提高性能
- 类比:就像"每个安检通道有独立的'档案柜'和'待检查列表'"
-
容量管理:管理会话表的最大容量
- 最大会话数 :
fa_conn_table_max_entries(默认500,000) - Hash表桶数 :
fa_conn_table_hash_num_buckets(默认64K) - Hash表内存 :
fa_conn_table_hash_memory_size(默认1GB) - 类比:就像"数据库的最大容量"(最大记录数、索引大小等)
- 最大会话数 :
-
性能优化:通过各种技术优化会话表性能
- 缓存行对齐:会话结构体对齐到缓存行(128字节)
- 预取优化:预取Hash表bucket和数据
- 批量处理:批量处理数据包
- 类比:就像"数据库性能优化"(索引优化、查询优化等)
12.1.2 会话表管理的架构
架构层次:
-
全局层 :ACL插件主结构体(
acl_main_t)- IPv4会话Hash表 :
fa_ip4_sessions_hash - IPv6会话Hash表 :
fa_ip6_sessions_hash - Per-worker数据数组 :
per_worker_data[] - 类比:就像"数据库系统的'总部'"(全局Hash表、Worker数据数组)
- IPv4会话Hash表 :
-
Worker层 :每个Worker线程的数据结构(
acl_fa_per_worker_data_t)- 会话Pool :
fa_sessions_pool - 超时链表 :
fa_conn_list_head[]、fa_conn_list_tail[] - 统计信息 :
fa_session_adds_by_sw_if_index[]、fa_session_dels_by_sw_if_index[] - 类比:就像"每个安检通道的'档案柜'和'待检查列表'"
- 会话Pool :
-
会话层 :单个会话结构体(
fa_session_t)- 5-tuple信息 :
info(用于Hash表key) - 状态信息 :
last_active_time、tcp_flags_seen等 - 链表信息 :
link_prev_idx、link_next_idx等 - 类比:就像"单个常客的'档案夹'"
- 5-tuple信息 :
数据流:
数据包 → 提取5-tuple → Hash表查找 → 找到会话 → 更新会话状态
↓
未找到 → ACL匹配 → 创建新会话 → 添加到Hash表
类比:就像"常客数据库系统"的数据流:
旅客 → 读取身份证号 → 数据库查找 → 找到常客 → 更新访问记录
↓
未找到 → 安检检查 → 登记新常客 → 添加到数据库
12.2 会话表初始化:如何"建立常客数据库"?
12.2.1 会话表初始化函数:acl_fa_verify_init_sessions
acl_fa_verify_init_sessions 用于初始化会话表系统(包括Per-worker会话Pool和Hash表):
c
//84:121:src/plugins/acl/sess_mgmt_node.c
static void
acl_fa_verify_init_sessions (acl_main_t * am)
{
if (!am->fa_sessions_hash_is_initialized) // 如果会话Hash表未初始化
{
u16 wk; // Worker线程索引(临时变量)
// 类比:就像"安检通道编号"
// ========== 第一部分:初始化Per-worker会话Pool ==========
/* Allocate the per-worker sessions pools */
// 注释:分配Per-worker会话Pool
for (wk = 0; wk < vec_len (am->per_worker_data); wk++) // 遍历所有Worker线程
{
acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk]; // 获取Worker线程的Per-worker数据
// per_worker_data:Per-worker数据数组(每个Worker线程一个)
// 类比:就像"获取'安检通道'的'数据'"
/*
* // In lieu of trying to preallocate the pool and its free bitmap, rather use pool_init_fixed
* pool_alloc_aligned(pw->fa_sessions_pool, am->fa_conn_table_max_entries, CLIB_CACHE_LINE_BYTES);
* clib_bitmap_validate(pool_header(pw->fa_sessions_pool)->free_bitmap, am->fa_conn_table_max_entries);
*/
// 注释:与其尝试预分配pool和其free bitmap,不如使用pool_init_fixed
// 为什么使用pool_init_fixed?简化初始化,自动管理free bitmap
// 类比:就像"使用'固定大小档案柜',自动管理'空闲位图'"
pool_init_fixed (pw->fa_sessions_pool, // 初始化固定大小的会话Pool
am->fa_conn_table_max_entries); // 参数:Pool指针、最大条目数
// pool_init_fixed:初始化固定大小的pool(预分配内存)
// fa_sessions_pool:会话Pool(每个Worker线程一个)
// fa_conn_table_max_entries:最大会话数(默认500,000)
// 为什么需要固定大小?避免动态分配,提高性能
// 类比:就像"初始化固定大小的'档案柜'"(预分配空间,避免动态分配)
}
// ========== 第二部分:初始化IPv6会话Hash表 ==========
/* ... and the interface session hash table */
// 注释:...以及接口会话Hash表
clib_bihash_init_40_8 (&am->fa_ip6_sessions_hash, // 初始化IPv6会话Hash表
"ACL plugin FA IPv6 session bihash", // Hash表名称(用于调试)
am->fa_conn_table_hash_num_buckets, // Hash表桶数量(默认64K)
am->fa_conn_table_hash_memory_size); // Hash表内存大小(默认1GB)
// clib_bihash_init_40_8:初始化40_8双向Hash表
// fa_ip6_sessions_hash:IPv6会话Hash表(40字节key,8字节value)
// fa_conn_table_hash_num_buckets:Hash表桶数量(在初始化时配置)
// fa_conn_table_hash_memory_size:Hash表内存大小(在初始化时配置)
// 为什么需要40字节key?IPv6地址128位(16字节)+ 其他字段
// 类比:就像"初始化'IPv6常客数据库'"(40字节key用于IPv6地址)
clib_bihash_set_kvp_format_fn_40_8 (&am->fa_ip6_sessions_hash, // 设置Hash表key-value格式化函数
format_ip6_session_bihash_kv); // 格式化函数(用于调试输出)
// clib_bihash_set_kvp_format_fn_40_8:设置40_8 Hash表的key-value格式化函数
// format_ip6_session_bihash_kv:IPv6会话Hash表key-value格式化函数
// 为什么需要?用于调试输出(show命令)
// 类比:就像"设置'数据库记录格式化函数'"(用于调试输出)
// ========== 第三部分:初始化IPv4会话Hash表 ==========
clib_bihash_init_16_8 (&am->fa_ip4_sessions_hash, // 初始化IPv4会话Hash表
"ACL plugin FA IPv4 session bihash", // Hash表名称(用于调试)
am->fa_conn_table_hash_num_buckets, // Hash表桶数量(默认64K)
am->fa_conn_table_hash_memory_size); // Hash表内存大小(默认1GB)
// clib_bihash_init_16_8:初始化16_8双向Hash表
// fa_ip4_sessions_hash:IPv4会话Hash表(16字节key,8字节value)
// 为什么需要16字节key?IPv4地址32位(4字节)+ 其他字段
// 类比:就像"初始化'IPv4常客数据库'"(16字节key用于IPv4地址)
clib_bihash_set_kvp_format_fn_16_8 (&am->fa_ip4_sessions_hash, // 设置Hash表key-value格式化函数
format_ip4_session_bihash_kv); // 格式化函数(用于调试输出)
// 类比:就像"设置'数据库记录格式化函数'"(用于调试输出)
// ========== 第四部分:设置初始化标志 ==========
am->fa_sessions_hash_is_initialized = 1; // 设置初始化标志为1
// fa_sessions_hash_is_initialized:会话Hash表初始化标志
// 为什么需要?避免重复初始化
// 类比:就像"标记'数据库系统'已初始化"
}
}
函数总结:
这个函数完成了以下工作:
- 初始化Per-worker会话Pool:为每个Worker线程初始化固定大小的会话Pool
- 初始化IPv6会话Hash表:初始化IPv6会话Hash表(40字节key,8字节value)
- 初始化IPv4会话Hash表:初始化IPv4会话Hash表(16字节key,8字节value)
- 设置初始化标志:设置初始化标志,避免重复初始化
初始化顺序:
- Per-worker Pool:先初始化Per-worker会话Pool(因为Hash表需要引用Pool中的会话)
- IPv6 Hash表:然后初始化IPv6会话Hash表
- IPv4 Hash表:最后初始化IPv4会话Hash表
类比:就像完整的"建立常客数据库系统"流程:
- 初始化档案柜:为每个"安检通道"初始化固定大小的"档案柜"
- 初始化IPv6数据库:初始化"IPv6常客数据库"(40字节key)
- 初始化IPv4数据库:初始化"IPv4常客数据库"(16字节key)
- 标记完成:标记"数据库系统"已初始化
12.3 Per-worker数据结构:如何"为每个安检通道建立档案系统"?
12.3.1 Per-worker数据结构:acl_fa_per_worker_data_t
acl_fa_per_worker_data_t 用于存储每个Worker线程的会话管理数据:
c
//168:235:src/plugins/acl/fa_node.h
typedef struct {
/* The pool of sessions managed by this worker */
fa_session_t *fa_sessions_pool; // 会话Pool(此Worker管理的会话)
// fa_sessions_pool:会话Pool(存储此Worker线程的所有会话)
// 为什么需要?每个Worker线程有独立的会话Pool,避免线程间竞争
// 类比:就像"此安检通道的'档案柜'"(存储此通道的所有常客档案)
/* incoming session change requests from other workers */
clib_spinlock_t pending_session_change_request_lock; // 待处理会话变更请求锁
// pending_session_change_request_lock:自旋锁(用于保护待处理会话变更请求)
// 为什么需要?多个线程可能同时访问待处理请求列表
// 类比:就像"待处理请求列表的'锁'"
u64 *pending_session_change_requests; // 待处理会话变更请求列表
// pending_session_change_requests:待处理的会话变更请求列表(从其他Worker线程接收)
// 为什么需要?允许其他Worker线程请求此Worker线程修改会话
// 类比:就像"待处理请求列表"(从其他通道接收的请求)
u64 *wip_session_change_requests; // 处理中的会话变更请求列表
// wip_session_change_requests:处理中的会话变更请求列表(Work In Progress)
// 为什么需要?在处理请求时,允许其他线程继续入队新请求(无锁设计)
// 类比:就像"处理中请求列表"(正在处理的请求)
u64 rcvd_session_change_requests; // 接收的会话变更请求计数
// rcvd_session_change_requests:接收的会话变更请求总数(统计信息)
// 类比:就像"接收的请求总数"
u64 sent_session_change_requests; // 发送的会话变更请求计数
// sent_session_change_requests:发送的会话变更请求总数(统计信息)
// 类比:就像"发送的请求总数"
/* per-worker ACL_N_TIMEOUTS of conn lists */
u32 *fa_conn_list_head; // 超时链表头节点索引数组
// fa_conn_list_head:超时链表头节点索引数组(每个超时类型一个)
// ACL_N_TIMEOUTS:超时类型数量(TCP Established、TCP Transient、UDP Idle等)
// 为什么需要数组?不同类型的会话有不同的超时时间,需要分别管理
// 类比:就像"待检查列表头数组"(每个类型一个列表头)
u32 *fa_conn_list_tail; // 超时链表尾节点索引数组
// fa_conn_list_tail:超时链表尾节点索引数组(每个超时类型一个)
// 类比:就像"待检查列表尾数组"(每个类型一个列表尾)
/* expiry time set whenever an element is enqueued */
u64 *fa_conn_list_head_expiry_time; // 超时链表头节点过期时间数组
// fa_conn_list_head_expiry_time:超时链表头节点过期时间数组(每个超时类型一个)
// 为什么需要?用于优化清理性能(如果头节点未到过期时间,不继续检查)
// 类比:就像"待检查列表头过期时间数组"(用于优化检查性能)
/* adds and deletes per-worker-per-interface */
u64 *fa_session_dels_by_sw_if_index; // 按接口索引的会话删除计数数组
// fa_session_dels_by_sw_if_index:按接口索引统计会话删除次数
// 为什么需要?用于统计和监控(每个接口的会话删除情况)
// 类比:就像"按通道统计的'常客删除计数'"
u64 *fa_session_adds_by_sw_if_index; // 按接口索引的会话添加计数数组
// fa_session_adds_by_sw_if_index:按接口索引统计会话添加次数
// 类比:就像"按通道统计的'常客添加计数'"
/* sessions deleted due to epoch change */
u64 *fa_session_epoch_change_by_sw_if_index; // 按接口索引的Policy Epoch变化计数数组
// fa_session_epoch_change_by_sw_if_index:按接口索引统计Policy Epoch变化次数
// 为什么需要?用于统计和监控(Policy Epoch变化导致的会话删除)
// 类比:就像"按通道统计的'规则版本号变化计数'"
/* Vector of expired connections retrieved from lists */
u32 *expired; // 过期会话索引向量
// expired:过期会话索引向量(从超时链表中检索到的过期会话)
// 为什么需要?用于批量处理过期会话(先收集,后处理)
// 类比:就像"过期常客列表"(从待检查列表中检索到的过期常客)
/* the earliest next expiry time */
u64 next_expiry_time; // 最早的下次过期时间
// next_expiry_time:最早的下次过期时间(用于优化清理调度)
// 为什么需要?用于优化清理进程的唤醒时间(避免频繁唤醒)
// 类比:就像"最早的下次检查时间"(用于优化检查调度)
/* if not zero, look at all the elements until their enqueue timestamp is after below one */
u64 requeue_until_time; // 重新入队截止时间
// requeue_until_time:重新入队截止时间(用于批量重新入队)
// 为什么需要?用于批量处理会话重新入队(提高性能)
// 类比:就像"重新入队截止时间"(用于批量重新入队)
/* Current time between the checks */
u64 current_time_wait_interval; // 当前检查间隔时间
// current_time_wait_interval:当前检查间隔时间(用于自适应调度)
// 为什么需要?用于自适应调整清理频率(根据负载动态调整)
// 类比:就像"当前检查间隔时间"(用于自适应调整)
/* Counter of how many sessions we did delete */
u64 cnt_deleted_sessions; // 删除的会话计数
// cnt_deleted_sessions:删除的会话计数(用于统计和监控)
// 类比:就像"删除的常客计数"
/* Counter of already deleted sessions being deleted - should not increment unless a bug */
u64 cnt_already_deleted_sessions; // 已删除会话的重复删除计数
// cnt_already_deleted_sessions:已删除会话的重复删除计数(用于检测bug)
// 为什么需要?用于检测bug(正常情况下应该为0)
// 类比:就像"已删除常客的重复删除计数"(用于检测bug)
/* Number of times we requeued a session to a head of the list */
u64 cnt_session_timer_restarted; // 会话定时器重启计数
// cnt_session_timer_restarted:会话定时器重启计数(用于统计和监控)
// 为什么需要?用于统计和监控(会话定时器重启的频率)
// 类比:就像"常客定时器重启计数"
/* swipe up to this enqueue time, rather than following the timeouts */
u64 swipe_end_time; // 清理截止时间
// swipe_end_time:清理截止时间(用于批量清理)
// 为什么需要?用于批量清理会话(提高性能)
// 类比:就像"清理截止时间"(用于批量清理)
/* bitmap of sw_if_index serviced by this worker */
uword *serviced_sw_if_index_bitmap; // 此Worker服务的接口索引位图
// serviced_sw_if_index_bitmap:此Worker线程服务的接口索引位图
// 为什么需要?用于追踪此Worker线程负责哪些接口
// 类比:就像"此安检通道服务的通道位图"(用于追踪负责哪些通道)
/* bitmap of sw_if_indices to clear. set by main thread, cleared by worker */
uword *pending_clear_sw_if_index_bitmap; // 待清理接口索引位图
// pending_clear_sw_if_index_bitmap:待清理接口索引位图(由主线程设置,Worker线程清除)
// 为什么需要?用于通知Worker线程清理指定接口的所有会话
// 类比:就像"待清理通道位图"(由主管理员设置,安检通道清除)
/* atomic, indicates that the swipe-deletion of connections is in progress */
u32 clear_in_process; // 清理进行中标志
// clear_in_process:清理进行中标志(原子变量,用于同步)
// 为什么需要?用于防止并发清理(确保同一时间只有一个清理操作)
// 类比:就像"清理进行中标志"(用于防止并发清理)
/* Interrupt is pending from main thread */
int interrupt_is_pending; // 中断挂起标志
// interrupt_is_pending:中断挂起标志(主线程发送的中断)
// 为什么需要?用于通知Worker线程有清理工作要做
// 类比:就像"中断挂起标志"(主管理员发送的中断)
/*
* Interrupt node on the worker thread sets this if it knows there is
* more work to do, but it has to finish to avoid hogging the
* core for too long.
*/
// 注释:Worker线程上的中断节点设置此标志,如果它知道还有工作要做,但必须完成以避免长时间占用核心。
int interrupt_is_needed; // 需要中断标志
// interrupt_is_needed:需要中断标志(Worker线程设置,表示需要更多中断)
// 为什么需要?用于自适应调度(如果还有工作,请求更多中断)
// 类比:就像"需要中断标志"(如果还有工作,请求更多中断)
/*
* Set to indicate that the interrupt node wants to get less interrupts
* because there is not enough work for the current rate.
*/
// 注释:设置此标志表示中断节点希望减少中断,因为当前速率下没有足够的工作。
int interrupt_is_unwanted; // 不需要中断标志
// interrupt_is_unwanted:不需要中断标志(Worker线程设置,表示不需要更多中断)
// 为什么需要?用于自适应调度(如果工作不足,减少中断频率)
// 类比:就像"不需要中断标志"(如果工作不足,减少中断频率)
/*
* Set to copy of a "generation" counter in main thread so we can sync the interrupts.
*/
// 注释:设置为主线程中"generation"计数器的副本,以便我们可以同步中断。
int interrupt_generation; // 中断生成计数
// interrupt_generation:中断生成计数(用于同步中断)
// 为什么需要?用于同步中断(确保中断的顺序和一致性)
// 类比:就像"中断生成计数"(用于同步中断)
/*
* work in progress data for the pipelined node operation
*/
// 注释:流水线节点操作的处理中数据
vlib_buffer_t *bufs[VLIB_FRAME_SIZE]; // 数据包缓冲区数组
// bufs:数据包缓冲区数组(用于批量处理)
// VLIB_FRAME_SIZE:帧大小(通常256)
// 为什么需要?用于批量处理数据包(提高性能)
// 类比:就像"数据包缓冲区数组"(用于批量处理)
u32 sw_if_indices[VLIB_FRAME_SIZE]; // 接口索引数组
// sw_if_indices:接口索引数组(用于批量处理)
// 类比:就像"通道索引数组"(用于批量处理)
fa_5tuple_t fa_5tuples[VLIB_FRAME_SIZE]; // 5-tuple数组
// fa_5tuples:5-tuple数组(用于批量处理)
// 类比:就像"旅客信息数组"(用于批量处理)
u64 hashes[VLIB_FRAME_SIZE]; // Hash值数组
// hashes:Hash值数组(用于批量处理)
// 类比:就像"Hash值数组"(用于批量处理)
u16 nexts[VLIB_FRAME_SIZE]; // 下一个节点索引数组
// nexts:下一个节点索引数组(用于批量处理)
// 类比:就像"下一个节点索引数组"(用于批量处理)
} acl_fa_per_worker_data_t;
结构体字段总结:
- 会话Pool:存储此Worker线程的所有会话
- 会话变更请求:处理来自其他Worker线程的会话变更请求
- 超时链表:管理不同类型的超时链表(TCP Established、TCP Transient、UDP Idle等)
- 统计信息:按接口索引统计会话添加、删除、Policy Epoch变化等
- 过期会话处理:收集和处理过期会话
- 清理调度:管理清理进程的调度(自适应调整)
- 接口管理:追踪此Worker线程负责的接口
- 中断管理:管理主线程和Worker线程之间的中断同步
- 批量处理数据:用于批量处理数据包(提高性能)
类比:就像"每个安检通道的完整档案系统":
- 档案柜:存储此通道的所有常客档案(会话Pool)
- 请求处理:处理来自其他通道的请求(会话变更请求)
- 待检查列表:管理不同类型的待检查列表(超时链表)
- 统计信息:按通道统计常客添加、删除等(统计信息)
- 过期处理:收集和处理过期常客(过期会话处理)
- 检查调度:管理检查进程的调度(清理调度)
- 通道管理:追踪此通道负责的通道(接口管理)
- 中断管理:管理主管理员和通道之间的中断同步(中断管理)
- 批量处理:用于批量处理旅客(批量处理数据)
12.4 会话表容量管理:如何"管理数据库容量"?
12.4.1 会话表容量参数
会话表的容量由以下参数控制:
c
//241:244:src/plugins/acl/acl.h
/* conn table per-interface conn table parameters */
u32 fa_conn_table_hash_num_buckets; // Hash表桶数量
// fa_conn_table_hash_num_buckets:Hash表桶数量(默认64K)
// 为什么需要?Hash表的桶数量影响Hash冲突率(桶越多,冲突越少)
// 类比:就像"数据库索引的'桶数量'"(影响查找性能)
uword fa_conn_table_hash_memory_size; // Hash表内存大小
// fa_conn_table_hash_memory_size:Hash表内存大小(默认1GB)
// 为什么需要?Hash表需要预先分配内存(避免动态分配)
// 类比:就像"数据库索引的'内存大小'"(影响容量)
u64 fa_conn_table_max_entries; // 最大会话数
// fa_conn_table_max_entries:最大会话数(默认500,000)
// 为什么需要?限制会话表的最大容量(避免内存耗尽)
// 类比:就像"数据库的'最大记录数'"(影响容量)
默认值:
c
//23:25:src/plugins/acl/fa_node.h
#define ACL_FA_CONN_TABLE_DEFAULT_HASH_NUM_BUCKETS (64 * 1024) // 默认Hash表桶数量:64K
#define ACL_FA_CONN_TABLE_DEFAULT_HASH_MEMORY_SIZE (1ULL<<30) // 默认Hash表内存大小:1GB
#define ACL_FA_CONN_TABLE_DEFAULT_MAX_ENTRIES 500000 // 默认最大会话数:500,000
容量计算:
- 每个会话大小 :128字节(
fa_session_t) - 最大会话数:500,000
- 会话Pool总内存:500,000 × 128字节 = 64MB(每个Worker线程)
- Hash表内存:1GB(IPv4和IPv6共享)
类比:就像"数据库容量配置":
- 索引桶数量:64K(影响查找性能)
- 索引内存大小:1GB(影响容量)
- 最大记录数:500,000(影响容量)
12.4.2 容量参数配置
容量参数可以通过CLI命令配置:
c
//2613:2657:src/plugins/acl/acl.c
if (unformat (input, "table"))
{
/* The commands here are for tuning/testing. No user-serviceable parts inside */
// 注释:这里的命令用于调优/测试。内部没有用户可维修的部分。
if (unformat (input, "max-entries"))
{
if (!unformat (input, "%u", &val)) // 解析最大会话数参数
{
error = clib_error_return (0,
"expecting maximum number of entries, got `%U`",
format_unformat_error, input);
goto done;
}
else
{
acl_set_session_max_entries (val); // 设置最大会话数
// acl_set_session_max_entries:设置最大会话数
// 作用:更新最大会话数配置
// 类比:就像"设置数据库的'最大记录数'"
goto done;
}
}
if (unformat (input, "hash-table-buckets"))
{
if (!unformat (input, "%u", &val)) // 解析Hash表桶数量参数
{
error = clib_error_return (0,
"expecting maximum number of hash table buckets, got `%U`",
format_unformat_error, input);
goto done;
}
else
{
am->fa_conn_table_hash_num_buckets = val; // 设置Hash表桶数量
// 类比:就像"设置数据库索引的'桶数量'"
goto done;
}
}
if (unformat (input, "hash-table-memory"))
{
if (!unformat (input, "%U", unformat_memory_size, &memory_size)) // 解析Hash表内存大小参数
{
error = clib_error_return (0,
"expecting maximum amount of hash table memory, got `%U`",
format_unformat_error, input);
goto done;
}
else
{
am->fa_conn_table_hash_memory_size = memory_size; // 设置Hash表内存大小
// 类比:就像"设置数据库索引的'内存大小'"
goto done;
}
}
CLI命令示例:
bash
# 设置最大会话数为1,000,000
set acl plugin session table max-entries 1000000
# 设置Hash表桶数量为128K
set acl plugin session table hash-table-buckets 131072
# 设置Hash表内存大小为2GB
set acl plugin session table hash-table-memory 2G
类比:就像"配置数据库容量":
- 设置最大记录数 :
set database max-entries 1000000 - 设置索引桶数量 :
set database index-buckets 131072 - 设置索引内存大小 :
set database index-memory 2G
12.5 会话表性能优化:如何"优化数据库性能"?
12.5.1 缓存行对齐优化
会话结构体(fa_session_t)设计为128字节,正好是2个缓存行(64字节 × 2):
c
//157:158:src/plugins/acl/fa_node.h
/* Let's try to fit within two cachelines */
CT_ASSERT_EQUAL(fa_session_t_size_is_128, sizeof(fa_session_t), 128); // 编译时断言:会话结构体大小为128字节
// CT_ASSERT_EQUAL:编译时断言(如果条件不满足,编译失败)
// 为什么需要128字节?正好是2个缓存行(现代CPU的缓存行大小通常是64字节)
// 为什么需要缓存行对齐?减少缓存未命中,提高访问速度
// 类比:就像"档案夹大小正好是2个'存储单元'"(减少存储访问次数)
缓存行对齐的优势:
- 减少缓存未命中:结构体对齐到缓存行,减少跨缓存行访问
- 提高访问速度:CPU可以一次性加载整个缓存行
- 减少伪共享:不同线程的会话不会共享同一个缓存行
类比:就像"档案夹大小优化":
- 对齐到存储单元:档案夹大小正好是2个"存储单元"
- 减少访问次数:一次可以加载整个"存储单元"
- 避免冲突:不同通道的档案不会共享同一个"存储单元"
12.5.2 预取优化
在查找会话之前,预取Hash表的bucket和数据:
c
//615:631:src/plugins/acl/session_inlines.h
always_inline void
acl_fa_prefetch_session_bucket_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
if (is_ip6) // 如果是IPv6
{
clib_bihash_prefetch_bucket_40_8 (&am->fa_ip6_sessions_hash, hash); // 预取IPv6会话Hash表的bucket
// clib_bihash_prefetch_bucket_40_8:预取40_8 Hash表的bucket
// 作用:提前加载Hash表的bucket到CPU缓存(减少延迟)
// 类比:就像"提前加载'数据库索引页'到'内存缓存'"
}
else // 如果是IPv4
{
clib_bihash_prefetch_bucket_16_8 (&am->fa_ip4_sessions_hash, hash); // 预取IPv4会话Hash表的bucket
// 类比:就像"提前加载'数据库索引页'到'内存缓存'"
}
}
always_inline void
acl_fa_prefetch_session_data_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
if (is_ip6) // 如果是IPv6
{
clib_bihash_prefetch_data_40_8 (&am->fa_ip6_sessions_hash, hash); // 预取IPv6会话Hash表的数据
// clib_bihash_prefetch_data_40_8:预取40_8 Hash表的数据
// 作用:提前加载Hash表的数据到CPU缓存(减少延迟)
// 类比:就像"提前加载'数据库记录'到'内存缓存'"
}
else // 如果是IPv4
{
clib_bihash_prefetch_data_16_8 (&am->fa_ip4_sessions_hash, hash); // 预取IPv4会话Hash表的数据
// 类比:就像"提前加载'数据库记录'到'内存缓存'"
}
}
预取优化策略:
- Bucket预取:在计算Hash值后,立即预取Hash表的bucket
- 数据预取:在查找bucket后,立即预取Hash表的数据
- 流水线处理:在处理当前数据包时,预取下一个数据包的Hash表数据
类比:就像"数据库查询优化":
- 索引页预取:在计算索引后,立即预取索引页
- 数据页预取:在查找索引后,立即预取数据页
- 流水线查询:在处理当前查询时,预取下一个查询的数据
12.5.3 批量处理优化
数据平面节点使用批量处理来提高性能:
c
//229:234:src/plugins/acl/fa_node.h
/*
* work in progress data for the pipelined node operation
*/
// 注释:流水线节点操作的处理中数据
vlib_buffer_t *bufs[VLIB_FRAME_SIZE]; // 数据包缓冲区数组
// bufs:数据包缓冲区数组(用于批量处理)
// VLIB_FRAME_SIZE:帧大小(通常256)
// 为什么需要?用于批量处理数据包(提高性能)
// 类比:就像"数据包缓冲区数组"(用于批量处理)
u32 sw_if_indices[VLIB_FRAME_SIZE]; // 接口索引数组
// sw_if_indices:接口索引数组(用于批量处理)
// 类比:就像"通道索引数组"(用于批量处理)
fa_5tuple_t fa_5tuples[VLIB_FRAME_SIZE]; // 5-tuple数组
// fa_5tuples:5-tuple数组(用于批量处理)
// 类比:就像"旅客信息数组"(用于批量处理)
u64 hashes[VLIB_FRAME_SIZE]; // Hash值数组
// hashes:Hash值数组(用于批量处理)
// 类比:就像"Hash值数组"(用于批量处理)
u16 nexts[VLIB_FRAME_SIZE]; // 下一个节点索引数组
// nexts:下一个节点索引数组(用于批量处理)
// 类比:就像"下一个节点索引数组"(用于批量处理)
批量处理流程:
- 批量提取:批量提取多个数据包的5-tuple
- 批量Hash:批量计算多个数据包的Hash值
- 批量预取:批量预取多个数据包的Hash表数据
- 批量查找:批量查找多个数据包的会话
性能提升:
- 减少函数调用开销:批量处理减少函数调用次数
- 提高缓存利用率:批量处理提高CPU缓存利用率
- 向量化优化:批量处理可以使用SIMD指令优化
类比:就像"批量处理旅客":
- 批量读取:批量读取多个旅客的身份证号
- 批量查找:批量在数据库中查找多个旅客
- 批量处理:批量处理多个旅客的安检
12.6 会话表监控和调试:如何"监控和调试数据库"?
12.6.1 会话表显示函数:acl_plugin_show_sessions
acl_plugin_show_sessions 用于显示会话表的详细状态信息:
c
//3443:3586:src/plugins/acl/acl.c
static void
acl_plugin_show_sessions (acl_main_t * am,
u32 show_session_thread_id,
u32 show_session_session_index)
{
vlib_main_t *vm = am->vlib_main; // 获取VPP主结构体
// 类比:就像"获取'系统主结构体'"
u16 wk; // Worker线程索引(临时变量)
// 类比:就像"安检通道编号"
vnet_interface_main_t *im = &am->vnet_main->interface_main; // 获取接口主结构体
// 类比:就像"获取'通道主结构体'"
vnet_sw_interface_t *swif; // 接口指针(临时变量)
// 类比:就像"通道指针"
u64 now = clib_cpu_time_now (); // 获取当前时间戳
// 类比:就像"获取当前时间"
u64 clocks_per_second = am->vlib_main->clib_time.clocks_per_second; // 获取每秒时钟周期数
// 类比:就像"获取每秒时间单位数"
// ========== 第一部分:显示全局统计信息 ==========
{
u64 n_adds = am->fa_session_total_adds; // 获取全局会话添加计数
// fa_session_total_adds:全局会话添加计数
// 类比:就像"获取全局'常客添加计数'"
u64 n_dels = am->fa_session_total_dels; // 获取全局会话删除计数
// fa_session_total_dels:全局会话删除计数
// 类比:就像"获取全局'常客删除计数'"
u64 n_deact = am->fa_session_total_deactivations; // 获取全局会话停用计数
// fa_session_total_deactivations:全局会话停用计数
// 类比:就像"获取全局'常客停用计数'"
vlib_cli_output (vm, "Sessions total: add %lu - del %lu = %lu", n_adds,
n_dels, n_adds - n_dels); // 显示会话总数(添加 - 删除)
// 类比:就像"显示'常客总数'(添加 - 删除)"
vlib_cli_output (vm, "Sessions active: add %lu - deact %lu = %lu", n_adds,
n_deact, n_adds - n_deact); // 显示活跃会话数(添加 - 停用)
// 类比:就像"显示'活跃常客数'(添加 - 停用)"
vlib_cli_output (vm, "Sessions being purged: deact %lu - del %lu = %lu",
n_deact, n_dels, n_deact - n_dels); // 显示正在清理的会话数(停用 - 删除)
// 类比:就像"显示'正在清理的常客数'(停用 - 删除)"
}
vlib_cli_output (vm, "now: %lu clocks per second: %lu", now,
clocks_per_second); // 显示当前时间和时钟周期
// 类比:就像"显示当前时间和时间单位"
// ========== 第二部分:显示Per-worker数据 ==========
vlib_cli_output (vm, "\n\nPer-thread data:"); // 显示Per-worker数据标题
// 类比:就像"显示'每个安检通道的数据'"
for (wk = 0; wk < vec_len (am->per_worker_data); wk++) // 遍历所有Worker线程
{
acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk]; // 获取Worker线程的Per-worker数据
// 类比:就像"获取'安检通道'的'数据'"
vlib_cli_output (vm, "Thread #%d:", wk); // 显示Worker线程编号
// 类比:就像"显示'安检通道编号'"
// ========== 显示指定会话的详细信息 ==========
if (show_session_thread_id == wk
&& show_session_session_index < pool_len (pw->fa_sessions_pool)) // 如果指定了会话索引
{
vlib_cli_output (vm, " session index %u:",
show_session_session_index); // 显示会话索引
// 类比:就像"显示'常客索引'"
fa_session_t *sess =
pw->fa_sessions_pool + show_session_session_index; // 获取会话指针
// 类比:就像"获取'常客档案夹'指针"
u64 *m = (u64 *) & sess->info; // 获取5-tuple信息指针(转换为u64指针)
// 类比:就像"获取'常客基本信息'指针"
vlib_cli_output (vm,
" info: %016llx %016llx %016llx %016llx %016llx %016llx",
m[0], m[1], m[2], m[3], m[4], m[5]); // 显示5-tuple信息(6个u64)
// 类比:就像"显示'常客基本信息'(6个u64)"
vlib_cli_output (vm, " sw_if_index: %u", sess->sw_if_index); // 显示接口索引
// 类比:就像"显示'通道编号'"
vlib_cli_output (vm, " tcp_flags_seen: %x",
sess->tcp_flags_seen.as_u16); // 显示TCP标志位
// 类比:就像"显示'访问记录'"
vlib_cli_output (vm, " last active time: %lu",
sess->last_active_time); // 显示最后活跃时间
// 类比:就像"显示'最后访问时间'"
vlib_cli_output (vm, " thread index: %u", sess->thread_index); // 显示线程索引
// 类比:就像"显示'安检通道编号'"
vlib_cli_output (vm, " link enqueue time: %lu",
sess->link_enqueue_time); // 显示链表入队时间
// 类比:就像"显示'加入列表时间'"
vlib_cli_output (vm, " link next index: %u",
sess->link_next_idx); // 显示链表下一个节点索引
// 类比:就像"显示'下一个常客索引'"
vlib_cli_output (vm, " link prev index: %u",
sess->link_prev_idx); // 显示链表前一个节点索引
// 类比:就像"显示'前一个常客索引'"
vlib_cli_output (vm, " link list id: %u", sess->link_list_id); // 显示链表ID
// 类比:就像"显示'列表类型'"
}
// ========== 显示按接口的统计信息 ==========
vlib_cli_output (vm, " connection add/del stats:", wk); // 显示连接添加/删除统计标题
// 类比:就像"显示'常客添加/删除统计'"
pool_foreach (swif, im->sw_interfaces) // 遍历所有接口
{
u32 sw_if_index = swif->sw_if_index; // 获取接口索引
// 类比:就像"获取'通道编号'"
u64 n_adds =
(sw_if_index < vec_len (pw->fa_session_adds_by_sw_if_index) ?
pw->fa_session_adds_by_sw_if_index[sw_if_index] :
0); // 获取接口的会话添加计数
// 类比:就像"获取'通道'的'常客添加计数'"
u64 n_dels =
(sw_if_index < vec_len (pw->fa_session_dels_by_sw_if_index) ?
pw->fa_session_dels_by_sw_if_index[sw_if_index] :
0); // 获取接口的会话删除计数
// 类比:就像"获取'通道'的'常客删除计数'"
u64 n_epoch_changes =
(sw_if_index < vec_len (pw->fa_session_epoch_change_by_sw_if_index) ?
pw->fa_session_epoch_change_by_sw_if_index[sw_if_index] :
0); // 获取接口的Policy Epoch变化计数
// 类比:就像"获取'通道'的'规则版本号变化计数'"
vlib_cli_output (vm,
" sw_if_index %d: add %lu - del %lu = %lu; epoch chg: %lu",
sw_if_index,
n_adds,
n_dels,
n_adds - n_dels, // 当前会话数(添加 - 删除)
n_epoch_changes); // Policy Epoch变化次数
// 类比:就像"显示'通道'的统计信息"
}
// ========== 显示超时链表信息 ==========
vlib_cli_output (vm, " connection timeout type lists:", wk); // 显示连接超时类型列表标题
// 类比:就像"显示'待检查列表'"
u8 tt = 0; // 超时类型索引(临时变量)
// 类比:就像"列表类型编号"
for (tt = 0; tt < ACL_N_TIMEOUTS; tt++) // 遍历所有超时类型
{
u32 head_session_index = pw->fa_conn_list_head[tt]; // 获取链表头节点索引
// 类比:就像"获取'列表头'索引"
vlib_cli_output (vm, " fa_conn_list_head[%d]: %d", tt,
head_session_index); // 显示链表头节点索引
// 类比:就像"显示'列表头'索引"
if (~0 != head_session_index) // 如果链表不为空
{
fa_session_t *sess = pw->fa_sessions_pool + head_session_index; // 获取链表头节点会话指针
// 类比:就像"获取'列表头'的'常客档案夹'指针"
vlib_cli_output (vm, " last active time: %lu",
sess->last_active_time); // 显示最后活跃时间
// 类比:就像"显示'最后访问时间'"
vlib_cli_output (vm, " link enqueue time: %lu",
sess->link_enqueue_time); // 显示链表入队时间
// 类比:就像"显示'加入列表时间'"
}
}
// ========== 显示清理调度信息 ==========
vlib_cli_output (vm, " Next expiry time: %lu", pw->next_expiry_time); // 显示下次过期时间
// 类比:就像"显示'下次检查时间'"
vlib_cli_output (vm, " Requeue until time: %lu",
pw->requeue_until_time); // 显示重新入队截止时间
// 类比:就像"显示'重新入队截止时间'"
vlib_cli_output (vm, " Current time wait interval: %lu",
pw->current_time_wait_interval); // 显示当前等待间隔时间
// 类比:就像"显示'当前等待间隔时间'"
// ========== 显示统计计数器 ==========
vlib_cli_output (vm, " Count of deleted sessions: %lu",
pw->cnt_deleted_sessions); // 显示删除的会话计数
// 类比:就像"显示'删除的常客计数'"
vlib_cli_output (vm, " Delete already deleted: %lu",
pw->cnt_already_deleted_sessions); // 显示已删除会话的重复删除计数
// 类比:就像"显示'已删除常客的重复删除计数'"
vlib_cli_output (vm, " Session timers restarted: %lu",
pw->cnt_session_timer_restarted); // 显示会话定时器重启计数
// 类比:就像"显示'常客定时器重启计数'"
vlib_cli_output (vm, " Swipe until this time: %lu",
pw->swipe_end_time); // 显示清理截止时间
// 类比:就像"显示'清理截止时间'"
// ========== 显示接口位图 ==========
vlib_cli_output (vm, " sw_if_index serviced bitmap: %U",
format_bitmap_hex, pw->serviced_sw_if_index_bitmap); // 显示服务的接口位图
// format_bitmap_hex:格式化位图为十六进制字符串
// 类比:就像"显示'服务的通道位图'"
vlib_cli_output (vm, " pending clear intfc bitmap : %U",
format_bitmap_hex,
pw->pending_clear_sw_if_index_bitmap); // 显示待清理接口位图
// 类比:就像"显示'待清理通道位图'"
// ========== 显示清理状态 ==========
vlib_cli_output (vm, " clear in progress: %u", pw->clear_in_process); // 显示清理进行中标志
// 类比:就像"显示'清理进行中标志'"
// ========== 显示中断状态 ==========
vlib_cli_output (vm, " interrupt is pending: %d",
pw->interrupt_is_pending); // 显示中断挂起标志
// 类比:就像"显示'中断挂起标志'"
vlib_cli_output (vm, " interrupt is needed: %d",
pw->interrupt_is_needed); // 显示需要中断标志
// 类比:就像"显示'需要中断标志'"
vlib_cli_output (vm, " interrupt is unwanted: %d",
pw->interrupt_is_unwanted); // 显示不需要中断标志
// 类比:就像"显示'不需要中断标志'"
vlib_cli_output (vm, " interrupt generation: %d",
pw->interrupt_generation); // 显示中断生成计数
// 类比:就像"显示'中断生成计数'"
// ========== 显示会话变更请求统计 ==========
vlib_cli_output (vm, " received session change requests: %d",
pw->rcvd_session_change_requests); // 显示接收的会话变更请求计数
// 类比:就像"显示'接收的请求总数'"
vlib_cli_output (vm, " sent session change requests: %d",
pw->sent_session_change_requests); // 显示发送的会话变更请求计数
// 类比:就像"显示'发送的请求总数'"
}
// ========== 第三部分:显示清理进程计数器 ==========
vlib_cli_output (vm, "\n\nConn cleaner thread counters:"); // 显示清理进程计数器标题
// 类比:就像"显示'清理进程计数器'"
#define _(cnt, desc) vlib_cli_output(vm, " %20lu: %s", am->cnt, desc); // 宏定义:显示计数器
// 类比:就像"显示计数器"
foreach_fa_cleaner_counter; // 遍历所有清理进程计数器(通过宏展开)
// 包括:fa_cleaner_cnt_delete_by_sw_index、fa_cleaner_cnt_unknown_event等
// 类比:就像"遍历所有'清理进程计数器'"
#undef _
vlib_cli_output (vm, "Interrupt generation: %d",
am->fa_interrupt_generation); // 显示中断生成计数
// 类比:就像"显示'中断生成计数'"
vlib_cli_output (vm,
"Sessions per interval: min %lu max %lu increment: %f ms current: %f ms",
am->fa_min_deleted_sessions_per_interval, // 最小删除会话数
am->fa_max_deleted_sessions_per_interval, // 最大删除会话数
am->fa_cleaner_wait_time_increment * 1000.0, // 等待时间增量(转换为毫秒)
((f64) am->fa_current_cleaner_timer_wait_interval) *
1000.0 / (f64) vm->clib_time.clocks_per_second); // 当前等待间隔(转换为毫秒)
// 类比:就像"显示'清理调度参数'"
vlib_cli_output (vm, "Reclassify sessions: %d", am->reclassify_sessions); // 显示会话重分类标志
// reclassify_sessions:会话重分类标志(是否启用Policy Epoch检查)
// 类比:就像"显示'会话重分类标志'"
}
函数总结:
这个函数显示了会话表的完整状态信息:
- 全局统计信息:会话总数、活跃会话数、正在清理的会话数
- Per-worker数据 :
- 指定会话的详细信息(如果指定了会话索引)
- 按接口的统计信息(添加、删除、Policy Epoch变化)
- 超时链表信息(每个超时类型的链表头)
- 清理调度信息(下次过期时间、等待间隔等)
- 统计计数器(删除计数、定时器重启计数等)
- 接口位图(服务的接口、待清理的接口)
- 清理状态(清理进行中标志)
- 中断状态(中断挂起、需要、不需要标志)
- 会话变更请求统计(接收、发送计数)
- 清理进程计数器:清理进程的各种计数器
- 清理调度参数:最小/最大删除会话数、等待时间增量、当前等待间隔
- 会话重分类标志:是否启用Policy Epoch检查
类比:就像完整的"数据库状态报告":
- 全局统计:总记录数、活跃记录数、正在清理的记录数
- 每个通道的数据 :
- 指定记录的详细信息
- 按通道的统计信息
- 待检查列表信息
- 检查调度信息
- 统计计数器
- 通道位图
- 检查状态
- 中断状态
- 请求统计
- 清理进程计数器:清理进程的各种计数器
- 清理调度参数:清理调度参数
- 记录重分类标志:是否启用记录重分类
12.6.2 Hash表显示函数:show_fa_sessions_hash
show_fa_sessions_hash 用于显示会话Hash表的详细信息:
c
//908:920:src/plugins/acl/sess_mgmt_node.c
void
show_fa_sessions_hash (vlib_main_t * vm, u32 verbose)
{
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
vlib_cli_output (vm, "\n\nIPv6 sessions hash table:"); // 显示IPv6会话Hash表标题
// 类比:就像"显示'IPv6常客数据库'标题"
vlib_cli_output (vm, "%U",
format_bihash_40_8, &am->fa_ip6_sessions_hash,
verbose); // 显示IPv6会话Hash表详细信息
// format_bihash_40_8:格式化40_8双向Hash表(用于调试输出)
// verbose:详细程度(0=简要,非0=详细)
// 类比:就像"显示'IPv6常客数据库'详细信息"
vlib_cli_output (vm, "\n\nIPv4 sessions hash table:"); // 显示IPv4会话Hash表标题
// 类比:就像"显示'IPv4常客数据库'标题"
vlib_cli_output (vm, "%U",
format_bihash_16_8, &am->fa_ip4_sessions_hash,
verbose); // 显示IPv4会话Hash表详细信息
// format_bihash_16_8:格式化16_8双向Hash表(用于调试输出)
// 类比:就像"显示'IPv4常客数据库'详细信息"
}
函数总结:
- 显示IPv6会话Hash表:显示IPv6会话Hash表的详细信息(bucket数量、条目数量、冲突情况等)
- 显示IPv4会话Hash表:显示IPv4会话Hash表的详细信息
输出信息:
- Bucket数量:Hash表的bucket数量
- 条目数量:Hash表中的条目数量
- 冲突情况:Hash冲突的统计信息
- 内存使用:Hash表的内存使用情况
类比:就像"显示数据库索引信息":
- 索引桶数量:索引的桶数量
- 索引条目数量:索引中的条目数量
- 冲突情况:索引冲突的统计信息
- 内存使用:索引的内存使用情况
12.7 会话表清理机制:如何"定期清理过期记录"?
12.7.1 清理进程参数
清理进程使用自适应调度机制,根据负载动态调整清理频率:
c
//249:268:src/plugins/acl/acl.h
/*
* If the cleaner has to delete more than this number
* of connections, it halves the sleep time.
*/
// 注释:如果清理进程需要删除超过此数量的连接,它将睡眠时间减半。
#define ACL_FA_DEFAULT_MAX_DELETED_SESSIONS_PER_INTERVAL 100 // 默认最大删除会话数:100
u64 fa_max_deleted_sessions_per_interval; // 最大删除会话数
// fa_max_deleted_sessions_per_interval:每次清理的最大删除会话数
// 为什么需要?限制每次清理的会话数,避免一次性清理太多(影响性能)
// 类比:就像"每次检查的最大删除常客数"(避免一次性删除太多)
/*
* If the cleaner deletes less than these connections,
* it increases the wait time by the "increment"
*/
// 注释:如果清理进程删除的连接少于这些,它将等待时间增加"增量"。
#define ACL_FA_DEFAULT_MIN_DELETED_SESSIONS_PER_INTERVAL 1 // 默认最小删除会话数:1
u64 fa_min_deleted_sessions_per_interval; // 最小删除会话数
// fa_min_deleted_sessions_per_interval:每次清理的最小删除会话数
// 为什么需要?如果删除的会话数少于最小值,增加等待时间(减少清理频率)
// 类比:就像"每次检查的最小删除常客数"(如果删除太少,减少检查频率)
#define ACL_FA_DEFAULT_CLEANER_WAIT_TIME_INCREMENT 0.1 // 默认等待时间增量:0.1秒
f64 fa_cleaner_wait_time_increment; // 等待时间增量
// fa_cleaner_wait_time_increment:等待时间增量(秒)
// 为什么需要?用于自适应调整清理频率(根据负载动态调整)
// 类比:就像"等待时间增量"(用于自适应调整检查频率)
u64 fa_current_cleaner_timer_wait_interval; // 当前清理定时器等待间隔
// fa_current_cleaner_timer_wait_interval:当前清理定时器等待间隔(时钟周期)
// 为什么需要?用于自适应调整清理频率(根据负载动态调整)
// 类比:就像"当前检查定时器等待间隔"(用于自适应调整检查频率)
自适应调度机制:
- 如果删除的会话数 > 最大删除数:将等待时间减半(增加清理频率)
- 如果删除的会话数 < 最小删除数:将等待时间增加增量(减少清理频率)
类比:就像"自适应检查机制":
- 如果删除的常客数 > 最大删除数:将检查间隔减半(增加检查频率)
- 如果删除的常客数 < 最小删除数:将检查间隔增加增量(减少检查频率)
12.7.2 清理进程计数器
清理进程使用多个计数器来统计清理操作:
c
//281:292:src/plugins/acl/acl.h
#define foreach_fa_cleaner_counter \
_(fa_cleaner_cnt_delete_by_sw_index, "delete_by_sw_index events") // 按接口删除事件计数
// fa_cleaner_cnt_delete_by_sw_index:按接口删除事件计数
// 为什么需要?统计按接口删除会话的事件数
// 类比:就像"按通道删除事件计数"
_(fa_cleaner_cnt_delete_by_sw_index_ok, "delete_by_sw_index handled ok") // 按接口删除成功计数
// fa_cleaner_cnt_delete_by_sw_index_ok:按接口删除成功计数
// 类比:就像"按通道删除成功计数"
_(fa_cleaner_cnt_unknown_event, "unknown events received") // 未知事件计数
// fa_cleaner_cnt_unknown_event:未知事件计数
// 为什么需要?统计未知事件数(用于检测bug)
// 类比:就像"未知事件计数"(用于检测bug)
_(fa_cleaner_cnt_timer_restarted, "session idle timers restarted") // 会话空闲定时器重启计数
// fa_cleaner_cnt_timer_restarted:会话空闲定时器重启计数
// 为什么需要?统计会话定时器重启次数(用于性能分析)
// 类比:就像"常客空闲定时器重启计数"
_(fa_cleaner_cnt_wait_with_timeout, "event wait with timeout called") // 带超时的等待计数
// fa_cleaner_cnt_wait_with_timeout:带超时的等待计数
// 为什么需要?统计带超时的等待次数(用于性能分析)
// 类比:就像"带超时的等待计数"
_(fa_cleaner_cnt_wait_without_timeout, "event wait w/o timeout called") // 不带超时的等待计数
// fa_cleaner_cnt_wait_without_timeout:不带超时的等待计数
// 为什么需要?统计不带超时的等待次数(用于性能分析)
// 类比:就像"不带超时的等待计数"
_(fa_cleaner_cnt_event_cycles, "total event cycles") // 总事件周期计数
// fa_cleaner_cnt_event_cycles:总事件周期计数
// 为什么需要?统计总事件周期数(用于性能分析)
// 类比:就像"总事件周期计数"
/* end of counters */
#define _(id, desc) u32 id; // 宏定义:定义计数器变量
foreach_fa_cleaner_counter // 遍历所有计数器(通过宏展开)
#undef _
计数器用途:
- 性能分析:统计清理操作的频率和性能
- Bug检测:检测未知事件和异常情况
- 监控:监控清理进程的健康状态
类比:就像"清理进程的统计信息":
- 性能分析:统计清理操作的频率和性能
- Bug检测:检测未知事件和异常情况
- 监控:监控清理进程的健康状态
12.8 会话表容量管理:如何"管理数据库容量"?
12.8.1 最大会话数设置函数:acl_set_session_max_entries
acl_set_session_max_entries 用于设置最大会话数:
c
//2494:2499:src/plugins/acl/acl.c
static void
acl_set_session_max_entries (u32 value)
{
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
am->fa_conn_table_max_entries = value; // 设置最大会话数
// fa_conn_table_max_entries:最大会话数(每个Worker线程)
// 为什么需要?限制会话表的最大容量(避免内存耗尽)
// 类比:就像"设置数据库的'最大记录数'"(限制容量)
}
函数总结:
- 获取ACL主结构体:获取ACL插件的全局状态
- 设置最大会话数:更新最大会话数配置
注意事项:
- 每个Worker线程:最大会话数是每个Worker线程的限制,不是全局限制
- 总容量 :如果有N个Worker线程,总容量 = N ×
fa_conn_table_max_entries - 内存计算 :每个会话128字节,总内存 = N ×
fa_conn_table_max_entries× 128字节
类比:就像"设置数据库的最大记录数":
- 每个通道:最大记录数是每个"安检通道"的限制
- 总容量:如果有N个"安检通道",总容量 = N × "最大记录数"
- 内存计算:每个记录128字节,总内存 = N × "最大记录数" × 128字节
12.8.2 容量参数配置
容量参数可以通过CLI命令配置:
c
//2613:2657:src/plugins/acl/acl.c
if (unformat (input, "table"))
{
/* The commands here are for tuning/testing. No user-serviceable parts inside */
// 注释:这里的命令用于调优/测试。内部没有用户可维修的部分。
if (unformat (input, "max-entries"))
{
if (!unformat (input, "%u", &val)) // 解析最大会话数参数
{
error = clib_error_return (0,
"expecting maximum number of entries, got `%U`",
format_unformat_error, input);
goto done;
}
else
{
acl_set_session_max_entries (val); // 设置最大会话数
// acl_set_session_max_entries:设置最大会话数(我们上面已经详细讲解过)
// 类比:就像"设置数据库的'最大记录数'"
goto done;
}
}
if (unformat (input, "hash-table-buckets"))
{
if (!unformat (input, "%u", &val)) // 解析Hash表桶数量参数
{
error = clib_error_return (0,
"expecting maximum number of hash table buckets, got `%U`",
format_unformat_error, input);
goto done;
}
else
{
am->fa_conn_table_hash_num_buckets = val; // 设置Hash表桶数量
// fa_conn_table_hash_num_buckets:Hash表桶数量
// 为什么需要?Hash表的桶数量影响Hash冲突率(桶越多,冲突越少)
// 类比:就像"设置数据库索引的'桶数量'"
goto done;
}
}
if (unformat (input, "hash-table-memory"))
{
if (!unformat (input, "%U", unformat_memory_size, &memory_size)) // 解析Hash表内存大小参数
{
error = clib_error_return (0,
"expecting maximum amount of hash table memory, got `%U`",
format_unformat_error, input);
goto done;
}
else
{
am->fa_conn_table_hash_memory_size = memory_size; // 设置Hash表内存大小
// fa_conn_table_hash_memory_size:Hash表内存大小
// 为什么需要?Hash表需要预先分配内存(避免动态分配)
// 类比:就像"设置数据库索引的'内存大小'"
goto done;
}
}
CLI命令示例:
bash
# 设置最大会话数为1,000,000
set acl plugin session table max-entries 1000000
# 设置Hash表桶数量为128K
set acl plugin session table hash-table-buckets 131072
# 设置Hash表内存大小为2GB
set acl plugin session table hash-table-memory 2G
类比:就像"配置数据库容量":
- 设置最大记录数 :
set database max-entries 1000000 - 设置索引桶数量 :
set database index-buckets 131072 - 设置索引内存大小 :
set database index-memory 2G
12.9 会话表性能优化:如何"优化数据库性能"?
12.9.1 缓存行对齐优化
会话结构体(fa_session_t)设计为128字节,正好是2个缓存行(64字节 × 2):
c
//157:158:src/plugins/acl/fa_node.h
/* Let's try to fit within two cachelines */
CT_ASSERT_EQUAL(fa_session_t_size_is_128, sizeof(fa_session_t), 128); // 编译时断言:会话结构体大小为128字节
// CT_ASSERT_EQUAL:编译时断言(如果条件不满足,编译失败)
// 为什么需要128字节?正好是2个缓存行(现代CPU的缓存行大小通常是64字节)
// 为什么需要缓存行对齐?减少缓存未命中,提高访问速度
// 类比:就像"档案夹大小正好是2个'存储单元'"(减少存储访问次数)
缓存行对齐的优势:
- 减少缓存未命中:结构体对齐到缓存行,减少跨缓存行访问
- 提高访问速度:CPU可以一次性加载整个缓存行
- 减少伪共享:不同线程的会话不会共享同一个缓存行
类比:就像"档案夹大小优化":
- 对齐到存储单元:档案夹大小正好是2个"存储单元"
- 减少访问次数:一次可以加载整个"存储单元"
- 避免冲突:不同通道的档案不会共享同一个"存储单元"
12.9.2 预取优化
在查找会话之前,预取Hash表的bucket和数据:
c
//615:631:src/plugins/acl/session_inlines.h
always_inline void
acl_fa_prefetch_session_bucket_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
if (is_ip6) // 如果是IPv6
{
clib_bihash_prefetch_bucket_40_8 (&am->fa_ip6_sessions_hash, hash); // 预取IPv6会话Hash表的bucket
// clib_bihash_prefetch_bucket_40_8:预取40_8 Hash表的bucket
// 作用:提前加载Hash表的bucket到CPU缓存(减少延迟)
// 类比:就像"提前加载'数据库索引页'到'内存缓存'"
}
else // 如果是IPv4
{
clib_bihash_prefetch_bucket_16_8 (&am->fa_ip4_sessions_hash, hash); // 预取IPv4会话Hash表的bucket
// 类比:就像"提前加载'数据库索引页'到'内存缓存'"
}
}
always_inline void
acl_fa_prefetch_session_data_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
if (is_ip6) // 如果是IPv6
{
clib_bihash_prefetch_data_40_8 (&am->fa_ip6_sessions_hash, hash); // 预取IPv6会话Hash表的数据
// clib_bihash_prefetch_data_40_8:预取40_8 Hash表的数据
// 作用:提前加载Hash表的数据到CPU缓存(减少延迟)
// 类比:就像"提前加载'数据库记录'到'内存缓存'"
}
else // 如果是IPv4
{
clib_bihash_prefetch_data_16_8 (&am->fa_ip4_sessions_hash, hash); // 预取IPv4会话Hash表的数据
// 类比:就像"提前加载'数据库记录'到'内存缓存'"
}
}
预取优化策略:
- Bucket预取:在计算Hash值后,立即预取Hash表的bucket
- 数据预取:在查找bucket后,立即预取Hash表的数据
- 流水线处理:在处理当前数据包时,预取下一个数据包的Hash表数据
类比:就像"数据库查询优化":
- 索引页预取:在计算索引后,立即预取索引页
- 数据页预取:在查找索引后,立即预取数据页
- 流水线查询:在处理当前查询时,预取下一个查询的数据
12.9.3 批量处理优化
数据平面节点使用批量处理来提高性能:
c
//229:234:src/plugins/acl/fa_node.h
/*
* work in progress data for the pipelined node operation
*/
// 注释:流水线节点操作的处理中数据
vlib_buffer_t *bufs[VLIB_FRAME_SIZE]; // 数据包缓冲区数组
// bufs:数据包缓冲区数组(用于批量处理)
// VLIB_FRAME_SIZE:帧大小(通常256)
// 为什么需要?用于批量处理数据包(提高性能)
// 类比:就像"数据包缓冲区数组"(用于批量处理)
u32 sw_if_indices[VLIB_FRAME_SIZE]; // 接口索引数组
// sw_if_indices:接口索引数组(用于批量处理)
// 类比:就像"通道索引数组"(用于批量处理)
fa_5tuple_t fa_5tuples[VLIB_FRAME_SIZE]; // 5-tuple数组
// fa_5tuples:5-tuple数组(用于批量处理)
// 类比:就像"旅客信息数组"(用于批量处理)
u64 hashes[VLIB_FRAME_SIZE]; // Hash值数组
// hashes:Hash值数组(用于批量处理)
// 类比:就像"Hash值数组"(用于批量处理)
u16 nexts[VLIB_FRAME_SIZE]; // 下一个节点索引数组
// nexts:下一个节点索引数组(用于批量处理)
// 类比:就像"下一个节点索引数组"(用于批量处理)
批量处理流程:
- 批量提取:批量提取多个数据包的5-tuple
- 批量Hash:批量计算多个数据包的Hash值
- 批量预取:批量预取多个数据包的Hash表数据
- 批量查找:批量查找多个数据包的会话
性能提升:
- 减少函数调用开销:批量处理减少函数调用次数
- 提高缓存利用率:批量处理提高CPU缓存利用率
- 向量化优化:批量处理可以使用SIMD指令优化
类比:就像"批量处理旅客":
- 批量读取:批量读取多个旅客的身份证号
- 批量查找:批量在数据库中查找多个旅客
- 批量处理:批量处理多个旅客的安检
12.10 会话表监控和调试:如何"监控和调试数据库"?
12.10.1 会话表显示函数:acl_plugin_show_sessions
acl_plugin_show_sessions 用于显示会话表的详细状态信息(我们已经在12.6.1节详细讲解过,这里不再重复)。
12.10.2 Hash表显示函数:show_fa_sessions_hash
show_fa_sessions_hash 用于显示会话Hash表的详细信息(我们已经在12.6.2节详细讲解过,这里不再重复)。
12.11 会话表CLI命令:如何"使用命令行管理数据库"?
12.11.1 会话表显示命令:show acl-plugin sessions
show acl-plugin sessions 命令用于显示会话表的详细状态(我们已经在12.9.1节详细讲解过,这里不再重复)。
12.11.2 会话表配置命令:set acl plugin session
set acl plugin session 命令用于配置会话表参数(我们已经在12.9.2节详细讲解过,这里不再重复)。
12.12 会话表初始化时机:何时"建立数据库"?
12.12.1 延迟初始化机制
会话表采用延迟初始化机制,只有在需要时才初始化:
c
//84:121:src/plugins/acl/sess_mgmt_node.c
static void
acl_fa_verify_init_sessions (acl_main_t * am)
{
if (!am->fa_sessions_hash_is_initialized) // 如果会话Hash表未初始化
{
// ... 初始化代码(我们已经在12.2节详细讲解过)...
}
}
延迟初始化的原因:
- 节省内存:如果不需要Flow-aware ACL,不初始化会话表(节省内存)
- 按需分配:只有在第一次使用Flow-aware ACL时才初始化
- 性能优化:避免不必要的初始化开销
初始化时机:
- 第一次启用Flow-aware ACL:当第一次在接口上启用Flow-aware ACL时
- 第一次创建会话:当第一次创建会话时(如果Hash表未初始化)
类比:就像"按需建立数据库":
- 节省资源:如果不需要"常客系统",不建立"数据库"(节省资源)
- 按需分配:只有在第一次使用"常客系统"时才建立"数据库"
- 性能优化:避免不必要的建立开销
12.13 会话表容量限制:如何"防止数据库溢出"?
12.13.1 会话创建容量检查:acl_fa_can_add_session
在创建新会话之前,需要检查是否可以创建(是否超过最大会话数):
c
//465:475:src/plugins/acl/session_inlines.h
always_inline int
acl_fa_can_add_session (acl_main_t * am, int is_input, u32 sw_if_index)
{
u64 curr_sess_count; // 当前会话数量(临时变量)
// 类比:就像"当前常客数量"
curr_sess_count = am->fa_session_total_adds - am->fa_session_total_dels; // 计算当前会话数量
// fa_session_total_adds:全局会话添加计数
// fa_session_total_dels:全局会话删除计数
// 当前会话数量 = 添加计数 - 删除计数
// 为什么使用全局计数?简化计算,避免遍历所有Worker线程
// 类比:就像"计算'当前常客数量' = '添加计数' - '删除计数'"
return (curr_sess_count < am->fa_conn_table_max_entries); // 检查是否小于最大会话数
// fa_conn_table_max_entries:最大会话数(每个Worker线程)
// 为什么使用全局计数与每个Worker的限制比较?简化实现,实际限制是每个Worker的限制
// 类比:就像"检查'当前常客数量'是否小于'最大常客数'"
}
函数总结:
- 计算当前会话数:当前会话数 = 添加计数 - 删除计数
- 检查限制:检查当前会话数是否小于最大会话数
容量限制说明:
- 全局计数 vs 每个Worker限制:使用全局计数与每个Worker的限制比较,简化实现
- 实际限制:实际限制是每个Worker线程的限制,不是全局限制
- 总容量 :如果有N个Worker线程,总容量 = N ×
fa_conn_table_max_entries
类比:就像"检查数据库容量":
- 计算当前记录数:当前记录数 = 添加计数 - 删除计数
- 检查限制:检查当前记录数是否小于最大记录数
12.13.2 会话回收机制:acl_fa_try_recycle_session
如果当前会话数已达到上限,需要尝试回收一些会话(删除最老的会话):
c
//475:511:src/plugins/acl/session_inlines.h
always_inline int
acl_fa_try_recycle_session (acl_main_t * am,
int is_input, u16 thread_index,
u32 sw_if_index, u64 now)
{
acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index]; // 获取线程的Per-worker数据
// 类比:就像"获取'安检通道'的'数据'"
fa_full_session_id_t sess_id; // 会话ID(临时变量)
sess_id.thread_index = thread_index; // 设置会话ID的线程索引
// ========== 第一部分:尝试从Purgatory链表中回收 ==========
sess_id.session_index = pw->fa_conn_list_head[ACL_TIMEOUT_PURGATORY]; // 获取Purgatory链表头节点索引
// ACL_TIMEOUT_PURGATORY:Purgatory链表ID(待删除会话)
// 类比:就像"获取'待删除列表'的'列表头'"
while ((FA_SESSION_BOGUS_INDEX != sess_id.session_index) // 如果链表不为空
&& (pw->fa_conn_list_head[ACL_TIMEOUT_PURGATORY] != FA_SESSION_BOGUS_INDEX)) // 且链表头节点有效
{
fa_session_t *sess = // 获取会话结构体指针
get_session_ptr (am, thread_index, sess_id.session_index); // 从pool中获取会话指针
// 类比:就像"获取'待删除常客'的'档案夹'"
if (sess->link_enqueue_time + fa_session_get_timeout (am, sess) < now) // 如果会话已过期
{
acl_fa_conn_list_delete_session (am, sess_id, now); // 从链表中删除会话
acl_fa_put_session (am, sw_if_index, sess_id); // 释放会话内存
// acl_fa_put_session:释放会话内存(我们第9章已经详细讲解过)
// 类比:就像"如果'待删除常客'已过期,删除并释放'档案夹'"
}
sess_id.session_index = pw->fa_conn_list_head[ACL_TIMEOUT_PURGATORY]; // 获取下一个节点索引
// 类比:就像"获取'下一个待删除常客'"
}
// ========== 第二部分:尝试从TCP Transient链表中回收 ==========
sess_id.session_index = pw->fa_conn_list_head[ACL_TIMEOUT_TCP_TRANSIENT]; // 获取TCP Transient链表头节点索引
// ACL_TIMEOUT_TCP_TRANSIENT:TCP Transient链表ID(未建立连接的TCP会话)
// 类比:就像"获取'TCP未建立连接列表'的'列表头'"
if (FA_SESSION_BOGUS_INDEX != sess_id.session_index) // 如果链表不为空
{
acl_fa_conn_list_delete_session (am, sess_id, now); // 从链表中删除会话
acl_fa_deactivate_session (am, sw_if_index, sess_id); // 停用会话(从Hash表移除)
acl_fa_conn_list_add_session (am, sess_id, now); // 添加到Purgatory链表
// 为什么需要?TCP Transient会话优先级较低,可以回收
// 类比:就像"如果'TCP未建立连接常客'存在,删除并添加到'待删除列表'"
}
return 1; // 返回成功
// 类比:就像"返回'成功'"
}
函数总结:
- 回收Purgatory会话:尝试从Purgatory链表中回收已过期的会话
- 回收TCP Transient会话:如果Purgatory链表为空,尝试回收TCP Transient会话(优先级较低)
回收策略:
- 优先级1:Purgatory会话(已标记为删除,可以立即回收)
- 优先级2:TCP Transient会话(未建立连接的TCP会话,优先级较低)
类比:就像"回收常客档案":
- 优先级1:待删除常客(已标记为删除,可以立即回收)
- 优先级2:未建立连接常客(优先级较低,可以回收)
12.14 会话表性能调优:如何"优化数据库性能"?
12.14.1 Hash表参数调优
Hash表的性能主要受以下参数影响:
-
Hash表桶数量 (
fa_conn_table_hash_num_buckets):- 默认值:64K
- 影响:桶越多,Hash冲突越少,查找性能越好,但内存占用越大
- 调优建议 :
- 低冲突率:如果Hash冲突少,可以减少桶数量(节省内存)
- 高冲突率:如果Hash冲突多,可以增加桶数量(提高性能)
-
Hash表内存大小 (
fa_conn_table_hash_memory_size):- 默认值:1GB
- 影响:内存越大,可以存储更多会话,但内存占用越大
- 调优建议 :
- 高流量场景:如果流量大,可以增加内存大小(支持更多会话)
- 低流量场景:如果流量小,可以减少内存大小(节省内存)
-
最大会话数 (
fa_conn_table_max_entries):- 默认值:500,000(每个Worker线程)
- 影响:限制会话表的最大容量
- 调优建议 :
- 高连接数场景:如果连接数多,可以增加最大会话数
- 低连接数场景:如果连接数少,可以减少最大会话数(节省内存)
调优示例:
bash
# 高流量场景:增加Hash表桶数量和内存大小
set acl plugin session table hash-table-buckets 131072 # 128K桶
set acl plugin session table hash-table-memory 2G # 2GB内存
set acl plugin session table max-entries 1000000 # 1M会话(每个Worker)
# 低流量场景:减少Hash表桶数量和内存大小
set acl plugin session table hash-table-buckets 32768 # 32K桶
set acl plugin session table hash-table-memory 512M # 512MB内存
set acl plugin session table max-entries 100000 # 100K会话(每个Worker)
类比:就像"数据库性能调优":
- 高流量场景:增加索引桶数量和内存大小,增加最大记录数
- 低流量场景:减少索引桶数量和内存大小,减少最大记录数
12.14.2 清理进程参数调优
清理进程的性能主要受以下参数影响:
-
最大删除会话数 (
fa_max_deleted_sessions_per_interval):- 默认值:100
- 影响:限制每次清理的会话数,避免一次性清理太多(影响性能)
- 调优建议 :
- 高流量场景:如果流量大,可以增加最大删除数(加快清理速度)
- 低流量场景:如果流量小,可以减少最大删除数(减少CPU占用)
-
最小删除会话数 (
fa_min_deleted_sessions_per_interval):- 默认值:1
- 影响:如果删除的会话数少于最小值,增加等待时间(减少清理频率)
- 调优建议 :
- 高流量场景:可以增加最小值(保持较高的清理频率)
- 低流量场景:可以减少最小值(降低清理频率)
-
等待时间增量 (
fa_cleaner_wait_time_increment):- 默认值:0.1秒
- 影响:用于自适应调整清理频率
- 调优建议 :
- 高流量场景:可以减少增量(更快响应负载变化)
- 低流量场景:可以增加增量(更慢响应负载变化)
调优示例:
bash
# 高流量场景:增加最大删除数,减少等待时间增量
set acl plugin session table max-deleted-per-interval 200
set acl plugin session table wait-time-increment 0.05
# 低流量场景:减少最大删除数,增加等待时间增量
set acl plugin session table max-deleted-per-interval 50
set acl plugin session table wait-time-increment 0.2
类比:就像"清理进程性能调优":
- 高流量场景:增加最大删除数,减少等待时间增量
- 低流量场景:减少最大删除数,增加等待时间增量
12.15 会话表监控指标:如何"监控数据库健康状态"?
12.15.1 关键监控指标
会话表的关键监控指标包括:
-
会话总数 :
fa_session_total_adds - fa_session_total_dels- 含义:当前活跃的会话总数
- 监控:如果接近最大会话数,需要关注容量问题
- 类比:就像"当前活跃的常客总数"(如果接近最大常客数,需要关注容量问题)
-
会话添加速率 :
fa_session_total_adds的变化速率- 含义:每秒添加的会话数
- 监控:如果添加速率过高,可能需要增加容量
- 类比:就像"每秒添加的常客数"(如果添加速率过高,可能需要增加容量)
-
会话删除速率 :
fa_session_total_dels的变化速率- 含义:每秒删除的会话数
- 监控:如果删除速率过低,可能需要检查清理进程
- 类比:就像"每秒删除的常客数"(如果删除速率过低,可能需要检查清理进程)
-
Hash冲突率 :通过
show acl-plugin sessions查看- 含义:Hash冲突的比率
- 监控:如果冲突率过高,可能需要增加Hash表桶数量
- 类比:就像"索引冲突率"(如果冲突率过高,可能需要增加索引桶数量)
-
清理进程性能 :通过
show acl-plugin sessions查看清理进程计数器- 含义:清理进程的各种计数器
- 监控:如果清理进程性能不佳,可能需要调整清理参数
- 类比:就像"清理进程性能"(如果清理进程性能不佳,可能需要调整清理参数)
监控命令:
bash
# 显示会话表状态(包括所有监控指标)
show acl-plugin sessions
# 显示Hash表详细信息(包括冲突率)
show acl-plugin sessions verbose
类比:就像"数据库监控指标":
- 记录总数:当前活跃的记录总数
- 添加速率:每秒添加的记录数
- 删除速率:每秒删除的记录数
- 索引冲突率:索引冲突的比率
- 清理进程性能:清理进程的性能指标
12.16 本章小结
通过这一章的详细讲解,我们了解了:
- 会话表管理的概念:什么是会话表管理,为什么需要它
- 会话表初始化:如何初始化会话表系统(Per-worker Pool和Hash表)
- Per-worker数据结构:每个Worker线程的完整数据结构(逐字段注释)
- 会话表容量管理:如何管理会话表的容量(最大会话数、Hash表参数等)
- 会话表性能优化:如何优化会话表性能(缓存行对齐、预取、批量处理等)
- 会话表监控和调试:如何监控和调试会话表(显示函数、CLI命令等)
- 会话表清理机制:如何定期清理过期会话(自适应调度、清理参数等)
- 会话表容量限制:如何防止会话表溢出(容量检查、会话回收等)
- 会话表性能调优:如何调优会话表性能(Hash表参数、清理进程参数等)
- 会话表监控指标:如何监控会话表健康状态(关键指标、监控命令等)
核心要点:
- 会话表采用Per-worker架构,每个Worker线程有独立的会话Pool和超时链表
- 延迟初始化机制,只有在需要时才初始化会话表(节省内存)
- 自适应清理调度,根据负载动态调整清理频率
- 容量管理通过最大会话数、Hash表参数等控制
- 性能优化通过缓存行对齐、预取、批量处理等技术实现
- 监控和调试通过CLI命令和显示函数实现
下一步:在下一章,我们会看到ACL插件的性能优化技术,包括批量处理、预取优化、流水线处理等。
第13章:ACL插件性能优化------如何"让安检更快更高效"?
生活类比:想象一下,你是一个"安检系统优化工程师",需要让安检系统处理更多旅客,同时保持高效。
- 批量处理:一次处理多个旅客,而不是一个一个处理(提高吞吐量)
- 预取优化:提前准备好下一个旅客的信息(减少等待时间)
- 流水线处理:在处理当前旅客时,同时准备下一个旅客(提高效率)
这一章,我们就跟着ACL插件的代码,看看它是如何"让安检更快更高效"的。每一步我都会详细解释,确保你完全理解。
13.1 性能优化的概念:什么是"性能优化"?
13.1.1 什么是性能优化?
性能优化(Performance Optimization):通过各种技术手段,提高系统的处理速度和效率。
关键概念详解:
-
批量处理(Vector Processing):一次处理多个数据包,而不是一个一个处理
- 优势:减少函数调用开销,提高CPU缓存利用率
- 类比:就像"一次检查多个旅客"(而不是一个一个检查)
-
预取优化(Prefetch Optimization):提前加载数据到CPU缓存
- 优势:减少内存访问延迟,提高处理速度
- 类比:就像"提前准备好下一个旅客的信息"(减少等待时间)
-
流水线处理(Pipeline Processing):在处理当前数据包时,同时准备下一个数据包
- 优势:隐藏内存访问延迟,提高CPU利用率
- 类比:就像"在处理当前旅客时,同时准备下一个旅客"(提高效率)
-
缓存优化(Cache Optimization):优化数据结构布局,减少缓存未命中
- 优势:减少内存访问次数,提高访问速度
- 类比:就像"优化档案柜布局"(减少查找时间)
-
分支预测优化(Branch Prediction Optimization) :使用
PREDICT_FALSE和PREDICT_TRUE提示编译器- 优势:帮助CPU更好地预测分支,减少流水线停顿
- 类比:就像"提前告诉CPU哪个分支更可能发生"(减少预测错误)
13.1.2 性能优化的目标
主要目标:
- 提高吞吐量:每秒处理更多的数据包
- 降低延迟:减少单个数据包的处理时间
- 提高CPU利用率:减少CPU空闲时间
- 减少内存访问:优化内存访问模式
性能指标:
- 吞吐量(Throughput):每秒处理的数据包数(PPS - Packets Per Second)
- 延迟(Latency):单个数据包的处理时间(微秒)
- CPU利用率(CPU Utilization):CPU使用率(百分比)
类比:就像"安检系统性能指标":
- 吞吐量:每秒检查的旅客数(人/秒)
- 延迟:单个旅客的检查时间(秒)
- CPU利用率:安检通道的使用率(百分比)
13.2 批量处理优化:如何"一次处理多个数据包"?
13.2.1 批量处理的概念
批量处理(Vector Processing):一次处理多个数据包,而不是一个一个处理。
为什么需要批量处理?
- 减少函数调用开销:批量处理减少函数调用次数
- 提高缓存利用率:批量处理提高CPU缓存利用率
- 向量化优化:批量处理可以使用SIMD指令优化
类比:就像"批量检查旅客":
- 减少调用开销:一次检查多个旅客,减少"检查函数"调用次数
- 提高效率:批量处理提高"安检通道"的利用率
- 向量化优化:可以使用"并行检查"技术
13.2.2 批量处理参数定义
ACL插件定义了批量处理的参数:
c
//228:229:src/plugins/acl/dataplane_node.c
#define ACL_PLUGIN_VECTOR_SIZE 4 // 批量处理大小:4个数据包
// ACL_PLUGIN_VECTOR_SIZE:批量处理的大小(一次处理4个数据包)
// 为什么是4?平衡性能和代码复杂度(太小效果不明显,太大代码复杂)
// 类比:就像"一次检查4个旅客"(平衡效率和复杂度)
#define ACL_PLUGIN_PREFETCH_GAP 3 // 预取间隔:3个批量大小
// ACL_PLUGIN_PREFETCH_GAP:预取间隔(提前3个批量大小预取)
// 为什么是3?平衡预取效果和内存占用(太小预取效果不明显,太大占用内存)
// 类比:就像"提前3个批量准备旅客信息"(平衡预取效果和内存占用)
参数说明:
ACL_PLUGIN_VECTOR_SIZE:批量处理大小(4个数据包)ACL_PLUGIN_PREFETCH_GAP:预取间隔(3个批量大小 = 12个数据包)
类比:就像"批量检查参数":
- 批量大小:一次检查4个旅客
- 预取间隔:提前12个旅客准备信息
13.2.3 批量处理准备函数:acl_fa_node_common_prepare_fn
acl_fa_node_common_prepare_fn 用于批量提取数据包信息(5-tuple、Hash值等):
c
//231:317:src/plugins/acl/dataplane_node.c
always_inline void
acl_fa_node_common_prepare_fn (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame, int is_ip6, int is_input,
int is_l2_path, int with_stateful_datapath)
/* , int node_trace_on,
int reclassify_sessions) */
{
u32 n_left, *from; // 剩余数据包数、数据包索引数组指针(临时变量)
// n_left:剩余需要处理的数据包数
// from:数据包索引数组指针(从frame中获取)
// 类比:就像"剩余旅客数"和"旅客索引数组"
acl_main_t *am = &acl_main; // 获取ACL插件主结构体
// 类比:就像"获取'安检服务提供商'的'总部'"
uword thread_index = os_get_thread_index (); // 获取当前线程索引
// os_get_thread_index:获取当前线程索引
// 为什么需要?每个线程有独立的Per-worker数据
// 类比:就像"获取'安检通道编号'"
acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index]; // 获取线程的Per-worker数据
// per_worker_data:Per-worker数据数组(每个线程一个)
// 类比:就像"获取'安检通道'的'数据'"
vlib_buffer_t **b; // 数据包缓冲区数组指针(临时变量)
// 类比:就像"旅客信息数组指针"
u32 *sw_if_index; // 接口索引数组指针(临时变量)
// 类比:就像"通道索引数组指针"
fa_5tuple_t *fa_5tuple; // 5-tuple数组指针(临时变量)
// 类比:就像"旅客信息数组指针"
u64 *hash; // Hash值数组指针(临时变量)
// 类比:就像"Hash值数组指针"
// ========== 第一部分:获取数据包缓冲区 ==========
from = vlib_frame_vector_args (frame); // 获取数据包索引数组
// vlib_frame_vector_args:获取frame中的数据包索引数组
// frame:VPP的数据包帧(包含多个数据包的索引)
// 类比:就像"获取'旅客索引数组'"
vlib_get_buffers (vm, from, pw->bufs, frame->n_vectors); // 获取数据包缓冲区数组
// vlib_get_buffers:根据索引数组获取数据包缓冲区数组
// pw->bufs:Per-worker数据包缓冲区数组(用于批量处理)
// frame->n_vectors:frame中的数据包数量
// 为什么需要?将数据包索引转换为数据包缓冲区指针
// 类比:就像"根据'旅客索引数组'获取'旅客信息数组'"
// ========== 第二部分:设置数组指针 ==========
/* set the initial values for the current buffer the next pointers */
// 注释:设置当前缓冲区的初始值和下一个指针
b = pw->bufs; // 设置数据包缓冲区数组指针
// 类比:就像"设置'旅客信息数组'指针"
sw_if_index = pw->sw_if_indices; // 设置接口索引数组指针
// 类比:就像"设置'通道索引数组'指针"
fa_5tuple = pw->fa_5tuples; // 设置5-tuple数组指针
// 类比:就像"设置'旅客信息数组'指针"
hash = pw->hashes; // 设置Hash值数组指针
// 类比:就像"设置'Hash值数组'指针"
// ========== 第三部分:批量处理循环(带预取) ==========
/*
* fill the sw_if_index, 5tuple and session hash,
* First in strides of size ACL_PLUGIN_VECTOR_SIZE,
* with buffer prefetch being
* ACL_PLUGIN_PREFETCH_GAP * ACL_PLUGIN_VECTOR_SIZE entries
* in front. Then with a simple single loop.
*/
// 注释:填充接口索引、5-tuple和会话Hash值
// 首先以ACL_PLUGIN_VECTOR_SIZE为步长批量处理
// 预取间隔为ACL_PLUGIN_PREFETCH_GAP * ACL_PLUGIN_VECTOR_SIZE个条目
// 然后使用简单的单循环处理剩余数据包
n_left = frame->n_vectors; // 初始化剩余数据包数
// 类比:就像"初始化剩余旅客数"
while (n_left >= (ACL_PLUGIN_PREFETCH_GAP + 1) * ACL_PLUGIN_VECTOR_SIZE) // 如果剩余数据包数足够进行批量处理(带预取)
{
const int vec_sz = ACL_PLUGIN_VECTOR_SIZE; // 批量处理大小(4个数据包)
// 类比:就像"批量处理大小(4个旅客)"
// ========== 预取数据包缓冲区 ==========
{
int ii;
for (ii = ACL_PLUGIN_PREFETCH_GAP * vec_sz; // 从预取位置开始
ii < (ACL_PLUGIN_PREFETCH_GAP + 1) * vec_sz; ii++) // 到下一个批量位置
{
clib_prefetch_load (b[ii]); // 预取数据包缓冲区指针
// clib_prefetch_load:预取数据到CPU缓存(用于指针)
// 作用:提前加载数据包缓冲区指针到CPU缓存
// 类比:就像"提前加载'旅客信息'指针到'内存缓存'"
CLIB_PREFETCH (b[ii]->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD); // 预取数据包数据
// CLIB_PREFETCH:预取数据到CPU缓存(用于数据)
// 参数:b[ii]->data(数据包数据指针)、2 * CLIB_CACHE_LINE_BYTES(预取大小,2个缓存行)、LOAD(加载操作)
// 作用:提前加载数据包数据到CPU缓存(2个缓存行 = 128字节)
// 为什么预取2个缓存行?数据包头部通常需要多个缓存行
// 类比:就像"提前加载'旅客信息'数据到'内存缓存'(2个存储单元)"
}
}
// ========== 批量提取接口索引、5-tuple和Hash值 ==========
get_sw_if_index_xN (vec_sz, is_input, b, sw_if_index); // 批量提取接口索引
// get_sw_if_index_xN:批量提取接口索引(N个数据包)
// 参数:vec_sz(批量大小)、is_input(是否输入方向)、b(数据包缓冲区数组)、sw_if_index(输出接口索引数组)
// 作用:批量提取N个数据包的接口索引
// 类比:就像"批量提取N个旅客的'通道编号'"
fill_5tuple_xN (vec_sz, am, is_ip6, is_input, is_l2_path, &b[0],
&sw_if_index[0], &fa_5tuple[0]); // 批量提取5-tuple
// fill_5tuple_xN:批量提取5-tuple(N个数据包)
// 参数:vec_sz(批量大小)、am(ACL主结构体)、is_ip6(是否IPv6)、is_input(是否输入方向)、is_l2_path(是否L2路径)、b(数据包缓冲区数组)、sw_if_index(接口索引数组)、fa_5tuple(输出5-tuple数组)
// 作用:批量提取N个数据包的5-tuple(源IP、目的IP、协议、源端口、目的端口)
// 类比:就像"批量提取N个旅客的'基本信息'"
if (with_stateful_datapath) // 如果启用有状态数据平面
{
make_session_hash_xN (vec_sz, am, is_ip6, &sw_if_index[0],
&fa_5tuple[0], &hash[0]); // 批量计算会话Hash值
// make_session_hash_xN:批量计算会话Hash值(N个数据包)
// 参数:vec_sz(批量大小)、am(ACL主结构体)、is_ip6(是否IPv6)、sw_if_index(接口索引数组)、fa_5tuple(5-tuple数组)、hash(输出Hash值数组)
// 作用:批量计算N个数据包的会话Hash值(用于Hash表查找)
// 类比:就像"批量计算N个旅客的'Hash值'(用于数据库查找)"
}
// ========== 更新指针和剩余数量 ==========
n_left -= vec_sz; // 减少剩余数据包数
// 类比:就像"减少剩余旅客数"
fa_5tuple += vec_sz; // 移动5-tuple数组指针
// 类比:就像"移动'旅客信息数组'指针"
b += vec_sz; // 移动数据包缓冲区数组指针
// 类比:就像"移动'旅客信息数组'指针"
sw_if_index += vec_sz; // 移动接口索引数组指针
// 类比:就像"移动'通道索引数组'指针"
hash += vec_sz; // 移动Hash值数组指针
// 类比:就像"移动'Hash值数组'指针"
}
// ========== 第四部分:单循环处理剩余数据包 ==========
while (n_left > 0) // 如果还有剩余数据包
{
const int vec_sz = 1; // 单数据包处理
// 类比:就像"单旅客处理"
get_sw_if_index_xN (vec_sz, is_input, b, sw_if_index); // 提取接口索引
// 类比:就像"提取'通道编号'"
fill_5tuple_xN (vec_sz, am, is_ip6, is_input, is_l2_path, &b[0],
&sw_if_index[0], &fa_5tuple[0]); // 提取5-tuple
// 类比:就像"提取'旅客基本信息'"
if (with_stateful_datapath) // 如果启用有状态数据平面
{
make_session_hash_xN (vec_sz, am, is_ip6, &sw_if_index[0],
&fa_5tuple[0], &hash[0]); // 计算会话Hash值
// 类比:就像"计算'Hash值'"
}
n_left -= vec_sz; // 减少剩余数据包数
// 类比:就像"减少剩余旅客数"
fa_5tuple += vec_sz; // 移动5-tuple数组指针
// 类比:就像"移动'旅客信息数组'指针"
b += vec_sz; // 移动数据包缓冲区数组指针
// 类比:就像"移动'旅客信息数组'指针"
sw_if_index += vec_sz; // 移动接口索引数组指针
// 类比:就像"移动'通道索引数组'指针"
hash += vec_sz; // 移动Hash值数组指针
// 类比:就像"移动'Hash值数组'指针"
}
}
函数总结:
这个函数完成了批量提取数据包信息的工作:
- 获取数据包缓冲区:从frame中获取数据包缓冲区数组
- 设置数组指针:设置各种数组指针(数据包、接口索引、5-tuple、Hash值)
- 批量处理循环 :
- 预取数据包:提前预取下一个批量的数据包缓冲区
- 批量提取:批量提取接口索引、5-tuple和Hash值
- 更新指针:移动数组指针,准备处理下一批
- 单循环处理剩余:处理剩余的数据包(不足一个批量)
批量处理流程:
数据包1-4: 提取信息 → 预取数据包13-16 → 处理数据包1-4
数据包5-8: 提取信息 → 预取数据包17-20 → 处理数据包5-8
...
剩余数据包: 逐个处理
类比:就像完整的"批量检查旅客"流程:
- 获取旅客信息:从"旅客队列"中获取"旅客信息数组"
- 设置数组指针:设置各种数组指针(旅客、通道、信息、Hash值)
- 批量检查循环 :
- 预取旅客:提前预取下一个批量的旅客信息
- 批量提取:批量提取通道编号、旅客信息和Hash值
- 更新指针:移动数组指针,准备检查下一批
- 单循环处理剩余:处理剩余的旅客(不足一个批量)
13.3 预取优化:如何"提前准备好数据"?
13.3.1 预取优化的概念
预取优化(Prefetch Optimization):提前将数据加载到CPU缓存,减少内存访问延迟。
为什么需要预取?
- 内存访问延迟:内存访问比CPU缓存访问慢得多(通常慢100倍以上)
- 隐藏延迟:在处理当前数据时,提前加载下一个数据到缓存
- 提高性能:减少CPU等待内存访问的时间
类比:就像"提前准备旅客信息":
- 内存访问延迟:从"档案柜"读取信息比从"桌面"读取慢得多
- 隐藏延迟:在处理当前旅客时,提前从"档案柜"读取下一个旅客的信息
- 提高性能:减少"安检员"等待"档案柜"的时间
13.3.2 数据包预取
在批量处理时,提前预取下一个批量的数据包:
c
//274:281:src/plugins/acl/dataplane_node.c
{
int ii;
for (ii = ACL_PLUGIN_PREFETCH_GAP * vec_sz; // 从预取位置开始(12个数据包后)
ii < (ACL_PLUGIN_PREFETCH_GAP + 1) * vec_sz; ii++) // 到下一个批量位置(16个数据包)
{
clib_prefetch_load (b[ii]); // 预取数据包缓冲区指针
// clib_prefetch_load:预取数据到CPU缓存(用于指针)
// 作用:提前加载数据包缓冲区指针到CPU缓存
// 为什么需要?数据包缓冲区指针可能不在缓存中,提前加载减少延迟
// 类比:就像"提前加载'旅客信息'指针到'内存缓存'"
CLIB_PREFETCH (b[ii]->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD); // 预取数据包数据
// CLIB_PREFETCH:预取数据到CPU缓存(用于数据)
// 参数:b[ii]->data(数据包数据指针)、2 * CLIB_CACHE_LINE_BYTES(预取大小,2个缓存行 = 128字节)、LOAD(加载操作)
// 作用:提前加载数据包数据到CPU缓存(2个缓存行)
// 为什么预取2个缓存行?数据包头部通常需要多个缓存行(IP头 + L4头)
// 类比:就像"提前加载'旅客信息'数据到'内存缓存'(2个存储单元)"
}
}
预取策略:
- 预取位置:提前3个批量大小(12个数据包)预取
- 预取大小:2个缓存行(128字节)
- 预取内容:数据包缓冲区指针和数据包数据
预取时机:
处理数据包1-4: 预取数据包13-16
处理数据包5-8: 预取数据包17-20
处理数据包9-12: 预取数据包21-24
...
类比:就像"提前准备旅客信息":
- 预取位置:提前12个旅客准备信息
- 预取大小:2个存储单元(128字节)
- 预取内容:旅客信息指针和旅客信息数据
13.3.3 Hash表预取
在查找会话时,提前预取Hash表的bucket和数据:
c
//396:410:src/plugins/acl/dataplane_node.c
default:
acl_fa_prefetch_session_bucket_for_hash (am, is_ip6, hash[5]); // 预取Hash表bucket(提前5个数据包)
// acl_fa_prefetch_session_bucket_for_hash:预取会话Hash表的bucket
// 参数:am(ACL主结构体)、is_ip6(是否IPv6)、hash[5](提前5个数据包的Hash值)
// 作用:提前加载Hash表的bucket到CPU缓存
// 为什么提前5个数据包?平衡预取效果和内存占用
// 类比:就像"提前加载'数据库索引页'到'内存缓存'"
/* fallthrough */
case 5:
case 4:
acl_fa_prefetch_session_data_for_hash (am, is_ip6, hash[3]); // 预取Hash表数据(提前3个数据包)
// acl_fa_prefetch_session_data_for_hash:预取会话Hash表的数据
// 参数:am(ACL主结构体)、is_ip6(是否IPv6)、hash[3](提前3个数据包的Hash值)
// 作用:提前加载Hash表的数据到CPU缓存
// 为什么提前3个数据包?在bucket预取之后,预取数据
// 类比:就像"提前加载'数据库记录'到'内存缓存'"
/* fallthrough */
case 3:
case 2:
acl_fa_find_session_with_hash (am, is_ip6, sw_if_index[1],
hash[1], &fa_5tuple[1],
&f_sess_id_next.as_u64); // 查找下一个会话(提前1个数据包)
// acl_fa_find_session_with_hash:使用Hash值查找会话
// 参数:am(ACL主结构体)、is_ip6(是否IPv6)、sw_if_index[1](下一个数据包的接口索引)、hash[1](下一个数据包的Hash值)、fa_5tuple[1](下一个数据包的5-tuple)、f_sess_id_next(输出会话ID)
// 作用:查找下一个数据包的会话(用于流水线处理)
// 为什么提前1个数据包?在处理当前数据包时,查找下一个数据包的会话
// 类比:就像"查找下一个旅客的'常客信息'"
if (f_sess_id_next.as_u64 != ~0ULL)
{
prefetch_session_entry (am, f_sess_id_next); // 预取会话条目
// prefetch_session_entry:预取会话条目
// 参数:am(ACL主结构体)、f_sess_id_next(会话ID)
// 作用:提前加载会话条目到CPU缓存
// 为什么需要?会话条目可能不在缓存中,提前加载减少延迟
// 类比:就像"提前加载'常客档案'到'内存缓存'"
}
三级预取流水线:
- 第一级:预取Hash表bucket(提前5个数据包)
- 第二级:预取Hash表数据(提前3个数据包)
- 第三级:查找会话并预取会话条目(提前1个数据包)
流水线示意图:
数据包0: 查找会话 → 处理会话
数据包1: 预取bucket[5] → 预取data[3] → 查找会话[1] → 预取会话[1] → 等待处理
数据包2: 预取bucket[6] → 预取data[4] → 查找会话[2] → 预取会话[2] → 等待处理
...
类比:就像"三级预取流水线":
- 第一级:预取"数据库索引页"(提前5个旅客)
- 第二级:预取"数据库记录"(提前3个旅客)
- 第三级:查找"常客信息"并预取"常客档案"(提前1个旅客)
13.3.4 会话条目预取
在找到会话后,提前预取会话条目:
c
//172:178:src/plugins/acl/dataplane_node.c
always_inline void
prefetch_session_entry (acl_main_t * am, fa_full_session_id_t f_sess_id)
{
fa_session_t *sess = get_session_ptr_no_check (am, f_sess_id.thread_index,
f_sess_id.session_index); // 获取会话指针
// get_session_ptr_no_check:获取会话指针(不检查边界)
// 参数:am(ACL主结构体)、f_sess_id.thread_index(线程索引)、f_sess_id.session_index(会话索引)
// 作用:从Per-worker Pool中获取会话指针
// 类比:就像"从'档案柜'中获取'常客档案'指针"
CLIB_PREFETCH (sess, sizeof (*sess), STORE); // 预取会话条目
// CLIB_PREFETCH:预取数据到CPU缓存
// 参数:sess(会话指针)、sizeof(*sess)(会话大小,128字节)、STORE(存储操作)
// 作用:提前加载会话条目到CPU缓存(128字节 = 2个缓存行)
// 为什么使用STORE?因为后续会修改会话(更新last_active_time等)
// 类比:就像"提前加载'常客档案'到'内存缓存'(2个存储单元)"
}
预取策略:
- 预取大小:会话结构体大小(128字节 = 2个缓存行)
- 预取类型:STORE(因为后续会修改会话)
- 预取时机:在找到会话后立即预取
类比:就像"预取常客档案":
- 预取大小:常客档案大小(2个存储单元)
- 预取类型:STORE(因为后续会更新访问记录)
- 预取时机:在找到常客后立即预取
13.4 流水线处理:如何"同时处理多个数据包"?
13.4.1 流水线处理的概念
流水线处理(Pipeline Processing):在处理当前数据包时,同时准备下一个数据包。
为什么需要流水线?
- 隐藏延迟:在处理当前数据包时,提前准备下一个数据包(隐藏内存访问延迟)
- 提高CPU利用率:减少CPU空闲时间
- 提高吞吐量:提高每秒处理的数据包数
类比:就像"流水线安检":
- 隐藏延迟:在处理当前旅客时,提前准备下一个旅客(隐藏查找档案的时间)
- 提高效率:减少"安检员"空闲时间
- 提高吞吐量:提高每秒检查的旅客数
13.4.2 三级预取流水线
ACL插件使用三级预取流水线来处理会话查找:
c
//360:410:src/plugins/acl/dataplane_node.c
/*
* Now the "hard" work of session lookups and ACL lookups for new sessions.
* Due to the complexity, do it for the time being in single loop with
* a pipeline of three prefetches:
* 1) bucket for the session bihash
* 2) data for the session bihash
* 3) worker session record
*/
// 注释:现在是会话查找和ACL查找的"硬"工作
// 由于复杂性,暂时使用单循环,带有三级预取流水线:
// 1) 会话双向Hash表的bucket
// 2) 会话双向Hash表的数据
// 3) Worker会话记录
fa_full_session_id_t f_sess_id_next = {.as_u64 = ~0ULL }; // 下一个会话ID(初始化为无效值)
// f_sess_id_next:下一个数据包的会话ID(用于流水线处理)
// 为什么需要?在处理当前数据包时,提前查找下一个数据包的会话
// 类比:就像"下一个旅客的'常客ID'"
/* find the "next" session so we can kickstart the pipeline */
// 注释:查找"下一个"会话,以便启动流水线
if (with_stateful_datapath) // 如果启用有状态数据平面
{
acl_fa_find_session_with_hash (am, is_ip6, sw_if_index[0], hash[0],
&fa_5tuple[0], &f_sess_id_next.as_u64); // 查找第一个数据包的会话(启动流水线)
// acl_fa_find_session_with_hash:使用Hash值查找会话
// 作用:查找第一个数据包的会话,启动流水线
// 类比:就像"查找第一个旅客的'常客信息',启动流水线"
}
n_left = frame->n_vectors; // 初始化剩余数据包数
// 类比:就像"初始化剩余旅客数"
while (n_left > 0) // 如果还有剩余数据包
{
u8 action = 0; // 动作(permit/deny)
// 类比:就像"检查结果"(允许/拒绝)
u32 lc_index0 = ~0; // Lookup Context索引(初始化为无效值)
// 类比:就像"规则集合索引"
int acl_check_needed = 1; // 是否需要ACL检查(默认需要)
// acl_check_needed:是否需要ACL检查(如果找到会话,可能不需要)
// 类比:就像"是否需要安检检查"
u32 match_acl_in_index = ~0; // 匹配的ACL索引(初始化为无效值)
// 类比:就像"匹配的规则表索引"
u32 match_acl_pos = ~0; // 匹配的ACL位置(初始化为无效值)
// 类比:就像"匹配的规则表位置"
u32 match_rule_index = ~0; // 匹配的规则索引(初始化为无效值)
// 类比:就像"匹配的规则索引"
next[0] = 0; /* drop by default */ // 默认丢弃
// next[0]:下一个节点索引(0表示丢弃)
// 类比:就像"默认拒绝"
/* Try to match an existing session first */
// 注释:首先尝试匹配现有会话
if (with_stateful_datapath) // 如果启用有状态数据平面
{
fa_full_session_id_t f_sess_id = f_sess_id_next; // 获取当前数据包的会话ID(从上一个循环的"下一个"获取)
// f_sess_id:当前数据包的会话ID(从上一个循环的f_sess_id_next获取)
// 为什么需要?流水线处理,当前数据包的会话ID是上一个循环预取的
// 类比:就像"当前旅客的'常客ID'(从上一个循环的'下一个'获取)"
switch (n_left) // 根据剩余数据包数选择预取策略
{
default: // 如果剩余数据包数 >= 6
acl_fa_prefetch_session_bucket_for_hash (am, is_ip6, hash[5]); // 预取Hash表bucket(提前5个数据包)
// 类比:就像"预取'数据库索引页'(提前5个旅客)"
/* fallthrough */
case 5:
case 4: // 如果剩余数据包数 >= 4
acl_fa_prefetch_session_data_for_hash (am, is_ip6, hash[3]); // 预取Hash表数据(提前3个数据包)
// 类比:就像"预取'数据库记录'(提前3个旅客)"
/* fallthrough */
case 3:
case 2: // 如果剩余数据包数 >= 2
acl_fa_find_session_with_hash (am, is_ip6, sw_if_index[1],
hash[1], &fa_5tuple[1],
&f_sess_id_next.as_u64); // 查找下一个数据包的会话(提前1个数据包)
// 类比:就像"查找下一个旅客的'常客信息'(提前1个旅客)"
if (f_sess_id_next.as_u64 != ~0ULL)
{
prefetch_session_entry (am, f_sess_id_next); // 预取会话条目
// 类比:就像"预取'常客档案'"
}
/* fallthrough */
case 1: // 如果剩余数据包数 >= 1
if (f_sess_id.as_u64 != ~0ULL) // 如果找到会话
{
// ... 处理现有会话的代码 ...
}
}
}
流水线处理流程:
循环1: 处理数据包0 → 预取bucket[5] → 预取data[3] → 查找会话[1] → 预取会话[1]
循环2: 处理数据包1(使用预取的会话[1]) → 预取bucket[6] → 预取data[4] → 查找会话[2] → 预取会话[2]
循环3: 处理数据包2(使用预取的会话[2]) → 预取bucket[7] → 预取data[5] → 查找会话[3] → 预取会话[3]
...
类比:就像"流水线安检":
- 循环1:检查旅客0 → 预取索引页[5] → 预取记录[3] → 查找常客[1] → 预取档案[1]
- 循环2:检查旅客1(使用预取的档案[1]) → 预取索引页[6] → 预取记录[4] → 查找常客[2] → 预取档案[2]
- 循环3:检查旅客2(使用预取的档案[2]) → 预取索引页[7] → 预取记录[5] → 查找常客[3] → 预取档案[3]
13.5 缓存优化:如何"优化内存访问"?
13.5.1 缓存行对齐优化
会话结构体设计为128字节,正好是2个缓存行:
c
//157:158:src/plugins/acl/fa_node.h
/* Let's try to fit within two cachelines */
CT_ASSERT_EQUAL(fa_session_t_size_is_128, sizeof(fa_session_t), 128); // 编译时断言:会话结构体大小为128字节
// CT_ASSERT_EQUAL:编译时断言(如果条件不满足,编译失败)
// 为什么需要128字节?正好是2个缓存行(现代CPU的缓存行大小通常是64字节)
// 为什么需要缓存行对齐?减少缓存未命中,提高访问速度
// 类比:就像"档案夹大小正好是2个'存储单元'"(减少存储访问次数)
缓存行对齐的优势:
- 减少缓存未命中:结构体对齐到缓存行,减少跨缓存行访问
- 提高访问速度:CPU可以一次性加载整个缓存行
- 减少伪共享:不同线程的会话不会共享同一个缓存行
类比:就像"档案夹大小优化":
- 对齐到存储单元:档案夹大小正好是2个"存储单元"
- 减少访问次数:一次可以加载整个"存储单元"
- 避免冲突:不同通道的档案不会共享同一个"存储单元"
13.5.2 内存访问模式优化
ACL插件优化了内存访问模式,减少缓存未命中:
- 顺序访问:按顺序访问数据包,提高缓存命中率
- 批量访问:批量访问数据,提高缓存利用率
- 预取访问:提前预取数据,减少缓存未命中
类比:就像"优化档案访问模式":
- 顺序访问:按顺序访问档案,提高"存储单元"命中率
- 批量访问:批量访问档案,提高"存储单元"利用率
- 预取访问:提前预取档案,减少"存储单元"未命中
13.6 分支预测优化:如何"帮助CPU预测分支"?
13.6.1 分支预测的概念
分支预测(Branch Prediction):CPU预测分支指令的执行路径,提前执行。
为什么需要分支预测?
- 流水线停顿:如果分支预测错误,需要清空流水线,造成性能损失
- 提高性能:正确的分支预测可以隐藏分支延迟
- 编译器提示 :使用
PREDICT_FALSE和PREDICT_TRUE提示编译器
类比:就像"提前预测检查结果":
- 流水线停顿:如果预测错误,需要重新检查,造成时间损失
- 提高性能:正确的预测可以隐藏检查延迟
- 编译器提示:使用提示告诉CPU哪个分支更可能发生
13.6.2 分支预测宏的使用
ACL插件使用PREDICT_FALSE和PREDICT_TRUE提示编译器:
c
//198:198:src/plugins/acl/dataplane_node.c
if (PREDICT_FALSE (old_timeout_type != new_timeout_type)) // 如果超时类型变化(不太可能发生)
// PREDICT_FALSE:提示编译器这个分支不太可能发生
// 作用:帮助CPU更好地预测分支,减少流水线停顿
// 为什么不太可能?会话超时类型通常不会变化
// 类比:就像"提示CPU'超时类型变化'不太可能发生"
{
acl_fa_restart_timer_for_session (am, now, f_sess_id); // 重启会话定时器
// 类比:就像"重启'常客定时器'"
}
PREDICT_FALSE的使用场景:
- 错误处理:错误情况通常不太可能发生
- 异常情况:异常情况通常不太可能发生
- 调试代码:调试代码通常不太可能执行
PREDICT_TRUE的使用场景:
- 正常路径:正常处理路径通常会发生
- 热点代码:热点代码通常会执行
类比:就像"提示CPU分支概率":
- PREDICT_FALSE:提示CPU"这个分支不太可能发生"(如错误处理)
- PREDICT_TRUE:提示CPU"这个分支很可能发生"(如正常处理)
13.7 性能调优建议:如何"优化ACL插件性能"?
13.7.1 批量处理调优
调优建议:
-
批量大小 :
ACL_PLUGIN_VECTOR_SIZE(默认4)- 增加批量大小:可以提高吞吐量,但增加代码复杂度
- 减少批量大小:可以降低延迟,但降低吞吐量
-
预取间隔 :
ACL_PLUGIN_PREFETCH_GAP(默认3)- 增加预取间隔:可以预取更远的数据,但占用更多内存
- 减少预取间隔:可以减少内存占用,但预取效果不明显
类比:就像"优化批量检查参数":
- 批量大小:一次检查的旅客数(默认4)
- 预取间隔:提前准备的旅客数(默认12)
13.7.2 缓存优化调优
调优建议:
- 数据结构对齐:确保关键数据结构对齐到缓存行
- 减少伪共享:避免不同线程访问同一个缓存行
- 提高缓存命中率:优化内存访问模式,提高缓存命中率
类比:就像"优化档案存储":
- 对齐到存储单元:确保关键档案对齐到"存储单元"
- 减少冲突:避免不同通道访问同一个"存储单元"
- 提高命中率:优化档案访问模式,提高"存储单元"命中率
13.7.3 分支预测调优
调优建议:
- 使用PREDICT宏 :在关键分支使用
PREDICT_FALSE和PREDICT_TRUE - 优化分支顺序:将更可能发生的分支放在前面
- 减少分支数量:减少不必要的分支
类比:就像"优化检查流程":
- 使用提示:在关键检查点使用"不太可能"和"很可能"提示
- 优化顺序:将更可能发生的情况放在前面
- 减少检查点:减少不必要的检查点
13.8 性能监控:如何"监控ACL插件性能"?
13.8.1 性能计数器
ACL插件提供了多个性能计数器:
- 数据包计数:处理的数据包总数
- 会话计数:创建的会话总数
- ACL匹配计数:ACL匹配的总数
监控命令:
bash
# 显示ACL插件统计信息
show acl-plugin sessions
# 显示接口ACL统计信息
show acl-plugin interface
类比:就像"监控安检系统性能":
- 旅客计数:检查的旅客总数
- 常客计数:登记的常客总数
- 规则匹配计数:规则匹配的总数
13.9 本章小结
通过这一章的详细讲解,我们了解了:
- 性能优化的概念:什么是性能优化,为什么需要它
- 批量处理优化 :如何一次处理多个数据包(
acl_fa_node_common_prepare_fn) - 预取优化:如何提前准备好数据(数据包预取、Hash表预取、会话条目预取)
- 流水线处理:如何同时处理多个数据包(三级预取流水线)
- 缓存优化:如何优化内存访问(缓存行对齐、内存访问模式优化)
- 分支预测优化 :如何帮助CPU预测分支(
PREDICT_FALSE、PREDICT_TRUE) - 性能调优建议:如何优化ACL插件性能
- 性能监控:如何监控ACL插件性能
核心要点:
- 批量处理:一次处理4个数据包,减少函数调用开销
- 三级预取流水线:bucket预取(提前5个) → data预取(提前3个) → 会话查找和预取(提前1个)
- 缓存优化:会话结构体对齐到2个缓存行(128字节),减少缓存未命中
- 分支预测 :使用
PREDICT_FALSE和PREDICT_TRUE提示编译器,帮助CPU预测分支 - 性能调优:根据实际场景调整批量大小、预取间隔等参数
下一步:在下一章,我们会看到ACL插件的调试工具和故障排查方法。
第14章:ACL插件调试工具------如何"诊断和排查问题"?
生活类比:想象一下,你是一个"安检系统维护工程师",需要诊断和排查安检系统的问题。
- Trace功能:记录每个旅客的检查过程(就像"检查记录")
- ELOG功能:记录系统事件(就像"系统日志")
- Show命令:显示系统状态(就像"查看系统状态")
这一章,我们就跟着ACL插件的代码,看看它是如何"诊断和排查问题"的。每一步我都会详细解释,确保你完全理解。
14.1 调试工具的概念:什么是"调试工具"?
14.1.1 什么是调试工具?
调试工具(Debugging Tools):用于诊断和排查系统问题的工具集合。
关键概念详解:
-
Trace功能(数据包跟踪):记录数据包的处理过程
- 作用:跟踪数据包经过ACL插件的完整路径
- 类比:就像"检查记录"(记录每个旅客的检查过程)
-
ELOG功能(事件日志):记录系统事件
- 作用:记录ACL插件的关键事件(如会话创建、删除等)
- 类比:就像"系统日志"(记录系统的重要事件)
-
Show命令(显示状态):显示系统状态信息
- 作用:显示ACL插件的当前状态(如会话数、ACL规则等)
- 类比:就像"查看系统状态"(查看安检系统的当前状态)
-
调试宏(Debug Macros):用于调试的宏定义
- 作用:在调试模式下输出调试信息
- 类比:就像"调试输出"(在调试模式下输出详细信息)
14.1.2 调试工具的分类
按功能分类:
- 数据包跟踪:Trace功能,跟踪单个数据包的处理过程
- 事件日志:ELOG功能,记录系统事件
- 状态显示:Show命令,显示系统状态
- 调试输出:调试宏,输出调试信息
按使用场景分类:
- 开发调试:开发时使用,输出详细的调试信息
- 生产排查:生产环境使用,输出关键信息
- 性能分析:性能分析时使用,输出性能相关数据
类比:就像"安检系统调试工具":
- 检查记录:记录每个旅客的检查过程(Trace功能)
- 系统日志:记录系统的重要事件(ELOG功能)
- 状态查看:查看系统的当前状态(Show命令)
- 调试输出:在调试模式下输出详细信息(调试宏)
14.2 Trace功能:如何"跟踪数据包处理过程"?
14.2.1 Trace功能的概念
Trace功能(数据包跟踪):记录数据包经过ACL插件的完整处理过程。
为什么需要Trace?
- 问题诊断:当数据包被错误处理时,可以查看Trace找出问题
- 流程验证:验证数据包是否按照预期流程处理
- 性能分析:分析数据包处理的性能瓶颈
类比:就像"检查记录":
- 问题诊断:当旅客被错误处理时,可以查看"检查记录"找出问题
- 流程验证:验证旅客是否按照预期流程检查
- 性能分析:分析检查流程的性能瓶颈
14.2.2 Trace数据结构:acl_fa_trace_t
Trace数据结构用于存储数据包的跟踪信息:
c
//35:45:src/plugins/acl/dataplane_node.c
typedef struct
{
u32 next_index; // 下一个节点索引
// next_index:数据包的下一个节点索引(用于转发)
// 为什么需要?记录数据包的转发路径
// 类比:就像"下一个检查点索引"
u32 sw_if_index; // 接口索引
// sw_if_index:数据包进入的接口索引
// 为什么需要?记录数据包的入口接口
// 类比:就像"入口通道编号"
u32 lc_index; // Lookup Context索引
// lc_index:用于匹配的Lookup Context索引
// 为什么需要?记录使用的Lookup Context
// 类比:就像"使用的规则集合索引"
u32 match_acl_in_index; // 匹配的ACL索引
// match_acl_in_index:匹配的ACL索引(在ACL列表中的位置)
// 为什么需要?记录匹配的ACL
// 类比:就像"匹配的规则表索引"
u32 match_rule_index; // 匹配的规则索引
// match_rule_index:匹配的规则索引(在ACL中的位置)
// 为什么需要?记录匹配的规则
// 类比:就像"匹配的规则索引"
u64 packet_info[6]; // 数据包信息(5-tuple,6个u64)
// packet_info:数据包的5-tuple信息(用于Hash表查找)
// 为什么需要?记录数据包的5-tuple信息
// 类比:就像"旅客的基本信息"
u32 trace_bitmap; // Trace位图
// trace_bitmap:Trace位图(记录Trace的各种标志位)
// 为什么需要?记录Trace的各种标志(如是否找到会话、是否匹配ACL等)
// 类比:就像"检查标志位图"
u8 action; // 动作(permit/deny)
// action:ACL匹配的动作(permit=允许,deny=拒绝)
// 为什么需要?记录ACL匹配的结果
// 类比:就像"检查结果"(允许/拒绝)
} acl_fa_trace_t;
结构体字段总结:
- next_index:下一个节点索引(用于转发)
- sw_if_index:接口索引(入口接口)
- lc_index:Lookup Context索引(使用的规则集合)
- match_acl_in_index:匹配的ACL索引(在ACL列表中的位置)
- match_rule_index:匹配的规则索引(在ACL中的位置)
- packet_info:数据包信息(5-tuple)
- trace_bitmap:Trace位图(各种标志位)
- action:动作(permit/deny)
类比:就像"检查记录结构":
- 下一个检查点:下一个检查点的索引
- 入口通道:入口通道编号
- 使用的规则集合:使用的规则集合索引
- 匹配的规则表:匹配的规则表索引
- 匹配的规则:匹配的规则索引
- 旅客信息:旅客的基本信息
- 检查标志:检查的各种标志
- 检查结果:检查结果(允许/拒绝)
14.2.3 Trace记录函数:maybe_trace_buffer
maybe_trace_buffer 用于记录数据包的Trace信息:
c
//79:102:src/plugins/acl/dataplane_node.c
always_inline void
maybe_trace_buffer (vlib_main_t * vm, vlib_node_runtime_t * node,
vlib_buffer_t * b, u32 sw_if_index0, u32 lc_index0,
u16 next0, int match_acl_in_index, int match_rule_index,
fa_5tuple_t * fa_5tuple, u8 action, u32 trace_bitmap)
{
if (PREDICT_FALSE (b->flags & VLIB_BUFFER_IS_TRACED)) // 如果数据包需要Trace
{
// PREDICT_FALSE:提示编译器这个分支不太可能发生
// VLIB_BUFFER_IS_TRACED:数据包需要Trace的标志位
// 为什么不太可能?大多数数据包不需要Trace(只有标记的数据包才需要)
// 类比:就像"如果旅客需要'检查记录'"
acl_fa_trace_t *t = vlib_add_trace (vm, node, b, sizeof (*t)); // 添加Trace记录
// vlib_add_trace:添加Trace记录到数据包
// 参数:vm(VPP主结构体)、node(节点)、b(数据包缓冲区)、sizeof(*t)(Trace结构体大小)
// 作用:为数据包添加Trace记录,返回Trace结构体指针
// 类比:就像"为旅客添加'检查记录'"
t->sw_if_index = sw_if_index0; // 设置接口索引
// 类比:就像"设置'入口通道编号'"
t->lc_index = lc_index0; // 设置Lookup Context索引
// 类比:就像"设置'使用的规则集合索引'"
t->next_index = next0; // 设置下一个节点索引
// 类比:就像"设置'下一个检查点索引'"
t->match_acl_in_index = match_acl_in_index; // 设置匹配的ACL索引
// 类比:就像"设置'匹配的规则表索引'"
t->match_rule_index = match_rule_index; // 设置匹配的规则索引
// 类比:就像"设置'匹配的规则索引'"
t->packet_info[0] = fa_5tuple->kv_40_8.key[0]; // 设置数据包信息(第1个u64)
// kv_40_8.key[0]:IPv6会话Hash表的key[0](包含源IP地址的一部分)
// 类比:就像"设置'旅客信息'(第1部分)"
t->packet_info[1] = fa_5tuple->kv_40_8.key[1]; // 设置数据包信息(第2个u64)
// kv_40_8.key[1]:IPv6会话Hash表的key[1](包含源IP地址的另一部分)
// 类比:就像"设置'旅客信息'(第2部分)"
t->packet_info[2] = fa_5tuple->kv_40_8.key[2]; // 设置数据包信息(第3个u64)
// kv_40_8.key[2]:IPv6会话Hash表的key[2](包含目的IP地址的一部分)
// 类比:就像"设置'旅客信息'(第3部分)"
t->packet_info[3] = fa_5tuple->kv_40_8.key[3]; // 设置数据包信息(第4个u64)
// kv_40_8.key[3]:IPv6会话Hash表的key[3](包含目的IP地址的另一部分)
// 类比:就像"设置'旅客信息'(第4部分)"
t->packet_info[4] = fa_5tuple->kv_40_8.key[4]; // 设置数据包信息(第5个u64)
// kv_40_8.key[4]:IPv6会话Hash表的key[4](包含L4信息)
// 类比:就像"设置'旅客信息'(第5部分)"
t->packet_info[5] = fa_5tuple->kv_40_8.value; // 设置数据包信息(第6个u64)
// kv_40_8.value:IPv6会话Hash表的value(包含会话ID等)
// 类比:就像"设置'旅客信息'(第6部分)"
t->action = action; // 设置动作
// 类比:就像"设置'检查结果'"
t->trace_bitmap = trace_bitmap; // 设置Trace位图
// 类比:就像"设置'检查标志位图'"
}
}
函数总结:
- 检查Trace标志:检查数据包是否需要Trace
- 添加Trace记录:为数据包添加Trace记录
- 填充Trace信息:填充Trace结构体的所有字段
Trace标志位说明:
VLIB_BUFFER_IS_TRACED:数据包需要Trace的标志位(由用户通过CLI命令设置)
类比:就像完整的"记录检查过程"流程:
- 检查标志:检查旅客是否需要"检查记录"
- 添加记录:为旅客添加"检查记录"
- 填充信息:填充"检查记录"的所有字段(通道、规则集合、规则、旅客信息、结果等)
14.2.4 Trace格式化函数:format_acl_plugin_trace
format_acl_plugin_trace 用于格式化Trace信息,用于显示:
c
//707:727:src/plugins/acl/dataplane_node.c
/* packet trace format function */
// 注释:数据包Trace格式化函数
static u8 *
format_acl_plugin_trace (u8 * s, va_list * args)
{
CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); // 获取VPP主结构体(未使用)
// CLIB_UNUSED:标记变量为未使用(避免编译器警告)
// va_arg:从可变参数列表中获取参数
// 类比:就像"获取'系统主结构体'(未使用)"
CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); // 获取节点(未使用)
// 类比:就像"获取'检查点'(未使用)"
acl_fa_trace_t *t = va_arg (*args, acl_fa_trace_t *); // 获取Trace结构体指针
// 类比:就像"获取'检查记录'指针"
// ========== 第一部分:格式化基本信息 ==========
s =
format (s,
"acl-plugin: lc_index: %d, sw_if_index %d, next index %d, action: %d, match: acl %d rule %d trace_bits %08x\n"
" pkt info %016llx %016llx %016llx %016llx %016llx %016llx",
t->lc_index, // Lookup Context索引
// 类比:就像"规则集合索引"
t->sw_if_index, // 接口索引
// 类比:就像"通道编号"
t->next_index, // 下一个节点索引
// 类比:就像"下一个检查点索引"
t->action, // 动作(permit/deny)
// 类比:就像"检查结果"
t->match_acl_in_index, // 匹配的ACL索引
// 类比:就像"匹配的规则表索引"
t->match_rule_index, // 匹配的规则索引
// 类比:就像"匹配的规则索引"
t->trace_bitmap, // Trace位图
// 类比:就像"检查标志位图"
t->packet_info[0], // 数据包信息(第1个u64)
// 类比:就像"旅客信息(第1部分)"
t->packet_info[1], // 数据包信息(第2个u64)
// 类比:就像"旅客信息(第2部分)"
t->packet_info[2], // 数据包信息(第3个u64)
// 类比:就像"旅客信息(第3部分)"
t->packet_info[3], // 数据包信息(第4个u64)
// 类比:就像"旅客信息(第4部分)"
t->packet_info[4], // 数据包信息(第5个u64)
// 类比:就像"旅客信息(第5部分)"
t->packet_info[5]); // 数据包信息(第6个u64)
// 类比:就像"旅客信息(第6部分)"
// ========== 第二部分:格式化5-tuple信息(人类可读格式) ==========
/* Now also print out the packet_info in a form usable by humans */
// 注释:现在也以人类可读的格式打印packet_info
s = format (s, "\n %U", format_fa_5tuple, t->packet_info); // 格式化5-tuple信息
// format_fa_5tuple:格式化5-tuple信息(人类可读格式)
// 作用:将5-tuple信息转换为人类可读的格式(如IP地址、端口等)
// 类比:就像"将'旅客信息'转换为人类可读的格式"
return s; // 返回格式化后的字符串
// 类比:就像"返回格式化后的'检查记录'"
}
函数总结:
- 获取Trace结构体:从可变参数列表中获取Trace结构体指针
- 格式化基本信息:格式化Trace的基本信息(Lookup Context索引、接口索引、动作等)
- 格式化5-tuple信息:将5-tuple信息转换为人类可读的格式
输出格式:
acl-plugin: lc_index: 0, sw_if_index 1, next index 2, action: 1, match: acl 0 rule 5 trace_bits 80000000
pkt info 20010db800000001 0000000000000000 20010db800000002 0000000000000000 0000000000000000 0000000000000000
[5-tuple in human readable format]
类比:就像"格式化检查记录":
- 基本信息:规则集合索引、通道编号、下一个检查点、检查结果、匹配的规则表、匹配的规则、检查标志
- 旅客信息:旅客的基本信息(人类可读格式)
14.3 ELOG功能:如何"记录系统事件"?
14.3.1 ELOG功能的概念
ELOG功能(事件日志):记录ACL插件的关键事件(如会话创建、删除等)。
为什么需要ELOG?
- 事件追踪:追踪系统事件的发生顺序
- 问题诊断:当出现问题时,可以查看ELOG找出原因
- 性能分析:分析系统事件的频率和模式
类比:就像"系统日志":
- 事件追踪:追踪系统事件的发生顺序
- 问题诊断:当出现问题时,可以查看"系统日志"找出原因
- 性能分析:分析系统事件的频率和模式
14.3.2 ELOG宏定义:elog_acl_cond_trace_X1
elog_acl_cond_trace_X1 用于记录带1个参数的事件:
c
//22:42:src/plugins/acl/elog_acl_trace.h
#define elog_acl_cond_trace_X1(am, trace_cond, acl_elog_trace_format_label, acl_elog_trace_format_args, acl_elog_val1) \
do { \
if (trace_cond) { // 如果Trace条件满足
// trace_cond:Trace条件(如果为真,则记录事件)
// 类比:就像"如果'记录条件'满足"
CLIB_UNUSED(struct { u8 available_space[18 - sizeof(acl_elog_val1)]; } *static_check); // 静态检查可用空间
// CLIB_UNUSED:标记变量为未使用(避免编译器警告)
// available_space:可用空间(18字节 - 参数大小)
// 为什么需要?确保ELOG记录不超过18字节(VPP的ELOG限制)
// 类比:就像"检查'日志记录'的可用空间"
u16 thread_index = os_get_thread_index (); // 获取线程索引
// os_get_thread_index:获取当前线程索引
// 为什么需要?记录事件发生的线程(用于多线程调试)
// 类比:就像"获取'安检通道编号'"
vlib_worker_thread_t * w = vlib_worker_threads + thread_index; // 获取Worker线程指针
// vlib_worker_threads:Worker线程数组
// 为什么需要?获取Worker线程的ELOG track(用于记录事件)
// 类比:就像"获取'安检通道'的'日志记录器'"
ELOG_TYPE_DECLARE (e) = // 声明ELOG事件类型
{
.format = "(%02d) " acl_elog_trace_format_label, // 格式化字符串
// format:ELOG事件的格式化字符串
// "(%02d) ":线程索引(2位数字)
// acl_elog_trace_format_label:用户提供的格式化标签
// 类比:就像"声明'日志事件类型'(包含格式化字符串)"
.format_args = "i2" acl_elog_trace_format_args, // 格式化参数
// format_args:ELOG事件的格式化参数
// "i2":线程索引(2字节整数)
// acl_elog_trace_format_args:用户提供的格式化参数
// 类比:就像"声明'日志事件类型'(包含格式化参数)"
};
CLIB_PACKED(struct // 声明打包结构体
{
u16 thread; // 线程索引
// thread:线程索引(用于记录事件发生的线程)
// 类比:就像"安检通道编号"
typeof(acl_elog_val1) val1; // 参数值1
// val1:用户提供的参数值1(类型由typeof自动推导)
// 类比:就像"参数值1"
}) *ed; // 结构体指针
ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e, w->elog_track); // 记录ELOG事件
// ELOG_TRACK_DATA:记录ELOG事件到ELOG track
// 参数:&vlib_global_main.elog_main(ELOG主结构体)、e(事件类型)、w->elog_track(Worker线程的ELOG track)
// 作用:记录事件到ELOG track,返回事件数据指针
// 类比:就像"记录事件到'日志记录器'"
ed->thread = thread_index; // 设置线程索引
// 类比:就像"设置'安检通道编号'"
ed->val1 = acl_elog_val1; // 设置参数值1
// 类比:就像"设置'参数值1'"
}
} while (0)
宏定义总结:
- 检查Trace条件:如果Trace条件满足,则记录事件
- 静态检查空间:确保ELOG记录不超过18字节
- 获取线程信息:获取当前线程索引和Worker线程指针
- 声明事件类型:声明ELOG事件类型(格式化字符串和参数)
- 记录事件:记录事件到ELOG track
- 填充事件数据:填充事件数据(线程索引、参数值)
使用示例:
c
elog_acl_cond_trace_X1(am, (am->trace_acl), "LOOKUP-CONTEXT: set-acl-list lc_index %d", "i4", lc_index);
类比:就像"记录系统事件":
- 检查条件:如果"记录条件"满足,则记录事件
- 检查空间:确保"日志记录"不超过限制
- 获取通道:获取当前"安检通道编号"和"日志记录器"
- 声明类型:声明"日志事件类型"(格式化字符串和参数)
- 记录事件:记录事件到"日志记录器"
- 填充数据:填充事件数据(通道编号、参数值)
14.3.3 ELOG宏定义:elog_acl_cond_trace_X2
elog_acl_cond_trace_X2 用于记录带2个参数的事件:
c
//45:70:src/plugins/acl/elog_acl_trace.h
#define elog_acl_cond_trace_X2(am, trace_cond, acl_elog_trace_format_label, acl_elog_trace_format_args, \
acl_elog_val1, acl_elog_val2) \
do { \
if (trace_cond) { // 如果Trace条件满足
// 类比:就像"如果'记录条件'满足"
CLIB_UNUSED(struct { u8 available_space[18 - sizeof(acl_elog_val1) - sizeof(acl_elog_val2)]; } *static_check); // 静态检查可用空间
// available_space:可用空间(18字节 - 参数1大小 - 参数2大小)
// 类比:就像"检查'日志记录'的可用空间"
u16 thread_index = os_get_thread_index (); // 获取线程索引
// 类比:就像"获取'安检通道编号'"
vlib_worker_thread_t * w = vlib_worker_threads + thread_index; // 获取Worker线程指针
// 类比:就像"获取'安检通道'的'日志记录器'"
ELOG_TYPE_DECLARE (e) = // 声明ELOG事件类型
{
.format = "(%02d) " acl_elog_trace_format_label, // 格式化字符串
// 类比:就像"声明'日志事件类型'(包含格式化字符串)"
.format_args = "i2" acl_elog_trace_format_args, // 格式化参数
// 类比:就像"声明'日志事件类型'(包含格式化参数)"
};
CLIB_PACKED(struct // 声明打包结构体
{
u16 thread; // 线程索引
// 类比:就像"安检通道编号"
typeof(acl_elog_val1) val1; // 参数值1
// 类比:就像"参数值1"
typeof(acl_elog_val2) val2; // 参数值2
// 类比:就像"参数值2"
}) *ed; // 结构体指针
ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e, w->elog_track); // 记录ELOG事件
// 类比:就像"记录事件到'日志记录器'"
ed->thread = thread_index; // 设置线程索引
// 类比:就像"设置'安检通道编号'"
ed->val1 = acl_elog_val1; // 设置参数值1
// 类比:就像"设置'参数值1'"
ed->val2 = acl_elog_val2; // 设置参数值2
// 类比:就像"设置'参数值2'"
}
} while (0)
宏定义总结:
与elog_acl_cond_trace_X1类似,但支持2个参数。
使用示例:
c
elog_acl_cond_trace_X2(am, (am->trace_acl), "lock acl %d in lc_index %d", "i4i4", acl, lc_index);
类比:就像"记录带2个参数的系统事件":
- 检查条件:如果"记录条件"满足,则记录事件
- 检查空间:确保"日志记录"不超过限制(考虑2个参数)
- 获取通道:获取当前"安检通道编号"和"日志记录器"
- 声明类型:声明"日志事件类型"(格式化字符串和参数)
- 记录事件:记录事件到"日志记录器"
- 填充数据:填充事件数据(通道编号、参数值1、参数值2)
14.4 Show命令:如何"显示系统状态"?
14.4.1 Show命令的概念
Show命令(显示状态):通过CLI命令显示ACL插件的当前状态。
为什么需要Show命令?
- 状态查看:查看ACL插件的当前状态(如会话数、ACL规则等)
- 问题诊断:当出现问题时,可以查看状态找出原因
- 性能监控:监控ACL插件的性能指标
类比:就像"查看系统状态":
- 状态查看:查看安检系统的当前状态(如常客数、规则数等)
- 问题诊断:当出现问题时,可以查看状态找出原因
- 性能监控:监控安检系统的性能指标
14.4.2 Show ACL命令:show acl-plugin acl
show acl-plugin acl 命令用于显示ACL规则:
c
//3231:3267:src/plugins/acl/acl.c
acl_plugin_show_acl (acl_main_t * am, u32 acl_index)
{
u32 i;
vlib_main_t *vm = am->vlib_main; // 获取VPP主结构体
// 类比:就像"获取'系统主结构体'"
for (i = 0; i < vec_len (am->acls); i++) // 遍历所有ACL
{
// 类比:就像"遍历所有'规则表'"
if (acl_is_not_defined (am, i)) // 如果ACL未定义,跳过
{
/* don't attempt to show the ACLs that do not exist */
// 注释:不要尝试显示不存在的ACL
continue;
// 类比:就像"如果'规则表'不存在,跳过"
}
if ((acl_index != ~0) && (acl_index != i)) // 如果指定了ACL索引,且当前索引不匹配,跳过
{
continue;
// 类比:就像"如果指定了'规则表索引',且当前索引不匹配,跳过"
}
acl_print_acl (vm, am, i); // 打印ACL信息
// acl_print_acl:打印ACL信息(包括规则列表)
// 类比:就像"打印'规则表'信息"
if (i < vec_len (am->input_sw_if_index_vec_by_acl)) // 如果ACL应用于输入接口
{
vlib_cli_output (vm, " applied inbound on sw_if_index: %U\n",
format_vec32, am->input_sw_if_index_vec_by_acl[i],
"%d"); // 显示应用的输入接口列表
// input_sw_if_index_vec_by_acl:ACL应用的输入接口列表
// 类比:就像"显示'规则表'应用的'入口通道'列表"
}
if (i < vec_len (am->output_sw_if_index_vec_by_acl)) // 如果ACL应用于输出接口
{
vlib_cli_output (vm, " applied outbound on sw_if_index: %U\n",
format_vec32, am->output_sw_if_index_vec_by_acl[i],
"%d"); // 显示应用的输出接口列表
// output_sw_if_index_vec_by_acl:ACL应用的输出接口列表
// 类比:就像"显示'规则表'应用的'出口通道'列表"
}
if (i < vec_len (am->lc_index_vec_by_acl)) // 如果ACL用于Lookup Context
{
vlib_cli_output (vm, " used in lookup context index: %U\n",
format_vec32, am->lc_index_vec_by_acl[i], "%d"); // 显示使用的Lookup Context列表
// lc_index_vec_by_acl:使用ACL的Lookup Context列表
// 类比:就像"显示使用'规则表'的'规则集合'列表"
}
}
}
函数总结:
- 遍历所有ACL:遍历所有已定义的ACL
- 过滤ACL:如果指定了ACL索引,只显示匹配的ACL
- 打印ACL信息:打印ACL的规则列表
- 显示应用信息:显示ACL应用的接口和Lookup Context
CLI命令处理函数:
c
//3269:3281:src/plugins/acl/acl.c
static clib_error_t *
acl_show_aclplugin_acl_fn (vlib_main_t * vm,
unformat_input_t * input, vlib_cli_command_t * cmd)
{
clib_error_t *error = 0;
acl_main_t *am = &acl_main; // 获取ACL主结构体
// 类比:就像"获取'安检系统主结构体'"
u32 acl_index = ~0; // 初始化ACL索引为无效值
// 类比:就像"初始化'规则表索引'为无效值"
(void) unformat (input, "index %u", &acl_index); // 解析命令行参数(可选)
// unformat:解析命令行参数
// 如果命令行包含"index N",则设置acl_index为N
// 类比:就像"解析命令行参数(可选)"
acl_plugin_show_acl (am, acl_index); // 显示ACL信息
// 类比:就像"显示'规则表'信息"
return error; // 返回错误(如果有)
// 类比:就像"返回错误(如果有)"
}
使用示例:
bash
# 显示所有ACL
show acl-plugin acl
# 显示指定ACL
show acl-plugin acl index 0
类比:就像"查看规则表":
- 显示所有规则表:显示所有已定义的规则表
- 显示指定规则表:显示指定索引的规则表
- 显示应用信息:显示规则表应用的通道和规则集合
14.4.3 Show Interface命令:show acl-plugin interface
show acl-plugin interface 命令用于显示接口的ACL配置:
c
//3313:3393:src/plugins/acl/acl.c
acl_plugin_show_interface (acl_main_t * am, u32 sw_if_index, int show_acl,
int detail)
{
vlib_main_t *vm = am->vlib_main; // 获取VPP主结构体
// 类比:就像"获取'系统主结构体'"
u32 swi; // 接口索引
// 类比:就像"通道索引"
u32 *pj; // ACL索引指针
// 类比:就像"规则表索引指针"
for (swi = 0; (swi < vec_len (am->input_acl_vec_by_sw_if_index)) ||
(swi < vec_len (am->output_acl_vec_by_sw_if_index)); swi++) // 遍历所有接口
{
// 类比:就像"遍历所有'通道'"
/* if we need a particular interface, skip all the others */
// 注释:如果我们需要特定的接口,跳过所有其他接口
if ((sw_if_index != ~0) && (sw_if_index != swi)) // 如果指定了接口索引,且当前索引不匹配,跳过
continue;
// 类比:就像"如果指定了'通道索引',且当前索引不匹配,跳过"
vlib_cli_output (vm, "sw_if_index %d:\n", swi); // 显示接口索引
// 类比:就像"显示'通道编号'"
if (swi < vec_len (am->input_policy_epoch_by_sw_if_index)) // 如果接口有输入Policy Epoch
vlib_cli_output (vm, " input policy epoch: %x\n",
vec_elt (am->input_policy_epoch_by_sw_if_index,
swi)); // 显示输入Policy Epoch
// input_policy_epoch_by_sw_if_index:接口的输入Policy Epoch
// Policy Epoch:策略版本号(用于检测ACL规则变更)
// 类比:就像"显示'入口通道'的'策略版本号'"
if (swi < vec_len (am->output_policy_epoch_by_sw_if_index)) // 如果接口有输出Policy Epoch
vlib_cli_output (vm, " output policy epoch: %x\n",
vec_elt (am->output_policy_epoch_by_sw_if_index,
swi)); // 显示输出Policy Epoch
// 类比:就像"显示'出口通道'的'策略版本号'"
if (intf_has_etype_whitelist (am, swi, 1)) // 如果接口有输入Ethertype白名单
{
vlib_cli_output (vm, " input etype whitelist: %U", format_vec16,
am->input_etype_whitelist_by_sw_if_index[swi],
"%04x"); // 显示输入Ethertype白名单
// etype whitelist:Ethertype白名单(允许的以太网类型)
// 类比:就像"显示'入口通道'的'以太网类型白名单'"
}
if (intf_has_etype_whitelist (am, swi, 0)) // 如果接口有输出Ethertype白名单
{
vlib_cli_output (vm, " output etype whitelist: %U", format_vec16,
am->output_etype_whitelist_by_sw_if_index[swi],
"%04x"); // 显示输出Ethertype白名单
// 类比:就像"显示'出口通道'的'以太网类型白名单'"
}
if ((swi < vec_len (am->input_acl_vec_by_sw_if_index)) &&
(vec_len (am->input_acl_vec_by_sw_if_index[swi]) > 0)) // 如果接口有输入ACL
{
vlib_cli_output (vm, " input acl(s): %U", format_vec32,
am->input_acl_vec_by_sw_if_index[swi], "%d"); // 显示输入ACL列表
// input_acl_vec_by_sw_if_index:接口的输入ACL列表
// 类比:就像"显示'入口通道'的'规则表'列表"
if (show_acl) // 如果要求显示ACL详情
{
vlib_cli_output (vm, "\n");
vec_foreach (pj, am->input_acl_vec_by_sw_if_index[swi]) // 遍历输入ACL列表
{
acl_print_acl (vm, am, *pj); // 打印每个ACL的详情
// 类比:就像"打印每个'规则表'的详情"
}
vlib_cli_output (vm, "\n");
}
}
if ((swi < vec_len (am->output_acl_vec_by_sw_if_index)) &&
(vec_len (am->output_acl_vec_by_sw_if_index[swi]) > 0)) // 如果接口有输出ACL
{
vlib_cli_output (vm, " output acl(s): %U", format_vec32,
am->output_acl_vec_by_sw_if_index[swi], "%d"); // 显示输出ACL列表
// output_acl_vec_by_sw_if_index:接口的输出ACL列表
// 类比:就像"显示'出口通道'的'规则表'列表"
if (show_acl) // 如果要求显示ACL详情
{
vlib_cli_output (vm, "\n");
vec_foreach (pj, am->output_acl_vec_by_sw_if_index[swi]) // 遍历输出ACL列表
{
acl_print_acl (vm, am, *pj); // 打印每个ACL的详情
// 类比:就像"打印每个'规则表'的详情"
}
vlib_cli_output (vm, "\n");
}
}
if (detail && (swi < vec_len (am->input_lc_index_by_sw_if_index))) // 如果要求显示详情,且接口有输入Lookup Context
{
vlib_cli_output (vm, " input lookup context index: %d",
am->input_lc_index_by_sw_if_index[swi]); // 显示输入Lookup Context索引
// input_lc_index_by_sw_if_index:接口的输入Lookup Context索引
// 类比:就像"显示'入口通道'的'规则集合索引'"
}
if (detail && (swi < vec_len (am->output_lc_index_by_sw_if_index))) // 如果要求显示详情,且接口有输出Lookup Context
{
vlib_cli_output (vm, " output lookup context index: %d",
am->output_lc_index_by_sw_if_index[swi]); // 显示输出Lookup Context索引
// output_lc_index_by_sw_if_index:接口的输出Lookup Context索引
// 类比:就像"显示'出口通道'的'规则集合索引'"
}
}
}
函数总结:
- 遍历所有接口:遍历所有配置了ACL的接口
- 过滤接口:如果指定了接口索引,只显示匹配的接口
- 显示Policy Epoch:显示接口的输入/输出Policy Epoch
- 显示Ethertype白名单:显示接口的输入/输出Ethertype白名单
- 显示ACL列表:显示接口的输入/输出ACL列表
- 显示ACL详情:如果要求,显示每个ACL的详情
- 显示Lookup Context:如果要求显示详情,显示接口的Lookup Context索引
CLI命令处理函数:
c
//3415:3430:src/plugins/acl/acl.c
static clib_error_t *
acl_show_aclplugin_interface_fn (vlib_main_t * vm,
unformat_input_t *
input, vlib_cli_command_t * cmd)
{
clib_error_t *error = 0;
acl_main_t *am = &acl_main; // 获取ACL主结构体
// 类比:就像"获取'安检系统主结构体'"
u32 sw_if_index = ~0; // 初始化接口索引为无效值
// 类比:就像"初始化'通道索引'为无效值"
(void) unformat (input, "sw_if_index %u", &sw_if_index); // 解析命令行参数(可选)
// 如果命令行包含"sw_if_index N",则设置sw_if_index为N
// 类比:就像"解析命令行参数(可选)"
int show_acl = unformat (input, "acl"); // 检查是否要求显示ACL详情
// 如果命令行包含"acl",则show_acl为真
// 类比:就像"检查是否要求显示'规则表'详情"
int detail = unformat (input, "detail"); // 检查是否要求显示详情
// 如果命令行包含"detail",则detail为真
// 类比:就像"检查是否要求显示详情"
acl_plugin_show_interface (am, sw_if_index, show_acl, detail); // 显示接口ACL配置
// 类比:就像"显示'通道'的'规则表'配置"
return error; // 返回错误(如果有)
// 类比:就像"返回错误(如果有)"
}
使用示例:
bash
# 显示所有接口的ACL配置
show acl-plugin interface
# 显示指定接口的ACL配置
show acl-plugin interface sw_if_index 0
# 显示指定接口的ACL配置(包括ACL详情)
show acl-plugin interface sw_if_index 0 acl
# 显示指定接口的ACL配置(包括所有详情)
show acl-plugin interface sw_if_index 0 acl detail
类比:就像"查看通道配置":
- 显示所有通道:显示所有已配置的通道
- 显示指定通道:显示指定编号的通道
- 显示规则表详情:显示通道的规则表详情
- 显示所有详情:显示通道的所有详情(包括规则集合索引)
14.4.4 Show Sessions命令:show acl-plugin sessions
show acl-plugin sessions 命令用于显示Flow-aware会话信息:
c
//3442:3586:src/plugins/acl/acl.c
acl_plugin_show_sessions (acl_main_t * am,
u32 show_session_thread_id,
u32 show_session_session_index)
{
vlib_main_t *vm = am->vlib_main; // 获取VPP主结构体
// 类比:就像"获取'系统主结构体'"
u16 wk; // Worker线程索引
// 类比:就像"安检通道索引"
vnet_interface_main_t *im = &am->vnet_main->interface_main; // 获取接口主结构体
// 类比:就像"获取'通道主结构体'"
vnet_sw_interface_t *swif; // 接口指针
// 类比:就像"通道指针"
u64 now = clib_cpu_time_now (); // 获取当前时间(CPU时钟)
// clib_cpu_time_now:获取当前CPU时钟时间
// 类比:就像"获取当前时间"
u64 clocks_per_second = am->vlib_main->clib_time.clocks_per_second; // 获取每秒时钟数
// clocks_per_second:每秒CPU时钟数(用于时间转换)
// 类比:就像"获取每秒时钟数"
// ========== 第一部分:显示会话统计信息 ==========
{
u64 n_adds = am->fa_session_total_adds; // 会话总添加数
// fa_session_total_adds:Flow-aware会话总添加数
// 类比:就像"常客总登记数"
u64 n_dels = am->fa_session_total_dels; // 会话总删除数
// fa_session_total_dels:Flow-aware会话总删除数
// 类比:就像"常客总注销数"
u64 n_deact = am->fa_session_total_deactivations; // 会话总停用数
// fa_session_total_deactivations:Flow-aware会话总停用数
// 类比:就像"常客总停用数"
vlib_cli_output (vm, "Sessions total: add %lu - del %lu = %lu", n_adds,
n_dels, n_adds - n_dels); // 显示会话总数(添加数 - 删除数)
// 类比:就像"显示常客总数(登记数 - 注销数)"
vlib_cli_output (vm, "Sessions active: add %lu - deact %lu = %lu", n_adds,
n_deact, n_adds - n_deact); // 显示活跃会话数(添加数 - 停用数)
// 类比:就像"显示活跃常客数(登记数 - 停用数)"
vlib_cli_output (vm, "Sessions being purged: deact %lu - del %lu = %lu",
n_deact, n_dels, n_deact - n_dels); // 显示正在清理的会话数(停用数 - 删除数)
// 类比:就像"显示正在清理的常客数(停用数 - 注销数)"
}
vlib_cli_output (vm, "now: %lu clocks per second: %lu", now,
clocks_per_second); // 显示当前时间和每秒时钟数
// 类比:就像"显示当前时间和每秒时钟数"
// ========== 第二部分:显示每个Worker线程的会话信息 ==========
vlib_cli_output (vm, "\n\nPer-thread data:");
// 类比:就像"显示每个'安检通道'的数据"
for (wk = 0; wk < vec_len (am->per_worker_data); wk++) // 遍历所有Worker线程
{
// 类比:就像"遍历所有'安检通道'"
acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk]; // 获取Worker线程数据
// per_worker_data:每个Worker线程的数据(包括会话池、超时列表等)
// 类比:就像"获取'安检通道'的数据(包括'常客池'、'超时列表'等)"
vlib_cli_output (vm, "Thread #%d:", wk); // 显示线程索引
// 类比:就像"显示'安检通道编号'"
if (show_session_thread_id == wk
&& show_session_session_index < pool_len (pw->fa_sessions_pool)) // 如果要求显示特定会话
{
vlib_cli_output (vm, " session index %u:",
show_session_session_index); // 显示会话索引
// 类比:就像"显示'常客索引'"
fa_session_t *sess =
pw->fa_sessions_pool + show_session_session_index; // 获取会话指针
// fa_sessions_pool:Worker线程的会话池
// 类比:就像"获取'常客档案'指针"
u64 *m = (u64 *) & sess->info; // 获取会话信息的指针(转换为u64数组)
// sess->info:会话信息(5-tuple等)
// 类比:就像"获取'常客信息'指针"
vlib_cli_output (vm,
" info: %016llx %016llx %016llx %016llx %016llx %016llx",
m[0], m[1], m[2], m[3], m[4], m[5]); // 显示会话信息(6个u64)
// 类比:就像"显示'常客信息'(6个u64)"
vlib_cli_output (vm, " sw_if_index: %u", sess->sw_if_index); // 显示接口索引
// 类比:就像"显示'通道编号'"
vlib_cli_output (vm, " tcp_flags_seen: %x",
sess->tcp_flags_seen.as_u16); // 显示已见的TCP标志
// tcp_flags_seen:已见的TCP标志(用于TCP会话跟踪)
// 类比:就像"显示已见的'TCP标志'"
vlib_cli_output (vm, " last active time: %lu",
sess->last_active_time); // 显示最后活跃时间
// last_active_time:会话最后活跃时间(CPU时钟)
// 类比:就像"显示最后活跃时间"
vlib_cli_output (vm, " thread index: %u", sess->thread_index); // 显示线程索引
// 类比:就像"显示'安检通道编号'"
vlib_cli_output (vm, " link enqueue time: %lu",
sess->link_enqueue_time); // 显示链表入队时间
// link_enqueue_time:会话加入超时链表的时间
// 类比:就像"显示'超时列表'入队时间"
vlib_cli_output (vm, " link next index: %u",
sess->link_next_idx); // 显示链表下一个索引
// link_next_idx:超时链表下一个会话的索引
// 类比:就像"显示'超时列表'下一个'常客索引'"
vlib_cli_output (vm, " link prev index: %u",
sess->link_prev_idx); // 显示链表上一个索引
// link_prev_idx:超时链表上一个会话的索引
// 类比:就像"显示'超时列表'上一个'常客索引'"
vlib_cli_output (vm, " link list id: %u", sess->link_list_id); // 显示链表ID
// link_list_id:超时链表ID(超时类型)
// 类比:就像"显示'超时列表ID'(超时类型)"
}
// ========== 第三部分:显示每个接口的会话统计 ==========
vlib_cli_output (vm, " connection add/del stats:", wk); // 显示连接添加/删除统计
// 类比:就像"显示'常客登记/注销'统计"
pool_foreach (swif, im->sw_interfaces) // 遍历所有接口
{
// 类比:就像"遍历所有'通道'"
u32 sw_if_index = swif->sw_if_index; // 获取接口索引
// 类比:就像"获取'通道编号'"
u64 n_adds =
(sw_if_index < vec_len (pw->fa_session_adds_by_sw_if_index) ?
pw->fa_session_adds_by_sw_if_index[sw_if_index] :
0); // 获取该接口的会话添加数
// fa_session_adds_by_sw_if_index:每个接口的会话添加数
// 类比:就像"获取该'通道'的'常客登记数'"
u64 n_dels =
(sw_if_index < vec_len (pw->fa_session_dels_by_sw_if_index) ?
pw->fa_session_dels_by_sw_if_index[sw_if_index] :
0); // 获取该接口的会话删除数
// fa_session_dels_by_sw_if_index:每个接口的会话删除数
// 类比:就像"获取该'通道'的'常客注销数'"
u64 n_epoch_changes =
(sw_if_index < vec_len (pw->fa_session_epoch_change_by_sw_if_index) ?
pw->fa_session_epoch_change_by_sw_if_index[sw_if_index] :
0); // 获取该接口的Policy Epoch变更数
// fa_session_epoch_change_by_sw_if_index:每个接口的Policy Epoch变更数
// Policy Epoch变更:当ACL规则变更时,会话需要重新分类
// 类比:就像"获取该'通道'的'策略版本变更数'"
vlib_cli_output (vm,
" sw_if_index %d: add %lu - del %lu = %lu; epoch chg: %lu",
sw_if_index,
n_adds,
n_dels,
n_adds -
n_dels,
n_epoch_changes); // 显示该接口的会话统计
// 类比:就像"显示该'通道'的'常客统计'"
}
// ========== 第四部分:显示超时链表信息 ==========
vlib_cli_output (vm, " connection timeout type lists:", wk); // 显示连接超时类型列表
// 类比:就像"显示'常客超时类型列表'"
u8 tt = 0;
for (tt = 0; tt < ACL_N_TIMEOUTS; tt++) // 遍历所有超时类型
{
// ACL_N_TIMEOUTS:超时类型数量(TCP、UDP、Transient等)
// 类比:就像"遍历所有'超时类型'"
u32 head_session_index = pw->fa_conn_list_head[tt]; // 获取超时链表的头索引
// fa_conn_list_head:每个超时类型的链表头索引
// 类比:就像"获取'超时列表'的头索引"
vlib_cli_output (vm, " fa_conn_list_head[%d]: %d", tt,
head_session_index); // 显示超时链表的头索引
// 类比:就像"显示'超时列表'的头索引"
if (~0 != head_session_index) // 如果链表不为空
{
fa_session_t *sess = pw->fa_sessions_pool + head_session_index; // 获取头会话指针
// 类比:就像"获取头'常客档案'指针"
vlib_cli_output (vm, " last active time: %lu",
sess->last_active_time); // 显示头会话的最后活跃时间
// 类比:就像"显示头'常客'的最后活跃时间"
vlib_cli_output (vm, " link enqueue time: %lu",
sess->link_enqueue_time); // 显示头会话的链表入队时间
// 类比:就像"显示头'常客'的'超时列表'入队时间"
}
}
// ========== 第五部分:显示清理进程信息 ==========
vlib_cli_output (vm, " Next expiry time: %lu", pw->next_expiry_time); // 显示下一个过期时间
// next_expiry_time:下一个会话过期时间(CPU时钟)
// 类比:就像"显示下一个'常客过期时间'"
vlib_cli_output (vm, " Requeue until time: %lu",
pw->requeue_until_time); // 显示重新入队截止时间
// requeue_until_time:重新入队截止时间(用于批量处理)
// 类比:就像"显示重新入队截止时间"
vlib_cli_output (vm, " Current time wait interval: %lu",
pw->current_time_wait_interval); // 显示当前TIME_WAIT间隔
// current_time_wait_interval:当前TIME_WAIT间隔(用于TCP会话)
// 类比:就像"显示当前'TIME_WAIT间隔'"
vlib_cli_output (vm, " Count of deleted sessions: %lu",
pw->cnt_deleted_sessions); // 显示已删除会话数
// cnt_deleted_sessions:已删除会话数(统计信息)
// 类比:就像"显示已删除'常客数'"
vlib_cli_output (vm, " Delete already deleted: %lu",
pw->cnt_already_deleted_sessions); // 显示重复删除会话数
// cnt_already_deleted_sessions:重复删除会话数(统计信息)
// 类比:就像"显示重复删除'常客数'"
vlib_cli_output (vm, " Session timers restarted: %lu",
pw->cnt_session_timer_restarted); // 显示会话定时器重启数
// cnt_session_timer_restarted:会话定时器重启数(统计信息)
// 类比:就像"显示'常客定时器'重启数"
vlib_cli_output (vm, " Swipe until this time: %lu",
pw->swipe_end_time); // 显示清理截止时间
// swipe_end_time:清理截止时间(用于批量清理)
// 类比:就像"显示清理截止时间"
vlib_cli_output (vm, " sw_if_index serviced bitmap: %U",
format_bitmap_hex, pw->serviced_sw_if_index_bitmap); // 显示已服务接口位图
// serviced_sw_if_index_bitmap:已服务接口位图(用于清理进程)
// 类比:就像"显示已服务'通道位图'"
vlib_cli_output (vm, " pending clear intfc bitmap : %U",
format_bitmap_hex,
pw->pending_clear_sw_if_index_bitmap); // 显示待清理接口位图
// pending_clear_sw_if_index_bitmap:待清理接口位图(用于清理进程)
// 类比:就像"显示待清理'通道位图'"
vlib_cli_output (vm, " clear in progress: %u", pw->clear_in_process); // 显示清理进行中标志
// clear_in_process:清理进行中标志(用于防止重复清理)
// 类比:就像"显示清理进行中标志"
vlib_cli_output (vm, " interrupt is pending: %d",
pw->interrupt_is_pending); // 显示中断挂起标志
// interrupt_is_pending:中断挂起标志(用于清理进程)
// 类比:就像"显示中断挂起标志"
vlib_cli_output (vm, " interrupt is needed: %d",
pw->interrupt_is_needed); // 显示中断需要标志
// interrupt_is_needed:中断需要标志(用于清理进程)
// 类比:就像"显示中断需要标志"
vlib_cli_output (vm, " interrupt is unwanted: %d",
pw->interrupt_is_unwanted); // 显示中断不需要标志
// interrupt_is_unwanted:中断不需要标志(用于清理进程)
// 类比:就像"显示中断不需要标志"
vlib_cli_output (vm, " interrupt generation: %d",
pw->interrupt_generation); // 显示中断生成数
// interrupt_generation:中断生成数(用于清理进程)
// 类比:就像"显示中断生成数"
vlib_cli_output (vm, " received session change requests: %d",
pw->rcvd_session_change_requests); // 显示接收的会话变更请求数
// rcvd_session_change_requests:接收的会话变更请求数(用于多线程同步)
// 类比:就像"显示接收的'常客变更请求数'"
vlib_cli_output (vm, " sent session change requests: %d",
pw->sent_session_change_requests); // 显示发送的会话变更请求数
// sent_session_change_requests:发送的会话变更请求数(用于多线程同步)
// 类比:就像"显示发送的'常客变更请求数'"
}
// ========== 第六部分:显示清理线程计数器 ==========
vlib_cli_output (vm, "\n\nConn cleaner thread counters:");
// 类比:就像"显示'清理线程'计数器"
#define _(cnt, desc) vlib_cli_output(vm, " %20lu: %s", am->cnt, desc); // 定义宏用于显示计数器
// 类比:就像"定义宏用于显示计数器"
foreach_fa_cleaner_counter; // 遍历所有清理线程计数器
// foreach_fa_cleaner_counter:遍历所有清理线程计数器的宏
// 类比:就像"遍历所有'清理线程'计数器"
#undef _
vlib_cli_output (vm, "Interrupt generation: %d",
am->fa_interrupt_generation); // 显示中断生成数
// fa_interrupt_generation:全局中断生成数
// 类比:就像"显示全局中断生成数"
vlib_cli_output (vm,
"Sessions per interval: min %lu max %lu increment: %f ms current: %f ms",
am->fa_min_deleted_sessions_per_interval, // 最小每间隔删除会话数
// fa_min_deleted_sessions_per_interval:最小每间隔删除会话数(用于自适应调度)
// 类比:就像"最小每间隔删除'常客数'"
am->fa_max_deleted_sessions_per_interval, // 最大每间隔删除会话数
// fa_max_deleted_sessions_per_interval:最大每间隔删除会话数(用于自适应调度)
// 类比:就像"最大每间隔删除'常客数'"
am->fa_cleaner_wait_time_increment * 1000.0, // 等待时间增量(毫秒)
// fa_cleaner_wait_time_increment:清理进程等待时间增量(用于自适应调度)
// 类比:就像"等待时间增量(毫秒)"
((f64) am->fa_current_cleaner_timer_wait_interval) *
1000.0 / (f64) vm->clib_time.clocks_per_second); // 当前等待间隔(毫秒)
// fa_current_cleaner_timer_wait_interval:当前清理进程定时器等待间隔(CPU时钟)
// 类比:就像"当前等待间隔(毫秒)"
vlib_cli_output (vm, "Reclassify sessions: %d", am->reclassify_sessions); // 显示重新分类会话标志
// reclassify_sessions:重新分类会话标志(当ACL规则变更时,是否重新分类会话)
// 类比:就像"显示重新分类'常客'标志"
}
函数总结:
- 显示会话统计信息:显示会话总数、活跃会话数、正在清理的会话数
- 显示每个Worker线程的会话信息:显示每个Worker线程的会话详情
- 显示每个接口的会话统计:显示每个接口的会话添加/删除统计
- 显示超时链表信息:显示每个超时类型的链表头信息
- 显示清理进程信息:显示清理进程的各种状态和统计信息
- 显示清理线程计数器:显示清理线程的各种计数器
使用示例:
bash
# 显示所有会话信息
show acl-plugin sessions
# 显示指定线程和会话的详情
show acl-plugin sessions thread 0 index 100
类比:就像"查看常客信息":
- 显示常客统计:显示常客总数、活跃常客数、正在清理的常客数
- 显示每个通道的常客信息:显示每个安检通道的常客详情
- 显示每个通道的常客统计:显示每个通道的常客登记/注销统计
- 显示超时列表信息:显示每个超时类型的列表头信息
- 显示清理进程信息:显示清理进程的各种状态和统计信息
14.5 调试宏:如何"输出调试信息"?
14.5.1 调试宏的概念
调试宏(Debug Macros):用于在调试模式下输出调试信息的宏定义。
为什么需要调试宏?
- 开发调试:开发时使用,输出详细的调试信息
- 问题诊断:当出现问题时,可以启用调试宏找出原因
- 性能分析:性能分析时使用,输出性能相关数据
类比:就像"调试输出":
- 开发调试:开发时使用,输出详细的调试信息
- 问题诊断:当出现问题时,可以启用调试输出找出原因
- 性能分析:性能分析时使用,输出性能相关数据
14.5.2 Hash查找调试宏:DBG0、DBG、DBG_UNIX_LOG
Hash查找模块使用调试宏来输出调试信息:
c
//18:32:src/plugins/acl/hash_lookup_private.h
#define ACL_HASH_LOOKUP_DEBUG 0 // Hash查找调试级别(0=关闭,1=警告,2=详细)
// ACL_HASH_LOOKUP_DEBUG:Hash查找调试级别
// 0:关闭调试(不输出任何信息)
// 1:警告级别(只输出警告信息)
// 2:详细级别(输出所有调试信息)
// 类比:就像"'Hash查找调试级别'(0=关闭,1=警告,2=详细)"
#if ACL_HASH_LOOKUP_DEBUG == 1 // 如果调试级别为1(警告级别)
#define DBG0(...) clib_warning(__VA_ARGS__) // DBG0:输出警告信息
// clib_warning:VPP的警告输出函数
// __VA_ARGS__:可变参数(传递给clib_warning)
// 类比:就像"输出警告信息"
#define DBG(...) // DBG:不输出(空宏)
// 类比:就像"不输出(空宏)"
#define DBG_UNIX_LOG(...) // DBG_UNIX_LOG:不输出(空宏)
// 类比:就像"不输出(空宏)"
#elif ACL_HASH_LOOKUP_DEBUG == 2 // 如果调试级别为2(详细级别)
#define DBG0(...) clib_warning(__VA_ARGS__) // DBG0:输出警告信息
// 类比:就像"输出警告信息"
#define DBG(...) do { void *prevheap = clib_mem_set_heap (vlib_global_main.heap_base); vlib_cli_output(&vlib_global_main, __VA_ARGS__); clib_mem_set_heap (prevheap); } while (0)
// DBG:输出CLI信息
// clib_mem_set_heap:设置内存堆(用于CLI输出)
// vlib_cli_output:VPP的CLI输出函数
// 作用:输出调试信息到CLI(需要设置正确的内存堆)
// 类比:就像"输出CLI信息"
#define DBG_UNIX_LOG(...) clib_unix_warning(__VA_ARGS__) // DBG_UNIX_LOG:输出Unix警告信息
// clib_unix_warning:VPP的Unix警告输出函数
// 作用:输出调试信息到Unix日志
// 类比:就像"输出Unix警告信息"
#else // 如果调试级别为0(关闭)
#define DBG0(...) // DBG0:不输出(空宏)
// 类比:就像"不输出(空宏)"
#define DBG(...) // DBG:不输出(空宏)
// 类比:就像"不输出(空宏)"
#define DBG_UNIX_LOG(...) // DBG_UNIX_LOG:不输出(空宏)
// 类比:就像"不输出(空宏)"
#endif
调试宏总结:
- DBG0:输出警告信息(调试级别1和2)
- DBG:输出CLI信息(仅调试级别2)
- DBG_UNIX_LOG:输出Unix警告信息(仅调试级别2)
使用示例:
c
// 在Hash查找代码中使用
DBG0("Hash lookup failed for ACL %d", acl_index);
DBG("Hash table entry: key=%llx value=%llx", key, value);
DBG_UNIX_LOG("Hash collision detected");
类比:就像"调试输出级别":
- 警告级别:只输出警告信息(DBG0)
- 详细级别:输出所有调试信息(DBG0、DBG、DBG_UNIX_LOG)
- 关闭:不输出任何信息(所有宏为空)
14.6 故障排查方法:如何"诊断和解决问题"?
14.6.1 故障排查的一般流程
故障排查流程:
- 收集信息:使用Show命令收集系统状态信息
- 启用Trace:启用Trace功能跟踪数据包处理过程
- 启用ELOG:启用ELOG功能记录系统事件
- 启用调试宏:启用调试宏输出详细调试信息
- 分析日志:分析Trace、ELOG和调试输出找出问题
- 修复问题:根据分析结果修复问题
类比:就像"故障排查流程":
- 收集信息:使用"查看系统状态"收集系统状态信息
- 启用检查记录:启用"检查记录"跟踪旅客检查过程
- 启用系统日志:启用"系统日志"记录系统事件
- 启用调试输出:启用"调试输出"输出详细调试信息
- 分析日志:分析"检查记录"、"系统日志"和"调试输出"找出问题
- 修复问题:根据分析结果修复问题
14.6.2 常见问题和解决方案
问题1:数据包被错误处理
排查步骤:
- 启用Trace :使用
trace add命令启用数据包Trace - 查看Trace :使用
show trace命令查看Trace信息 - 分析Trace:分析Trace信息找出问题(如匹配的ACL、规则等)
类比:就像"排查旅客被错误处理":
- 启用检查记录:启用"检查记录"跟踪旅客检查过程
- 查看检查记录:查看"检查记录"信息
- 分析检查记录:分析"检查记录"找出问题(如匹配的规则表、规则等)
问题2:会话未正确创建
排查步骤:
- 查看会话统计 :使用
show acl-plugin sessions查看会话统计 - 启用ELOG:启用ELOG功能记录会话创建事件
- 分析ELOG:分析ELOG信息找出问题(如会话创建失败的原因等)
类比:就像"排查常客未正确登记":
- 查看常客统计:查看"常客统计"信息
- 启用系统日志:启用"系统日志"记录常客登记事件
- 分析系统日志:分析"系统日志"找出问题(如常客登记失败的原因等)
问题3:性能问题
排查步骤:
- 查看性能计数器 :使用
show acl-plugin sessions查看性能计数器 - 启用性能分析:启用性能分析功能(如ELOG、Trace等)
- 分析性能数据:分析性能数据找出瓶颈(如会话查找时间、ACL匹配时间等)
类比:就像"排查性能问题":
- 查看性能计数器:查看"性能计数器"信息
- 启用性能分析:启用性能分析功能(如系统日志、检查记录等)
- 分析性能数据:分析性能数据找出瓶颈(如常客查找时间、规则匹配时间等)
14.7 本章小结
通过这一章的详细讲解,我们了解了:
- 调试工具的概念:什么是调试工具,为什么需要它
- Trace功能 :如何跟踪数据包处理过程(
maybe_trace_buffer、format_acl_plugin_trace) - ELOG功能 :如何记录系统事件(
elog_acl_cond_trace_X1、elog_acl_cond_trace_X2) - Show命令 :如何显示系统状态(
show acl-plugin acl、show acl-plugin interface、show acl-plugin sessions) - 调试宏 :如何输出调试信息(
DBG0、DBG、DBG_UNIX_LOG) - 故障排查方法:如何诊断和解决问题
核心要点:
- Trace功能:记录数据包的处理过程,用于问题诊断和流程验证
- ELOG功能:记录系统事件,用于事件追踪和问题诊断
- Show命令:显示系统状态,用于状态查看和问题诊断
- 调试宏:在调试模式下输出调试信息,用于开发调试和问题诊断
- 故障排查:使用Trace、ELOG、Show命令和调试宏诊断和解决问题
下一步:在下一章,我们会看到ACL插件的其他高级功能和扩展。