深入理解Linux网络技术内幕学习笔记第二章:一些重要的数据结构

第二章:一些重要的数据结构

struct sk_buff :所有网络分层都会使用这个结构体来存储其抱头和有效载荷。套接字所对应的缓冲区实际上就是指这个结构体。当缓冲区往下经过每个分层时,会先调用skb_reserve函数来为相应的报头分配空间。

内核在一个双向链表中维护所有的sk_buff结构,为了每个节点都能迅速找到头,该双向链表定义了一个头节点sk_buff_head(该节点不存放数据),每个sk_buff中会有一个指针指向该头节点。

struct sk_buff_head{

struct sk_buff *next;

next 和prev必须放在前面

struct  sk_buff *prev;

__u32 qlen;     

表示双向链表中的元素数目

spinlock_t lock;

用于防止对表的并发访问

};

struct sk_buff的字段大致分为四部分:布局,通用,功能专用,管理函数。

strcut _sk_buff

struct sk_buff *next;

下面的字段都是一些布局相关的

struct sk_buff *prev;

struct sk_buff_head *list;

指向sk_buff_head

struct sock *sk;

指向拥有此缓冲区的套接字。当数据被接收或者发送时会用到这个。若只是转发,则不需要

unsigned int len;

缓冲区中数据区的大小。数据往协议上层走,会丢掉相应协议头(没有真正丢掉,只是偏移了指针),往下则相反(调用sk_reserve来分配头的空间)。len也包含了协议头‘

unsigned int data_len;

只计算数据大小,不包含协议头部

unsigned int mac_len;

MAC包头大小

atomic_t users;

使用这个缓冲区的实例的数目。users有时会用atomic_inc和atomic_dec来递增或递减。大多数时候都是用skb_get和kfree_skb处理

unsigned int truesize;

表示缓冲区总的大小,sk_buff结构体本身的大小加上分配的数据区大小即上面的len字段,len+sizeof(sk_buff)

unsigned char *head;

head和end指向已分配缓冲区的开始和结尾。

unsigned char *end

unsigned char *data

data和tail指向实际数据的头部和尾部。data包括协议头。

unsigned char *tail

void (*destructor)(…)

该函数指针用于缓冲区被删除时完成某些工作。若缓冲区不属于某个套接字,该指针通常不会初始化。否则,通常设成sock_rfree或sock_wfree。

下面开始的是一些通用字段,和特定内核功能无关。

struct timeval stamp;

时间戳,通常只对一个接收的封包才有意义。表示封包何时被接收的。或者有时表示封包预定的传输时间。

struct net_device *dev;

描述一个网络设备。接收封包时,指向接收的设备,传输时,指向传输的设备。

struct net_device *input_dev;

已被接收的封包所源自的设备。此字段主要由流量控制字段使用。

struct net_device *real_dev;

此字段只对虚拟设备有意义。代表虚拟设备所关联的真实设备。

union {…}h;

union{…}nh;

union{…}mac;

这些是指向tcp/ip协议栈的协议包头的指针。h针对传输层,nh针对网络层,mac针对链路层。

struct dst_entry dst;

由路由子系统使用。

char cb[40];

用来存储每层的私有数据。

unsigned int csum;

unsigned int ip_summed;

csum和ip_summed代表校验和及相关联的状态标识。十九章有用法描述。

unsigned char cloned;

当其为true时,标识该结构是另一个sk_buff的克隆。

unsigned char pkt_type;

该字段会根据帧的L2目的地址进行类型划分。

PACKET_HOST:已接受帧的目的地址,即接收接口。

PACKET_MULTICAST:已接收帧的目的地址是该接口已注册的多播地址之一。

PACKET_BROADCAST:已接收帧的目的地址是接收接口的广播地址。

还有其他一些字段这里不在描述。详情可见include/linux/if_packet.h。                                           

__u32 priority;

标识正被传输或转发的封包Qos等级。若封包有本地产生,套接字层会定义该字段,若只是转发,则会根据ip报文的tos字段来决定。

unsigned short ptotocol;

低层用来通知上一层应该使用哪个协议。

unsigned short security;

已弃用

下面是一些功能专用字段。Linux内核是模块化的,只有当内核编译为支持特定功能,如防火墙(netfilter)或Qos,某些字段才会被包含在sk_buff结构体中。

unsigned long nfmark;

__u32 nfcache;

__u32 nfctinfo;

struct nf_conntrack *nfct;

unsigned int nfdebug;

struct nf_bridge_info *nf_bride;

上面这几个参数由Netfilter(防火墙代码)使用。

union {…}private;

由HIPPI(高性能并行接口使用)。

__u32 tc_index;

__u32 tc_verd;

__u32 tc_classid;

这几个参数由流量控制功能使用.

struct sec_path *sp;

由IPsec协议组使用,记录转换信息。IPSec是通过对IP协议的分组进行加密和认证来保护IP协议的网络传输协议簇(一些相互关联的协议的集合)

下面的是一些管理函数。并不是skb_buff的成员,只是用来管理skb_buff某些成员的函数。???

alloc_skb函数

用来分配内存,有两次分配过程,一次是分配数据缓冲区,一次是分配sk_buff结构

dev_alloc_skb函数

由设备驱动程序使用的缓冲区分配函数,由中断事件处理函数调用。该函数分配内存也是调用的alloc_skb

kfree_skb函数和dev_kfree_skb函数

dev_kfree_skb(驱动程序使用)包裹kfree_skb,真正的释放内存由kfree_skb完成。只有当sk_buff->users为1时,调用该函数才会真正释放内存,否则只是计数器减一。当sk_buff->destructor被初始化了时,会在这里调用。

深入理解Linux网络技术内幕学习笔记第二章:一些重要的数据结构_第1张图片

 

数据预留及对齐函数:

skb_reserve,skb_put,skb_push,

skb_pull

TCP层在传输数据时,会在缓冲区头部预留足够的空间来容纳所有层(tcp,ip,链路层)的报头。

skb_reserve会在缓冲区头部预留一些空间,通常允许插入一个包头,或者强迫数据对齐。

skb_push把数据块添加到缓冲区的开端skb_put把数据添加到缓冲区的尾端。skb_pull把一个数据块从缓冲区头部删除。这些函数并没有真正添加或删除数据,只是移动data和tail这两个指针而已。

skb_shared_info结构和skb_shinfo函数

在缓冲区尾端有一个skb_shared_info的数据结构(这个结构也是之前调用alloc_skb函数分配的,但不作为缓冲区的一部分,缓冲区指的时head和end指针之间的区域)。紧跟在尾端的end指针之后。

这个结构体用来保存数据区块的附加信息。

skb_shinfo是用来获取到这个结构体的一个宏

struct skb_shared_info{

atomic_t dataref; 数据块用户数目。可以理解为有多少个sk_buff指向该数据缓冲区。每克隆一次,该字段加1

unsigned int nr_frags;      nr_frags,frags_list,frags,用于处理ip片段

unsigned short tso_size;  TSO功能会用到tso_size和tso_segs

unsigned short tso_segs;

struct sk_buff *frag_list;

skb_frag_t frags[MAX_SKB_FRAGS];

};

为适应高速网络,现代网卡中普遍承担了部分 L3-L4 层的处理逻辑(e.g. 校验和计算、传输层分片重组等),来减轻 Host CPU 的处理负担。甚至有些网卡(e.g. RDMA 网卡)还将整个 L4 层的处理都转移到硬件上,以完全解放 Host CPU。得益于这些硬件技术,Host OS 的网络协议栈处理才能与现有的高速网络相匹配。TSO(TCP Segmentation Offload):是一种利用网卡对大数据包进行分片,从而减小 CPU 负荷的一种技术。

skb_clone函数

pskb_copy函数

skb_copy函数

当同一个缓冲区由多个消费者处理时,可以只克隆skb_buff结构体,不克隆缓冲区(当缓冲区不会被修改时,否则必须连同缓冲区一起复制),然后使用引用计数。skb_buff的克隆没有链接到链表上,也没有引用套接字的拥有者。克隆的和被克隆的cloned字段都置1,克隆的users置1。但是dataref会递增。

 

当只修改head和end之间的数据时,使用pskb_copy复制该区域。若连同frags里的数据也要改,则使用skb_copy.

常用列表管理函数(指管理保存skb_buff结构的双向链表)

完整的函数列表见include/linux/skbuff.h和

net/core/skbuff.c

skb_queue_head_init:初始化一个,只有一个哑节点struct skb_buff_head

skb_dequeue,skb_queue_tail:把一个元素添加到链表头部,尾部

skb_dequeue,skb_dequeue_tail:把一个元素从链表的头或尾去掉

skb_queue_purge:把链表变为空

skb_queue_walk:遍历链表。

这类函数操作链表前必须获取sk_buff_head结构提供的回旋锁。

struct net_device的字段可以分为:配置,统计数据,设备状态,列表管理,流量管理,功能专用,通用,函数指针(或VFT)。

所有的net_device结构放在一个全局变量dev_base所指的全局列表中。

此结构有很多来自不同分层的参数,很庞大,很有可能会因为优化而做出一些改变。

struct net_device

int ifindex;

独一无二的ID,设备注册时分派给每个设备

int iflink;

主要有(虚拟)隧道设备使用,标识抵达隧道另一端的真实设备

unsigned short dev_id

目前在zSeries OSA NIC 上又ipv6使用。见net/ipv6/addrconf.c中的注释

下面是一些配置字段。有些配置字段是内核给的默认值,有些是驱动程序填写。

char name[IFNAMSIZ];

设备名称 如eth0

unsigned long mem_start;

unsigned long mem_end;

这两个字段描述设备所用的共享内存,用于设备和内核通信。初始化和访问在驱动程序内进行,较高层不需要关系

unsigned long base_add;

设备自有内存映射到I/O内存的起始地址。

unsigned int irq;

设备与内核对话的终端编号。此值可由多个设备共享。驱动程序使用request_irq和free_irq来分配和释放此变量

unsigned char if_port;

此接口所使用的端口类型。例如以太网10base2,10base-set等

unsigned char dma;

设备使用的DMA通道。设备的DMA通道由reuqest_dma和free_dma来分配和释放。取得DMA通道后,可以用各种include/asm-architecture文件中所提供的enable_dma和disable_dma来开启或关闭。这些函数由ISA设备使用(啥意思)。PCI设备采用了其他方法。不是所有设备都可以用DMA,有些总线不使用DMA

unsigned short flags;

该字段中某些位如(IFF_MULTICAST)代表网络设备的功能,其他位代表状态的改变,如IFF_UP,IF_RUNNING.在include/linux/if.h中可以看到完整列表。驱动程序会在初始化期间设置这些功能,而状态标识由内核管理。

深入理解Linux网络技术内幕学习笔记第二章:一些重要的数据结构_第2张图片

 

unsigned short gflags;

几乎不在使用,只是由于兼容性而存在

unsigned short priv_flags;

存储用户空间不可见的标识。目前,该字段由VLAN,和Bridge虚拟设备使用。上面三个标识可以通过dev_change_flags来修改

int features;

存储设备的其他功能。如网卡能否对所有封包做检验和的工作。此参数由驱动程序初始化。可以在net_device数据结构定义中找到NETIF_F_XXX特征功能列表以及很好的注释

unsigned mtu;

表示设备能处理的帧的最大尺寸

unsigned short type;

设备类型(Ethernet,Frame Relay(帧中继)等)。可以在include/linux/if_arp.h找到完整类型列表

unsigned short hard_header_len;

以字节为单位的设备头的大小。如Ethernet报头是14字节。

unsigned char broadcast[MAX_ADDR_LEN];

链路层广播地址

unsigned char dev_addr[MAX_ADDR_LEN];

设备链路层地址。地址长度字节由下面的addr_len指定,依赖于设备的类型。Ethernet地址都是6字节即MAC地址

unsigned char addr_len;

int promiscuity;

混杂模式相关。一个设备如果可以接收所有封包,意味着其处于混杂模式。

这是计数器而不是bool。因为可能有多个客户程序要求混杂模式。

net_device没有用来记录统计数据的收集字段,但是有一个由驱动程序涉及的priv指针,指向一个存储有关接口信息的私有数据结构。不同的设备类型,结构不一样,但几乎所有的结构都有一个net_device_stats字段(定义在include/linux/netdevice.h),这个字段包含所有网络设备共有的统计数据,可以通过get_stats方法获取。但是无线设备

不使用net_device_stats,而是用iw_statistics字段,可以通过get_wireless_stats方法获取。

下面是一些设备状态字段

unsigned long state

网络队列子系统使用的一组标识。

enum {…} reg_state

设备的注册状态,参见第八章

unsigned long trans_start

最近一个帧传输启动的时间(jiffies测量),驱动程序设置。用于检测网卡问题。

unsigned long last_rx

最后一个封包的到达时间(以jiffies测量)

struct net_device *master

有些协议允许一组设备群集起来作为单一设备,若此接口是群组成员,则指向群组中的主设备。

spinlock_t xmit_lock

该所是驱动程序函数的访问串行化,意味着cpu每次只能对给定的一个设备做一次传输

int xmit_lock_owner

持有上面那个锁的cpu ID,对于单处理器总是0,对于多处理器,锁没有被取走时,值为-1。驱动程序支持时,也可以做无锁传输,参见十一章。

void *atalk_ptr

void *ip_ptr

void *dn_ptr

void *ip6_ptr

void *ec_ptr

void *ax25_ptr

这个六个字段都是指针,指向特定协议专用的数据结构。

下面时一些列表管理字段。net_device数据结构被插入到一个全局列表和两个hash表中。

struct net_device *next

指向全局列表中的下一个元素

struct hlist_node name_hlist

struct hlist_node index_hilst

这两个字段表示把net_device结构链接至两个hash表的bucket列表

struct dev_mc_list *mc_list

每个设备都会为其监听的链路层多播地址保存一个struct dev_mc_list 结构实例。该字段指向设备的dev_mc_list结构列表表头的指针

int mc_count

此设备的多播地址数目,及mc_list所指的列表长度。

int allmulti

非零时,引起此设备监听所有多播地址。该值是个引用计数,因为多台设备如vlan和绑定设备可能各自需要监听所有地址?(这里不太理解)

下面是一些流量管理的字段。

struct net_device *next_sched

由软中断之一使用,参见第十一章

struct Qdisc *qdisc

struct Qdisc *qdisc_sleeping

struct Qdisc *qdisc_ingress

struct list_head qdisc_list

这些字段用于管理入口和出口的封包队列,以及不同cpu对此设备的访问

spinlock_t queue_lock

spinlock_t ingress_lock

流量控制子系统为每个网络设备定义了一个私有出口队列,queue_lock用于防止对出口队列的并发访问。ingress_lock则是针对入口流量做同样的事

unsigned long tx_queue_len

设备的传送队列长度,当内核支持流量控制时,可能不用该值,该值可以通过sysfs文件系统来调整参见/sys/class/net/device_name/目录

下面是功能专用字段。当所属功能包含在内核时,这些参数才会出现在net_device的定义中

struct divert_blk *divert

分流器是一种功能,允许你改变输入的封包的源和目的地址。

struct net_bridge_port *br_port

当此设备配置成桥接端口所需要的额外信息

void (*vlan_rx_register)(…)

把设备注册为拥有vlan标记功能参见net/8021q/vlan.c

void (*vlan_rx_add_vid)(…)

把vlan添加至设备

void (*vlan_rx_kill_vid)(…)

把vlan从设备删除

int netpoll_rx

void (*poll_controller)(…)

这两个参数由选用的Netpoll功能使用,第十章会介绍

下面是一些通用字段。不同于之前介绍的列表管理字段,下面这些字段用于管理一些结构

atomic_t refcnt

引用计数。该值为零前,设备无法除名

int watchdog_timeo

struct timer_list watchdog_timer

这两个字段加上之前的tx_timeout字段,实现了看门狗定时器,参见第十一章

int (*poll)(…)

struct list_head poll_list

int quota

int weight

这几个字段由NAPI功能使用,参见第十章

const struct iw_handler_def *wireless_handlers

struct iw_public_data wireless_data

这两个字段表示其他无线设备使用的参数和函数指针。可以参见get_wireless_stats

struct list_head todo_list

用于网络设备的除名

struct class_device class_dev

由新的通用内核驱动程序基础架构使用

下面是一些通用函数指针。

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。其他数据严格由内核使用

int (*hard_start_xmit)(…)

传输一个帧,见十一章

int (*hard_header)(…)

int (*rebuild_header)(…)

int (*hard_header_cache)(…)

void (*header_cache_update)(…)

int (*hard_header_parse)(…)

int (*neigh_setup)(…)

这几个函数由邻居层使用。参见二十七章

int (*do_ioctl)(…)

执行ioctl系统调用,用于向设备发出命令

void (*set_multicast_list)(…)

先前介绍过mc_list和mc_count用于管理链路层多播地址列表,这个函数用于要求设备驱动程序配置设备以监听这些地址。通常不会直接调用,而是通过包裹函数来使用。

int (*set_mac_address)(…)

改变设备的mac地址。当设备没有提供此功能时,设为NULL

int (*set_config)(…)

配置驱动程序参数,如硬件参数,irq,io_addr,if_port。较高分层的参数如协议地址是由do_ioctl来处理。

int (*change_mtu)(…)

改变设备的MTU值。改变此字段对设备驱动程序没有影响,只是强制内核软件接受新的MTU,再据此分段处理

void (*tx_timeout)(…)

在看门狗到期之时调用此方法,用于确认该次传送是否花了很长一段很可疑的时间才完成。只有定义了该方法,看门狗定时器才会启动。

int (*accept_fastpath)(…)

FASTROUTE是一种内核功能,这个方法测试设备是否可以使用FASTROUTE功能。内核2.6.8版本开始不再使用FASTROUTE

深入理解Linux网络技术内幕学习笔记第二章:一些重要的数据结构_第3张图片

 

你可能感兴趣的:(linux,学习)