摘要

  • Click 的连接表示数据包可能经过的前向交接路径;它本身既不是运行轨迹,也没有说明由源端还是目的端发起本次交接。
  • push 从源端开始,控制与数据包一同向下游推进;pull 从目的端开始,请求向上游回走,而返回的数据包仍沿箭头前进。显式 Queue 可以连接两种节奏。
  • 配置图让责任可读,但不能吞并端口类型、初始化结果、队列状态、调度选择、设备完成和远端接收等独立证据。

一张图里的两种时间方向

Click 把路由器的处理过程拆成细粒度 element。读取网卡、检查报头、查路由、分类、计数、排队和发送,都可以由不同 element 承担。用户用连接把它们组成有向图。Kohler、Robert Morris、Benjie Chen、John Jannotti 与 M. Frans Kaashoek 在 2000 年 TOCS 论文中把边定义为“可能的数据包交接路径”。

“可能”二字限制了图的证据范围。配置里存在一条边,不等于某个数据包已经走过。箭头指向 B,也不等于每次都是 A 主动调用 B。Click 用 push 和 pull 进一步说明控制从哪一端进入。

这一区分改变了故障归因。输出暂停可能是发送设备尚未准备好,并非上游没有数据。分支存在可能是配置允许,并非分类器在本次选择了它。若监控系统只保存箭头,不保存调用方向,就会把允许关系误写成执行事实。

push 由到达事件点火

接收设备拿到新数据包时,下游此前并不知道这次到达。接收 element 因而主动调用下一个 push 端口。控制和数据包一起前进,经过一串 element,直到被使用、丢弃或者存入某个位置。

源端拥有发起时机,并不拥有后续全部结果。检查 element 可以拒绝无效报头,分类器可以改走另一输出,容量受限的队列可以丢弃。接收计数上升,只证明入口看见了数据;它不能替发送完成作证。

一条可审计链至少要保存入口事件、运行配置身份、发起 element、实际分支和关键边界结果。若只有几个相隔很远的总计数,事后无法自动还原某个数据包的因果链。

pull 由可用容量发问

发送侧面对的是另一种条件。网卡只有在能够发送时才需要下一个数据包。Click 让目的端 element 发起 pull:请求逆着连接向上游传递,直到某个 element 返回一个数据包,或者报告当前没有可用数据。

Kohler 的博士论文用一幅时序图说明这种非对称。push 时,控制从接收设备出发并向前走;pull 时,控制从发送设备出发并向后走;无论哪一种,数据包本身始终向前。箭头没有改变,控制历史却相反。

pull 也让调度器成为可组合 element。它可以有多个 pull 输入和一个 pull 输出。下游请求到来后,调度器选择一个输入,再向那个来源提问。图能指出候选者和决策所在位置,却不能回答某一时刻选中了谁、未选中的队列是否为空,或者是否已经等待过久。

Queue 承认两只时钟并不一致

最简单的 Click 路径在接收侧 push 与发送侧 pull 之间放置一个 Queue。新数据包被推进入口,按 FIFO 保存;发送设备准备好后,从 Queue 的输出发起 pull,取走队首。

这个 element 的价值不只在缓存。它公开承认:生产者与消费者不共用同一节拍,两者之间的一段时间必须由某个组件负责。Click 没把它藏进庞大函数,而是给它名称、端口、容量和可观察状态。

但“入队成功”仍是一张窄收据。它证明这个内存队列在某个配置下接收了数据包,不证明断电后仍存在,不承诺最大等待,不保证调度器以后会选择它,也不说明发送设备最终完成。出队也只证明离开这一队列,不能代替下游所有结果。

FIFO 更不是系统公平证书。多个队列可能汇入一个 scheduler;数据可能在到达 Queue 前已被丢弃;通知机制、批处理与网卡就绪会改变 pull 时机。显式队列让问题能够被准确提出,却不会替所有层给出同一答案。

用端口规则拒绝矛盾承诺

运行中的 Click 端口会成为 push、pull,或者可随邻居适配的 agnostic。push 端口只能与 push 端口形成 push 连接,pull 端口与 pull 端口形成 pull 连接;直接把 push 接到 pull 是非法配置。

这是一种很小的共同规范。不同开发者可以各自编写 element,不需要一个持续存在的机构逐项批准组合;但连接双方必须同意“谁可以发起下一次交接”。若一端只会主动推送,另一端只会主动拉取,中间又没有 Queue 之类的转换边界,初始化就能按确定规则拒绝。

拒绝机制也划清“文件”与“运行”的差异。SMP Click 论文描述的顺序是:解析配置、创建 router、尝试初始化,成功后才安装并开始转发。因此,仓库里出现文件、界面画出图、语法解析成功,均不等于生产 datapath 已采用它。

优化之后,逻辑边与机器调用不必一一对应

细 element 提高可组合性,却会增加调用和重复计算。Kohler、Morris 与 Chen 后来把类似编译器的优化用于整个 Click 配置。相关工具还可以特化 classifier、删除死代码、消除部分虚调用、检查数据对齐,以及合并多个 router 定义。

优化后的指令边界可能不再与原图每一条边对应。原图并未因此失真,它仍描述逻辑责任;只是审计必须保留谱系:作者配置、转换顺序、生成输出、构建身份与安装实例。

丢掉谱系会产生两种相反误判。一种把原始图当作实际调用跟踪,忽视优化已经改变执行形态;另一种把优化后的二进制视为不可解释黑箱,忘记它来自可读的声明式结构。运行代码优先不是抛弃文档,而是要求证明文档如何被实现、验证、部署和采用。

人物署名也不能压成一根箭头

Kohler 的 Harvard 官方页面把 Click 列为其项目,扩展后的 2000 年期刊论文以 Kohler 为首位作者,MIT 博士论文系统展开了 Click 架构与语言工具。但同一组来源也明确显示协作关系:Morris、Chen、Jannotti 和 Kaashoek 共同署名核心论文,Massimiliano Poletto 参与语言技术报告,代码历史里还有许多后续贡献者。

把这段历史讲成单人发明,会在人类层面重复技术上的错误:看到一根箭头,就把所有调用都塞给一个端点。更准确的评价是,Kohler 帮助形成了一种方法——让小责任可组合,让调用权可声明,并让真正运行的系统承担最终证明。

出处