Linux内核原理之网络

这篇博客详细探讨了Linux内核中的网络实现,重点介绍了网络分层模型和套接字缓冲区的管理。网络实现基于TCP/IP模型,通过分层结构处理数据,套接字缓冲区在各层间交换数据,减少复制操作,提高性能。文章还涉及网络访问层、网络设备表示、网络层(包括IPv4)、传输层(如UDP和TCP)以及应用层的相关处理,同时提到了netfilter框架和iptables的使用,用于分组过滤和操作。
摘要由CSDN通过智能技术生成

网络

网络实现的分层模型

内核网络子系统的实现与TCP/IP模型很相似,相关的C语言代码划分为不同层次,各层次都有明确定义的任务,各个层次只能通过明确定义的接口与上下紧邻的层次通信(这样设计的优点:可以组合使用各种设备、传输机制和协议),如下图是内核对于分层结构的实现

image-20200720194433868.png

该子系统处理了大量特定于协议的细节,穿越各层的代码路径中有大量的函数指针,没有直接的函数调用(因为各个层次存在多个组合关系)。

套接字缓冲区

内核采用套接字缓冲区用于在网络实现的各个层次之间交换数据,无须来回复制分组数据,提高了性能

其结构定义如下

在这里插入图片描述

在这里插入图片描述

使用套接字缓冲区管理数据

套接字缓冲区通过其中包含的各种指针与一个内存区域相关联,网络分组的数据位于该区域。

套接字缓冲区的基本思想是:通过操作指针来增删协议首部

  • head和end指向数据在内存中的起始和结束位置

  • data和tail指向协议数据区域的起始和结束位置

在这里插入图片描述

  • mac_header指向MAC协议首部的起始,network_header和transport_header分别指向网络层和传输层协议首部的起始;在字长32位的系统上,数据类型sk_buff_data_t表示各种类型为简单指针的数据

    typedef unsigned char *sk_buff_data_t;
    
  • data和tail使得在不同协议层之间传递数据时,无须显式地复制操作,如下图展示了分组的合成方式

在这里插入图片描述

在一个新分组产生时,TCP层首先在用户空间分配内存来容纳该分组数据(首部和净荷),分配的空间大于数据实际需要的长度,因此较低的协议层可以进一步增加首部

然后分配一个套接字缓冲区,使得head和end分别指向上述内存区的起始和结束地址,而TCP数据位于data和tail之间

在套接字缓冲区传递到互联网络层时,必须增加一个新层,只需要向已经分配但尚未占用的那部分内存写入数据即可,除了data之外所有的指针都不变,data现在指向IP首部的起始处,下面的各层会重复这样的操作,直至分组完成通过网络发送

为了保证套接字缓冲区的长度尽可能小,在64位CPU上,将sk_buff_data_t改为整型变量,由于整型变量占用的内存只有指针变量的一半(前者4字节,后者8字节),该结构的长度缩减了20字节。

typedef unsigned int sk_buff_data_t;

其中data和head仍然是常规的指针,而所有sk_buff_data_t类型的成员是前两者的偏移,如指向传输层的首部指针计算如下:

在这里插入图片描述

管理套接字缓冲区数据

除了前述的指针外,套接字缓冲区还包括用于处理相关的数据和管理套接字缓冲区自身的其他成员,主要成员如下:

  • tstamp:保存了分组到达的时间
  • dev指定了分组的网络设备
  • iif:输入设备的接口索引号
  • sk:指向处理该分组套接字对应的socket实例的指针
  • dst:改分组接下来通过内核网络实现的路由
  • next和prev:将套接字缓冲区保存在一个双链表中(没有用内核标准链表实现,使用了手工实现的版本)
  • qlen:指定了等待的长度

sk_buff_head和sk_buff的next和prev用于创建一个循环链表,套接字缓冲区的list成员指向表头,如下图

在这里插入图片描述

网络访问层

网络访问层主要负责在计算机之间传输信息,与网卡的设备驱动程序直接协作

网络设备的表示

在内核中,每个网络设备都表示为net_device结构的实例,在分配并填充该实例之后,必须用net/dev.c中的register_device函数将其注册到内核。该函数完成一些初始化任务,并将该设备注册到通用设备机制内,这会创建一个sysfs项/sys/class/net/,关联到该设备对应的目录

在这里插入图片描述

网络设备不是全局的,是按照命名空间进行管理,每个命名空间(net实例)有如下3个实例可用:

  • 所有的网络设备都保存在一个单链表中,表头为dev_base
  • 按设备名散列:辅助函数dev_get_by_name(struct net *net, const char *name)根据设备名在该散列表上查找网络设备
  • 按接口索引散列:辅助函数dev_get_by_index(struct net *net, int ifindex)根据给定的接口索引查找net_device的实例

net_device结构包含了与特定设备相关的所有信息,该结构非常复杂,如下图为部分成员

在这里插入图片描述

一些成员定义了与网络层和网络访问层相关的设备属性:

  • mtu指定了一个传输帧的最大长度
  • type保存了设备的硬件类型
  • dev_addr存储了设备的硬件地址(如以太网的MAC地址),addr_len指向该地址的长度,broadcast是广播地址
  • ip_ptr、ip6_ptr、atalk_ptr等指针指向特定于协议的数据

net_device的大多数成员都是函数指针,执行与网卡相关的典型任务,这些成员表示了与下一个协议层的抽象接口,实现同一组接口访问所有的网卡,而网卡的驱动程序负责实现细节

接收分组

所有现代的设备驱动程序都使用中断来通知内核有分组到达。网卡驱动程序对特定于设备的中断设置了一个处理例程,每当中断被引发时,内核都会调用中断处理程序,将数据从网卡传输到物理内存(通过DMA方式能够将数据从网卡传输到物理内存),或者通知内核在一定时间后进行处理

如下图是一个分组到达网络适配器之后,该分组穿过内核到达网络层函数的路径

在这里插入图片描述

分组是在中断上下文中接收的,处理例程只能执行一些基本任务,避免系统其他任务延迟太长时间

在中断上下文中,数据由3个短函数处理,执行下列任务:

  • net_interrupt是设备驱动程序设置的中断处理程序,它用于确定中断是否真的由接收到的分组所引发的,如果确实如此,则控制转到net_rx
  • net_rx函数也是特定于网卡,首先创建一个套接字缓冲区,并指向一块物理内存,分组的内容接下来从网卡传输到缓冲区(也就是物理内存),然后分析首部数据,确定分组数据所使用的网络层协议
  • 接下来调用netif_rx,与前两个方法不同,netif_rx不是特定于网络驱动程序的,该函数位于net/core/dev.c函数中,调用该函数,标志着控制由特定于网卡的代码转到网络层的通用接口部分。该函数的作用在于,将接收的分组放置到一个特定于CPU的等待队列上,并退出中断上下文

内核在全局定义的softnet_data数组中管理进出分组的等待队列,数组类型为softnet_data,为提高多处理器系统的性能,对每个CPU都会创建等待队列,支持分组的并行处理。不需要显式的使用锁机制,因为每个CPU只会处理自身的队列,不会干扰其他CPU的工作

softnet_data结构如下,inpput_pkt_queue使用前面提到的sk_buff_head表头,对所有进入的分组建立一个链表

在这里插入图片描述

netif_rx在结束之前将软中断NET_RX_SOFTIRQ标记为即将执行,然后退出中断上下文,接下来net_rx_action用于该软中断的处理程序,其代码流程图如下(这里是简化版本)

在这里插入图片描述

在一些准备工作之后,工作转移到process_backlog,该函数在循环中处理下列步骤

  • __skb_dequeue从等待队列中移除一个套接字缓冲区,该缓冲区管理着一个接收到的分组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值