Chinaunix首页 | 论坛 | 博客
  • 博客访问: 760652
  • 博文数量: 230
  • 博客积分: 6330
  • 博客等级: 准将
  • 技术积分: 2188
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-10 15:55
个人简介

脚踏实地

文章分类

全部博文(230)

文章存档

2017年(1)

2016年(7)

2015年(10)

2014年(32)

2013年(24)

2012年(33)

2011年(50)

2010年(30)

2009年(43)

分类: LINUX

2011-12-25 23:11:37

From: http://blog.csdn.net/adcxf/article/details/1928451

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



Linux 高级网络栈架构

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





回页首


系统调用接口可以从两个角度进行描述。用户发起网络调用时,通过系统调用接口进入内核的过程应该是多路的。最后调用 ./net/socket.c 中的 sys_socketcall 结束该过程,然后进一步将调用分路发送到指定目标。系统调用接口的另一种描述是使用普通文件操作作为网络 I/O。例如,典型的读写操作可以在网络 socket 上执行(socket 使用一个文件描述符表示,与一个普通文件一样)。因此,尽管有很多操作是网络专用的(使用 socket 调用创建一个 socket,使用 connect 调用连接一个收信方,等等),但是也有一些标准的文件操作可以应用于网络对象,就像操作普通文件一样。最后,系统调用接口提供了在用户空间应用程序和内核之间转移控制的方法。





回页首


socket 层是一个协议无关接口,它提供了一组通用函数来支持各种不同协议。socket 层不但可以支持典型的 TCP 和 UDP 协议,而且还可以支持 IP、裸以太网和其他传输协议,例如 SCTP(Stream Control Transmission Protocol)。

通过网络栈进行的通信都需要对 socket 进行操作。Linux 中的 socket 结构是 struct sock,这个结构是在 linux/include/net/sock.h 中定义的。这个巨大的结构中包含了特定 socket 所需要的所有状态信息,其中包括 socket 所使用的特定协议和在 socket 上可以执行的一些操作。

网络子系统可以通过一个定义了自己功能的特殊结构来了解可用协议。每个协议都维护了一个名为 proto 的结构(可以在 linux/include/net/sock.h 中找到)。这个结构定义了可以在从 socket 层到传输层中执行特定的 socket 操作(例如,如何创建一个 socket,如何使用 socket 建立一个连接,如何关闭一个 socket 等等)。





回页首


网络协议这一节对一些可用的特定网络协议作出了定义(例如 TCP、UDP 等)。它们都是在 linux/net/ipv4/af_inet.c 文件中一个名为 inet_init 的函数中进行初始化的(因为 TCP 和 UDP 都是 inet 簇协议的一部分)。 inet_init 函数使用 proto_register 函数来注册每个内嵌协议。这个函数是在 linux/net/core/sock.c 中定义的,除了可以将这个协议添加到活动协议列表中之外,如果需要,该函数还可以选择分配一到多个 slab 缓存。

通过 linux/net/ipv4/ 目录中 udp.c 和 raw.c 文件中的 proto 接口,您可以了解各个协议是如何标识自己的。这些协议接口每个都按照类型和协议映射到 inetsw_array,该数组将内嵌协议与操作映射到一起。inetsw_array 结构及其关系如图 3 所示。最初,会调用 inet_init 中的 inet_register_protosw 将这个数组中的每个协议都初始化为 inetsw。

函数 inet_init 也会对各个 inet 模块进行初始化,例如 ARP、ICMP 和 IP 模块,以及 TCP 和 UDP 模块。

inetsw_array的定义三种socket:
  1. /* Upon startup we insert all the elements in inetsw_array[] into
  2.  * the linked list inetsw.
  3.  */
  4. static struct inet_protosw inetsw_array[] =
  5. {
  6.     {
  7.         .type = SOCK_STREAM,
  8.         .protocol = IPPROTO_TCP,
  9.         .prot = &tcp_prot,
  10.         .ops = &inet_stream_ops,
  11.         .capability = -1,
  12.         .no_check = 0,
  13.         .flags = INET_PROTOSW_PERMANENT |
  14.              INET_PROTOSW_ICSK,
  15.     },

  16.     {
  17.         .type = SOCK_DGRAM,
  18.         .protocol = IPPROTO_UDP,
  19.         .prot = &udp_prot,
  20.         .ops = &inet_dgram_ops,
  21.         .capability = -1,
  22.         .no_check = UDP_CSUM_DEFAULT,
  23.         .flags = INET_PROTOSW_PERMANENT,
  24.        },


  25.        {
  26.      .type = SOCK_RAW,
  27.      .protocol = IPPROTO_IP,    /* wild card */
  28.      .prot = &raw_prot,
  29.      .ops = &inet_sockraw_ops,
  30.      .capability = CAP_NET_RAW,
  31.      .no_check = UDP_CSUM_DEFAULT,
  32.      .flags = INET_PROTOSW_REUSE,
  33.        }
  34. };
struct list_head {
    struct list_head *next, *prev;
};

static struct list_head inetsw[SOCK_MAX];



Internet 协议数组结构



回想以下在创建 socket 时,需要指定类型和协议,例如my_sock = socket( AF_INET, SOCK_STREAM, 0 )。AF_INET 表示一个 Internet 地址簇,它使用的是一个流 socket,定义为 SOCK_STREAM(如此处的 inetsw_array 所示)。

注意在 图 3 中,proto 结构定义了传输特有的方法,而 proto_ops 结构则定义了通用的 socket 方法。可以通过调用 inet_register_protosw 将其他协议加入到 inetsw 协议中。例如,SCTP 就是通过调用 linux/net/sctp/protocol.c 中的 sctp_init 加入其中的。有关 SCTP 的更多信息,请参阅 参考资料 一节的内容。

socket 中的数据移动是使用一个所谓的 socket 缓冲区(sk_buff)的核心结构实现的。sk_buff 中包含了报文数据,以及涉及协议栈中多个层次的状态数据。所发送或接收的每个报文都是使用一个 sk_buff 表示的。sk_buff 结构是在 linux/include/linux/skbuff.h 中定义的,如图 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 中可以找出网络设备专用的驱动程序。





回页首

知识补充之NAPI

NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据。随着网络的接收速度的增加,NIC 【“网络适配器”,英文全称为“Network Interface Card”】触发的中断能做到不断减少

根据实验数据表明采用NAPI技术可以大大改善短长度数据包接收的效率,减少中断触发的时间。
 
但是 NAPI 存在一些比较严重的缺陷:
1.              对于上层的应用程序而言,系统不能在每个数据包接收到的时候都可以及时地去处理它,而且随着传输速度增加,累计的数据包将会耗费大量的内存,经过实验表明在 Linux 平台上这个问题会比在 FreeBSD 上要严重一些;
2.              另外一个问题是对于大的数据包处理比较困难,原因是大的数据包传送到网络层上的时候耗费的时间比短数据包长很多(即使是采用 DMA 方式),所以正如前面所说的那样,NAPI 技术适用于对高速率的短长度数据包的处理。

阅读(1316) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~