简介:《OpenvSwitch源码阅读笔记.pdf》面向刚开始阅读 OVS 源码的网络开发与 SDN 学习者,围绕虚拟交换机实现、OpenFlow 流表下发与数据面转发建立从架构到代码的入门路径。压缩包仅含 1 个 PDF 文件,大小约 1012KB,便于在本地检索和批注;内容以 OVS 2.3.90 版本为参照,梳理 ovs-vswitchd、ovsdb-server、ofproto、ofproto-dpif、netdev 等组件关系,并深入到 datapath 初始化、收包处理、流表哈希桶与流表创建流程,解释匹配、动作和 upcall 的协作方式。笔记还结合数据流向图说明 Linux 协议栈与 OVS 转发路径的差异,适合作为阅读源码时的对照提纲和排错思路参考。目前已有 977 人学习,适合具备一定 Linux 网络基础、希望系统理解 OVS 与 OpenFlow 实现细节的读者。
1. 知识地图:OVS 源码阅读笔记先抓哪几条主线
把 OVS 源码阅读笔记当成一张地图,先别急着从main()逐行啃。OVS 同时牵着 SDN 控制面和 OpenFlow 协议面,又在内核 datapath 与用户态 vswitchd 之间来回穿梭;如果一上来就陷进函数调用,很容易把“配置如何落库、流表如何下发、报文如何 upcall”三条线搅在一起。更稳的切入方式是先记住一张四层地图:内核 datapath 负责快路径匹配与动作执行,ovs-vswitchd 负责配置与编排,ofproto/ofproto-dpif 负责把 OpenFlow 语义翻译成 datapath 能执行的规则,ovsdb-server 与各类 ovs-* 工具负责配置落库和查询。这份笔记最适合已经懂一点 Linux 网络、准备系统读源码或复习 SDN 交换面的人;零基础读者先把网桥、流表、端口这三个词弄明白,再回来按模块推进,才不会把“配置面”和“数据面”混成一条线。
2. datapath 内核路径:从收包、建流到 upcall 的复习主线
2.1 datapath、vport 与流表的最小模型
读内核模块先建立最小模型:一个 datapath 可以挂多个 vport,vport 类似物理交换机的端口;一个 datapath 关联一张流表,流表条目由 match/key 和 action 两部分组成。这个模型看起来简单,却决定了后面所有调用链的解释方式:收包从 vport 进来,提取 key 后去流表匹配,匹配成功执行 action,匹配失败才走 upcall 送到用户态。复习时如果只记函数名不记这个对象关系,看到ovs_vport_receive()、ovs_flow_key_extract()、ovs_dp_process_packet()就会散成点;把 datapath、vport、flow table、action 四者画成一张对象关系图,再往函数上挂,记忆会稳很多。
这里最容易忽略的是“流表不是用户态独有”的错觉。内核 datapath 维护的是快路径流表,用户态 ofproto 层维护的是 OpenFlow 规则与翻译结果;两边通过 netlink 和 upcall 交互,但职责不同。复习 datapath 时要把“内核流表条目”和“OpenFlow 流表项”分开看:前者关注 key 与 action 的可执行形式,后者关注 match 字段、instruction 和 action 集合的协议语义。把这两个层次分开,后面看ovs_flow_cmd_new()和 flow_mod 解析才不会互相打架。
2.2 收包到 ovs_dp_process_packet 的调用链
收包主线可以先用一段伪调用链固定顺序,再回到源码补参数和错误分支。下面这段只保留复习主线,省略了参数、锁和错误处理,目的是让大脑先记住“谁调用谁”,而不是一上来被复杂入参拖住。
/* 调用链仅用于复习主线,省略参数和错误分支 */ netdev_frame_hook(...) -> netdev_port_receive(...) -> ovs_vport_receive(...) -> ovs_flow_key_extract(...) /* 从 skb 提取匹配 key */ -> ovs_dp_process_packet(...) /* 进入流表匹配 */这段链里,netdev_frame_hook是 vport 注册的回调入口,skb是内核网络报文,ovs_flow_key_extract()负责把报文中的以太网头、IP 头、传输层字段等提取成sw_flow_key,ovs_dp_process_packet()才是真正进入 OVS 数据包处理的地方。参数不需要在第一遍背全,但要知道 key 的生成发生在匹配之前,action 的执行发生在匹配之后;如果 key 提取错,后面流表再正确也匹配不上。复习时可以把“收包入口、key 提取、流表处理”三段分别标在调用链旁边,看到任何函数都先问它属于哪一段。
再往下看,流表匹配成功就执行 action,匹配失败就通过 upcall 送用户态。这个分界是 datapath 复习的重点:快路径追求少切换,慢路径负责把未命中报文交给用户态决策。很多初学者会把 upcall 当成异常路径,其实它是 OVS 学习流表、下发流表的关键机制;没有 upcall,控制器和用户态就没有机会补规则。把“命中执行 action、未命中 upcall”当成一条固定判断,再去看ovs_dp_upcall()和queue_userspace_packet()的构造过程,思路会清楚很多。
2.3 流表哈希桶与 sw_flow_key 的匹配逻辑
流表用哈希桶组织,笔记里提到alloc_buckets()初始化、hlist_head、flex_array、reciprocal_elems等结构。复习时不必一上来背所有字段,但要知道哈希桶解决的是“在大量流表条目里快速定位候选”的问题,真正匹配仍然要拿 key 和 mask 做比较。sw_flow_key里的in_port、skb_mark、recirc_id、priority、以太网字段、IPv4/IPv6 字段、传输层端口等,都是匹配域的一部分;字段越多,key 提取和 mask 比较越要小心。
重点难点在 mask 缓存和分段查询。笔记里提到先查mask_cache_entry,查询成功后形成 cache,并采用分段查询方式加速。复习时要抓住“缓存的是 mask 查询结果,不是最终 action”这个点:缓存能减少重复找 mask 的开销,但最终仍要确认 key 与 mask 的匹配关系。把flow_lookup()、masked_flow_lookup()的职责分开看,一个负责整体查找流程,一个负责带 mask 的比较,才不会把缓存命中当成流表命中。
提示:复习哈希桶时先画“桶数组—元素空间—parts 数组”的层级,再回到字段名,比直接背结构体有效。
2.4 action 执行与 upcall 的分界
action 处理入口是do_execute_actions(),笔记列了OVS_ACTION_ATTR_OUTPUT、OVS_ACTION_ATTR_USERSPACE、OVS_ACTION_ATTR_HASH、OVS_ACTION_ATTR_PUSH_VLAN、OVS_ACTION_ATTR_POP_VLAN、OVS_ACTION_ATTR_RECIRC、OVS_ACTION_ATTR_SET、OVS_ACTION_ATTR_SAMPLE等类型。复习时不要只记名字,要把 action 分成四类:转发类(OUTPUT、USERSPACE)、报文修改类(PUSH_VLAN、POP_VLAN、SET)、元数据类(HASH)、控制类(RECIRC、SAMPLE)。分类之后再看每个分支调用的函数,记忆负担会小很多。
upcall 的数据格式也值得单独过一遍:netlink 消息头、genlmsghdr、ovs_header,再加上OVS_PACKET_ATTR_KEY、OVS_PACKET_ATTR_USERDATA、OVS_PACKET_ATTR_PACKET等属性;key 部分对应sw_flow_key字段,原始数据包部分用skb_zerocopy()拷贝。复习时要问三个问题:这条 upcall 为什么发生,带了哪些匹配信息,用户态拿到后如何转成 flow 并继续分类。把这三个问题答顺,datapath 到用户态的桥就算搭起来了。
3. vswitchd 与 ofproto:配置同步、对象族和 udpif 线程
3.1 vswitchd 主循环:daemon、数据库 IDL 与 bridge_run
ovs-vswitchd 是用户态守护进程,主循环里同时处理数据库配置、OpenFlow 连接、ofproto 运行和 netdev 状态更新。笔记中的主线包括daemonize_start()、unixctl_server_create()、bridge_init()、ovsdb_idl_create()、bridge_run()、poll_block()等。复习时可以把主循环拆成“初始化—同步配置—运行 ofproto—处理事件—阻塞等待”五段:初始化阶段建立数据库 IDL、桥模型和 unixctl;同步配置阶段由bridge_reconfigure()把 ovsdb 中的网桥、端口、控制器配置落到 ofproto;运行阶段调用ofproto_run()处理 OpenFlow 消息;事件阶段处理 unixctl 命令和 netdev 状态;最后poll_block()等待事件或超时。
重点在于“配置不是直接写内核”。ovs-vsctl改的是 ovsdb-server 中的配置,vswitchd 通过 IDL 感知变化,再经bridge_reconfigure()同步到 ofproto,最后才通过 dpif 和 netlink 落到内核 datapath。复习时如果能把这条链按“工具—数据库—vswitchd—ofproto—dpif—datapath”排出来,很多对象就不会乱。下面这段命令只示意工具与对象的对应关系,不依赖具体环境,br0和eth0都是示意名。
# 只示意工具与对象的对应关系,不依赖具体环境 ovs-vsctl add-br br0 # 配置写入 ovsdb-server ovs-vsctl add-port br0 eth0 # 端口对象进入配置树 ovs-ofctl dump-flows br0 # 通过 OpenFlow 侧查看流表ovs-vsctl走的是数据库配置面,ovs-ofctl走的是 OpenFlow 协议面;前者改变 vswitch 配置,后者查询或修改流表。参数上要知道网桥名和端口名只是本地标识,真正落到 datapath 还要经过 ofproto 端口创建和 dpif 端口添加。复习时把“配置面”和“协议面”分开,看到bridge_reconfigure()就不会误以为它在直接处理每一个数据包。
3.2 ofproto 对象族:ofproto、ofport、ofrule、ofgroup
ofproto 层是 OpenFlow 接口层,核心对象可以整理成一张表。复习时先记对象职责,再记结构体字段,比反过来轻松。
| 对象 | 职责 | 复习抓手 |
|---|---|---|
| ofproto | 代表一个 OpenFlow 交换机实现 | 关联 ofproto_class、oftable、connmgr |
| ofport | 代表一个 OpenFlow 端口 | 关联 netdev 设备和 dpif 端口 |
| ofrule | 代表一条 OpenFlow 规则 | 包含 match、priority、actions |
| ofgroup | 代表一组 action 组合 | OpenFlow 1.1+ 的 group 语义 |
ofproto是整体结构,ofproto_class指向具体实现,笔记里对应ofproto_dpif_class;ofport把 OpenFlow 端口和 netdev 设备连起来;ofrule里放规则和动作;ofgroup处理行为组合。复习时最容易漏的是“接口对象”和“实现对象”的成对关系:ofproto对ofproto_dpif,ofport对ofport_dpif,ofrule对rule_dpif,ofgroup对group_dpif。把这四对关系写在一张纸上,再看ofproto_class的回调,就不会把抽象层和实现层混在一起。
重点难点在创建流程。bridge_init_ofproto()先做 ofproto 初始化,bridge_reconfigure()触发ofproto_create(),找到ofproto_dpif_class后依次走alloc()、construct()、init_ports(),其中construct()里会打开 dpif_backer,创建 udpif,初始化 ofproto 表,再进入 ofport 的打开和安装。复习时可以把创建流程拆成“找类—分配对象—构造对象—打开 datapath—初始化表—打开端口—安装端口”七步,每一步问“创建的是抽象对象还是实现对象”,思路会清晰。
3.3 ofproto 创建流程与 dpif_backer
ofproto_create()的主线里,ofproto_class_find__()找到具体类,alloc()分配ofproto_dpif对象(其中包含ofproto),construct()回调初始化ofproto_dpif,再通过open_dpif_backer()打开 datapath。dpif_backer里挂着udpif和dpif,dpif的具体实现可能是dpif_netlink_class或dpif_netdev_class。复习时要把dpif_backer当成“用户态和内核 datapath 之间的桥”,它既持有 dpif,又持有 udpif;很多 upcall 线程和流表操作都从这里找到入口。
ofproto_init_tables()和add_internal_flows()负责初始化 ofproto 表并添加内部流;init_ports()走到ofport_open()、netdev_open(),以 system 类型为例会进入netdev_linux_alloc()、netdev_linux_construct();最后ofport_install()创建ofport_dpif并完成端口构造。复习时不要把这些函数当成平铺清单,而要按“datapath 先打开,端口后安装”的顺序理解:没有 dpif_backer,端口安装没有落点;没有 netdev,ofport 也没有设备可关联。
注意:
ofproto创建和ofport创建不是同一层,前者是交换机整体,后者是端口对象,复习时分开记。
3.4 udpif 多线程与 upcall 处理
udpif 负责用多个线程处理内核送来的 upcall。udpif_set_threads()进入udpif_start_threads(),分配 handlers 空间,再通过ovs_thread_create()创建n_handlers个线程,回调是udpif_upcall_handler()。每个线程里recv_upcalls()调dpif_recv(),再走dpif_netlink_recv()、epoll_wait()、nl_sock_recv()收数据,parse_odp_packet()把 ofpbuf 转成dpif_upcall,odp_flow_key_to_flow()把 key 转成 flow,最后process_upcall()做分类。
重点在classify_upcall()和upcall_xlate():MISS_UPCALL进入xlate_in_init()、xlate_actions(),生成 dp 需要的xlate_out;rule_dpif_lookup()查规则,rule_dpif_get_actions()取动作,do_xlate_actions()执行不同 action 的翻译。若fail_open使能,会调用ofproto_dpif_send_packet_in()把 packet-in 发给控制器;若存在慢路径,compose_slow_path()会增加慢路径 action;如果是DPIF_UC_MISS,ukey_create_from_upcall()创建udpif_key。复习时把“收 upcall—转 flow—查规则—翻译动作—决定 packet-in 或慢路径”当成一条链,比单记线程数更有用。
4. OpenFlow 连接与消息:ofconn、ofservice、flow_mod 解析重点
4.1 主动连接 ofconn 与被动监听 ofservice
OpenFlow 连接处理在bridge_configure_remotes()里展开,核心对象是ofconn和ofservice。ofconn作为客户端主动连接远端控制器,ofservice作为服务器提供被动监听。笔记里的主动连接链是connmgr_set_controllers()、add_controller()、ofconn_create()、rconn_connect()、reconnect()、vconn_open(),再往下到vconn_stream_open()、stream_open()、tcp_open()、inet_open_active()、socket()、connect()。被动监听链是ofservice_create()、pvconn_open()、pvconn_pstream_listen(),再走pstream_open()、punix_open()、make_unix_socket()、inet_open_passive()、bind_unix_socket()、listen()。
复习时抓住两条线的差异:主动连接有重连逻辑,被动监听有接受连接逻辑。connmgr统一管理all_conns和controllers,ofconn里挂rconn,rconn再挂vconn;vconn下面是stream,stream下面是stream_fd和具体stream_class。这串对象看起来长,但按“管理器—连接—虚拟连接—流—文件描述符”分层,就能把 TCP 和 Unix 两种路径统一起来。tcp_vconn_class、unix_vconn_class、tcp_stream_class、punix_pstream_class只是不同层的实现类,复习时先认层,再认类。
4.2 flow_mod 的四段结构
flow_mod 是 OpenFlow 流表项处理里最重要的一类消息。笔记把它拆成四部分:OpenFlow 头部、flow_mod 固定字段、match 字段和 instruction 字段。match 可以是OFPMT_STANDARD或OFPMT_OXM,可以包含多个 OXM;instruction 可以包含多个 action,也可以没有。复习时用一张表把四段固定下来,比抓包后临时猜字段更稳。
| 消息段 | 作用 | 易错点 |
|---|---|---|
| OpenFlow 头部 | 标识消息类型和长度 | 长度字段与后续 TLV 边界要一致 |
| flow_mod 固定字段 | 表 ID、优先级、超时等 | 表 ID 和优先级影响匹配顺序 |
| match 字段 | 描述匹配域 | OXM 的 class、field、mask 要合法 |
| instruction 字段 | 描述动作和跳转 | instruction 类型与 action 解码要对应 |
复习 flow_mod 时不要只背字段名,要记住解析入口:handle_openflow()是 OpenFlow 消息处理入口,match 解析走ofputil_pull_ofp11_match(),核心是nx_pull_raw();instruction 解析走ofpacts_pull_openflow_instructions(),其中decode_openflow11_instructions()把不同 instruction 放入ofp11_instruction *insts[N_OVS_INSTRUCTIONS]数组。N_OVS_INSTRUCTIONS由OVS_INSTRUCTIONS推导,笔记里对应值为 6。这个数字不是死记点,重点是知道 instruction 类型和后续处理函数有一一对应关系。
4.3 match 字段:OXM TLV 与合法性检查
match 的 OXM 是 TLV 格式,头里有 class、field、length、hasmask 等信息,后面跟 value,可能还跟 mask。解析函数nx_pull_match_entry()先在all_nxm_fields里找 header 相等的nxm_field,再根据mf_field_id找到mf_field,然后做四类检查:mf_are_prereqs_ok()检查依赖条件,mf_is_all_wild()检查重复性,mf_is_mask_valid()和mf_is_value_valid()检查 mask 与 value 的合法性,最后mf_set()根据 value 和 mask 设置 match。复习时要把“依赖检查、重复检查、有效性检查、写入 match”四步按顺序记,因为顺序错了就容易把非法字段放进去。
重点难点是 prerequisites。比如某些字段只有在特定协议层存在时才有意义,mf_are_prereqs_ok()就是防止在错误的 flow 条件下匹配不该出现的字段。复习时不要只看 value 对不对,还要问“这个字段在当前 match 上下文中允许出现吗”。另一个易漏点是 mask 与 value 的关系:mf_is_mask_valid()和mf_is_value_valid()分别检查不同合法性,不能用一个检查代替另一个。把这两类检查分开,再看nx_pull_entry__()的整体流程,match 解析就不会只剩字段名。
4.4 instruction 与 action 的解码
instruction 解码的核心是OVS_INSTRUCTIONS宏展开后形成的类型与处理函数对应关系。笔记里列出OVSINST_OFPIT13_METER、OVSINST_OFPIT11_APPLY_ACTIONS、OVSINST_OFPIT11_CLEAR_ACTIONS、OVSINST_OFPIT11_WRITE_ACTIONS、OVSINST_OFPIT11_WRITE_METADATA、OVSINST_OFPIT11_GOTO_TABLE等类型,对应ofpact_put_METER()、ofpacts_decode()、ofpact_put_CLEAR_ACTIONS()、ofpacts_decode_for_action_set()、ofpact_put_WRITE_METADATA()、ofpact_put_GOTO_TABLE()等处理。复习时把 instruction 分成“动作类、元数据类、跳转类、计量类”四组,再看每组落到 ofpact 的方式,会比逐条背宏轻松。
action 解码和 instruction 解码要连起来看:APPLY_ACTIONS和WRITE_ACTIONS都涉及 action 集合,但语义不同;CLEAR_ACTIONS是清空,WRITE_METADATA是写元数据,GOTO_TABLE是跳表。复习时最容易漏的是“instruction 可以没有 action,也可以包含多个 action”,所以解析代码不能假设 action 一定存在。把“instruction 类型—是否带 action—落到哪个 ofpact 构造函数”做成三列表,复习时一眼就能看出边界。
5. 易错点复盘:datapath、ofproto 与 OpenFlow 解析里最容易翻车的五处
5.1 路径类易错:快路径与慢路径混在一起
易漏点 → 把 datapath 的流表匹配和用户态 ofproto 的 OpenFlow 规则当成同一张表。为什么漏 → 两边都叫“流表”,函数名里也都有 flow,复习时很容易把ovs_flow_cmd_new()和 flow_mod 解析混成一条线。怎么补 → 先在纸上写“内核快路径表”和“用户态 OpenFlow 规则表”两个框,再把ovs_dp_process_packet()、ovs_dp_upcall()放左边,把handle_openflow()、ofproto_run()放右边,中间只留 netlink 和 upcall 两条箭头。每次看到 flow 相关函数,先问它在哪一边,再问它处理的是 key/action 还是 match/instruction。
易漏点 → 把 upcall 当成错误分支。为什么漏 → 命中流表时路径很短,未命中才进 upcall,初学者容易把 upcall 归到异常处理。怎么补 → 把 upcall 改叫“补规则入口”,在笔记里标出MISS_UPCALL、xlate_actions()、ukey_create_from_upcall()三个点,说明未命中报文如何进入用户态、如何查规则、如何生成 udpif_key。这样再看queue_userspace_packet()的报文构造,就不会觉得它只是错误上报。
5.2 对象类易错:配置对象与实现对象错位
易漏点 → 把ofproto和ofproto_dpif当成同一个对象。为什么漏 → 结构体里有struct ofproto up这种嵌入关系,代码里又经常用ofproto指针访问,复习时容易忽略具体实现。怎么补 → 记住四对关系:ofproto对ofproto_dpif,ofport对ofport_dpif,ofrule对rule_dpif,ofgroup对group_dpif。在创建流程里标出alloc()分配的是实现对象,construct()初始化的是实现对象,ofproto_class是回调入口。
易漏点 → 把ovs-vsctl的配置修改当成直接改内核 datapath。为什么漏 → 命令行执行后很快能看到网桥和端口,容易误以为工具直接操作了数据面。怎么补 → 把链路写成“ovs-vsctl—ovsdb-server—vswitchd IDL—bridge_reconfigure—ofproto—dpif—netlink—datapath”,并在每一步旁边写“改的是配置、对象还是内核端口”。复习bridge_reconfigure()时重点看它如何同步 cfg,如何调用ofproto_create()、bridge_add_ports()、iface_create()、dpif_port_add(),这样就不会把配置面和数据面压成一层。
5.3 协议类易错:match 与 instruction 的边界
易漏点 → 只检查 match 的 value,不检查 prerequisites。为什么漏 → OXM 字段看起来就是“类型 + 长度 + 值”,初学者容易直接取值比较,忽略字段依赖的协议层条件。怎么补 → 在nx_pull_match_entry()旁边标四步检查:mf_are_prereqs_ok()、mf_is_all_wild()、mf_is_mask_valid()/mf_is_value_valid()、mf_set()。每读一个字段,先问“当前 flow 上下文允许它出现吗”,再问“mask 和 value 是否合法”,最后才看写入结果。
易漏点 → 把 instruction 当成 action 列表。为什么漏 →APPLY_ACTIONS和WRITE_ACTIONS里确实带 action,复习时容易忽略 instruction 还有GOTO_TABLE、WRITE_METADATA、CLEAR_ACTIONS、METER等类型。怎么补 → 用OVS_INSTRUCTIONS宏展开后的六种类型做一张对照表,左边写 instruction 类型,中间写是否带 action,右边写落到哪个ofpact_put_*或ofpacts_decode*函数。每次解析 flow_mod,先分解出 instruction 数组,再分别处理,不要从 action 反推 instruction。
5.4 线程类易错:udpif 线程数与 upcall 处理混淆
易漏点 → 把udpif_set_threads()当成 upcall 处理本身。为什么漏 → 函数名里有 threads,复习时容易只记“创建 n_handlers 个线程”,忽略线程里真正跑的是udpif_upcall_handler()和recv_upcalls()。怎么补 → 把 udpif 拆成“线程创建”和“upcall 处理”两段:创建段记udpif_start_threads()、ovs_thread_create();处理段记dpif_recv()、parse_odp_packet()、odp_flow_key_to_flow()、upcall_receive()、process_upcall()、classify_upcall()、upcall_xlate()。线程数只是并发度,处理链才是复习重点。
易漏点 → 忽略fail_open和慢路径动作。为什么漏 → 正常路径看多了,容易只记xlate_actions()生成xlate_out,忘记还有 packet-in 和慢路径。怎么补 → 在upcall_xlate()旁边写三个分支:fail_open使能时发ofproto_packet_in,存在慢路径时compose_slow_path()增加 action,DPIF_UC_MISS时ukey_create_from_upcall()创建 udpif_key。把这三个分支当成固定检查项,复习 upcall 时逐个确认。
6. 复习清单:按调用链、对象表和协议字段做三轮查漏
第一轮只画调用链,不抠参数。datapath 画“收包—key 提取—流表处理—action/upcall”,vswitchd 画“初始化—IDL 同步—bridge_reconfigure—ofproto_run—poll_block”,ofproto 画“找类—alloc—construct—open_dpif_backer—init_ports—ofport_install”,OpenFlow 画“ofconn/ofservice—rconn/vconn—stream—fd”。每画完一条链,用一句话说出它的起点和终点;说不出来就回到笔记对应章节补箭头,不要急着看结构体字段。第二轮补对象表,把ofproto、ofport、ofrule、ofgroup和它们的实现对象成对写出来,再把dpif_backer、udpif、dpif三者的关系补上。第三轮只攻协议字段,拿 flow_mod 的四段结构做检查表,match 看 prerequisites、mask 合法性、重复性,instruction 看六种类型和 action 解码分支。
| 轮次 | 动作 | 检查点 | 产出 |
|---|---|---|---|
| 第一轮 | 画调用链 | 起点、终点、分界点 | 四张主线图 |
| 第二轮 | 列对象表 | 抽象对象与实现对象成对 | 一张对象关系表 |
| 第三轮 | 查协议字段 | match 四步检查、instruction 六类 | 一张 flow_mod 检查表 |
查漏时优先看三类地方:一是函数名相近但层次不同的,比如ovs_flow_cmd_new()和 flow_mod 解析;二是对象名相近但职责不同的,比如ofproto和ofproto_dpif;三是路径名相近但方向不同的,比如主动连接和被动监听。每找到一处,就在笔记旁边写“它属于哪条链、哪一层、哪一侧”,写不出来就说明还没串起来。复习时间紧的时候,先保证三条链能口述:收包到 upcall、配置到端口安装、flow_mod 到 instruction 解码;这三条顺了,细节再补也不会散。
从那以后我每次读 OVS 源码都先画调用链再开编辑器,看到新函数先问它挂在哪条链上,实在串不起来就退回对象表重画一遍,这个习惯帮我少走了很多“只看懂局部、整体还是糊”的弯路。希望帮到你。需要对照原笔记的读者,按获取入口自取即可,把它当成路线图和查漏表来用,比从头逐行硬啃更稳。
本文还有配套的精品资源,点击获取