Linux操作系统原理—内核网络协议栈( 五 )


NOTE:在整个协议栈实现中 dev.c 文件的作用重大 , 它衔接了其下的硬件层和其上的网络协议层 , 可以称它为链路层模块 , 或者设备无关层的实现 。
网络协议层:就以 IP 数据报为例 , 从设备无关层向网络协议层传递时会调用 ip_rcv 。 该函数会根据 IP 首部中使用的传输层协议来调用相应协议的处理函数 。 UDP 对应 udp_rcv、TCP 对应 tcp_rcv、ICMP 对应 icmp_rcv、IGMP 对应 igmp_rcv 。 以 tcp_rcv 为例 , 所有使用 TCP 协议的套接字对应的 sock 结构体都被挂入 tcp_prot 全局变量表示的 proto 结构之 sock_array 数组中 , 采用以本地端口号为索引的插入方式 。 所以 , 当 tcp_rcv 接收到一个数据包 , 在完成必要的检查和处理后 , 其将以 TCP 协议首部中目的端口号为索引 , 在 tcp_prot 对应的 sock 结构体之 sock_array 数组中得到正确的 sock 结构体队列 , 再辅之以其他条件遍历该队列进行对应 sock 结构体的查询 , 在得到匹配的 sock 结构体后 , 将数据包挂入该 sock 结构体中的缓存队列中(由 sock 结构体中的 receive_queue 字段指向) , 从而完成数据包的最终接收 。
NOTE:虽然这里的 ICMP、IGMP 通常被划分为网络层协议 , 但是实际上他们都封装在 IP 协议里面 , 作为传输层对待 。
协议无关层和系统调用接口层:当用户需要接收数据时 , 首先根据文件描述符 inode 得到 socket 结构体和 sock 结构体 , 然后从 sock 结构体中指向的队列 recieve_queue 中读取数据包 , 将数据包 copy 到用户空间缓冲区 。 数据就完整的从硬件中传输到用户空间 。 这样也完成了一次完整的从下到上的传输 。
协议栈发包流程概述
1、应用层可以通过系统调用接口层或文件操作来调用内核函数 , BSD socket 层的 sock_write 会调用 INET socket 层的 inet_wirte 。 INET socket 层会调用具体传输层协议的 write 函数 , 该函数是通过调用本层的 inet_send 来实现的 , inet_send 的 UDP 协议对应的函数为 udp_write 。
2、在传输层 udp_write 调用本层的 udp_sendto 完成功能 。 udp_sendto 完成 sk_buff 结构体相应的设置和报头的填写后会调用 udp_send 来发送数据 。 而在 udp_send 中 , 最后会调用 ip_queue_xmit 将数据包下放的网络层 。
3、在网络层 , 函数 ip_queue_xmit 的功能是将数据包进行一系列复杂的操作 , 比如是检查数据包是否需要分片 , 是否是多播等一系列检查 , 最后调用 dev_queue_xmit 发送数据 。
4、在链路层中 , 函数调用会调用具体设备提供的发送函数来发送数据包 , e.g. dev->hard_start_xmit(skb, dev); 。 具体设备的发送函数在协议栈初始化的时候已经设置了 。 这里以 8390 网卡为例来说明驱动层的工作原理 , 在 net/drivers/8390.c 中函数 ethdev_init 的设置如下: