第一章概述主要讲述了一个执行发送消息和接受消息的UDP程序,内核经历了哪些事。其中发送和接受的数据都存储在mbuf这个数据结构。
1.UDP示例程序
其中主要的三个系统调用为socket、sendto、recvfrom
socket引出了描述符、sendto引出了输出处理、recvfrom引出了输入处理。
2. 描述符
当调用socket后,会返回一个描述符fd
我们知道,对于文件系统中,fopen函数也会返回一个描述符,描述符指向进程已打开的文件结构,文件结构指向该文件的inode,而inode则存储着该文件的数据所在磁盘的扇区号。
对于网络,socket会返回一个描述符,描述符指向一个文件结构,文件结构不再指向一个inode而是socket,socket中so_type如果是UDP类型,那么so_pcb则指向一个存储所有UDP数据报的PCB的双向队列。
3. 输出处理
输出指的是示例程序调用sendto,本机利用内存mbuf结构封装UDP数据报,然后从传输层到物理层,层层封装,输出到其他电脑(本例其他电脑依然指的是本机)。
处理模块的顺序如图
显然插口层与TCP/IP协议无关。
3.1 插口层
首先sendto中传入的参数有目的ip、目的端口号、以及需要输出的数据。
根据传入的参数,插口层将目的ip、目的端口号存储在一个mbuf中,如下图
将需要传输的150字节数据存储在一个mbuf链表中,如下图
3.2. UDP输出例程
插口层将上述存储数据的mbuf链表的指针传递给协议层的UDP输出例程,并调用UDP输出例程,在UDP输出例程中,需要在这150字节中加入IP首部和UDP首部。具体如下图
第一个mbuf中灰色的72字节部分留给后续层添加以太网首部。
UDP输出例程填写UDP首部和IP首部中它能填写的部分,然后进行UDP校验并把计算和填写在UDP首部,UDP校验需要遍历这150字节数据,这是第二次遍历,第一次是将这150字节数据拷贝到内核mbuf中。
接着UDP输出例程调用IP输出例程。
3.3 IP输出例程
IP输出例程要填写IP首部中剩余字段,包括:IP校验和。确定数据报应发到哪个输出接口(这是IP路由功能),必要时对IP保温分片,以及调用接口输出函数。
假设输出接口是一个以太网接口,再次把mbuf链表的指针作为一个参数,调用一个通用的以太网输出函数
3.4 以太网输出函数
此时协议层已经结束,来到了接口层中的以太网输出。
以太网输出函数第一个功能,就是ARP,即利用32位的目的IP地址获得48位的目的MAC地址。
然后把一个14字节的以太网首部添加到链表的第一个mbuf中,紧接IP首部。
之后此mbuf链表被加到此接口的输出队列队尾。如果接口不忙,接口的"开始输出"例程立即被调用。若接口忙,在它处理完输出队列中其他缓存后,再来处理本mbuf链表。
当接口处理它输出队列的一个mbuf时,它把数据复制到它的传输缓存中,并且开始输出。本例子192字节被复制到传输缓存,14字节以太网首部,20字节IP首部,8字节UDP首部以及150字节用户数据。这是内核第三次遍历数据。一旦mbuf被复制到设备传输缓存,mbuf链表就会被以太网设备驱动程序释放。这三个mbuf被放回到内核的自由缓存池中。
4. 输入处理
输入处理指的是,调用recvfrom,从其他电脑接收到传输过来的mbuf,然后从物理层到传输层再到进程、层层解析,最后将数据读到内存。
输入是异步的,也就是说,当进程调用recvfrom,自身会自主软中断,阻塞,中断程序从物理层到传输层再到进程,层层解析,最后交给调用recvfrom的进程后,输入软中断结束,进程会被唤醒。
4.1 设备驱动程序和以太网输入函数
该部分是接口层(即物理层+数据链路层)
进程调用recvfrom后会中断,首先由以太网设备驱动程序来处理这个中断,假设设备驱动程序接收到传输过来的数据(共54字节,20字节IP首部、8字节UDP首部及26字节数据),那么会把该54字节数据复制到mbuf中,如下图
其中灰色部分16字节留给接口层首部(比如以太网首部),但没有使用。
设备驱动程序把mbuf传给一个通用以太网输入例程。
然后以太网输入例程通过以太网帧中的类型字段确定哪个协议层
来接收此分组。
在这个例子中,类型字段表示一个IP数据报,从而mbuf被加入到IP输入队列中。
另外产生一个软中断来执行IP输入例程。
4.2 IP输入例程
IP输入是异步的,并通过一个软中断来执行。
整个IP输入队列被处理完后返回。
IP输入例程处理每个接收到的IP数据报,它验证IP首部校验和,处理IP选项,验证数据报的目标IP与主机IP是否一致,并当系统被配置为一个路由器且数据报被标注为其他的IP地址时转发此数据报,如果IP数据报到达它的最终目标,调用IP首部中标识的协议(ICMP、IGMP、TCP、UDP)的输入例程。本例是UDP输入例程
4.3 UDP输入例程
UDP输入例程验证UDP首部中的各字段,然后确定一个进程是否应该接收此数据报。
一个进程可以接收到指定UDP端口的所有数据报。
本例中,UDP输入例程从一个全局变量udp开始,查看所有UDP协议控制块链表,寻找一个本地端口号(inp_lport)与接收的UDP数据报的目的端口号匹配的协议控制块。这个PCB由本例程序socket创建。
UDP输入例程将需要发送给进程的数据放入mbuf中,如下图
删除了IP首部和UDP首部,只保留了26字节的数据。
最后mbuf链表指针传递给进程,并且进程被唤醒,等待内核调度。
4.4 进程输入
本例进程调用recvfrom时被阻塞,在内核中处于睡眠状态,现在进程被唤醒。26字节数据被内核从mbuf复制到我们程序的缓存中。
我们程序把recvfrom的第5和第6个参数设置为空指针,告诉系统在接收过程中不关心发送方的IP地址和UDP端口号。这使得系统调用recvfrom略过链表第一个mbuf,仅返回第二个mbuf的26字节数据。
然后内核的recvfrom代码释放这两个mbuf,放回到自由mbuf池中。