Sendfile & Zero-copy

1. 典型IO调用

一个典型的IO调用过程如下:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

首先调用read将文件从磁盘读取到tmp_buf,然后调用write将tmp_buf写入到socket,在这过程中会出现四次数据 copy,过程如下图:


  1. 当调用 read 系统调用时,通过DMA(Direct Memory Access)将数据copy到内核模式
  2. 然后由 CPU 控制将内核模式数据 copy 到用户模式下的 buffer 中
  3. read 调用完成后,write 调用首先将用户模式下 buffer 中的数据 copy 到内核模式下的 socket buffer
  4. 最后通过 DMA copy 将内核模式下的 socket buffer 中的数据 copy 到网卡设备中传送

从上面的过程可以看出,数据从内核模式到用户模式走了一 圈,浪费了两次copy,而这两次copy都是CPU copy,即占用CPU资源。


2. sendfile zero-copy

Linux 2.1 版本内核引入了 sendfile 函数,用于将文件通过socket传送。

sendfile(socket, file, len);

该函数通过一次系统调用完成了文件的传送,减少了原来 read/write 方式的模式切换。此外更是减少了数据的copy,sendfile的详细过程如下图


  1. 首先通过 DMA copy 将数据从磁盘读取到 kernel buffer
  2. 然后通过 CPU copy 将数据从 kernel buffer copy到 sokcet buffer
  3. 最终通过 DMA copysocket buffer 中数据 copy 到网卡 buffer 中发送

sendfile 与 read/write 方式相比,少了 一次模式切换和一次 CPU copy。但是从上述过程中也可以发现从 kernel buffer 中将数据 copy 到 socket buffer 是没必要的。



Linux 2.4 内核对 sendfile 做了改进,如下图所示



改进后的处理过程如下:

  1. DMA copy 将磁盘数据 copy 到 kernel buffer
  2. socket buffer 中追加当前要发送的数据在 kernel buffer 中的位置和偏移量
  3. DMA gather copy 根据 socket buffer 中的位置和偏移量直接将 kernel buffer 中的数据 copy 到网卡上。

经过上述过程,数据只经过了 2 次 copy 就从磁盘传送出去了。

3. Java NIO transferTo()

Java NIO中

FileChannel.transferTo(long position, long count, WriteableByteChannel target)

方法将当前通道中的数据传送到目标通道target中,在支持 Zero-Copy 的linux系统中,transferTo() 的实现依赖于 sendfile()调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码是函数 `bsdtcp_server_send` 的定义,它接受五个参数:`data`(要发送的数据的指针),`num`(要发送的数据的数量),`sdidx`(socket描述符索引),`srv`(指向 `bsdtcp_server_ds` 结构体的指针),以及 `timeout`(超时时间)。函数返回一个 `int32_t` 类型的发送计数。 首先,代码定义了一些变量,包括指向 `client_conn_info` 结构体的指针 `cci`,字符变量 `optval`,以及其他一些用于计数和状态的变量。 接下来,代码进行了一系列的条件判断。首先,检查传入的参数是否有效,包括 `data` 不为空且 `num` 大于零,以及 `srv` 和 `srv->res` 都不为空且具有正确的魔数。如果参数检查失败,代码通过 `goto exit` 跳转到函数结束处。 然后,代码根据给定的 `sdidx` 值来确定要使用的客户端连接信息 `cci`。如果 `sdidx` 在有效范围内且对应的连接被标记为使用且活动状态,则获取 `SO_KEEPALIVE` 套接字选项的值。如果该选项的值为0,则跳转到标签 `ok` 处。 如果没有找到合适的客户端连接信息,则使用默认的活动连接信息 `srv->actcci` 进行相同的操作。 如果仍然没有找到合适的客户端连接信息,则遍历所有的连接信息,直到找到一个 `SO_KEEPALIVE` 选项值为0的连接,并将其设置为活动连接信息。如果找到了合适的连接信息,则跳转到标签 `ok` 处。 在标签 `ok` 处,调用 `cci_send` 函数发送数据,并将返回值赋给 `sendcnt` 变量。 最后,通过 `goto exit` 跳转到函数结束处,在 `exit` 标签处返回 `sendcnt` 变量的值。 需要注意的是,这段代码只是一个函数定义,没有提供关于 `cci_send` 函数的具体实现细节。要完整理解代码的功能和行为,需要查看 `cci_send` 函数的实现代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值