linux内核视角看零拷贝

本文详细介绍了零拷贝技术的工作原理,包括内核如何接收用户数据、通过协议栈处理、数据在TCP/IP层的传输,以及为何并非完全无拷贝。特别强调了sendfile系统调用和JavafileChannel.transferTo()的零拷贝机制。
摘要由CSDN通过智能技术生成

一、什么是零拷贝?

在很多性能优化方案中都有提到零拷贝,零拷贝到底是怎么回事,是真的没有数据的拷贝吗?零拷贝(Zero-copy)是一种数据传输技术,旨在减少数据在内核态和用户态之间的复制操作。其实并不是真的没有数据的拷贝。

二、内核发送数据包

2.1 网卡启动准备

linux启动的时候,在网卡能够收发数据包之前,要做很多准备工作。比如ksoftirqd内核线程的创建,注册好协议处理函数,网卡驱动初始化等。这些初始化工作完成后,就可以启动网卡了。网卡启动的时候,会创建好RingBuffer,现在的服务器上的网卡一般都是支持多队列的,每一个队列都是由一个RingBuffer表示
在这里插入图片描述

2.2 数据包发送

整体流程是:用户数据被拷贝到内核态,然后经过协议栈处理后进入网卡RingBuffer,网卡驱动真正将数据发送出去,当发送完成时,网卡发起硬中断来通知CPU,最后清理RingBuffer。

2.2.1 协议栈处理

在这里插入图片描述
用户进程进行系统调用时,找到内核的socket对象,之后进入内核协议栈处理。

在进入协议栈inet_sendmsg函数后,内核会找到socket对象上具体的协议发送函数,对于TCP协议就是tcp_sendmsg。

//file: net/ipv4/tcp.c
int tcp_sendmsg(...){
    while(...){
        ......
        //申请内核态内存skb
        skb = sk_stream_alloc_skb(...);
        //把skb挂到socket的发送队列上,sk就是socket
        skb_entail(sk,skb);
        ......
        //将用户空间的数据拷贝到skb,from是用户空间的数据地址
        skb_add_data_nocache(sk,skb,from,copy);
    }
}

在这里插入图片描述
注意:在协议栈处理这里,完成了一次用户数据到内核socket对象发送队列的拷贝。

2.2.1.1 传输层处理
//file: net/ipv4/tcp_output.c
static bool tcp_write_xmit(...){
    //循环从socket发送队列获取待发送skb
    while((skb = tcp_send_head(sk))){
        ......
        //传输层发送
        tcp_transmit_skb(sk,skb,1,gfp);
    }
}
//file net/ipv4/tcp_output.c
static int tcp_transmit_skb(...){
    //循环socket发送队列克隆出新的skb
    if(likely(clone_it)){
        skb = skb_clone(skb,gfp_mask);
        ......
    }
    //封装TCP头
    th = tcp_hdr(skb);
    th->source  =  inet->inet_sport;
    th->dest    =  inet->inet_dport;
    ......
    //调用网络层发送接口
    ip_queue_xmit(...);
}

在这里插入图片描述
这里需要注意的是:传输层需要从socket发送队列克隆一个新的skb,那么为什么要复制一个skb出来?这是因为skb后续在调用网络层,最后到达网卡发送完成的时候,这个skb会被释放掉。而TCP协议是支持丢失重传的,在收到对方的ACK之前,socket发送队列上的skb不能被删除,等收到ACK再真正删除。 因此,传输层从socket发送队列拷贝skb也是不能少的。
自此,传输层的工作都完成了。数据离开传输层,接下来将会进入内核网络层的处理。

2.2.1.2 网络层处理
//file: net/ipv4/ip_output.c
int ip_queue_xmit(...){
    ......
    //为skb设置路由表,路由表可以查到目的网络应该通过哪个网卡,哪个网关发送出去
    skb_dst_set_noref(skb,&rt->dst);
    //设置IP头
    iph = ip_hdr(skb);
    iph->protocol  = sk->sk_protocol;
    ......
    //发送
    ip_local_out(skb);
}

在这里插入图片描述
如果使用iptables配置了一些规则,那么这里将检测是否命中规则,如果设置复杂的netfiler规则,将会增大CPU开销。

2.2.1.3 邻居子系统处理

邻居子系统是位于网络层和数据链路层中间的一个系统,其作用是为网络层提供一个下层的封装,让下层决定发送到哪个MAC地址。
在这里插入图片描述

2.2.1.4 网络设备子系统处理

在这里插入图片描述
QDisc(queueing discipline )位于IP层和网卡的ringbuffer之间。ringbuffer是一个简单的FIFO队列,这种设计使网卡的驱动层保持简单和快速。而QDisc实现了流量管理的高级功能,包括流量分类,优先级和流量整形(rate-shaping)。

2.2.1.5 网卡驱动处理

在驱动函数中,会将skb挂到RingBuffer上,并且将skb数据映射到网卡可以访问的DMA内存区域。最后驱动会触发真实的数据发送。

三、零拷贝到底是怎么回事?

传统的read + send系统调用:
在这里插入图片描述
sendfile系统调用:

在sendfile系统调用中,数据不需要拷贝到用户空间,用户进程可直接操作内核Page Cache数据,减少了拷贝的次数,所以,零拷贝并不是说完全没有数据拷贝。java的fileChannel.transferTo()底层就是sendfile系统调用。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值