> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20160304-jiao-xue/this-month-in-freebsd.md).

# FreeBSD 本月动态：访谈 Gleb Smirnoff

* 原文：[This Month in FreeBSD: An Interview with Gleb Smirnoff](https://freebsdfoundation.org/wp-content/uploads/2016/04/This-Month-in-FreeBSD.pdf)
* 作者：**Dru Lavigne**

接下来几期，我们将深入关注 2016 年版本中的若干新特性和背后的开发者。

本月我们对话 **Gleb Smirnoff**，他是 FreeBSD 核心团队、发布工程团队和安全团队的成员，也是 Netflix 公司的高级软件开发工程师。

## 请介绍一下你自己。你是如何接触 FreeBSD 的？在 FreeBSD 项目中承担什么工作？

我从 2000 年开始用 FreeBSD，那时我 18 岁。在大学宿舍，我们建局域网时用 FreeBSD 做路由、网络共享和 Web 服务。很快我就从读文档转向读源码。**netgraph(4)** 让我着迷。2004 年我获得提交者权限，从那时起我的人生就与 FreeBSD 紧紧相连。

## 你一直在开发新一代 **sendfile(2)**，它通常用于提升 Web 服务器性能。请概述原来的 **sendfile(2)** 实现。新的 **sendfile(2)** 是否解决了它的一些不足？带来多大性能提升？

原来的 sendfile 相当直接。它锁定 socket 缓冲区以防止其他写入，为请求的数据分配内存，用从磁盘读取的数据填充这块内存。从磁盘读到数据后，把数据放入 socket 缓冲区，解锁缓冲区并返回。

它的不足在于从磁盘读取数据需要时间。举例来说，假设从磁盘读取请求数据需要 5 毫秒，这意味着每秒只能处理 200 个请求。要处理更多请求，就要为 sendfile 工作派生额外的线程或进程。事实上，我们最终会创建额外的上下文，仅仅用于等待磁盘。

像 nginx 这样的高性能 Web 服务器采用事件分发器模式编写：把所有 socket 和文件描述符置为非阻塞模式，然后轮询。描述符可读或可写时，Web 服务器发送或读取数据，然后处理下一个。当系统调用很快时这种方式工作良好。随着现代需求的增长，原来的 sendfile 成为瓶颈，尤其在连接数和吞吐量方面。

这是一个早在十年前就广为人知的问题。FreeBSD 用一个特殊标志 `SF_NODISKIO` 告诉 sendfile：如果数据未缓存就避免从磁盘读取，并立即返回特殊错误码。Web 服务器随后用 **aio\_read(4)** 预缓存数据，再重试 sendfile。尽管有许多额外操作，也无法保证数据会留在缓存里等 sendfile，但这种方式比让原来的 sendfile 阻塞在磁盘上要好得多。

我们决定做得更好。思路是 sendfile 不等待磁盘读数据，立即返回。这意味着 Web 服务器不会停顿几毫秒，可以继续处理下一个描述符。简而言之，这就是全部思路，但实现起来并不像描述那么简单。

## 实现新的 **sendfile(2)** 是否需要对 FreeBSD 内核或其子系统做大幅修改？实现过程中是否遇到意外的 bug？

是的，需要的改动很大。首先，我们要在内核里创建异步接口来读数据。原来的 **sendfile(2)** 像普通 **read(2)** 系统调用一样通过 `VOP_READ` 文件系统操作与磁盘对话。这本身就有问题，因为这个接口设计用于把数据复制到用户态，而 sendfile 的初衷正是要避免这一步。原来的 sendfile 先把对应待读数据的页面驻留，然后用 `VOP_READ` 把数据复制到一个无人接收的目的地。副作用是这些页面被换入，从而准备好发送给 socket。因此，我们决定不深入探究 `VOP_READ_ASYNC` 接口，而是改用 `VOP_GETPAGES`，它正是为把单个页面带入内存而设计。我们实现了 `VOP_GETPAGES_ASYNC`，并在其上构建了新的 sendfile。

其次，重大改动涉及 socket 缓冲区：我们要把数据放入 socket，但数据尚未就绪。由于这些页面尚未从磁盘读取，我们无法发送。但它们必须占据在 socket 中的位置，以保持 socket 中数据的正确顺序。在统计 socket 缓冲区限制时，也必须把这部分数据计算在内。这一切都要求在 socket 缓冲区中引入“未就绪数据”的概念，和写入、后续激活它的函数。

至于 bug：当然，实验过程中我们发现了不少。谁不会遇到 bug 呢？其中一个有意思的是 `vnode_pager_haspage()` 中的算术 bug，它继承自 FreeBSD 之前的时代，会假定可以读到文件末尾之后。我们是第一个大量使用这个函数的人，所以 20 年后才发现它。该 bug 在 FreeBSD 提交 r282426 中修复。

## 新的 **sendfile(2)** 是 NGINX Inc. 与 Netflix 联合开发的成果。为什么选择 FreeBSD 作为参考实现？是否计划把这一系统调用移植到 Linux 等其他操作系统？

FreeBSD 用于 Netflix OpenConnect CDN \[1] 的核心，向全球 Netflix 客户提供流媒体数据，是互联网上最大的流量来源。所以我们并非为了好玩或思想实验去构建新 sendfile 的参考实现，而是在改进 OpenConnect 软件，让单台服务器能服务更多数据。这顺带改进了 FreeBSD 本身。

我们这边没有把它移植到其他操作系统的计划。我们专注于 FreeBSD。

## 作为 Netflix 的开发者，你有独特机会使用为推动海量互联网流量而设计的 CDN。Netflix 的规模是否需要其他 FreeBSD 改进？Netflix 是否将大部分改进上游以惠及 FreeBSD 社区？

是的，我们为服务自己的流量对 FreeBSD 做了大量额外改动。我们也确实在努力把它们都上游。不过过程并不容易。开源代码的质量标准实际上高于生产代码。生产环境中你只关心自己运行的架构，不关心其他架构；只关心自己的工作负载，只关心你使用的那部分操作系统。要把某些改动上游，我们必须照顾所有其他平台、工作负载、API 和标准的潜在用户。同时，让我们的代码满足开源标准的同时不能让我们自己的场景性能下降。有时同时满足内部和开源两方面的要求会相当困难。

尽管如此，上游化改进的过程仍在继续。关注 emax@、gallatin@、glebius@、imp@、lstewart@、rrs@ 和 scottl@ 的提交，可以了解 FreeBSD 11 和 12 的进展。

## 你还在做其他有趣的项目吗？

眼下我手头没有大项目，但 Netflix 的同事们有很多有趣的工作。

比如基于新 sendfile 构建的 `SSL_sendfile()`。思路是 TLS 会话协商完成后，Web 服务器把会话密钥交给内核，然后就可以在 TLS socket 上使用 sendfile。实现需要两阶段异步数据处理：先是磁盘读，再是加密。只有此后的 socket 中的数据才会被激活。

此外还有 I/O 调度器，对 VM 子系统、TCP、存储驱动和网卡驱动也有大量改进。这些话题其实值得单独成文，我就不展开，留给它们的作者。

\[1] <https://www.nginx.com/blog/why-netflix-chose-nginx-as-the-heart-of-its-cdn/>

***

**Dru Lavigne** 是 FreeBSD 基金会董事，BSD 认证小组主席。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20160304-jiao-xue/this-month-in-freebsd.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
