Linux 网络栈剖析

来源:互联网 发布:生产erp软件 编辑:程序博客网 时间:2024/05/03 19:27

  Linux® 操作系统的最大特性之一就是它的网络栈。它最初源于 BSD 的网络栈,具有一套非常干净的接口,组织得非常好。其接口范围从协议无关层(例如通用 socket 层接口或设备层)到各种网络协议的具体层。本文将从分层角度对 Linux 网络栈的接口进行探索,并介绍其中的一些主要结构。
协议简介

        虽然对于网络的正式介绍一般都参考了 OSI(Open Systems Interconnection)模型,但是本文对 Linux 中基本网络栈的介绍分为四层的 Internet 模型(如图 1 所示)。


                              图 1. 网络栈的 Internet 模型

                          
       这个栈的最底部是链路层。链路层是指提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备。链路层上面是网络层,它负责将报文定向到目标位置。再上一层称为传输层,负责端到端的通信(例如,在一台主机内部)。尽管网络层负责管理主机之间的通信,但是传输层需要负责管理主机内部各端之间的通信。最后一层是应用层,它通常是一个语义层,能够理解要传输的数据。例如,超文本传输协议(HTTP)就负责传输服务器和客户机之间对 Web内容的请求与响应。

       实际来说,网络栈的各个层次有一些更为人所熟知的名字。在链路层上,可以找到以太网,这是最常用的一种高速介质。更早的链路层协议包括一些串口协议,例如SLIP(Serial Line Internet Protocol)、CSLIP(CompressedSLIP)和PPP(Point-to-Point Protocol)。最常见的网络层协议是 IP(InternetProtocol),但是网络层中还存在一些满足其他需求的协议,例如 ICMP(Internet Control Message Protocol)和ARP( Address ResolutionProtocol)。在传输层上是 TCP(Transmission Control Protocol)和 UDP(User DatagramProtocol)。最后,应用层中包含很多大家都非常熟悉的协议,包括标准的 Web 协议 HTTP 和电子邮件协议 SMTP(SimpleMail Transfer Protocol)。

核心网络架构

        现在继续了解 Linux网络栈的架构以及如何实现这种 Internet 模型。图 2 提供了 Linux网络栈的高级视图。最上面是用户空间层,或称为应用层,其中定义了网络栈的用户。底部是物理设备,提供了对网络的连接能力(串口或诸如以太网之类的高速网络)。中间是内核空间,即网络子系统,也是本文介绍的重点。流经网络栈内部的是 socket缓冲区(sk_buffs),它负责在源和汇点之间传递报文数据。您很快就将看到 sk_buff 的结构。


                                           图 2. Linux 高级网络栈架构

                        
       首先,让我们来快速浏览一下 Linux 网络子系统的核心元素,后续章节中会更详细进行介绍。顶部(请参阅图2)是系统调用接口。它简单地为用户空间的应用程序提供了一种访问内核网络子系统的方法。位于其下面的是一个协议无关层,它提供了一种通用方法来使用底层传输层协议。然后是实际协议,在 Linux 中包括内嵌的协议 TCP、UDP,当然还有IP。然后是另外一个协议无关层,提供了与各个设备驱动程序通信的通用接口,最下面是设备驱动程序本身。

   图 3. Internet 协议数组结构

                     
        注意在 图 3 中,proto 结构定义了传输特有的方法,而 proto_ops 结构则定义了通用的 socket 方法。可以通过调用 inet_register_protosw 将其他协议加入到 inetsw 协议中。例如,SCTP 就是通过调用 linux/net/sctp/protocol.c 中的 sctp_init 加入其中的。

       socket 中的数据移动是使用一个所谓的 socket 缓冲区(sk_buff)的核心结构实现的。sk_buff中包含了报文数据,以及涉及协议栈中多个层次的状态数据。所发送或接收的每个报文都是使用一个 sk_buff 表示的。sk_buff 结构是在linux/include/linux/skbuff.h 中定义的,如图 4 所示。


                                     图 4. Socket 缓冲区及其与其他结构的关系

                       

       如图所示,多个 sk_buff 可以针对某个给定连接链接在一起。每个 sk_buff都在设备结构(net_device)中标识报文发送的目的地,或者接收报文的来源地。由于每个报文都是使用一个 sk_buff表示的,因此报文头都可以通过一组指针(th、iph 和 mac[用于 Media Access Control 或者 MAC头])方便地进行定位。由于 sk_buff 是 socket数据管理的中心,因此创建了很多支持函数来对它们进行管理。其中有些函数用于创建和销毁 sk_buff 结构,或对它进行克隆或排队管理。

        针对给定的 socket,Socket 缓冲区可以链接在一起,这样可以包含众多信息,包括到协议头的链接、时间戳(报文是何时发送或接收的),以及与这个报文相关的设备。

设备无关接口

        协议层下面是另外一个无关接口层,它将协议与具有很多各种不同功能的硬件设备连接在一起。这一层提供了一组通用函数供底层网络设备驱动程序使用,让它们可以对高层协议栈进行操作。

       首先,设备驱动程序可能会通过调用 register_netdevice 或 unregister_netdevice在内核中进行注册或注销。调用者首先填写 net_device 结构,然后传递这个结构进行注册。内核调用它的 init函数(如果定义了这种函数),然后执行一组健全性检查,并创建一个 sysfs 条目,然后将新设备添加到设备列表中(内核中的活动设备链表)。在linux/include/linux/netdevice.h 中可以找到这个 net_device 结构。这些函数都是在linux/net/core/dev.c 中实现的。

        要从协议层向设备中发送 sk_buff,就需要使用dev_queue_xmit 函数。这个函数可以对 sk_buff 进行排队,从而由底层设备驱动程序进行最终传输(使用 sk_buff中引用的 net_device 或 sk_buff->dev 所定义的网络设备)。dev 结构中包含了一个名为hard_start_xmit 的方法,其中保存有发起 sk_buff 传输所使用的驱动程序函数。

       报文的接收通常是使用 netif_rx 执行的。当底层设备驱动程序接收一个报文(包含在所分配的 sk_buff 中)时,就会通过调用netif_rx 将 sk_buff 上传至网络层。然后,这个函数通过 netif_rx_schedule 将 sk_buff在上层协议队列中进行排队,供以后进行处理。可以在 linux/net/core/dev.c 中找到 dev_queue_xmit 和netif_rx 函数。

       最近,内核中引入了一种新的应用程序编程接口(NAPI),该接口允许驱动程序与设备无关层(dev)进行交互。有些驱动程序使用的是NAPI,但是大多数驱动程序仍然在使用老式的帧接收接口(比例大约是 6 比 1)。NAPI在高负载的情况下可以产生更好的性能,它避免了为每个传入的帧都产生中断。

设备驱动程序

        网络栈底部是负责管理物理网络设备的设备驱动程序。例如,包串口使用的 SLIP 驱动程序以及以太网设备使用的以太网驱动程序都是这一层的设备。

       在进行初始化时,设备驱动程序会分配一个 net_device 结构,然后使用必须的程序对其进行初始化。这些程序中有一个是dev->hard_start_xmit,它定义了上层应该如何对 sk_buff 排队进行传输。这个程序的参数为sk_buff。这个函数的操作取决于底层硬件,但是通常 sk_buff所描述的报文都会被移动到硬件环或队列中。就像是设备无关层中所描述的一样,对于 NAPI 兼容的网络驱动程序来说,帧的接收使用了netif_rx 和 netif_receive_skb 接口。NAPI 驱动程序会对底层硬件的能力进行一些限制。有关更详细的信息,请参阅参考资料 一节的内容。

        设备驱动程序在 dev 结构中配置好自己的接口之后,调用 register_netdevice 便可以使用该配置。在 linux/drivers/net 中可以找出网络设备专用的驱动程序。

展望

        Linux 源代码是学习有关大多数设备类型的设备驱动程序设计最佳方法,包括网络设备驱动程序。在这里可以找到的是各种设计的变化以及对可用内核 API的使用,但是所学到的每一点都会非常有用,都可以作为新设备驱动程序的起点。除非您需要一种新协议,否则网络栈中的其余代码都是通用的,都会非常有用。即使现在,TCP(用于流协议)或 UDP(用于基于消息的协议)的实现都可以作为开始新开发有用模块使用。

原创粉丝点击