inet_protocol 结构和 packet_type 结构的作用是类似的,都是作为两层之间的衔接只用,只不过 inet_protocol 结构用于传输层和网络层之间,而 packet_type 结构用于网络层和链路层之间。
综上所述,我们得到网络栈自下而上的数据包传输通道(接收),如下:
硬件监听物理传输介质,进行数据的接收,当完全接收一个数据包后,产生中断(这个过程完全由网络设备硬件负责);
中断产生后,系统调用驱动程序注册的中断处理程序进行数据接收,一般在接收过程中,我们完成数据从硬件缓冲区到内核缓冲区的复制,将其封装为内核特定结构(sk_buff结构),最后调用链路层提供的接口函数netif_rx(net/inet/dev.c),将数据包由驱动层传递到链路层(netif_rx是驱动程序调用,将接收到的数据包缓存与backlog队列中);
网络下半部分执行函数 net_bh,从backlog队列中取数据包,在进行完本层相关处理后,遍历ptype_base 指向的网络层协议队列,进行协议号的匹配,找到协议号匹配的packet_type 结构,调用结构中接收函数,完成数据包从链路层到网络层的传递。对于ARP协议,是调用 p_rcv函数,IP协议则是 ip_rcv函数;
假设数据包是用的是IP协议,那么从链路层传递到网络层时,将进入 ip_rcv 总入口函数,ip_rcv 完成本层处理后,以本层首部(IP首部)中标识的传输层协议号为散列值,对 inet_protos 散列表进行匹配查询,以寻找到核实的 inet_protocol 结构,进而调用结构中接收函数,完成数据包从网络层到传输层的传递。udp协议则调用 udp_rcv函数,tcp 则调用 tcp_rcv函数(前面有源码介绍),等;
假定数据包使用的是tcp传输层协议,那么此时将进入 tcp_rcv函数。所有使用tcp协议的套接字对应sock结构都被挂入tcp_prot全局变量表示的proto结构的 sock_array 数组(实际是个散列表)中,采用以本地端口号为索引的插入方式,所以当tcp_rcv函数接收到一个数据包,在完成必要的检查和处理后,其将以tcp协议首部中目的端口号为索引,在sock_array中得到正确的sock结构队列,在辅之以其他条件遍历该队列进行对应sock结构的查询,以得到匹配的sock结构,然后将数据包挂入该sock结构中的缓存队列中(sock结构中receive_queue字段指向),从而完成数据包的最终接收。
当用户需要读取数据时,其首先根据文件描述符得到对应的节点(inode结构表示),由节点得到对应的socket结构(作为inode结构中union类型字段存在),进而得到对应的sock结构,之后从sock结构的receive_queue指向的队列中取数据包,将数据包中的数据拷贝到用户缓冲区,从而完成数据的读取。
在如今Linux最新版本中,虽然网络栈实现代码作了很大的改变,但这个通道基本未变。
ok,前面介绍的倾向于上层协议向下层协议的衔接工作,即注重于数据包接收通道的创建工作,那么数据包发送通道是如何创建的:那就是下层向上层提供发送接口函数供上层直接进行调用。实际上这部分前面博文已经介绍过了(网络栈数据包发送)
这里再简单叙述一下:驱动程序通过hard_start_xmit函数指针向链路层提供发送函数,链路层提供dev_queue_xmit发送函数供网络层调用,而网络层提供ip_queue_xmit 函数供传输层调用。其中hard_start_xmit函数指针是根据不同的网络设备动态赋值。
另外,这些函数如dev_queue_xmit、ip_queue_xmit 并未采用任何向上层模块注册的方式工作,换句话说,它们都是作为上层模块的已知函数,当上层调用下层发送数据包函数时,直接调用对应函数即可(硬编码的含义)。
当应用层调用write函数开始,我们会先后经历sock_write、inet_write、tcp_write,只有到达tcp_write函数时,才进行数据的真正处理。
tcp_write 函数完成数据的封装:将数据从用户缓冲区复制到内核缓冲区中,并封装在sk_buff结构中,根据网络的拥塞情况,此时可能出现两种情况:一不经过sock结构的write_queue队列直接被发送出去,二数据包暂时缓存在write_queue队列中,稍后发送,作为传输层协议而言,其将调用ip_queue_xmit函数将数据包发往下层–网络层进行处理;
ip_queue_xmit 函数继续对数据帧进行完善后,调用dev_queue_xmit函数将数据包送往链路层进行处理,同时将数据包缓存与sock结构的send_head队列,目的在于tcp协议需要保证可靠性数据传输,一旦出现数据包的丢失,将启动数据包重传机制,重新发送send_head队列中数据包,另外,数据包在传递给ip_queue_xmit函数时,已经从write_queue队列中删除,所以不会出现一个数据包同时存在于这两个队列中。
这两个队列中的数据包的区别在于:write_queue队列中数据包是从用户层接收的新数据包,尚未进行发送,而send_head队列中数据包是tcp协议为了保证可靠性数据传输而缓存的已经发送出去的数据包。对于网络层而言,其将直接调用dev_queue_xmit函数进行发送;