Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1371353
  • 博文数量: 244
  • 博客积分: 10311
  • 博客等级: 上将
  • 技术积分: 3341
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-14 21:50
文章分类

全部博文(244)

文章存档

2013年(6)

2012年(5)

2011年(16)

2010年(11)

2009年(172)

2008年(34)

分类: LINUX

2009-04-29 10:43:47

Socket Buffers
   
使用分成许多层,每一层使用其它层的服务,这样的网络协议的一个问题是,每一个协议都需要在传送的时候在数据上增加协议头和尾,而在处理接收的数据的时候需要删除。这让协议之间传送数据缓冲区相当困难,因为每一层都需要找出它的特定的协议头和尾在哪里。一个解决方法是在每一层都拷贝缓冲区,但是这样会没有效率。替代的,
Linux 使用 socket 缓冲区或者说 sock_buffs 在协议层和网络设备驱动程序之间传输数据。 Sk_buffs
包括指针和长度域,允许每一协议层使用标准的函数或方法操纵应用程序数据。
 
 
图 10.4 显示了 sk_buff 数据结构:每一个 sk_buff 都有它关联的一块数据。 Sk_buff 有四个数据指针,用于操纵和管理 socket
缓冲区的数据
参见 include/linux/skbuff.h
head 指向内存中的数据区域的起始。在 sk_buff 和它相关的数据块被分配的时候确定的。
Data 指向协议数据的当前起始为止。这个指针随着当前拥有这个 sk_buff 的协议层不同而变化。
Tail 指向协议数据的当前结尾。同样,这个指针也随拥有的协议层不同而变化。
End 指向内存中数据区域的结尾。这是在这个 sk_buff 分配的时候确定的。
    另有两个长度字段 len 和 truesize ,分别描述当前协议报文的长度和数据缓冲区的总长度。 Sk_buff
处理代码提供了标准的机制用于在应用程序数据上增加和删除协议头和尾。这种代码安全地操纵了 sk_buff 中的 data 、 tail 和 len 字段。
Push 这把 data 指针向数据区域的起始移动,并增加 len 字段。用于在传送的数据前面增加数据或协议头
参见 include/linux/skbuff.h skb_push()
Pull 把 data 指针从数据区域起始向结尾移动,并减少 len 字段。用于从接收的数据中删除数据或协议头。
参见 include/linux/skbuff.h skb_pull()
Put 把 tail 指针向数据区域的结尾移动并增加 len 字段,用于在传输的数据尾部增加数据或协议信息
参见 include/linux/skbuff.h skb_put()
trim 把 tail 指针向数据区域的开始移动并减少 len 字段。用于从接收的数据中删除数据或协议尾
参见 include/linux/skbuff.h skb_trim()
sk_buff 数据结构也包括一些指针,使用这些指针,在处理过程中这个数据结构可以存储在 sk_buff 的双向环形链表中。有通用的 sk_buff
例程,在这些列表的头和尾中增加 sk_buffs 和删除其中的 sk_buff 。
10.5.2 Receiving IP Packets
    第 8 章描述了 Linux 的网络设备驱动程序如何建立到核心以及被初始化。这产生了一系列 device 数据结构,在 dev_base
列表中链接在一起。每一个 device
数据结构描述了它的设备并提供了一组回调例程,当需要网络驱动程序工作的时候网络协议层可以调用。这些函数大多数和传输数据以及网络设备的地址有关。当一个网络设备从它的网络上接收到数据报文的时候,它必须把接收到的数据转换到
sk_buff 数据结构。这些接收的 sk_buff 在接收的时候被网络驱动程序增加到 backlog 队列。如果 backlog 队列增长的太大,那么接收的
sk_buff 就被废弃。如果有工作要执行,这个网络的 button half 标记成准备运行。
参见 net/core/dev.c netif_rx()
    当网络的 bottom half 处理程序被调度程序调用的时候,它首先处理任何等待传送的网络报文,然后才处理 sk_buff 的
backlog backlo 队列,确定接收到的报文需要传送到那个协议层。当 Linux 网络层初始化的时候,每一个协议都登记自己,在 ptype_all
列表或者 ptype_base hash table 中增加一个 packet_type 的数据结构。这个 packet_type
数据结构包括协议类型,一个网络驱动设备的指针,一个协议的数据接收处理例程的指针和一个指针,指向这个列表或者 hash table 下一个 packet_type
数据类型。 Ptype_all 链表用于探测( snoop )从任意网络设备上接收到的所有的数据报文,通常不使用。 Ptype_base hash table
使用协议标识符 hash ,用于确定哪一种协议应该接收进来的网络报文。网络的 bottom half 把进来的 sk_buff 的协议类型和任一表中的一个或多个
packet_type 条目进行匹配。协议可能会匹配一个或多个条目,例如当窥测所有的网络通信的时候,这时,这个 sk_buff 会被克隆。这个 sk_buff
被传递到匹配的协议的处理例程。
参见 net/core/dev.c net_bh()
参见 net/ipv4/ip_input.c ip_recv()
10.5.3 Sending IP Packets
    报文在应用程序交换数据的过程中传送,或者也可能是为了支持已经建立的连接或为了建立连接而由网络协议产生产生。不管数据用什么方式产生,都建立一个包含数据的
sk_buff ,并当它通过协议层的时候增加许多头。
    这个 sk_buff 需要传递到进行传输的网络设备。但是首先,协议,例如 IP ,需要决定使用哪一个网络设备。这依赖于这个报文的最佳路由。对于通过
modem 连接到一个网络的计算机,比如通过 PPP 协议,这种路由选择比较容易。报文应该要么通过 loopback 设备传送给本地主机,要么传送到 PPP
modem 连接的另一端的网关。对于连接到以太网的计算机而言,这种选择比较困难,因为网络上连接了许多计算机。
    对于传送的每一个 IP 报文, IP 使用路由表解析目标 IP 地址的路由。对于每一个 IP
目标在路由表中进行的查找,成功就会返回一个描述要使用的路由的 rtable 数据结构。包括使用的源 IP 地址,网络 device
数据结构的地址,有时候还会有一个预先建立的硬件头。这个硬件头和网络设备相关,包含源和目的物理地址和其它同介质相关的信息。如果网络设备是以太网设备,硬件头会在图
10.1 中显示,其中的源和目的地址会是物理的以太网地址。硬件头和路由缓存在一起,因为在这个路由传送的每一个 IP
报文都需要追加这个头,而建立这个头需要时间。硬件头可能包含必须使用 ARP
协议才能解析的物理地址。这时,发出的报文会暂停,直到地址解析成功。一旦硬件地址被解析,并建立了硬件头,这个硬件头就被缓存,这样以后使用这个接口的 IP
报文就不需要进行 ARP 。
参见 include/net/route.h
10.5.4 Data Fragmentation
    每一个网络设备都有一个最大的报文尺寸,它无法传送或接收更大的数据报文。 IP 协议允许这种数据,会把数据分割成网络设备可以处理的报文大小的更小的单元。
IP 协议头包含一个分割字段,包含一个标记和分割的偏移量。
    当要传输一个 IP 报文的时候, IP 查找用来发送 IP 报文的网络设备。通过 IP
路由表来查找这个设备。每一个设备都有一个字段描述它的最大传输单元(字节),这是 mtu 字段。如果设备的 mtu 比等待传送的 IP 报文的报文尺寸小,那么这个
IP 报文必须被分割到更小的碎片( mtu 大小)。每一个碎片用一个 sk_buff 代表:它的 IP 头标记了它被分割,以及这个 IP
报文在数据中的偏移量。最后一个报文被标记为最后一个 IP 碎片。如果在分割成碎片的过程中, IP 无法分配一个 sk_buff ,这次传送就失败。
    接收 IP 碎片比发送更难,因为 IP 碎片可能以任意顺序被接收,而且它们必须在重组之前全部接收到。每一次一个 IP
报文被接收的时候,都检查它是否是一个 IP 碎片。收到一个消息的第一个碎片, IP 就建立一个新的 ipq 数据结构,并连接到等待组装的 IP 碎片的
ipqueue 列表中。当更多的 IP 碎片接收到的时候,就查到正确的 ipq 数据结构并建立一个新的 ipfrag 数据结构来描述这个碎片。每一个 ipq
数据结构都唯一描述了一个成为碎片的 IP 接收帧,包括它的源和目标 IP 地址,上层协议标识符和这个 IP
帧的标识符。当接收到所有的碎片的时候,它们被组装在一起成为一个单一的 sk_buff ,并传递到下一个协议层去处理。每一个 ipq
包括一个计时器,每一次接收到一个有效的碎片的时候就重新启动。如果这个计时器过期,这个 ipq 数据结构和它的 ipfrag
就被去除,并假设这个消息在传输过程中丢失了。然后由高层的协议负责重新传输这个消息。
参见 net/ipv4/ip_input.c ip_rcv()
10.6 The Address Resolution Protocol (ARP)
    地址解析协议的任务是提供 IP 地址到物理硬件地址的转换,例如以太网地址。 IP 在它把数据(用一个 sk_buff
的形式)传送到设备驱动程序进行传送的时候才需要这种转换。它进行一些检查,看这个设备是否需要一个硬件头,如果是,这个报文的硬件头是否需要重建。 Linux
缓存硬件头以免频繁地重建。如果硬件头需要重建,它就调用和设备相关的硬件头重建例程。所有的一台设备使用相同的通用的头重建例程,然后使用 ARP 服务把目标的 IP
地址转换到物理地址。
参见 net/ipv4/ip_output.c ip_build_xmit()
参见 net/ethernet/eth.c rebuild_header()
    ARP 协议本身非常简单,包含两种消息类型: ARP 请求和 ARP 应答。 ARP 请求包括需要转换的 IP 地址,应答(希望)包括转换的 IP
地址和硬件地址。 ARP 请求被广播到连接到网络的所有的主机,所以,对于一个以太网所有连在以太网上的机器都可以看到这个 ARP 请求。拥有这个请求中包括的 IP
地址的机器会回应这个 ARP 请求,用包含它自己物理地址的 ARP 应答。
    Linux 中的 ARP 协议层围绕着一个 arp_table 数据结构的表而建立。每一个描述一个 IP 和物理地址的对应。这些条目在 IP
地址需要转换的时候创建,随着时间推移变得陈旧的时候被删除。每一个 arp_table 数据结构包含以下域:
Last used 这个 ARP 条目上一次使用的时间
Last update 这个 ARP 条目上一次更新的时间
Flags 描述这个条目的状态:它是否完成等等
IP address 这个条目描述的 IP 地址
Hardware address 转换(翻译)的硬件地址
Hardware header 指向一个缓存的硬件头的指针
Timer 这是一个 timer_list 的条目,用于让没有回应的 ARP 请求超时
Retries 这个 ARP 请求重试的次数
Sk_buff queue 等待解析这个 IP 地址的 sk_buff 条目的列表
    ARP 表包含一个指针( arp_tables 向量表)的表,把 arp_table
的条目链接在一起。这些条目被缓存,以加速对它们的访问。每一个条目用它的 IP 地址的最后两个字节做表的索引进行查找,然后跟踪这个条目链,直到找到正确的条目。
Linux 也缓存从 arp_table 条目预先建立的硬件头,用 hh_cache 数据结构的形式进行缓存。
    当请求一个 IP 地址转换的时候,没有对应的 arp_table 条目, ARP 必须发送一个 ARP 请求消息。它在表中创建一个新的
arp_table 条目,并把需要地址转换的包括了网络报文的 sk_buff 放到这个新的条目的 sk_buff 队列。它发出一个 ARP 请求并让 ARP
过时计时器运行。如果没有回应, ARP 会重试几次。如果仍旧没有回应, ARP 会删除这个 arp_table 条目。任何排队等待这个 IP 地址进行转换的
sk_buff 数据结构会被通知,由传输它们的上层协议负责处理这种失败。 UDP 不关心丢失的报文,但是 TCP 会在一个建立的 TCP
连接上试图重新发送。如果这个 IP 地址的属主用它的硬件地址应答,这个 arp_table 条目标记为完成,任何排队的 sk_buff
会被从对队列中删除,继续传送。硬件地址被写到每一个 sk_buff 的硬件头中。
ARP 协议层也必须回应指明它的 IP 地址的 ARP 请求。它登记它的协议类型( ETH_P_ARP ),产生一个 packet_type
数据结构。这意味着网络设备接收到的所有的 ARP 报文都会传给它。象 ARP 应答一样,这也包括 ARP 请求。它使用接收设备的 device
数据结构中的硬件地址产生 ARP 应答。
    网络拓扑结构不断变化, IP 地址可能被重新分配到不同的硬件地址。例如,一些拨号服务为它建立的每一个连接分配一个 IP 地址。为了让 ARP
表中包括最新的条目, ARP 运行一个定期的计时器,检查所有的 arp_table
条目,看哪一个超时了。它非常小心,不删除包含包含一个或多个缓存的硬件头的条目。删除这些条目比较危险,因为其它数据结构依赖它们。一些 arp_table
条目是永久的,并被标记,所以它们不会被释放。 ARP 表不能增长的太大:每一个 arp_table 条目都要消耗一些核心内存。每当需要分配一个新的条目而 ARP
表到达了它的最大尺寸的时候,就查找最旧的条目并删除它们,从而修整这个表。
10.7 IP Routing
    IP 路由功能确定发向一个特定的 IP 地址的 IP 报文应该向哪里发送。当传送 IP
报文的时候,会有许多选择。目的地是否可以到达?如果可以,应该使用哪一个网络设备来发送?是不是有不止一个网络设备可以用来到达目的地,哪一个最好? IP
路由数据库维护的信息可以回答这些问题。有两个数据库,最重要的是转发信息数据库( Forwarding Information Database
)。这个数据库是已知 IP 目标和它们最佳路由的详尽的列表。另一个小一些,更快的数据库,路由缓存( route cache )用于快速查找 IP
目标的路由。象所有缓存一样,它必须只包括最常访问的路由,它的内容是从转发信息数据库中得来的。
    路由通过 BSD socket 接口的 IOCTL 请求增加和删除。这些请求被传递到具体的协议去处理。 INET
协议层只允许具有超级用户权限的进程增加和删除 IP
路由。这些路由可以是固定的,或者是动态的,不断变化的。多数系统使用固定路由,除非它们本身是路由器。路由器运行路由协议,不断地检查所有已知 IP
目标的可用的路由。不是路由器的系统叫做末端系统( end system )。路由协议用守护进程的形式来实现,例如 GATED ,它们也使用 BSD socket
接口的 IOCTL 来增加和删除路由。
10.7.1 The Route Cache
    不论何时查找一个 IP 路由的时候,都首先在路由缓存中检查匹配的路由。如果在路由缓存中没有匹配的路由,才查找转发信息数据库。如果这里也找不到路由, IP
报文发送会失败,并通知应用程序。如果路由在转发信息数据库而不在路由缓存中,就为这个路由产生一个新的条目并增加到路由缓存中。路由缓存是一个表(
ip_rt_hash_table ),包括指向 rtable 数据结构链的指针。路由表的索引是基于 IP 地址最小两字节的 hash
函数。这两个字节通常在目标中有很大不同,让 hash value 可以最好地分散。每一个 rtable 条目包括路由的信息:目标 IP 地址,到达这个 IP
地址要使用的网络设备( device 结构),可以使用的最大的信息尺寸等等。它也有一个引用计数器( refrence count ),一个使用计数器(
usage count )和上次使用的时间戳(在 jiffies
中)。每一次使用这个路由的时候这个引用计数器就增加,显示利用这个路由的网络连接数目,当应用程序停止使用这个路由的时候就减少。使用计数器每一次查找路由的时候就增加,用来让这个
hash 条目链的 rtable 条目变老。路由缓存中所有条目的最后使用的时间戳用于定期检查这个 rtable
是否太老。如果这个路由最近没有使用,它就从路由表中废弃。如果路由保存在路由缓存中,它们就被排序,让最常用的条目在 hash
链的前面。这意味着当查找路由的时候找到这些路由会更快。
参见 net/ipv4/route.c check_expire()
 
10.7.2 The Forwarding Information Database
 
      转发信息数据库(图 10.5 显示)包含了当时从 IP
的观点看待系统可用的路由。它是非常复杂的数据结构,虽然它已经进行了合理有效的安排,但是它对于参考而言并不是一个快速的数据库。特别是如果每一个传输的 IP
报文都在这个数据库中查找目标会非常慢。这也是为什么要有路由缓存:加速已经知道最佳路由的 IP
报文的传送。路由缓存从这个转发信息数据库得到,表示了它最常用的条目。
    每一个 IP 子网用一个 fib_zone 数据结构表示。所有这些都被 fib_zones hash 表指向。 Hash 索引取自 IP
子网掩码。所有通向同一子网的路由都用排在每一个 fib_zone 数据结构的 fz_list 队列中得的成对的 fib_node 和 fib_info
数据结构来描述。如果这个子网的路由数目变得太大,就生成一个 hash table ,让 fib_node 数据结构的查找更容易。
    对于同一个 IP 子网,可能存在多个路由,这些路由可能穿过多个网关之一。 IP
路由层不允许使用相同的一个网关对于一个子网有多于一个路由。换句话说,如果对于一个子网有多个路由,那么要保证每一个路由都是用不同的网关。和每一个路由关联的是它的量度(
metric ),这是用来衡量这个路由的益处。一个路由的量度,基本上,是它在到达目标子网之前必须跳过的子网数目。这个量度越高,路由越差。
阅读(1158) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~