首页 > 社交 > 科普中国

25

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

//file: net/ipv4/tcp_output.c  
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,  
    gfp_t gfp_mask)  
{  
 //1.克隆新 skb 出来  
 if (likely(clone_it)) {  
  skb = skb_clone(skb, gfp_mask);  
  ......  
 }  
  
 //2.封装 TCP 头  
 th = tcp_hdr(skb);  
 th->source  = inet->inet_sport;  
 th->dest  = inet->inet_dport;  
 th->window  = ...;  
 th->urg   = ...;  
 ......  
  
 //3.调用网络层发送接口  
 err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);  
}  

第一件事是先克隆一个新的 skb,这里重点说下为什么要复制一个 skb 出来呢?gEf拜客生活常识网

是因为 skb 后续在调用网络层,最后到达网卡发送完成的时候,这个 skb 会被释放掉。而我们知道 TCP 协议是支持丢失重传的,在收到对方的 ACK 之前,这个 skb 不能被删除。所以内核的做法就是每次调用网卡发送的时候,实际上传递出去的是 skb 的一个拷贝。等收到 ACK 再真正删除。gEf拜客生活常识网

第二件事是修改 skb 中的 TCP header,根据实际情况把 TCP 头设置好。这里要介绍一个小技巧,skb 内部其实包含了网络协议中所有的 header。在设置 TCP 头的时候,只是把指针指向 skb 的合适位置。后面再设置 IP 头的时候,在把指针挪一挪就行,避免频繁的内存申请和拷贝,效率很高。gEf拜客生活常识网

gEf拜客生活常识网

tcp_transmit_skb 是发送数据位于传输层的最后一步,接下来就可以进入到网络层进行下一层的操作了。调用了网络层提供的发送接口icsk->icsk_af_ops->queue_xmit()。gEf拜客生活常识网

在下面的这个源码中,我们的知道了 queue_xmit 其实指向的是 ip_queue_xmit 函数。gEf拜客生活常识网

//file: net/ipv4/tcp_ipv4.c  
const struct inet_connection_sock_af_ops ipv4_specific = {  
 .queue_xmit    = ip_queue_xmit,  
 .send_check    = tcp_v4_send_check,  
 ...  
}  

自此,传输层的工作也就都完成了。数据离开了传输层,接下来将会进入到内核在网络层的实现里。gEf拜客生活常识网

4.3 网络层发送处理

Linux 内核网络层的发送的实现位于 net/ipv4/ip_output.c 这个文件。传输层调用到的 ip_queue_xmit 也在这里。(从文件名上也能看出来进入到 IP 层了,源文件名已经从 tcp_xxx 变成了 ip_xxx。)gEf拜客生活常识网

在网络层里主要处理路由项查找、IP 头设置、netfilter 过滤、skb 切分(大于 MTU 的话)等几项工作,处理完这些工作后会交给更下层的邻居子系统来处理。gEf拜客生活常识网

gEf拜客生活常识网

我们来看网络层入口函数 ip_queue_xmit 的源码:gEf拜客生活常识网

//file: net/ipv4/ip_output.c  
int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)  
{  
 //检查 socket 中是否有缓存的路由表  
 rt = (struct rtable *)__sk_dst_check(sk, 0);  
 if (rt == NULL) {  
  //没有缓存则展开查找  
  //则查找路由项, 并缓存到 socket 中  
  rt = ip_route_output_ports(...);  
  sk_setup_caps(sk, &rt->dst);  
 }  
  
 //为 skb 设置路由表  
 skb_dst_set_noref(skb, &rt->dst);  
  
 //设置 IP header  
 iph = ip_hdr(skb);  
 iph->protocol = sk->sk_protocol;  
 iph->ttl      = ip_select_ttl(inet, &rt->dst);  
 iph->frag_off = ...;  
  
 //发送  
 ip_local_out(skb);  
}  

ip_queue_xmit 已经到了网络层,在这个函数里我们看到了网络层相关的功能路由项查找,如果找到了则设置到 skb 上(没有路由的话就直接报错返回了)。gEf拜客生活常识网

在 Linux 上通过 route 命令可以看到你本机的路由配置。gEf拜客生活常识网

gEf拜客生活常识网

在路由表中,可以查到某个目的网络应该通过哪个 Iface(网卡),哪个 Gateway(网卡)发送出去。查找出来以后缓存到 socket 上,下次再发送数据就不用查了。gEf拜客生活常识网

接着把路由表地址也放到 skb 里去。gEf拜客生活常识网

//file: include/linux/skbuff.h  
struct sk_buff {  
 //保存了一些路由相关信息  
 unsigned long  _skb_refdst;  
}  

接下来就是定位到 skb 里的 IP 头的位置上,然后开始按照协议规范设置 IP header。

相关阅读:

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