数据报文如何从网卡到应用层

前面的物理层和数据链路层先不介绍。此处通过IPv4进行介绍

1.进入网络层
首先看ip头结构:
在这里插入图片描述

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)   // 小端
         __u8    ihl:4,                 // 首部长度(4位):首部长度指的是IP层头部占32 bit字的数目(也就是IP层头部包含多少个4字节 -- 32位),包括任何选项
                 version:4;             // 版本(4位),目前的协议版本号是4,称作IPv4。(注意头最长是:当4位全部取1即1111=15,那么15*32/8=60B)
 #elif defined (__BIG_ENDIAN_BITFIELD)  // 大端:调换位置
         __u8    version:4,             // 因为这两个字段共享一个字节,所以必须要区分大小端
                 ihl:4;
 #else
 #error  "Please fix <asm/byteorder.h>"
 #endif
         __u8    tos;                   // 服务类型字段(8位): 服务类型(TOS)字段包括一个3 bit的优先权子字段,4 bit的TOS子字段和1 bit未用位但必须置0。4 bit的TOS子字段分别代表:最小时延、最大吞吐量、最高可靠性和最小费用。4 bit中只能设置其中1 bit。如果所有4 bit均为0,那么就意味着是一般服务。
         __be16  tot_len;               // 总长度字段(16位)是指整个IP数据报的长度,以字节为单位。
         __be16  id;                    // 标识字段(16位)唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。
         __be16  frag_off;              // frag_off域的低13位 -- 分段偏移(Fragment offset)域指明了该分段在当前数据报中的什么位置上。除了一个数据报的最后一个分段以外,其他所有的分段(分片)必须是8字节的倍数。这是8字节是基本分段单位。iphdr->frag_off的高3位(1) 比特0是保留的,必须为0;(2) 比特1是“更多分片”(MF -- More Fragment)标志。除了最后一片外,其他每个组成数据报的片都要把该比特置1。 (3) 比特2是“不分片”(DF -- Don't Fragment)标志,如果将这一比特置1,IP将不对数据报进行分片,这时如果有需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。
         __u8    ttl;                   // 生存时间字段设置了数据报可以经过的最多路由器数
         __u8    protocol;              // 协议字段(8位):指明了该将它交给哪个传输进程。TCP是一种可能,但是UDP或者其他的协议也是可能的。
         __sum16 check;                 // 首部检验和字段(16位)是根据IP首部计算的检验和码。它不对首部后面的数据进行计算。
         __be32  saddr;                 // 源IP地址
         __be32  daddr;                 // 目的ip地址
         /*The options start here. */   // 
 };

a.IP层入口ip_rcv函数。函数首先做类似package checksum在内的各个检查,
    IP_INC_STATS_BH给重组碎片进行计数->
    skb_share_check判断缓冲区skb是不是共享->
    pskb_may_pull判断skb和ip头长度,如果skb比ip头长度还小,肯定出错->
    ip_fast_csum校验ip头->
    pskb_trim_rcsum去除空数据,把skb->len和len统一起来->
    ip_rcv_finish
b.ip_rcv_finish函数,决定包的去向:被转发;或传递给上层。
    skb->dst ()== NULL的时候会调用ip_route_input(dst_entry可以理解为路由表的缓冲区,每次主机发送数据时询问路由表后,都会将记录记在一个cache内)->
     #ifdef CONFIG_NET_CLS_ROUTE宏到结束主要是QOS相关操作->
     dst_input->
c.ip_route_input查找路由并且将结果记录到skb->dst中,此时收到的包有两种情形:1)发往本地则调用ip_local_deliver 2)进行转发调用ip_forward.skb->dst->input
    rcu_read_lock()在高速缓存中进行匹配,查找路由表->
    ip_route_input_mc()返回组播处理->
    ip_route_input_slow()缓存中没有找到或者不是组播数据进行处理。
1)发往本机:
    调用ip_local_delived函数,该函数根据package的下一个处理层的protocal number,调用下一层接口,包括tcp_v4_rcv,udp_rcv,icmp_rcv,igmp_rcv。对于TCP来书,函数tcp_v4_rcv函数会被调用,从而处理流程进入TCP栈。
2)转发:
    该流程需要处理TTL,再调用dst_input函数。该函数会
    a.处理netfilter hook
    b.执行 ip fragmentation
    c.调用dev_queue_xmit,进入链路层处理流程。

2.进入传输层
    a.传输层TCP包处理入口在tcp_v4_rcv函数,它会做tcp header检查等处理。
    b.调用__inet_lookup接口查找open socket,如果找不到那么报文会被丢弃。
    c.如果socket和connection一切正常,调用tcp_prequeue使package从内核进入user space(针对prequeue不大了解可以参照博客Linux 网络协议栈收消息过程-TCP Protocol Layer),放进socket的receive queue。然后socket会被唤醒,调用system call,并最终调用tcp_recvmsg函数去从socket recieve queue中获取segment。

3.进入应用层

  1. 每当用户应用调用 read 或者 recvfrom 时,该调用会被映射为/net/socket.c 中的 sys_recv 系统调用,并被转化为 sys_recvfrom 调用,然后调用 sock_recgmsg 函数。
  2. 对于 INET 类型的 socket,/net/ipv4/af inet.c 中的 inet_recvmsg 方法会被调用,它会调用相关协议的数据接收方法。
  3. 对 TCP 来说,调用 tcp_recvmsg。该函数从 socket buffer 中拷贝数据到 user buffer。
  4. 对 UDP 来说,从 user space 中可以调用三个 system call recv()/recvfrom()/recvmsg() 中的任意一个来接收 UDP package,这些系统调用最终都会调用内核中的 udp_recvmsg 方法。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值