首页 > 社交 > 科普中国

25

常驻编辑 科普中国 2022-06-02 子系统   网络   队列   内核   网卡   函数   源码   内存   过程   数据   用户
gEf拜客生活常识网

tcp_sendmsg 这个函数比较长,我们分多次来看它。先看这一段gEf拜客生活常识网

//file: net/ipv4/tcp.c  
int tcp_sendmsg(...)  
{  
 while(...){  
  while(...){  
   //获取发送队列  
   skb = tcp_write_queue_tail(sk);  
  
   //申请skb 并拷贝  
   ......  
  }  
 }  
}  
//file: include/net/tcp.h  
static inline struct sk_buff *tcp_write_queue_tail(const struct sock *sk)  
{  
 return skb_peek_tail(&sk->sk_write_queue);  
}  

理解对 socket 调用 tcp_write_queue_tail 是理解发送的前提。如上所示,这个函数是在获取 socket 发送队列中的最后一个 skb。skb 是 struct sk_buff 对象的简称,用户的发送队列就是该对象组成的一个链表。gEf拜客生活常识网

gEf拜客生活常识网

我们再接着看 tcp_sendmsg 的其它部分。gEf拜客生活常识网

//file: net/ipv4/tcp.c  
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  
  size_t size)  
{  
 //获取用户传递过来的数据和标志  
 iov = msg->msg_iov; //用户数据地址  
 iovlen = msg->msg_iovlen; //数据块数为1  
 flags = msg->msg_flags; //各种标志  
  
 //遍历用户层的数据块  
 while (--iovlen >= 0) {  
  
  //待发送数据块的地址  
  unsigned char __user *from = iov->iov_base;  
  
  while (seglen > 0) {  
  
   //需要申请新的 skb  
   if (copy <= 0) {  
  
    //申请 skb,并添加到发送队列的尾部  
    skb = sk_stream_alloc_skb(sk,  
         select_size(sk, sg),  
         sk->sk_allocation);  
  
    //把 skb 挂到socket的发送队列上  
    skb_entail(sk, skb);  
   }  
  
   // skb 中有足够的空间  
   if (skb_availroom(skb) > 0) {  
    //拷贝用户空间的数据到内核空间,同时计算校验和  
    //from是用户空间的数据地址   
    skb_add_data_nocache(sk, skb, from, copy);  
   }   
   ......  

这个函数比较长,不过其实逻辑并不复杂。其中 msg->msg_iov 存储的是用户态内存的要发送的数据的 buffer。接下来在内核态申请内核内存,比如 skb,并把用户内存里的数据拷贝到内核态内存中。这就会涉及到一次或者几次内存拷贝的开销gEf拜客生活常识网

gEf拜客生活常识网

至于内核什么时候真正把 skb 发送出去。在 tcp_sendmsg 中会进行一些判断。gEf拜客生活常识网

//file: net/ipv4/tcp.c  
int tcp_sendmsg(...)  
{  
 while(...){  
  while(...){  
   //申请内核内存并进行拷贝  
  
   //发送判断  
   if (forced_push(tp)) {  
    tcp_mark_push(tp, skb);  
    __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);  
   } else if (skb == tcp_send_head(sk))  
    tcp_push_one(sk, mss_now);    
   }  
   continue;  
  }  
 }  
}  

只有满足 forced_push(tp) 或者 skb == tcp_send_head(sk) 成立的时候,内核才会真正启动发送数据包。其中 forced_push(tp) 判断的是未发送的数据数据是否已经超过最大窗口的一半了。gEf拜客生活常识网

条件都不满足的话,这次的用户要发送的数据只是拷贝到内核就算完事了!gEf拜客生活常识网

2)传输层发送

假设现在内核发送条件已经满足了,我们再来跟踪一下实际的发送过程。对于上小节函数中,当满足真正发送条件的时候,无论调用的是 __tcp_push_pending_frames 还是 tcp_push_one 最终都实际会执行到 tcp_write_xmit。gEf拜客生活常识网

所以我们直接从 tcp_write_xmit 看起,这个函数处理了传输层的拥塞控制、滑动窗口相关的工作。满足窗口要求的时候,设置一下 TCP 头然后将 skb 传到更低的网络层进行处理。gEf拜客生活常识网

gEf拜客生活常识网

我们来看下 tcp_write_xmit 的源码。gEf拜客生活常识网

//file: net/ipv4/tcp_output.c  
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,  
      int push_one, gfp_t gfp)  
{  
 //循环获取待发送 skb  
 while ((skb = tcp_send_head(sk)))   
 {  
  //滑动窗口相关  
  cwnd_quota = tcp_cwnd_test(tp, skb);  
  tcp_snd_wnd_test(tp, skb, mss_now);  
  tcp_mss_split_point(...);  
  tso_fragment(sk, skb, ...);  
  ......  
  
  //真正开启发送  
  tcp_transmit_skb(sk, skb, 1, gfp);  
 }  
}  

可以看到我们之前在网络协议里学的滑动窗口、拥塞控制就是在这个函数中完成的,这部分就不过多展开了,感兴趣同学自己找这段源码来读。我们今天只看发送主过程,那就走到了 tcp_transmit_skb。

相关阅读:

  • 量子子系统的新理论
  • 无法检验的科学是科学吗?
  • 伊朗成功发射太空拖船“萨曼轨道传输器”
  • HUAWEI
  • 纳莱迪人儿童遗骸发现谜团重重
  • 并不是所有的物联网平台都适合系统集成
  • 网络销售怎么去聊客户(网络销售出单难吗)
  • 多益网络怎么样(广州多益网络工作感受)
  • 网络短视频内容审核标准细则发布:短视频节目不得未经
  • 移动网络怎么样(移动300兆相当于电信多少兆)
    • 网站地图 |
    • 声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不做权威认证,如若验证其真实性,请咨询相关权威专业人士。