临江网
标题:
Linux Kernel核心中文手册 10-1
[打印本页]
作者:
sam
时间:
2007-7-18 22:58
标题:
Linux Kernel核心中文手册 10-1
10.4.1 Creating a BSD Socket (创建一个 BSD Socket )
创建一个新的 socket 的系统调用需要传递它的地址族的标识符、 socket 的类型和协议。首先,用请求的地址族在 pops 向量表中查找一个匹配的地址族。它可能是一个使用核心模块实现的特殊的地址族,如果这样, kerneld 核心进程必须加载这个模块,我们才能继续。然后分配一个新的 socket 数据结构来表示这个 BSD socket 。实际上这个 socket 数据结构物理上是 VFS inode 数据结构的一部分,分配一个 socket 实际上就是分配一个 VFS inode 。这看起来比较奇怪,除非你考虑让 socket 可以用和普通文件一样的方式进行操作。象所有文件都用 VFS inode 数据结构表示一样,为了支持文件操作, BSD socket 也必须用一个 VFS inode 数据结构表示。
这个新创建的 BSD socket 数据结构包括一个指针指向和地址族相关的 socket 例程,这个指针被设置到从 pops 向量表中取出的 proto_ops 数据结构。它的类型被设置成请求的 socket 类型: SOCK_STREAM 、 SOCK_DGRAM 等等其中之一,然后用 proto_ops 数据结构中保存的地址调用和地址族相关的创建例程。
然后从当前进程的 fd 向量表中分配一个空闲的文件描述符,它所指向的 file 数据结构也被初始化。这包括设置文件操作指针,指向 BSD socket 接口支持的 BSD socket 文件操作例程。所有将来的操作会被定向到 socket 接口,依次通过调用支撑的地址族的操作例程传递到相应的地址族。
10.4.2 Binding an Address to an INET BSD Socket (为一个 INET BSD socket 绑定一个地址)
为了监听进来的网际连接请求,每一个服务器必须创建一个 INET BSD socket 并把自己的地址绑定到它上面。 Bind 的操作大部分由 INET socket 层处理,另一些需要底层的 TCP 和 UDP 协议层的支持。已经绑定了一个地址的 socket 不能用于其它通讯。这意味着这个 socket 的状态必须是 TCP_CLOSE 。传递给 bind 操作的 sockaddr 包括要绑定的 IP 地址和一个端口号(可选)。通常,绑定的地址会是分配给支持 INET 地址族的网络设备的地址之中的一个,而且接口必须是开启的并能够使用。你可以用 ifconfig 命令看系统中哪一个网络接口当前是激活的。 IP 地址也可以是 IP 广播地址(全是 1 或 0 )。这是意味着“发送给每一个人”的特殊地址。如果这个机器作为一个透明的 proxy 或者防火墙,这个 IP 地址也可以设置成任何 IP 地址。不过只有具有超级用户特权的进程可以绑定任意 IP 地址。这个绑定的 IP 地址被存在 sock 数据结构的 recv_addr 和 saddr 域中。它们分别用于 hash 查找和发送 IP 地址。端口号是可选的,如果没有设置,会向支撑的网络请求一个空闲的。按照惯例,小于 1024 的端口号不能被没有超级用户特权的进程使用。如果底层的网络分配端口号,它总是分配一个大于 1024 的端口。
当底层的网络设备接收报文的时候,这些报文必须被转到正确的 INET 和 BSD socket 才能被处理。为此, UDP 和 TCP 维护 hash table ,用于查找进来的 IP 信息的地址,把它们转到正确的 socket/sock 对。 TCP 是一个面向连接的协议,所以处理 TCP 报文比处理 UDP 报文所包括的信息要多。
UDP 维护一个已经分配的 UDP 端口的 hash table , udp_table 。这包括 sock 数据结构的指针,用一个根据端口号的 hash 函数作为索引。因为 UDP hash table 比允许的端口号要小的多( udp_hash 只有 128 , UDP_HTABLE_SIZE )表中的一些条目指向一个 sock 数据结构的链表,用每一个 sock 的 next 指针连接在一起。
TCP 更加复杂,因为它维护几个 hast table 。但是,在绑定操作中, TCP 实际上并不把绑定的 sock 数据结构加到它的 hash table 中,它只是检查请求的端口当前没有被使用。在 listen 操作中 sock 数据结构才加到 TCP 的 hash table 中。
10.4.3 Making a Connection to an INET BSD Socket
一旦创建了一个 socket ,如果没有用于监听进来的连接请求,它就可以用于建立向外的连接请求。对于无连接的协议,比如 UDP ,这个 socket 操作不需要做许多,但是对于面向连接的协议如 TCP ,它涉及在两个应用程序之间建立一个虚拟电路。
一个向外的连接只能在一个正确状态的 INET BSD socket 上进行:就是说还没有建立连接,而且没有用于监听进来的连接。这意味着这个 BSD socket 数据结构必须在 SS_UNCONNECTED 状态。 UDP 协议不在两个应用程序之间建立虚拟连接,所有发送的消息都是数据报,发出的消息可能到到也可能没有到达它的目的地。但是,它也支持 BSD socket 的 connect 操作。在一个 UDP INET BSD socket 上的一个连接操作只是建立远程应用程序的地址:它的 IP 地址和它的 IP 端口号。另外,它也要建立一个路由表条目的缓存区,这样,在这个 BSD socket 上发送的 UDP 数据报不需要在检查路由表数据库(除非这个路由变成无效)。这个缓存的路由信息被 INET sock 数据结构中的 ip_route_cache 指针指向。如果没有给出地址信息,这个 BSD socket 发送的消息就自动使用这个缓存的路由和 IP 地址信息。 UDP 把 sock 的状态改变成为 TCP_ESTABLISHED 。
对于在一个 TCP BSD socket 上进行的连接操作, TCP 必须建立一个包括连接信息的 TCP 消息,并发送到给定的 IP 目标。这个 TCP 消息包括连接的信息:一个独一无二的起始消息顺序编号、发起主机可以管理的消息的最大尺寸、发送和接收的窗口大小等等。在 TCP 中,所有的消息都编了号,初始顺序编号用作第一个消息编号。 Linux 选择一个合理的随机数以避免恶意的协议攻击。每一个从 TCP 连接的一端发送,被另一端成功接收的消息被确认,告诉它成功地到达,而且没有损坏。没有确认的消息会被重发。发送和接收窗口大小是确认前允许的消息的数目。如果接收端的网络设备支持的最大消息尺寸比较小,则这个连接会使用两个中间最小的一个。执行向外的 TCP 连接请求的应用程序现在必须等待目标应用程序的响应,是接受还是拒绝这个连接请求。对于期望进来的消息的 TCP sock ,它被加到了 tcp_listening_hash ,这样进来的 TCP 消息可以定向到这个 sock 数据结构。 TCP 也启动计时器,这样如果目标应用程序对于请求不响应,向外的连接请求会超时。
10.4.4 Listening on an INET BSD Socket
一旦一个 socket 拥有了一个绑定的地址,它就可以监听指定这个绑定地址的进来的连接请求。一个网络应用程序可以不绑定地址直接在一个 socket 上监听,这种情况下, INET socket 层找到一个未用的端口号(对于这种协议而言),自动把它绑定到这个 socket 上。这个 socket 的 listen 函数把 socket 变成 TCP_LISTEN 的状态,并且执行所需的和网络相关的工作,一边允许进来的连接。
对于 UDP socket ,改变 socket 的状态已经足够,但是 TCP 已经激活它现在要把 socket 的 sock 数据结构加到它的两个 hash table 中。这是 tcp_bound_hash 和 tcp_listening_hash 表。这两个表都通过一个基于 IP 端口号的 hash 函数进行索引。
不论何时接收到一个对于激活的监听 socket 的进来的 TCP 连接请求, TCP 都要建立一个新的 sock 数据结构表示它。这个 sock 数据结构在它最终被接受之前成为这个 TCP 连接的 buttom half 。它也克隆包含连接请求的进来的 sk_buff 并把它排在监听的 sock 数据结构的 receive_queue 队列中。这个克隆的 sk_buff 包括一个指针,指向这个新创建的 sock 数据结构。
10.4.5 Accepting Connection Requests
UDP 不支持连接的概念,接受 INET socket 的连接请求只应用于 TCP 协议,在一个监听的 sock 上进行接受( accept )操作会从原来的监听的 socket 克隆出一个新的 socket 数据结构。然后这个 accept 操作传递给支撑的协议层,在这种情况下,是 INET 去接受任何进来的连接请求。如果底层的协议,比如 UDP 不支持连接, INET 协议层的 accept 操作会失败。否则,连接的请求会传递到真正的协议,在这里,是 TCP 。这个 accept 操作可能是阻塞,也可能是非阻塞的。在非阻塞的情况下,如果没有需要 accept 的进来的连接,这个 accept 操作会失败,而新创建的 socket 数据结构会被废弃。在阻塞的情况下,执行 accept 操作的网络应用程序会被加到一个等待队列,然后挂起,直到接收到一个 TCP 的连接请求。一旦接收到一个连接请求,包含这个请求的 sk_buff 会被废弃,这个 sock 数据结构被返回到 INET socket 层,在这里它被连接到先前创建的新的 socket 数据结构。这个新的 socket 的文件描述符( fd )被返回给网络应用程序,应用程序就可以用这个文件描述符对这个新创建的 INET BSD socket 进行 socket 操作。
10.5 The IP Layer ( IP 层)
10.5.1 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 ,并当它通过协议层的时候增加许多头。
欢迎光临 临江网 (http://bbs.linjiang.com/)
Powered by Discuz! X2.5