前面的物理层和数据链路层先不介绍。此处通过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.进入应用层
每当用户应用调用 read 或者 recvfrom 时,该调用会被映射为/net/socket.c 中的 sys_recv 系统调用,并被转化为 sys_recvfrom 调用,然后调用 sock_recgmsg 函数。对于 INET 类型的 socket,/net/ipv4/af inet.c 中的 inet_recvmsg 方法会被调用,它会调用相关协议的数据接收方法。对 TCP 来说,调用 tcp_recvmsg。该函数从 socket buffer 中拷贝数据到 user buffer。对 UDP 来说,从 user space 中可以调用三个 system call recv()/recvfrom()/recvmsg() 中的任意一个来接收 UDP package,这些系统调用最终都会调用内核中的 udp_recvmsg 方法。