PRF:20170628 Notes on BPF and eBPF.md

@qhwdw
This commit is contained in:
Xingyu.Wang 2018-05-12 08:36:56 +08:00
parent f3b17a2f6c
commit 8aa4db62a0

View File

@ -7,17 +7,16 @@
我想在讨论的基础上去写一些笔记,因为,我觉得它超级棒!
这是 [幻灯片][9] 和一个 [pdf][10]。这个 pdf 非常好,结束的位置有一些链接,在 PDF 中你可以直接点击这个链接。
开始前,这里有个 [幻灯片][9] 和一个 [pdf][10]。这个 pdf 非常好,结束的位置有一些链接,在 PDF 中你可以直接点击这个链接。
### 什么是 BPF
在 BPF 出现之前,如果你想去做包过滤,你必须拷贝所有进入用户空间的包,然后才能去过滤它们(使用 “tap”
在 BPF 出现之前,如果你想去做包过滤,你必须拷贝所有的包到用户空间,然后才能去过滤它们(使用 “tap”
这样做存在两个问题:
1. 如果你在用户空间中过滤,意味着你将拷贝所有进入用户空间的包,拷贝数据的代价是很昂贵的。
2. 使用的过滤算法很低效
1. 如果你在用户空间中过滤,意味着你将拷贝所有的包到用户空间,拷贝数据的代价是很昂贵的。
2. 使用的过滤算法很低效。
问题 #1 的解决方法似乎很明显,就是将过滤逻辑移到内核中。(虽然具体实现的细节并没有明确,我们将在稍后讨论)
@ -35,12 +34,11 @@
### 为什么 BPF 要工作在内核中
这里的关键点是包仅仅是个字节的数组。BPF 程序是运行在这些字节的数组上。它们不允许有循环loops),但是,它们 _可以_  有聪明的办法知道 IP 包头IPv6 和 IPv4 长度是不同的)以及基于它们的长度来找到 TCP 端口
这里的关键点是包仅仅是个字节的数组。BPF 程序是运行在这些字节的数组上。它们不允许有循环loop但是它们 _可以_  有聪明的办法知道 IP 包头IPv6 和 IPv4 长度是不同的)以及基于它们的长度来找到 TCP 端口
```
x = ip_header_length
port = *(packet_start + x + port_offset)
```
(看起来不一样,其实它们基本上都相同)。在这个论文/幻灯片上有一个非常详细的虚拟机的描述,因此,我不打算解释它。
@ -48,13 +46,9 @@ port = *(packet_start + x + port_offset)
当你运行 `tcpdump host foo` 后,这时发生了什么?就我的理解,应该是如下的过程。
1. 转换 `host foo` 为一个高效的 DAG 规则
2. 转换那个 DAG 规则为 BPF 虚拟机的一个 BPF 程序BPF 字节码)
3. 发送 BPF 字节码到 Linux 内核,由 Linux 内核验证它
4. 编译这个 BPF 字节码程序为一个原生native代码。例如 [在 ARM 上是 JIT 代码][1] 以及为 [x86][2] 的机器码
4. 编译这个 BPF 字节码程序为一个<ruby>原生<rt>native</rt></ruby>代码。例如,这是个[ARM 上的 JIT 代码][1] 以及 [x86][2] 的机器码
5. 当包进入时Linux 运行原生代码去决定是否过滤这个包。对于每个需要去处理的包,它通常仅需运行 100 - 200 个 CPU 指令就可以完成,这个速度是非常快的!
### 现状eBPF
@ -63,19 +57,15 @@ port = *(packet_start + x + port_offset)
关于 eBPF 的一些事实是:
* eBPF 程序有它们自己的字节码语言,并且从那个字节码语言编译成内核原生代码,就像 BPF 程序
* eBPF 程序有它们自己的字节码语言,并且从那个字节码语言编译成内核原生代码,就像 BPF 程序一样
* eBPF 运行在内核中
* eBPF 程序不能随心所欲的访问内核内存。而是通过内核提供的函数去取得一些受严格限制的所需要的内容的子集。
* eBPF 程序不能随心所欲的访问内核内存。而是通过内核提供的函数去取得一些受严格限制的所需要的内容的子集
* 它们  _可以_  与用户空间的程序通过 BPF 映射进行通讯
* 这是 Linux 3.18 的 `bpf` 系统调用
### kprobes 和 eBPF
你可以在 Linux 内核中挑选一个函数(任意函数),然后运行一个你写的每次函数被调用时都运行的程序。这样看起来是不是很神奇。
你可以在 Linux 内核中挑选一个函数(任意函数),然后运行一个你写的每次函数被调用时都运行的程序。这样看起来是不是很神奇。
例如:这里有一个 [名为 disksnoop 的 BPF 程序][12],它的功能是当你开始/完成写入一个块到磁盘时,触发它执行跟踪。下图是它的代码片断:
@ -92,45 +82,37 @@ b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_start")
```
从根本上来说,它声明一个 BPF 哈希(它的作用是当请求开始/完成时,这个程序去触发跟踪),一个名为 `trace_start` 的函数将被编译进 BPF 字节码,然后附加 `trace_start` 到内核函数 `blk_start_request` 上。
上它声明一个 BPF 哈希(它的作用是当请求开始/完成时,这个程序去触发跟踪),一个名为 `trace_start` 的函数将被编译进 BPF 字节码,然后附加 `trace_start` 到内核函数 `blk_start_request` 上。
这里使用的是 `bcc` 框架,它可以使你写的 Python 化的程序去生成 BPF 代码。你可以在 [https://github.com/iovisor/bcc][13] 找到它(那里有非常多的示例程序)。
这里使用的是 `bcc` 框架,它可以让你写 Python 式的程序去生成 BPF 代码。你可以在 [https://github.com/iovisor/bcc][13] 找到它(那里有非常多的示例程序)。
### uprobes 和 eBPF
因为我知道可以附加 eBPF 程序到内核函数上,但是,我不知道能否将 eBPF 程序附加到用户空间函数上!那会有更多令人激动的事情。这是 [在 Python 中使用一个 eBPF 程序去计数 malloc 调用的示例][14]。
因为我知道可以附加 eBPF 程序到内核函数上,但是,我不知道能否将 eBPF 程序附加到用户空间函数上!那会有更多令人激动的事情。这是 [在 Python 中使用一个 eBPF 程序去计数 malloc 调用的示例][14]。
### 附加 eBPF 程序时应该考虑的事情
* 带 XDP 的网卡(我之前写过关于这方面的文章)
* tc egress/ingress (在网络栈上)
* kprobes任意内核函数
* uprobes很明显任意用户空间函数像带符号的任意 C 程序)
* uprobes很明显任意用户空间函数像带调试符号的任意 C 程序)
* probes 是为 dtrace 构建的名为 “USDT probes” 的探针(像 [这些 mysql 探针][3])。这是一个 [使用 dtrace 探针的示例程序][4]
* [JVM][5]
* 跟踪点
* seccomp / landlock 安全相关的事情
* 更多的事情
* 等等
### 这个讨论超级棒
在幻灯片里有很多非常好的链接,并且在  iovisor 仓库里有个 [LINKS.md][15]。现在已经很晚了,但是,很快我将写我的第一个 eBPF 程序了!
在幻灯片里有很多非常好的链接,并且在  iovisor 仓库里有个 [LINKS.md][15]。虽然现在已经很晚了,但是我马上要去写我的第一个 eBPF 程序了!
--------------------------------------------------------------------------------
via: https://jvns.ca/blog/2017/06/28/notes-on-bpf---ebpf/
作者:[Julia Evans ][a]
作者:[Julia Evans][a]
译者:[qhwdw](https://github.com/qhwdw)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出