在BSD的TCP/IP代码中的一个基本概念是存储器缓存,成为mbuf,在整个实现中用于存储各种信息。(在linux中使用
的是skbuff,UNUX和LINUX在TCP/IP实现上有不同。)
下图是BSD TCP/IP代码的大概组织。
下图是一个包含socket地址结构的mbuf。
mbuf的前20个字节是首部,它包含关于这个mbuf的一些信息。mbuf总长是128个字节。
mbuf使用成员m_next和m_nextpkt链接起来。
成员m_data指向mbuf中的数据,成员m_len指示它的长度,成员m_type指示包含在mbuf中的数数据类型。上图是MT_SONAME。
接口层将sendto调用中指定的数据缓存中的数据复制到一个或多个mbuf中。下图显示了150个字节的数据时如何存储在两个
mbuf中的。
成员m_next把链表中的所有mbuf都链接在一起。
另一个变化是链表中的第一个mbuf的首部的另外两个成员:m_pkthdr.len和m_pkthdr.rcvif。这两个成员只用于链表的第一个
mbuf中。m_flags的值是M_PKTHDR,只是这个mbuf包含一个分组首部。rcvif包含了一个指向接收分组的接收接收接口结构
指针。
在插口层将目标插口地址结构复制到一个mbuf中,并把数据复制到mbuf链中后,与此接口描述符对应的协议层被调用。即
UDP输出例程被调用,指向mbuf的指针被作为一个参数传递。这个例程要在数据的前面添加一个IP首部和一个UDP首部,
然后将这些mbuf传递给IP输出例程。
mbuf链表中添加这些数据的方法是分配另外一个mbuf,把它放在链首,并将分组首部从带有100字节数据的mbuf复制到
这个mbuf。
输入处理与输出处理不同,因为输入时异步的。它是通过一个接受完成中断驱动以太网设备驱动程序来接收一个输入分组,而
不是通过进程的系统调用。内核处理这个设备中断,并调度设备驱动程序进入运行状态。
以太网设备驱动程序处理这个中断,假定它表示一个正常的接收已完成,数据从设备读到mbuf链表中。如下图所示。
这个mbuf是一个分组首部(m_flag被设置成M_PKTHDR),它是一个数据记录的第一个mbuf。分组首部的成员len包含
数据的总长度,成员rcvif包含一个指针,它指向接收数据的接口接口的结构。rcvif用于接收分组而不是输出分组。数据就
存放在这个mbuf中。
设备驱动程序把mbuf传给一个通用以太网输入例程,它通过以太网帧中的类型字段来确定哪个协议层来接收此分组,在
这个例子中,类型字段标识一个IP数据报,从而mbuf被加入到IP输入队列,另外会产生一个软中断来执行IP输入例程。
IP输入是异步的,并且通过一个软中断来执行,当接口层在系统的一个接口上收到一个IP数据报时,它就设置这个软中断,
当IP输入例程执行它时,循环处理在它的输入队列中每一个IP数据报,并在整个队列被处理完后返回。
IP输入例程处理每个接收到的IP数据包。它验证IP首部检验和,处理IP选项,然后进行转发或者调用IP首部中标识的协议的
输入例程。
UDP输入例程验证UDP首部中的各字段,然后确定是否一个进程应该接收此数据报。
UDP输入例程从一个全局变量开始,查看所有UDP协议控制块链表,寻找一个本地端口号与接收的UDP数据报的目标端口号
匹配的协议控制块。这个PCB是由我们调用socket创建的,它的成员inp_socket指向相应插口结构,并允许接收的数据在此
插口排队。
因为这个UDP数据报要传递给我们的进程,发送方的IP地址和UDP端口号被放置到一个mbuf中,这个mbuf和数据被追加到此
插口的接收队列中。下图是被追加到这个插口的接收队列中的这两个mbuf。
上图的第二个mbuf,成员m_len和m_pkthdr.len都减少了28个字节,并且指针m_data也减少了28个字节,这有效第将IP的UDP
首部删去。只保留了26字节数据追加到插口接收队列。
在链表的第一个mbuf中包括一个16字节internet插口地址结构,它带有发送方IP地址和UDP端口号。它的类型是MT_SONAME。
这个mbuf是插口层创建的,将这些信息返回给通过调用系统调用recvform或recvmsg的调用进程。即使这个链表的第二个mbuf中
有空间存储这个插口地址结构,它也必须存放到它自己的mbuf,因为它们的类型不同,一个是MT_SONAME,一个是MT_DATA。
我们的进程调用recvfrom时被阻塞,在内核中处以休眠状态,现在进程被唤醒。UDP层追加到插口接收队列中的26字节的数据被
内核从mbuf复制到我们程序的缓存中。