5.7 TCP receive copy offload

  TCP receive copy offload是一种异步IO技术,这种技术使用网络协议栈中的DMA引擎代替CPU来实现copy-to-user的操作,节约了CPU周期。DMA引擎是指数据移动加速(Data Movement Acceleration)引擎,它将某些传输数据的操作从CPU转移到专用硬件,从而可以进行异步传输并减轻CPU负载。

  支持这个功能需要开启CONFIG_NET_DMA内核编译选项(硬件方面的支持目前尚不清楚)。DMA接收器会在tcp_recvmsg函数中安装:

 1545 int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
1546         size_t len, int nonblock, int flags, int *addr_len)
1547 {
1548     struct tcp_sock *tp = tcp_sk(sk);
1549     int copied = 0;
1550     u32 peek_seq;
1551     u32 *seq;
1552     unsigned long used;
1553     int err;
1554     int target;     /* Read at least this many bytes */
1555     long timeo;
1556     struct task_struct *user_recv = NULL;
1557     bool copied_early = false;
1558     struct sk_buff *skb;
1559     u32 urg_hole = 0;
...
1596 #ifdef CONFIG_NET_DMA
1597     tp->ucopy.dma_chan = NULL;
1598     preempt_disable(); //禁止内核抢占
1599     skb = skb_peek_tail(&sk->sk_receive_queue);
1600     {
1601         int available = 0;
1602
1603         if (skb)
1604             available = TCP_SKB_CB(skb)->seq + skb->len - (*seq);//计算receive_queue队列中未copy数据的总长度
1605         if ((available < target) && //当前接收队列中未读数据长度无法满足要求
1606             (len > sysctl_tcp_dma_copybreak) && !(flags & MSG_PEEK) && //进程缓存中的空间大于启用DMA copy的下限
1607             !sysctl_tcp_low_latency && //允许较大延迟
1608             net_dma_find_channel()) { //能够找到DMA通道
1609             preempt_enable_no_resched(); //激活内核抢占但不再检查任何被挂起的需调度任务
1610             tp->ucopy.pinned_list =
1611                     dma_pin_iovec_pages(msg->msg_iov, len); //固定住用户缓存中len长度的页用于DMA通信
1612         } else {
1613             preempt_enable_no_resched();
1614         }
1615     }
1616 #endif
...
1748 #ifdef CONFIG_NET_DMA
1749         if (tp->ucopy.dma_chan) { //通过DMA传输过数据
1750             if (tp->rcv_wnd == 0 && //通告窗口为0,意味着接收缓存空间十分紧张
1751                 !skb_queue_empty(&sk->sk_async_wait_queue)) { //异步等待队列非空
1752                 tcp_service_net_dma(sk, true); //清理全部sk->sk_async_wait_queue中等待DMA传输确认的包
1753                 tcp_cleanup_rbuf(sk, copied);
1754             } else
1755                 dma_async_issue_pending(tp->ucopy.dma_chan); //使用异步机制等待数据到来
1756         }
1757 #endif
1758         if (copied >= target) {
1759             /* Do not sleep, just process backlog. */
1760             release_sock(sk);
1761             lock_sock(sk);
1762         } else
1763             sk_wait_data(sk, &timeo);
1764
1765 #ifdef CONFIG_NET_DMA
1766         tcp_service_net_dma(sk, false);  /* Don't block */ //在release_sock过程中会有一些包由软中断利用DAM传输数据到用户缓存
1767         tp->ucopy.wakeup = 0; //允许在快速路径中进行early copy
1768 #endif
...
1826         if (!(flags & MSG_TRUNC)) {
1827 #ifdef CONFIG_NET_DMA
1828             if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
1829                 tp->ucopy.dma_chan = net_dma_find_channel();//查找DMA通道
1830
1831             if (tp->ucopy.dma_chan) {
1832                 tp->ucopy.dma_cookie = dma_skb_copy_datagram_iovec(
1833                     tp->ucopy.dma_chan, skb, offset,
1834                     msg->msg_iov, used,
1835                     tp->ucopy.pinned_list);//利用DMA将数据copy到用户缓存空间;这个copy是异步的,此函数返回时数据copy可能尚未完成
1836
1837                 if (tp->ucopy.dma_cookie < 0) { //DMA数据传输出现异常
1838
1839                     pr_alert("%s: dma_cookie < 0\n",
1840                          __func__);
1841
1842                     /* Exception. Bailout! */
1843                     if (!copied)
1844                         copied = -EFAULT;
1845                     break; 
1846                 }
1847
1848                 dma_async_issue_pending(tp->ucopy.dma_chan);//将等待中的事件交由硬件处理
1849
1850                 if ((offset + used) == skb->len) //整个skb的数据全部copy了
1851                     copied_early = true;
1852
1853             } else
1854 #endif
...
1883         if (!(flags & MSG_PEEK)) {         
1884             sk_eat_skb(sk, skb, copied_early);
1885             copied_early = false;      
1886         }
1887         continue;        
1888 
1889     found_fin_ok:
1890         /* Process the FIN. */
1891         ++*seq;          
1892         if (!(flags & MSG_PEEK)) {         
1893             sk_eat_skb(sk, skb, copied_early);
1894             copied_early = false;      
1895         }
..
1918 #ifdef CONFIG_NET_DMA
1919     tcp_service_net_dma(sk, true);  /* Wait for queue to drain */
1920     tp->ucopy.dma_chan = NULL;
1921
1922     if (tp->ucopy.pinned_list) {
1923         dma_unpin_iovec_pages(tp->ucopy.pinned_list); //释放DMA资源
1924         tp->ucopy.pinned_list = NULL;
1925     }
1926 #endif
...
  1597-1613:安装DMA接收器。值得注意的是,receive队列中未读数据如果足够多则不会安装接收器;另外, net.ipv4.tcp_low_latency内核选项为必须为0,且不能使用PEEK模式

  1837-1845:出现异常则会退出循环,skb则还会留在receive_queue中

  1850-1851:如果整个skb的数据由DMA传输完毕,则copied_early的值为true

  1884-1885、1893-1894:在调用sk_eat_skb函数删除skb时:

2187 #ifdef CONFIG_NET_DMA 
2188 static inline void sk_eat_skb(struct sock *sk, struct sk_buff *skb, bool copied_early)
2189 {
2190     __skb_unlink(skb, &sk->sk_receive_queue);
2191     if (!copied_early)
2192         __kfree_skb(skb);
2193     else
2194         __skb_queue_tail(&sk->sk_async_wait_queue, skb);
2195 }
2196 #else
2197 static inline void sk_eat_skb(struct sock *sk, struct sk_buff *skb, bool copied_early)
2198 {
2199     __skb_unlink(skb, &sk->sk_receive_queue);
2200     __kfree_skb(skb);
2201 }   
2202 #endif
  可见对于DMA传输成功的skb不会释放,只是会将其从receive_queue中转移到async_wait_queue中作为备份,以便DMA传输失败时使用。
  tcp_service_net_dma负责处理 async_wait_queue中的skb:

1399 #ifdef CONFIG_NET_DMA
1400 static void tcp_service_net_dma(struct sock *sk, bool wait)
1401 {
1402     dma_cookie_t done, used;
1403     dma_cookie_t last_issued;
1404     struct tcp_sock *tp = tcp_sk(sk);
1405 
1406     if (!tp->ucopy.dma_chan)
1407         return;
1408 
1409     last_issued = tp->ucopy.dma_cookie;
1410     dma_async_issue_pending(tp->ucopy.dma_chan);
1411 
1412     do {
1413         if (dma_async_is_tx_complete(tp->ucopy.dma_chan,
1414                           last_issued, &done,
1415                           &used) == DMA_SUCCESS) { //最后一次DMA传输成功
1416             /* Safe to free early-copied skbs now */
1417             __skb_queue_purge(&sk->sk_async_wait_queue); //释放整个队列
1418             break;
1419         } else { //否则再传输一次
1420             struct sk_buff *skb;
1421             while ((skb = skb_peek(&sk->sk_async_wait_queue)) &&
1422                    (dma_async_is_complete(skb->dma_cookie, done,
1423                               used) == DMA_SUCCESS)) {
1424                 __skb_dequeue(&sk->sk_async_wait_queue);
1425                 kfree_skb(skb);
1426             }
1427         }
1428     } while (wait); 
1429 }
1430 #endif
  1428:wait为true则会一直循环到成功传输全部数据为止 ,否则只传输一次

  在进程释放sock时,如果DMA接收器已经安装则skb不会被放入prequeue队列中:

 1961 int tcp_v4_rcv(struct sk_buff *skb)
1962 {
1963     const struct iphdr *iph;
1964     const struct tcphdr *th;
1965     struct sock *sk;
1966     int ret;
1967     struct net *net = dev_net(skb->dev);
...
2026     if (!sock_owned_by_user(sk)) {
2027 #ifdef CONFIG_NET_DMA
2028         struct tcp_sock *tp = tcp_sk(sk);
2029         if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)//tp->ucopy.pinned_list不为空意味着有用户进程在通过DMA通道收取数据
2030             tp->ucopy.dma_chan = net_dma_find_channel();
2031         if (tp->ucopy.dma_chan)
2032             ret = tcp_v4_do_rcv(sk, skb);
2033         else
2034 #endif
2035         {
2036             if (!tcp_prequeue(sk, skb))
2037                 ret = tcp_v4_do_rcv(sk, skb);
2038         }
...
  在快速处理路径中也会使用DMA收数据:
 5076 int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
5077             const struct tcphdr *th, unsigned int len)
5078 {
5079     struct tcp_sock *tp = tcp_sk(sk);
...
5160             int eaten = 0;
5161             int copied_early = 0;
5162             bool fragstolen = false;
5163
5164             if (tp->copied_seq == tp->rcv_nxt &&
5165                 len - tcp_header_len <= tp->ucopy.len) {
5166 #ifdef CONFIG_NET_DMA
5167                 if (tp->ucopy.task == current && //处于进程上下文
5168                     sock_owned_by_user(sk) && //进程正在掉release_sock处理backlog中的包
5169                     tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {//利用DMA传送数据到用户缓存
5170                     copied_early = 1;
5171                     eaten = 1;
5172                 }
5173 #endif
5174                 if (tp->ucopy.task == current &&
5175                     sock_owned_by_user(sk) && !copied_early) { //如果DMA传输成功则不用再copy数据了
5176                     __set_current_state(TASK_RUNNING);
5177 
5178                     if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
5179                         eaten = 1;
5180                 }
...
5238 no_ack:
5239 #ifdef CONFIG_NET_DMA    
5240             if (copied_early)
5241                 __skb_queue_tail(&sk->sk_async_wait_queue, skb);//将DMA传输成功的包放入队列中作为备份
5242             else
5243 #endif
...
  tcp_dma_try_early_copy函数会使用DMA传输数据:
4935 #ifdef CONFIG_NET_DMA    
4936 static bool tcp_dma_try_early_copy(struct sock *sk, struct sk_buff *skb,
4937                   int hlen)
4938 {
4939     struct tcp_sock *tp = tcp_sk(sk);
4940     int chunk = skb->len - hlen;   
4941     int dma_cookie;
4942     bool copied_early = false;
4943
4944     if (tp->ucopy.wakeup) //不允许在快速路径中进行early copy
4945         return false;
4946
4947     if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
4948         tp->ucopy.dma_chan = net_dma_find_channel();
4949
4950     if (tp->ucopy.dma_chan && skb_csum_unnecessary(skb)) {
4951
4952         dma_cookie = dma_skb_copy_datagram_iovec(tp->ucopy.dma_chan,
4953                              skb, hlen,                     
4954                              tp->ucopy.iov, chunk,          
4955                              tp->ucopy.pinned_list); //利用DMA传输数据
4956
4957         if (dma_cookie < 0) //传输失败
4958             goto out;    
4959
4960         tp->ucopy.dma_cookie = dma_cookie;
4961         copied_early = true;
4962
4963         tp->ucopy.len -= chunk;        
4964         tp->copied_seq += chunk;       
4965         tcp_rcv_space_adjust(sk); //调整接收缓存空间
4966
4967         if ((tp->ucopy.len == 0) ||        //用户空间已满
4968             (tcp_flag_word(tcp_hdr(skb)) & TCP_FLAG_PSH) || //有PSH标记位
4969             (atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1))) { //TCP接收缓存空间紧张
4970             tp->ucopy.wakeup = 1;           //禁用快速路径中的earl copy
4971             sk->sk_data_ready(sk, 0);   //唤醒进程收数据
4972         }
4973     } else if (chunk > 0) { //skb中有数据
4974         tp->ucopy.wakeup = 1; //禁用快速路径中的earl copy
4975         sk->sk_data_ready(sk, 0);   //唤醒进程收数据
4976     }
4977 out:
4978     return copied_early;
4979 }
4980 #endif /* CONFIG_NET_DMA */
  在使用DMA传输数据时,虽然 net.ipv4.tcp_low_latency内核选项为0,但prequeue队列会无法使用。不过在DMA传输失败或在慢速处理路径中接收数据时,TCP所使用的数据copy方法与prequeue机制是一样的。

你可能感兴趣的:(tcp,linux内核)