eBPF 指令集规范

1 BPF Instruction Set Specification, v1.0 — The Linux Kernel documentation

寄存器和调用约定

eBPF有10个通用寄存器和一个只读的帧指针寄存器,所有的寄存器都是64bits位长。

eBPF的调用约定如下:

  • R0: 函数调用的返回值和eBPF程序的退出值
  • R1 - R5: 函数调用的参数
  • R6 - R9: callee saved registers,由函数调用负责保存到栈上
  • R10: 只读的帧指针,用于访问栈 R0 - R5是临时寄存器,eBPF程序在函数调用时如需使用需要将它们保存/填充。

指令编码

eBPF使用64bit指令,编码如下:

32位(最高有效位) 16位 4位 4位 8位(最低有效位)
immediate offset source register dest register opcode
  • imm: 有符号整型立即数
  • offset: 有符号整型数,用于指针算术
  • src_reg: 除开特殊指令的编码外(如64位立即数指令)指的是源寄存器(R0 - R10)
  • dst_reg: 目标寄存器(R0 - R10)
  • opcode: 操作码

绝大多数指令不会使用所有的字段。没有使用的字段应当被置0。多字节的字段(如imm和offset)在大端BPF中以大端字节序存储,小端BPF中以小端字节序存储。

opcode                  offset imm          assembly
       src_reg dst_reg
07     0       1        00 00  44 33 22 11  r1 += 0x11223344 // little
       dst_reg src_reg
07     1       0        00 00  11 22 33 44  r1 += 0x11223344 // big

除了这些基本指令编码外,eBPF还有一种宽指令编码,在基本指令之后使用第2个64位立即数做扩展。第2个立即数包括一个伪指令,和基本指令的格式一样,不过所有的字段(除了imm)都置为0。它的imm作为整个宽指令中的imm64值的高32位:

eBPF 全局变量的实现

Linux内核在v5.2支持了eBPF全局变量(见v5.2)。

全局变量会被Clang编译器放入.bss, .data, .rodata sections。那么支持全局变量即如何把这些sections中的数据加载进内核,并且在relocation时,给访问这些变量的指令正确的地址。

早期 workaround

关于 eBPF 静态变量支持的讨论在Linux Plumbers Conference 2018 由Cilium提出。早期的 workaround 如下:

#include <linux/bpf.h>

typedef unsigned int __u32;
typedef long long unsigned int __64;

#ifndef __section
# define __section(NAME)				\
	__attribute__((section(NAME), used))
#endif
#ifndef __fetch
# define __fetch(x) (__u32)(__u64)(&(x))
#endif

__u32 foo = 42; 			// .data section
// __u32 foo;   			// .bss section
// const __u32 foo = 42; 	// .rodata section

int __main(struct __sk_buff *skb)
{
    skb->mark = __fetch(foo);
    return 0;
}

char __license[] __section("license") = "";

编译后,foo变量存储在.data section内。

# llvm-readelf -S test.o
There are 8 section headers, starting at offset 0x148:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .strtab           STRTAB          0000000000000000 0000fa 00004b 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000028 00  AX  0   0  8
  [ 3] .rel.text         REL             0000000000000000 0000e8 000010 10      7   2  8
  [ 4] .data             PROGBITS        0000000000000000 000068 000004 00  WA  0   0  4
  [ 5] license           PROGBITS        0000000000000000 00006c 000001 00  WA  0   0  1
  [ 6] .llvm_addrsig     LLVM_ADDRSIG    0000000000000000 0000f8 000002 00   E  7   0  1
  [ 7] .symtab           SYMTAB          0000000000000000 000070 000078 18      1   2  8
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

.rel.text section中也存在foo变量的relocation信息:

Linux 收包和发包流程

流程图

From 《Understanding Linux Network Internals》

image-20220128172744059

image-20220129171450456

收包流程

TL; DR

image-20220129171450456

  • 收包NET_RX_SOFTRQ的软中断处理函数是net_rx_action
  • net_rx_action中会调用网卡驱动注册的poll回调函数处理
  • poll回调函数将数据帧从网卡ring buffer中取出,构造skb:
    • 运行xdpdrv上的bpf program,得到action result
    • 如果是XDP_PASS,构造skb,并初始化skb中一些metadata字段
  • 调用内核的GRO和RPS处理流程
  • 进入__netif_receive_skb_core,处理skb:
    • 运行xdpgeneric上的bpf program,得到action result
    • 如果是XDP_PASS,遍历ptype_alldev->ptype_all,进行抓包处理
    • tc ingress 处理sch_handle_ingress
    • 查找ptype_basedev->ptype_specific,交由对应的三层协议栈回调函数处理

NAPI

**NAPI的思想是从完全的中断收包模型,改用中断和polling混合。**如果内核在处理旧的数据帧时,收到了新的数据帧,网卡设备没有必要再触发一个中断。内核继续处理设备input queue里的数据(该设备的interrupt禁止了),在队列为空时重新使能中断。

从内核的角度,NAPI有如下的优势:

  • 降低CPU的负载(更少的中断)
  • 更多的设备处理公平性

以ixgbe网卡为例,描述下NAPI处理流程。

注册

ixgbe驱动在初始化中断向量时会调用netif_napi_add初始化NAPI,==ixgbe_poll函数注册到napi结构体,并将napi加入到设备的napi_list内==:

/**
 * ixgbe_alloc_q_vector - Allocate memory for a single interrupt vector
 * @adapter: board private structure to initialize
 * @v_count: q_vectors allocated on adapter, used for ring interleaving
 * @v_idx: index of vector in adapter struct
 * @txr_count: total number of Tx rings to allocate
 * @txr_idx: index of first Tx ring to allocate
 * @xdp_count: total number of XDP rings to allocate
 * @xdp_idx: index of first XDP ring to allocate
 * @rxr_count: total number of Rx rings to allocate
 * @rxr_idx: index of first Rx ring to allocate
 *
 * We allocate one q_vector.  If allocation fails we return -ENOMEM.
 **/
static int ixgbe_alloc_q_vector(struct ixgbe_adapter *adapter,
                int v_count, int v_idx,
                int txr_count, int txr_idx,
                int xdp_count, int xdp_idx,
                int rxr_count, int rxr_idx)
{
	/* ... */

    /* initialize NAPI */
    netif_napi_add(adapter->netdev, &q_vector->napi,
               ixgbe_poll, 64);
}

中断处理函数

ixgbe驱动收到中断后,会调用ixgbe_msix_clean_rings

make命令和编译

Makefile

标准格式

target:dependencies1 dependencies2 ...
   recipe

.PHONY: clean
clean:
   -rm ...

注意事项

  • 运行make时如果没有指定target,那么make会构建Makefile中的第一个target
  • 通常情况下,目标名即生成的文件名。如果一条规则的目标文件存在并且该文件比它所有的依赖都要新,那么make会跳过recipe;如果目标文件不存在,那么目标文件的 timestamp 为开始的时间;否则timestamp为相应文件的修改时间。
  • 每次运行 make clean,”clean“中的recipe都会被执行,因为clean文件永远都不会被创建。(可以使用 .PHONY 创建伪目标使Makefile可读性更高)
  • recipes必须用tab缩进
  • 可以通过并行的方式运行recipes:make -j 4(指定并行的任务数)
  • 如果没有指定规则,Make会自动化创建规则。例如,本地有一个C文件”program.c“,当运行 make program时,Make会自动编译生成 program

变量

  • $@ 目标文件
  • $^ 所有的依赖文件
  • $< 第一个依赖

头文件

环境变量

  • C_INCLUDE_PATH C语言头文件路径
  • CPLUS_INCLUDE_PATH C++ 头文件路径

搜索路径

#include<>

  1. 先搜索 -I 指定的目录
  2. 然后搜索gcc的环境变量 CPLUS_INCLUDE_PATH
  3. 最后搜索gcc的内定目录
    1. /usr/include
    2. /usr/local/include
    3. /usr/lib/gcc/x86_64-redhat-linux/4.1.1/include

#include ""

  • 搜索当前目录,#include<>方式不会搜索当前目录

动态库

环境变量

  • LD_LIBRARY_PATH 动态链接库搜索路径
  • PKG_CONFIG_PATH .pc文件(package config)文件搜索路径

搜索路径

  1. 首先在环境变量 LD_LIBRARY_PATH 所记录的路径中查找
  2. 在程序链接时指定的 rpath 中查找,可以 readelf binfile | grep RPATH
  3. 然后从缓存文件/etc/ld.so.cache中查找。这个缓存文件由/sbin/ldconfig命令读取配置文件/etc/ld.so.conf 之后生成(也可以在 ld.so.conf.d 目录下增加 .conf 文件,里面写入库路径,在 ld.so.conf 中 include ld.so.conf.d/.conf )
  4. 如果上述步骤都找不到,则到默认的系统路径中查找,先是/usr/lib然后是/lib

编译参数

-shared

指定生成动态连接库

邓小平时代

《邓小平时代》

作者 傅高义

导言 这个人和他的使命

毛确实犯了严重的错误,但在邓小平看来,更大的问题是导致这些错误的制度缺陷。政治体系控制到每家每户的做法搞过了头,造成了恐惧和主动精神的丧失;对经济体系的控制也搞过了头,导致的是失去活力的僵化。中国的领导人究竟怎样才能做到既维持国家稳定,又为社会松绑?

作为最高领导人,邓小平并不认为自己的任务是提出新思想,他认为自己的职责是掌控设计和落实新体制的颠覆性的过程。他要承担最后责任,做出正确判断。

吉米·卡特总统曾评论道,邓小平跟苏联领导人不一样,他有一种内在的自信,这使他能直奔实质问题。他从不纠缠于过去的错误或谁要对其负责。他经常打桥牌,就像他打牌时的表现一样,他只想把摸到手的牌打好。他能认识并接受权力现实,在可能的范围内做事。

从那时起,通过和这些中国革命的大战略家交往,邓小平培养起一种看问题的独特眼光,能够从一个“统领全局”的高度思考如何将理论加以落实、如何用理论来影响社会。

邓小平1974年会见一个美国的大学代表团时说:“我没有上过大学,但我一向认为,从我出生那天起,就在上着人生这所大学。它没有毕业的一天,直到去见上帝。”邓小平终其一生都在不断地学习和解决问题。

第1章 革命者、建设者、改革者,1904—1969

还在莫斯科时,21岁的邓小平就萌生了一些对于一个年轻人而言非同寻常的想法,而且这些想法终生未曾改变。不妨举个例子,他在1926年8月12日的课堂作业中写道:“集中的权力要自上而下地行使。服从上级命令是绝对必要的。允许多少民主,要视周围的环境变化而定。”

第2章 放逐与回归,1969—1974

中国领导人应当颂扬毛泽东,继续尊敬他。但是在解释毛泽东思想时,不应把它当作僵化的意识形态,而应看作对时代环境的成功适应,这样理解毛的思想可以为毛泽东的接班人提供适应新环境的回旋余地。

十大后的21名新政治局委员中有4个激进派——王洪文、张春桥、江青和姚文元;他们虽然不是一个一起工作的小团体,但有着相似的观点,后来变成了臭名昭著的“四人帮”。

第3章 整顿,1974—1975

周恩来作报告时,很多人大代表都为他痛苦的表情落下了眼泪;念完报告后,他们全体起立,向他热烈鼓掌达数分钟之久。这种情感上的反应,是他们向这位临终前的领导人表达的敬意,他把自己的一生奉献给了党和国家,工作出类拔萃;他在“文革”中还保护了他们中间很多人。

邓小平看来,从组织的可靠性上说,一个领导班子要优于一名领导人,不管后者多么能干。一名领导人说不定会出事,但如果是一个小班子,一旦出了问题,其他人可以随时接过工作。理想的安排是,领导班子的成员不但能提供必要的全面领导,而且能掌握各自分管领域的专业知识,例如工业、文化和政法等等。大单位的领导班子可以有七八个成员,小单位也许只需要两三个人。对于领导应当如何开展工作,要给予他们足够的活动空间,只要他们能完成上级下达的目标即可。

这充分显示了邓小平的特色:讲清大局,说明为什么需要做某些事,把注意力集中在任务上面,打好思想基础,为撤换无所作为的干部争取公众支持。

德不配位

实际上,王洪文曾做过一系列的努力以承担起主持党的日常工作的职责;有些了解他的人觉得,他并没有参与“四人帮”犯下的罪行。但是北京城里有众多有经验的优秀干部,像王洪文这样一个年轻的新贵,突然之间蹿升到更有经验、更能干的干部之上,很难赢得一个高层领导人不可缺少的尊重。

第4章 向前看,1975

对周荣鑫的批判甚至比对邓小平的批判还要严厉。他在1975年12月不断挨批,直到病倒被送进医院。尽管如此,他仍被从医院揪出来参加了50多场批斗会。最后,周荣鑫在1976年4月12日上午的批斗会上昏倒并于次日黎明前去世,年仅59岁。中国的教育改革也一时归于沉寂。

第5章 靠边站,1976

追悼会过后,按周恩来遗孀邓颖超的请求,由她陪伴周恩来的骨灰前往机场。在那里,工人将骨灰送上一架飞机,从空中撒向他奉献了一生的中国大地。

第7章 三个转折点,1978

他再次建议首先看大局,然后再想局部;先讲大道理,再讲小道理。

如何鼓励新思想,同时尽量减少保守派干部的抵制;如何既尊重毛泽东,又要摆脱他的路线;如何既保持乐观,又要避免以后的失望;如何既维护稳定,又开放经济;如何既给予地方干部灵活空间,又能维护国家的发展重点。

在12月13日下午中央工作会议的闭幕会上,邓小平一开口就直奔主题:“今天我主要讲一个问题,就是解放思想,开动脑筋,实事求是,团结一致向前看。”邓小平称赞这次工作会议是1957年以来党内最好、最开放的一次讨论会。他说,要允许大家说出对真实情况的看法。“必须有充分的民主,才能做到正确的集中。当前这个时期,特别需要强调民主。因为在过去一个相当长的时间内……民主太少。……应当允许群众提一些意见,即使有个别心怀不满的人,想利用民主闹一点事,也没有什么可怕……最可怕的是鸦雀无声。”邓小平无论在当时还是任何时候,都没有提倡过不受限制的言论自由。

中国的两次大灾难——“大跃进”和“文革”,是由于制度造成的,这种制度允许一人统治,容不下不同的声音。因此中国需要建立法制,这样的话一个人不管能力有多大,都不能由他一个人说了算。法制一开始可能不健全、不完善,但可以逐步使其变得公正合理。

第8章 为自由设限,1978—1979

此外,鼓吹自由民主的人,和他们的批评者一样,对国外的情况缺少体验,知之甚少。他们开始质疑毛泽东思想和马克思主义理论,又看到另一些国家在经济上远比中国发达,于是对西方民主表现出几近天真的信仰。

尽管《人民日报》没有报道西单的事情,但报社里支持民主墙的人在1979年1月3日发表了一篇大胆的社论《发扬民主和实现四化》,其中说,“让人说话,天塌不下来。……真正可怕的倒是听不到不同的声音。……害怕人民讲话,实际是软弱和神经衰弱的表现。……安定团结和发扬民主并不是对立的。”

在这篇重要讲话中,邓小平阐明了不容挑战的四项基本原则,在可接受和不可接受的事情之间划定了界限。写作不能挑战以下四点:(1)社会主义道路;(2)无产阶级专政;(3)共产党的领导;(4)马克思列宁主义和毛泽东思想。

作为一名遵守纪律的党员,胡耀邦于4月3日在务虚会闭幕式的讲话中表示完全拥护邓小平坚持四项基本原则的立场。但是,在务虚会第一阶段听过胡耀邦讲话的人都知道,胡耀邦本人更希望看到一个较为开放的社会,他相信国家不会因为人们更自由地表达不同观点而陷入混乱。

尽管邓小平和胡耀邦都致力于现代化,仍然合作共事,但是在如何划定自由的界限上,他们的分歧却愈演愈烈,最终导致邓小平在1987年决定将胡耀邦撤职。

第9章 苏联—越南的威胁,1978—1979

毛泽东会见外国人时散发着帝王般的自信,谈论哲学、历史和文学,纵论天下大势。周恩来在国内外会见外宾时,则表现得博学而儒雅,他态度亲切,体贴入微,对客人照顾得十分周到。他既谈大事,也愿意讨论细节。

邓小平的战略分析起点和毛泽东是一样的:分清主要敌人,广结盟友与之对抗;分化敌人的盟友,使其疏远敌人。

邓小平要应付的一个棘手问题是,与日美恢复关系时,如何尽量减少对中朝关系的伤害。邓小平不想让朝鲜更加靠近苏联。因此他决定,上策是事先向朝鲜人做出充分解释,不使他们事后感到意外。

即使越南攻入柬埔寨,中国也不会像朝鲜战争时帮助朝鲜那样出兵。邓小平担心陷入其中难以自拔。他决定以军事进攻的方式“给越南一个教训”,拿下几个县城,表明中国可以继续深入,然后迅速撤出。这也可以减少苏联派兵增援越南的风险。越南将由此明白,苏联并不总是能靠得住的,因而要收敛在这个地区的野心。通过攻打越南而不是苏联,中国也可以向苏联表明,它在该地区建立武力的任何做法都是代价高昂的。

1963年马来西亚取得独立后,马来人害怕拥有强大政党的华人可能主导他们的政府。为了避免这种情况,人口的75%是华人、当时仍是马来亚一部分的新加坡在1965年遭到遗弃,被迫变成了一个独立国家。

第10章 向日本开放,1978

中国与这个相邻岛国2200年的交往史中,邓小平是第一个踏上日本国土的中国领导人,也是第一个拜会日本天皇的中国领导人。

第11章 向美国开放,1978—1979

为了实现同美国建交这个目标,邓小平准备在很多问题上采取灵活的立场。然而在一个问题——台湾问题——上,就像毛泽东和周恩来一样,他有着不可动摇的“原则”。除非美国与台湾断交,终止《美台共同防御条约》,撤出全部驻台美军,他不会和美国建交。

这也是埋下了一个伏笔

会谈结束时,邓小平提醒说,如果卡特总统公开宣扬对台售武,中方将不得不做出反应,任何公开争论都将有损于中美建交的重大意义。

就像美国人对邓小平做出了过度反应一样,很多中国人也对邓向美国的开放做出了过度反应。有些中国人想一夜之间就能得到一切,没有意识到在能够享受经济增长的成果之前,中国需要做出多少改变。还有一些人急于引进中国的现实还难以适应的制度和价值观。在中国和西方道路之间找到适当的平衡并不容易,但是对外开放带来了中西的杂交优势和思想的复兴,它们随着时间的推移将重新塑造中国。

他出访是因为他要为自己的国家完成一项任务。他认为自己有责任改善与邻国的关系,向日本和美国进一步敞开国门。这既是为了遏制苏联,也是为中国的现代化争取帮助。现在他已经完成了自己的使命,履行了自己的职责,他可以转向另一些重要任务了。邓小平在当时的13个月里5次出访国外。虽然他又活了18年,但是从此再也没有迈出国门。

第12章 重组领导班子,1979—1980

邓小平对他所说的党内民主的含义做了解释:党员有意见可以提出来,这有助于解决问题;党的领导听取各种意见后,一旦做出决定,党员就要执行。

邓小平年轻时极其敬佩毛泽东,几十年忠诚地为他工作,却被他抛弃了两次,受到公开批判。他的长子由于毛泽东的红卫兵而造成下肢终身瘫痪。如果说他对毛无怨言,那不合情理;尽管邓小平性格刚强,但他也有常人的爱憎之情。不过,在对待历史问题上他并不显露任何个人感情。

文件的第一稿于1980年2月完成。据说邓小平对它并不满意,他把胡耀邦、胡乔木和邓力群叫去,建议他们:(1)要对毛主席的历史作用做出积极评价;(2)本着“实事求是”的精神,说明毛在“文革”中的错误;(3)得出一个全面的结论,使人民能够团结一致向前看。在这三条中,第一条“最重要、最根本、最关键”。不管他本人因毛泽东的批判和决定受过多少罪,他对起草人说,要讲清楚党和人民必须继续坚持毛泽东思想。

第13章 邓小平的统制术

据说北京的三类人有着融洽的关系:(1)“团派”,即过去在共青团工作过的干部;(2)“太子党”,指中共高干子女,其中一些人还有同窗之谊;(3)“秘书帮”,指给高级干部当过秘书的人。但邓小平愿意跟所有这三类人共事,只要他们能干、忠于上级、不搞帮派活动。他鼓励其他人也这样做。

第14章 广东和福建的试验,1979—1984

蛇口由此成为中国第一个允许境外直接投资的地方,也是中国第一个允许境外人士对位于内地的公司进行决策的地区。