当前位置: 首页 > news >正文

深入理解Linux网络技术内 幕(二)——关键数据结构

文章目录

  • 前言
  • 套接字缓冲区:sk_buff结构
  • 网络选项以及内核结构
  • 布局字段
  • 通用字段
  • 功能专用字段
  • 管理函数
  • 缓冲区的克隆和拷贝
  • 列表管理函数
  • net_device结构
  • 标识符
  • 配置
  • 接口类型和端口
  • 链路层多播
  • 函数指针
  • 本章主要设计的文件


前言

Linux网络代码中有些关键数据结构随处可见。在阅读本书以及直接研究源码时,必须了解这些数据结构里的字段。当然,逐一研究这些数据结构的字段比起解析函数更加乏味,但这是很重要的基础。传奇软件工程师Frederick P.Broooks曾说过:“给我看你的数据”

本章介绍下列数据结构,同时说明操作这些数据结构的一些函数和宏。

struct sk_buff
一个封包就存储在这里。所有网络分层都会使用这个结构来存储其报头、有关用户数据的信息(有效载荷),以及用来协调其工作的其他内部信息。

struct net_device
在Linux内核中每种网络设备都用这个数据结构表示,包括软硬件的配置信息。第八章详细的描述了net_device数据结构分配的时机以及分配方式。

Linux网络所用的另一个关键数据结构是struct sock,用于存储套接字的网络信息。本书不涉及套接字,故本章没有把sock包含在内。

套接字缓冲区:sk_buff结构

这可能是Linux网络代码中最重要的数据结构,代表以接收或正要传输的数据的报头。此结构定义在<include/linux/skbuff.h>头文件中,由巨大的变量堆(heap)组成,试图满足所有人的所有需求。

在内核的进化历程中,这个结构历经多次变动,不但新增了选项,同时也重组了现存的字段,使布局更为清晰。其字段可粗略划分为以下几种类型:

  • 布局(Layout)
  • 通用(General)
  • 功能专用(Feature-specific)
  • 管理函数(Management functions)
    多个不同的网络分层(MAC或L2分层上的另一种链路层协议、L3的IP以及L4的TCP或UDP)都会使用这个结构。而且当该结构从一个分层传到另一个分层时,其不同的字段会随之发生变化。L4在传给L3之前会附加一个报头,而L3在传给L2之前又会加上自己的报头。附加报头比起把数据从一个分层拷贝到另一个分层更有效率。由于要在一个缓冲区开端新增空间——也就是要改变指向该缓冲区的变量——是一种复杂的运算,内核提供了skb_reserve函数(稍后说明)来执行一个操作。所以,当缓冲区往下传经灭个分层是,每层的协议首先做的就是调用skb_reserve函数,为该协议的报头预留空间。在后面的“数据预留和对齐:skb_reserve、skb_put、skb_push以及skb_pull”中,我们会看一个一个实例,说明缓冲区穿越各个分层时,内核如何确保缓冲区头部有足够的空间预留下来,允许每个分层都能加上其报头。

当缓冲区往上传经各个网络分层时,每个源自于旧分层的报头就不再有用处。例如,L2报头只由处理L2协议的设备驱动程序使用,所以对L3而言并无用处。不过,并没有把L2的报头从缓冲区中删除,而是把其指向有效载荷开端的指针向前移到L3报头的开端,这样做只需要很少的CPU周期。

网络选项以及内核结构

粗略阅读TCP/IP说明书或内核配置后可知,网络代码提供了大量的选项,虽然有用,但不一定总需要,如防火墙、多播(multicasting)以及其他功能。这些选项大多数要求在内核数据结构中附加其他字段。因此,sk_buff是被C预处理的#ifdef指示附加的字段。例如,在sk_buff定义的低端附近可以发现:

struct sk_buff
{
.....
#ifdef CONFIG_NET_SCHED
	__u32  tc_index;
#ifdef CONFIG_NET_CLS_ACT
	__u32  tc_verd;
	__u32 tc_classid;
#endif
#endif
}

这表示只有在编译期间定义了CONFIG_NET_SCHED符号,也就是管理员或自动安装工具已经使能了某种版本的make config的正确选项,字段tc_index才会是此数据结构的一部分(就此而言,就是“Device Drivers --> Networking support -->Networking options -->QoS and/or fair queueing”)

前述的例子实际上是两个嵌套选项:只有"QoS and/or fair queueing"的支持存在,CONFIG_NET_CLS_ACT(packet classifier ,封包分类器)所用的字段才会被引入

顺便提一下,应该注意到QoS选项不能编译成一个模块。其原因在于内核编译之后,开启该选项所得的多数结果为不可逆。一般而言,任何内核数据结构改变的选项(如把tc_index字段添加到sk_buff结构),都不适合编译成一个模块。

通常你会想知道make config或其他变量的那个编译选项和特定的#ifdef符号相配,以了解一个代码区块什么时候会在包含到内核中。最版本2.6中,找到这种关联的最快的方式,就是在那些分散的源码树中的kconfig文件中(每个目录都有一个)寻找到该符号。对于版本2.4而言,可以参考Documentation/Configure.help文件。

布局字段

sk_buff有些字段只是为了方便搜寻以及组织数据结构本身。内核在一个双向链表中维护所有的sk_buff结构,但是该表的组织比传统的双向链表更为复杂。

像任何双向链表一样,通过每个sk_buff结构中的next和prev字段实现联系。next字段指向前,而prev指向后。但是,这个表还有另一项必要需求:每个sk_buff结构必须能够迅速找到整个表的头。为了实现这项需求,在表的开端额外增加一个sk_buff_head结构作为一种哑元元素。sk_buff_head的结构如下:

struct sk_buff_head
{
	/*这两个成员必须写在前面*/
	struct sk_buff *next;
	struct sk_buff *prev;
	__u32 qlen;
	spinlock_t lock;
}

qlen代表表中元素的数目。lock是用于防止对表的并发访问,在本章稍后的“表管理函数”一节中会予以描述。

sk_buff和sk_buff_head的前两个元素是相同的:next和prev指针。尽管sk_buff_head与sk_buff相比实在太小,但还是允许两个结构共同存在于一个表中。另外,同样的函数也可用于操作sk_buff和sk_buff_head二者。

每个sk_buff结构都包含一个指针,指向专一的sk_buff_head结构,增加了其复杂性。这个指针的字段名为list。下图有助于你了解这些数据结构的关系。
在这里插入图片描述

sk_buff其他感兴趣的字段如下:
struct sock *sk
这是一个指针,指向拥有此缓冲区的套接字sock数据结构。当数据在本地产生活着正由本地进程接收时,就需要这个指针,因为该数据以及套接字相关的信息会由L4(TCP或UDP)以及用户应用程序使用。当缓冲区只是被转发时(也就是说本地机器不是来源也不是目的地),该指针就是NULL

unsigned int len
这是缓冲区中数据块的大小。这个长度包括主要缓冲区(由head所指)的数据以及一些片段(fragment)的数据。当缓冲区从一个网络分层移往下一个网络分层时,其值就会变化,因为协议栈往上移动时报头会被丢弃,但是往下移动时就会添加进来。len也会把协议报头算在内,如“数据预留和对齐:skb_reserve、skb_put、skb_pull)”

unsigned int data_len
与len不同,data_len只计算片段中的数据大小。

unsigned int mac_len
MAC报头的大小

atomic_t users
这是引用技术,或者使用这个sk_buff缓冲区的实例的数目。这个参数的主要用途是避免在某人依然使用此sk_buff结构时,把这个结构给释放掉。因此,此缓冲区的每个用户在必要时都要递增和递减此字段。此计数器只计算sk_buff数据结构的用户,此缓冲区所包含的实际数据由一个相似的字段(dataref)所包含。;本章稍后的“skb_shared_info结构和skb_shinfo结构”一节将予以介绍。

users有时会直接用atomic_inc和atomic_dec函数递增和递减,但在大多数情况下,采用skb_get和kfree_skb进行处理。

unsigned int truesize
此字段代表此缓冲区总的大小,包括sk_buff结构本身。当此缓冲区得到所分配的len个字节的数据请求空间时,此字段的初始化由alloc_skb函数设置成len+sizeof(sk_buff)

struct sk_buff *alloc_skb(unsigned int size, int gfp_mask)
{
	......
	skb->truesize = size +sizeof(struct sk_buff)......
}

每当skb_len的值增加时,此字段就会得到更新。
unsigned char *head
unsigned char *end
unsigned char *data
unsigned char *tail

这些字段代表缓冲区的边界以及其中的数据。当每一分层为其工作而准备缓冲区时,可能会为每一个报头或更多的数据分配更多的空间。head和end指向已分配缓冲区空间的开端和尾端,而data和tail则指向实际数据的开端和尾端。参见下图所示,然后该分层可以把head和data之间的空隙填上一个协议报头,或者以新数据填入tail和end之间的空隙。在稍后的“分配内存”:alloc_skb和dev_alloc_skb一节中就会知道,下图右边的缓冲区在底端包含一个附加报头。

在这里插入图片描述

void (*destructor)(....)
此函数指针可以被初始化为一个函数,当此缓冲区被删除时,可完成某些工作。当此缓冲区不属于一个套接字时,destructor通常不会被初始化。当此缓冲区属于一个套接字时,通常设成sock_rfree或sock_wfree(分别由skb_set_owner_r和skb_set_owner_w初始化函数设置)。这两个sock_xxx函数可用于更新套接字队列中所持有的内存。

通用字段

这一节讨论sk_buff的主要字段,而这些字段都与特定内核功能无关:
struct timeval stamp
通常支队一个已接受的封包才有意义。这是一个时间戳记,用于表示封包何时被接受,或者有时用于表示封包预定传输的时间。此字段由netif_rx函数用net_timestamp设置,而该函数在接受每个封包之后由设备驱动程序调用,对此第二十一章给出了描述。

struct net_device *dev
此字段描述一个网络设备,其类型(net_device)将在本章稍后详细说明。由dev所代表的的设备的角色,依赖于存储在该缓冲区内的封包是即将传输的还是刚被接收的而定。
当接收到一个封包时,设备驱动程序会用代表此接口的数据结构的指针更新此字段,如下列取自vortex_rx的代码片段所示,当接收到一帧时,3c59x系列的以太网卡的驱动程序就会调用该函数(在drivers/net/3c59x.c中)

static int vortex_rx(struct net_device *dev)
{
	.......
	skb->dev = dev;
	.......
	skb->protocol = eth_type_trans(skb, dev);
	netif_rx(skb);/*把封包传到较高的分层*/
}

当传输一个封包时,此参数代表的是发送封包的设备。设置此值的代码要比接收封包的代码更为复杂,所以把相关讨论推迟到后面进行。
有些网络功能允许一些设备按组集合起来代表专一的虚拟接口(也就是没有和硬设备直接相关联),由一个虚拟设备驱动程序提供接口服务。当该设备驱动程序被调用时,dev参数会指向此虚拟设备的net_device数据结构。驱动程序会从其群组中选择一个特定设备,然后把dev参数改为指向该设备的net_device数据结构。因此,这些情况下,在封包处理期间传输设备的指针可能变化。

struct net_device *input_dev
这是已被接收的封包所源自的设备。当封包是由本地产生时,其值为NULL指针。就Ethernet设备而言,此字段在eth_type_trans中初始化。此字段主要由流量控制(traffic Control)使用。

struct net_device *real_dev
此字段只对虚拟设备有意义,代表的是与虚拟设备所关联的真实设备。例如,Bonding和VLAN接口使用该字段,用以记下真实设备的输入流量是从什么地方接收得到的。

union {...} h
union {...} nh
union {...} mac
这些是指向TCP/IP协议栈的协议报头的指针:h指针针对L4、nh指针针对L3、而mac针对L2.每个字段都指向一个由各种结构组成的联合(union),每个协议的结构都由内核中该层来解释。例如,h是一个联合,内核所解释的每个L4协议的报头中在h中都有一个字段。每个联合都有一个名为raw的成员用于初始化。而后续的所有访问都是通过协议指定的成员。
当接收一个数据封包时,负责处理第n层报头的函数,会从第n-1层接收一个缓冲区,而该缓冲区的skb->data指向第n层报头的开端。处理第n层的函数会为该层初始化适当的指针(例如,L3的处理函数的skb->nh),用于保存skb->data子段,因为在下一层进行处理时,skb->data会设成缓冲区内另一个不同的偏移量。这个指针的内容就会丢失。接着,此函数完成第n层处理,把封包传递给第n+1层的处理函数前,先更新skb->data,使其指向第n层报头的尾端,也就是第n+1层报头的开始。见下图
传送一个封包就是此过程的逆过程,而所增加的复杂度就是在每一层加上一个新的报头。
在这里插入图片描述没咋弄懂没关系,后面会有详细的例子

struct dst_entry dst
这个结构由路由器子系统使用。由于这个数据结构相当复杂,需要了解其他子系统如何工作的知识,因此,推迟在后面的章节进行分析。

char cb[40]
这是一个“控制器缓冲区(Controller buffer)”,或者说是私有信息的存储空间,为每一层内部使用起维护作用。该字段在sk_buff结构内静态分配(目前大小是40个字节),而且容量足以容纳每个层所需的私有数据。在每一层的代码中都是通过宏进行访问的,这样使代码更具可读性。例如,TCP使用这个空间来存储一个tcp_skb_cb数据结构,该数据结构定义在include/net/tcp.h中

struct tcp_skb_cb
{
	... ... ...
	__u32 seq; /*起始序列编号*/
	__u32 end_seq; /*SEQ+FIN+SYN+datalen*/
	__u32 when;  /*用于计算rtt's*/
	__u32 flags; /*TCP报头标志*/
	... ... ...
};

以下是TCP代码用于访问该结构的宏。该宏由一些简单的指针组成
#define TCP_SKB_CB(__SKB) ((struct tcp_skb_cb *) & ((__skb)->cb[0]))
以下是TCP子系统在段的收条中填写此结构的实例:

int tcp_v4_rcv(struct sk_buff *skb)
{
	... ... ...
	th = skb->h.th;
	TCP_SKB_CB(skb)->seq = ntohl(th->seq);
	TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin + skb->len - th->doff *4);
	TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
	TCP_SKB_CB(skb)->when = 0;
	TCP_SKB_CB(skb)->flags = skb->nh.iph->tos;
	TCP_SKB_CB(skb)->sacked = 0;
	... ... ...
}

为了了解cb缓冲区中的参数是如何找回的,可以看一下net/ipv4/tcp_output.c中的tcp_transmit_skb函数。TCP用该函数把一个数据段压入IP层以便于传输。

后面你还将了解到IPv4如何采用cb存储有关IP分片的信息。
unsigned int csum
unsigned char ip_summed
这些代表校验和(checksum)以及相关联的状态标识。其用法将在第十九章进行分析。
unsigned char cloned
当一个boolean标识位置时,表示该结构是另一个sk_buff缓冲区的克隆。
unsigned char pkt_type
此字段会根据帧的L2目的地址进行类型划分。可能的取值列于include/linux/if_packet.h中。对于Ethernet设备而言,此参数由函数eth_type_trans进行初始化。
可能被分派的主要值如下:
PACKET_HOST
已接收帧的目的地址,即接收接口;换句话说,封包已到达其目的地。
PACKET_MULTICAST
已接收的帧的目的地址是该接口已注册的多播地址之一。
PACKET_BROADCAST
已接收的帧的目的地址是接收接口的广播地址。
PACKET_OTHERHOST
已接收的帧的目的地址不属于与该接口相匹配的地址(单播(unicast)、多播)以及广播);因此,如果转发机制使能,该帧将不得不被转发,否则就会被丢弃。
PACKET_OUTGOING
封包正被发送。此标识的用户是Decnet协议,并且给每个网络分流器一个输出封包副本的函数
PACKET_LOOPBACK
封包正传送至回环设备(loopback device)。由此标识,当处理回环设备时,内核可以跳过一些真实设备所需的操作。
PACKET_FASTROUTE
用Fastroute功能路由封包。2.6版本内核不再支持Fastroute
后面详细描述了这些值如何根据L2目的地址而设置。
__u32 priority
此字段表示正被传输或转发的封包QoS(Quality of Service,服务质量)等级。如果该封包由本地产生,套接字层会定义优先级值。相反的,如果此封包正被转发,则函数rt_tos2priority(由ip_forward函数调用)会根据IP报头本身的ToS(Type of Service, 服务类型)字段的值而定义此字段的值。
unsigned short protocol
从L2层的设备驱动程序的角度来看,就是用在下一个较高层的协议。典型的协议有IP、IPv6以及ARP;完整的列表可以在include/linux/if_ether.h中找到。由于每种协议都有自己的函数处理例程用来处理输入的封包,因此,驱动程序使用这个字段通知其上层该使用那个处理例程。每个驱动程序会调用netif_rx用来启动上面的网络分层的处理例程,所以,在该函数被调用前protocol字段必须被初始化。
unsigned short security
这是封包的安全等级。最初引入这个字段是为了IPsec,但是现在已经不用了。

功能专用字段

Linux内核是模块化的,允许你选择要包含什么及要省略什么。因此,只有当内核编译为支持特定功能,如防火墙(Netfilter)或QoS,某些字段才会包含在sk_buff数据结构中:

unsigned long nfmark
__u32 nfcache
__u32 nfctinfo
struct nf_conntrack *nfct
unsigned int nfdebug
struct nf_bridge_info *nf_bridge

这些参数由Netfilter(防火墙代码)使用,更明确地讲就是指由内核选项“Device Drivers->Networking support ->Networking options -> Networking packet filtering”及其两个子选项“Network packet filtering debugging”和“Bridge IP/ARP packets filtering”所用。

union {...} private
这个联合由HIPPI(High Performance Parallel Interface,高性能并行接口)使用。
相关联的内核选项为“Device Drivers ->Networking support -> Networking device support ->HIPPI driver support”。

__u32 tc_index
__u32 tc_verd
__u32 tc_classid
这些参数是由流量控制功能使用,更明确地讲就是指由内核选项“Device Drivers->Networking support -> Networking options ->QoS and/or fair queueing”及其子选项“Packet classifier API”使用。

struct sec_path *sp
由IPsec协议组使用,以记录转换信息。

管理函数

内核通常提供许多很短,很简单的函数,用以操作sk_buff元素或元素列表。在下图中,将描述其中最重要的几个函数。首先,我们来看用于分配和释放缓冲区的函数,然后是用于在帧头部或尾部预留空间的操作指针(即skb->data)函数。

如果你查看一下include/linux/skbuff.hnet/core/skbuffer.c文件,就会注意到几乎所有函数都有着两个版本,名称类似do_something和__do_something。通常来讲,第一个是包裹函数,增加了额外的合理性检查或者在调用第二个函数前后加上锁机制。内部所用的__do_something形式通常不会被直接调用(除非满足特定条件,例如,上锁条件或命名)。违反规则时通常都是写的很差的函数,而最终都会被修正。
在这里插入图片描述

分配内存:alloc_skb和dev_alloc_skb
定义在net/core/skbuff.c中的alloc_skb是分配缓冲区的主要函数。我们已经知道,数据缓冲区和报头(sk_buff数据结构)是两种不同的实例,也就是,建立一个缓冲区会涉及到两次内存分配(一个是分配缓冲区,而另一个是分配sk_buff结构)。
alloc_skb通过调用kmem_cache_alloc函数,从一个缓存中取得一个sk_buff数据结构,然后调用kmalloc(如果有缓存可用也会使用)以取得一个数据缓冲区。其代码如下:

skb = kmem_cache_alloc(skbuff_head_cache, gfp_mask & ~__GFP_DMA);
... ... ...
size = SKB_DATA_ALLGN(size);
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask)

调用kamlloc之前,size参数被SKB_DATA_ALIGN宏进行调整以强制对齐。返回之前,此函数会对结构中的一些参数做初始化,产生的最后结果如下图所示。
在图的右侧内存区块的底端,你可以发现引入了填充区域(padding area)以强制对齐。skb_shared_info块主要用于处理一些IP片段。
在这里插入图片描述

dev_alloc_skb是由设备驱动程序使用的缓冲区分配函数,而且应该是在中断模式中执行。此函数是一个包裹alloc_skb的函数,为了优化的原因在申请的大小之上在加上16个字节。而且因为此函数是由中断事件处理函数调用的,所以会要求原子操作。
(GFP_ATOMIC)

static inline struct sk_buff *dev_alloc_skb(unsigned int length)
{
	return __dev_alloc_skb(length, GFP_ATOMIC);
}
static inline
struct sk_buff *__dev_alloc_skb(unsigned int length, int gfp_mask)
{
	struct sk_buff *skb = alloc_skb(length + 16, gfp_mask);
	if(likely(skb))
		skb_reserve(skb,16);
	return skb;
}

当没有体系结构说明(architecture-specific)定义时,__dev_alloc_skb的定义就是默认的。

释放内存:kfree_skb和dev_kfree_skb

这两个函数会释放一个缓冲区,使其返回缓存池(缓存)。kfree_skb是直接由dev_kfree_skb包裹函数调用并启动的。随后由设备驱动程序定义并使用,而其名称和dev_alloc_skb类似,但其组成只有一个简单的宏,这个宏不做任何事情,只是调用kfree_skb。只有当skb->users计数器为1时(该缓冲区已无任何用户时),这个基本函数才会释放一个缓冲区。否则,就只是递减该计数器。所以,如果一个缓冲区有三位用户,则只有第三次调用dev_kfree_skb或kfree_skb时才会释放内存。

下图显示出释放一个缓冲区时的所有步骤。一个sk_buff结构可以持有一次对dst_entry数据结构的引用。因此,当sk_buff结构被释放时,也必须调用dest_release以递减相关的dest_entry数据结构的引用计数值。

destructor函数指针已经被初始化时,就会在这里调用(参考本章稍早的“配置字段”一节)。

下图看起来是一个简单的场景:一个sk_buff数据结构与另一个实际存储数据的内存块相关联。然而,在该数据块底端的skb_shared_info数据结构可以持有一些指向其他内存片段的指针。
在这里插入图片描述

数据预留及对齐:skb_release、skb_put、skb_push以及skb_pull
skb_reserve会在缓冲区的头部预留一些空间(头空间),通常允许插入一个报头,或者强迫数据对齐某个边界。此函数会移动标记有效载荷开端指针data和tail。显示出调用skb_reserve(skb,n)之后的结果。缓冲区分配之后,通常马上就会调用此函数,此时data和tail的值仍然相同。
如果你看一下集中Ethernet驱动程序之一的接受函数(例如,drivers/net/3c59x.c中的vortex_rx),你就会发现,他们把任何数据存储在刚分配到的缓冲区之前,都会使用下列命令
skb_reserve(skb,2);/*把IP对齐在16字节地址边界上*/
由于知道要把一个带有14字节头的Ethernet帧拷贝到缓冲区,参数2会使缓冲区的头移动2个字节。这样IP报头就可以从缓冲区开始按照16字节边界对其,并紧接在Ethernet报头之后,如下所示:
在这里插入图片描述

下图为数据在传输期间在相反方向使用skb_reserve的实例
在这里插入图片描述

缓冲区穿过协议栈从TCP层向下到链路层

  1. 当TCP被请求传输一些数据时,他会根据一些准则[TCP mss(Maximum Segment Size),最大阶段尺寸],支持分散-聚集IO(scatter-gather IO等等)分配一个缓冲区。
  2. TCP会在缓冲区头部预留足够的空间(用skb_reserve),以容纳所有层(TCP、IP、链路)的报头。参数MAX_TCP_HEADER是所有层报头的总和,其计算要考虑最坏情况:因为TCP层不知道传输所用的接口类型,因此会为每个分层预留最大可能的报头。甚至会考虑多个IP报头的可能性(因为当内核编译成支持IP-over-IP通道时,就可能有多个IP报头)。
  3. TCP有效载荷拷贝到缓冲区。注意,上图只是一个例子而已。TCP有效载荷可能以不同方式组织,如可以作为多个片段来存储。
  4. TCP层添加报头
  5. TCP层把缓冲区传给IP层,IP层也同样添加其报头
  6. IP层把IP封包传给邻居层,邻居层则把链路层报头添加进来。

缓冲区的克隆和拷贝

当同一个缓冲区需要由不同消费者个别处理时,那些消费者可能需要修改sk_buff描述符的内容(指向协议报头的h和nh指针),但内核不需要完全拷贝sk_buff结构和相关的数据缓冲区。相反,为了提高效率,内核可以克隆(clone)原始值,也就是拷贝sk_buff结构,然后使用引用技术,以免过早释放共享的数据块。缓冲区的克隆由skb_clone函数实现。

在这里插入图片描述

列表管理函数

这些函数会操作sk_buff元素列表,列表也称为队列(queue)。函数的完整列表参见include/linux/skbuff.h和net/core/skbuff.c。一些最常用的函数如下。
skb_queue_head_init
用一个元素为空的队列对sk_buff_head。
skb_queue_head, skb_queue_tail
把一个缓冲区分别添加到队列的头或尾。
skb_dequeue,skb_dequeue_tail
把一个元素分别从队列的头或尾去掉。第二个函数可能应该称为skb_dequeue_head才能与其他队列函数之名相称。
skb_queue_purge
把队列变为空队列
skb_queue_walk
依次循环运行队列里中的每个元素。

net_device结构

在这里插入图片描述

net_device结构的字段可以分成一下几种类型:

  • 配置(Configuration)
  • 统计数据
  • 设备状态
  • 列表管理
  • 流量管理
  • 功能专用
  • 通用
  • 函数指针

标识符

net_device结构有3个标识符,不能搞混:
int ifindex
独一无二的ID,当设备以dev_new_index注册时分派给每个设备。
int iflink
这个字段主要由(虚拟)隧道设备使用,可用于标识抵达隧道另一端的真实设备。
unsigned short dev_id
目前在zSeries OSANIC上由IPv6所用。此字段用于区别可由不同OS同时共享的同一种设备的诸多虚拟实例。

配置

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

接口类型和端口

在这里插入图片描述

链路层多播

多播是一种用于把数据传递给多位接受者的机制。多播可以在L3网络层(IP)以及L2链路层(Ethernet)中使用。本节我们关注的是后者。

链路层多播的传送,可以通过在链路层报头中使用特殊地址或控制信息(当链路层协议不支持使,也可以模拟)。Ethernet本身就支持多播。

利用一个特定位把多播地址和其他范围的地址分开来。这意味着可能地址中有5成是多播。
每个设备都会为其监听的每个链路层多播地址保存一个dev_mc_list结构的实例。链路层多播地址可以分别用dev_mc_add和dev_mc_delete函数添加或删除。net_device结构中相关的字段包括:
struct dev_mc_list *mc_list
指向此设备的dev_mc_list结构列表表头的指针。
int mc_count
此设备的多播地址数目,也就是mc_list所指的列表长度。

int allmulti
为非零值时,引起此设备监听所有多播地址。如同本章之前所讨论的promiscuity,allmulti是引用计数,而非一个简单的布尔值。这是因为多台设备(例如,VLAN和绑定设备)可能各自都需要监听所有地址。当此变量从0变为非0时,就会调用dev_set_allmulti函数,以指示该接口监听所有多播地址。

函数指针

net_device数据结构中也有不少的函数指针。这类函数主要用于:

  • 传输和接收帧
  • 在缓冲区上添加或解析链路层报头
  • 改变配置的一部分
  • 获取统计数据
  • 与特定功能交互
    前面说明完成特定任务所需的字段时,已介绍了一些函数指针。以下是通用的函数指针:

struct ethtool_ops *ethtool_ops
指向一组函数指针的指针,用于设置或取出不同设备参数的配置。

int (*init)(...)
void (*uninit)(...)
void (*destructor)(...)
int (*open)(...)
int (*stop)(...)

用于初始化,清理,销毁,开启以及关闭一个设备。不是所有的函数都会被用到。
struct net_device_stats* (*get_stats)(...)
struct iw_statistics* (*get_wireless_stats)(...)
设备驱动程序所收集的一些统计数据可以使用用户空间应用程序予以显示,如ifconfig和ip,而其他统计数据则严格由内核使用。
这两种方法用于收集统计数据,get_stats针对一般设备,而get_wireless_stats针对无线设备。

int (*hard_start_xmit)(...)
用于传输一个帧
int (*hard_headrt)(...)
int (*rebuild_header)(...)
int (*hard_header_cache)(...)
void (*header_cache_update)(...)
int (*hard_header_parse)(...)
int (*neigh_setup)(...)
由邻居层使用。
int (*do_ioctl)(...)
ioctl是系统调用,用于向设备发出命令。调用此方法可以处理一些ioctl命令
void (*set_multicast_list)(...)
在“链路层多播”一节已知道mc_list和mc_count用于管理L2多播地址列表。这个方法用于要求设备驱动程序配置设备以监听这些地址。通常不会直接调用此方法。而是通过包裹函数,如dev_mc_upload或其他无锁版本__dev_mc_upload。当一个设备无法安装多播地址列表时,就只会将其全部开启。

int (*set_mac_address)(...)
改变设备MAC地址。当设备没有提供此功能时(如同Bridge虚拟设备的情况),就会置为NULL。
int (*set_config)(...)
配置驱动程序的参数,如硬件参数irq、io_addr以及if_port。较高分层参数(如协议地址)是由do_ioctl来处理。使用此方法的设备不是很多,特别是能实现探测的新设备。
int (*change_mtu)(...)
改变设备的MTU值。改变此字段对社会被驱动程序没什么影响,只是强制内核软件接受新的MTU,再据此处理分段。
void (*tx_timeout)(...)
在看门狗定时器到期之前调用此方法,用于确认该次传送是否花了一段很可疑的长时间才完成。只有定义了该方法,看门狗定时器才会启动。
int (*accept_fastpath)(...)
快速交换是一种内核功能,允许设备驱动程序在中断期间使用一个小的缓存用来路由输入的流量(跳过所有软件分层),也称FASTROUTE。从内核2.6.8版本开始不再支持快速交换功能。这种方法用于测试设备是否可以使用快速交换功能。

本章主要设计的文件

在这里插入图片描述

相关文章:

  • 基于springboot,vue企业网盘系统
  • Dreamweaver:Dreamweaver软件的界面简介、安装、案例应用之详细攻略
  • Atlas数据治理
  • echarts拖动进度条,动态更新数据
  • MATLAB | MATLAB中绘图的奇淫技巧合集
  • 指针笔试题解析(4)
  • [Spring Boot 3] 整合NoSQL与构建RESTful服务
  • 基于非线规划算法的船舶能量调度
  • 二、PL/SQL 编程基础
  • NGINX源码之:ngx_open_cached_file
  • 【路径规划-机器人栅格地图】基于蚁群算法求解大规模栅格地图路径规划及避障附Matlab代码
  • SpringCache的介绍和使用
  • java-php-python-ssm艾灸减肥管理网站计算机毕业设计
  • 力扣每日一题2022-09-23中等题:设计链表
  • 内存数据库简介-内存数据库性能排行
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • 2017 年终总结 —— 在路上
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • Intervention/image 图片处理扩展包的安装和使用
  • Linux各目录及每个目录的详细介绍
  • orm2 中文文档 3.1 模型属性
  • Swoft 源码剖析 - 代码自动更新机制
  • 阿里研究院入选中国企业智库系统影响力榜
  • 记一次和乔布斯合作最难忘的经历
  • 力扣(LeetCode)21
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 什么是Javascript函数节流?
  • - 转 Ext2.0 form使用实例
  • Spring第一个helloWorld
  • ​批处理文件中的errorlevel用法
  • ​如何在iOS手机上查看应用日志
  • #LLM入门|Prompt#3.3_存储_Memory
  • #控制台大学课堂点名问题_课堂随机点名
  • #微信小程序:微信小程序常见的配置传旨
  • (0)Nginx 功能特性
  • (js)循环条件满足时终止循环
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (接口自动化)Python3操作MySQL数据库
  • (十六)一篇文章学会Java的常用API
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (转)程序员技术练级攻略
  • .cfg\.dat\.mak(持续补充)
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET Core引入性能分析引导优化
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .NET 中让 Task 支持带超时的异步等待
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • /etc/sudoers (root权限管理)
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • @JSONField或@JsonProperty注解使用
  • @RequestMapping-占位符映射
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节