linux为了解决对读文件产生的从应用空间到内核空间复制数据产生的效率影响引进了零拷贝。什么是零拷贝?这里就不多说了,我们主要是讲述nginx怎么是用sendfile的。

nginx通过使用sendfile指令来控制是不是用linux提供的零拷贝功能。具体配置如下:

 

sendfile on/off


注:这个指令只能用在HTTP框架下。

上面指令的作用就是给struct ngx_http_core_loc_conf_s 的sendfile赋值,逻辑也比较简单。

 

下面以nginx做源服务来讲述这个功能。nginx做源服务都会通过ngx_http_static_handler讲url转换成文件的目录(ngx_http_map_uri_to_path)。

 

static ngx_int_t
ngx_http_static_handler(ngx_http_request_t *r)
{
......
ngx_memzero(&of, sizeof(ngx_open_file_info_t));
......
if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
!= NGX_OK)
......
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
b->file_pos = 0;
b->file_last = of.size;

b->in_file = b->file_last ? 1: 0;
b->last_buf = (r == r->main) ? 1: 0;
b->last_in_chain = 1;

b->file->fd = of.fd;
b->file->name = path;
b->file->log = log;
b->file->directio = of.is_directio;

out.buf = b;
out.next = NULL;

return ngx_http_output_filter(r, &out);
......
}


通过 ngx_http_map_uri_to_path找到路径后就会调用ngx_open_cached_file打开文件,并获取到文件的相关信息。下面创建一个buf,注意这个buf的初始化在后面会用到。ngx_http_output_filter进入到filter链中。最后会调用到ngx_http_copy_filter。

 

 



static ngx_int_t
ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
......
ctx->sendfile = c->sendfile;
ctx->need_in_memory = r->main_filter_need_in_memory
|| r->filter_need_in_memory;
ctx->need_in_temp = r->filter_need_temporary;
for ( ;; ) {
rc = ngx_output_chain(ctx, in);
......
}

注意ctx->sendfile的值也是非要重要的。


ngx_int_t
ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
{
......
if (in->next == NULL
#if (NGX_SENDFILE_LIMIT)
&& !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT)
#endif
&& ngx_output_chain_as_is(ctx, in->buf))
{
return ctx->output_filter(ctx->filter_ctx, in);
}
......
}

static ngx_inline ngx_int_t
ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)
{
......
if (!sendfile) {

if (!ngx_buf_in_memory(buf)) {
return 0;
}

buf->in_file = 0;
}
......
}

这里面sendfile和buf中in_file就是判断数据是不是在文件中。如果上面的判断为真就会直接调用发送函数(ngx_linux_sendfile_chain),要不然就会调用ngx_output_chain_copy_buf把数据从文件中读取出来。

讲到现在也没讲到sendfile指令是怎么影响nginx是否使用linux的零拷贝的。上面代码中其中有一段是这样的:


ctx->sendfile = c->sendfile


前面讲过sendfile会影响是否读取文件内容,而这个sendfile就是ctx->sendfile的值。那c->sendfile,我们来看下ngx_http_update_location_config:



void
ngx_http_update_location_config(ngx_http_request_t *r)
{
......
if ((ngx_io.flags & NGX_IO_SENDFILE) && clcf->sendfile) {
r->connection->sendfile = 1;

} else {
r->connection->sendfile = 0;
}
......
}

到这里就能理解了sendfile怎么影响nginx的sendfile处理了。下面来看最后一个函数, ngx_linux_sendfile_chain


ngx_chain_t *
ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
{
......
if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) {
file = cl->buf;
......
if (file) {
......
rc = sendfile(c->fd, file->file->fd, &offset, file_size);
......
} else {
rc = writev(c->fd, header.elts, header.nelts);
......
}

可以看出如果file有值就会调用sendfile,但是在上面的if语句中cl->buf->in_file在前面判断是否读取文件内容的时候会进行修改,也就是如果要读取文件内容就会复位0,这样读取了文件内容后就直接调用writev发送数据,否则就调用sendfile发送文件到客户端。