深入理解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被初始化了时,会在这里调用。 |
数据预留及对齐函数: 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中可以看到完整列表。驱动程序会在初始化期间设置这些功能,而状态标识由内核管理。 |
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网络技术内幕学习笔记第二章:一些重要的数据结构相关推荐
- 深入理解Linux网络技术内幕学习笔记第十九章:因特网协议第四版(IPv4):Linux的原理和功能
本章主要介绍Linux支持IP的数据结构和基本活动,如入口IP包如何传递至IP接收函数,校验和如何验证,以及IP选项如何处理. 主要的IPv4数据结构: struct iphdr{ };//ip报头 ...
- 深入立即Linux网络技术内幕学习笔记第十六章:桥接:Linux实现
网桥设备抽象: 对Linux而言,网桥是虚拟设备,要想传输或接收数据,需要将真实设备绑定到虚拟网桥上. 上图中,有几点需要注意: LAN1和LAN2通过网桥连接在一起,子网都是一样的. 网桥连接到路由 ...
- 深入理解linux网络技术内幕读书笔记(十)--帧的接收
Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...
- 深入理解Linux网路技术内幕学习笔记第四章:通知链
第四章:通知链 内核很多子系统之间具有很强的依赖性,其中一个子系统侦测到的或者产生的事件,其他子系统可能都感兴趣,为了实现这种需求,Linux使用了通知链.通知链只在内核子系统之间使用. 通知链就是一 ...
- 深入理解Linux网络技术内幕(十)——帧的接收
文章目录 前言 与其他功能交互 设备的开启和关闭 队列 通知内核帧已接收:NAPI和netif_rx NAPI简介 NAPI所用之net_device字段 net_rx_action和NAPI 新旧驱 ...
- 《深入理解LINUX网络技术内幕》小记1
1.函数指针是一种很方便的方式,使用函数指针的优点是可以根据不同准则以及该对象所扮演的角色进行初始化.函数指针在网络代码中 广为使用,举例说明: a.当入口数据封包或出口数据封包由路由器子系统处理时, ...
- 《深入理解Linux网络技术内幕》阅读笔记(十一)
准备接收: 准备传输: 每个cpu都有一个output_queue队列,里面包含有数据要传输的设备.每个设备有自己的qdisc队列(若设备的队列规则存在时),里面包含该设备需要发送的sk_buff队列 ...
- linux dev alloc name,深入理解Linux网络技术内幕-设备注册和初始化(二)
NIC注册和注销的通用架构 Linux系统中NIC网络设备驱动程序利用网络代码进行注册和注销有其通用的架构,这里以PCI Ethernet NIC为例,其他设备类型只是所以函数名称和调用方式不同,主要 ...
- linux c 网络事件 通知,深入理解Linux网络技术内幕—通知链
内核的很多子系统之间具有很强的相互依赖关系,其中一个子系统发现的或产生的事件,其他子系统可能都有兴趣.为了实现这种交互需求,Linux使用了通知链(Notification Chain)机制. 通知链 ...
- linux网口初始化_深入理解Linux网络技术内幕——网络设备初始化
概述 内核的初始化过程过程中,与网络相关的工作如下所示: 内核引导时执行start_kernel,start_kernel结束之前会调用rest_init,rest_init初始化内核线程init(在 ...
最新文章
- Objective-C自动生成文档工具:appledoc
- 【组队学习】【30期】6. 树模型与集成学习
- 女博士生爱上中专男,父亲直言自己只能打50分,你怎么看恋人学历差距大?...
- 英伟达“暴力碾压”谷歌:53分钟训练完BERT,2.2毫秒完成推理,创下NLP三项新纪录...
- 看看你能认出多少种编程语言
- wxWidgets:wxActivateEvent类用法
- nginx+tomcat+memcached负载均衡
- 如何从零开始开发一款嵌入式产品(20年的嵌入式经验分享学习,来自STM32神舟系列开发板设计师的总结)
- 【深度学习系列】基础知识、模型学习
- s6730堆叠_不再只是堆叠硬件 ivvi S6全方位评测
- visa linux 串口 通信,使用visa进行串口通信
- 世界地球日:全国网友用手机种出“保护黄河幸福林”
- arm架构linux运行docker失败,armdocker:在x86上模拟运行arm容器
- Design and Model Analysis of the E-Commerce Development Platform for 3-Tiered Web Applications
- 谷歌强烈推荐!浏览器助手,让你的浏览器至少提升10个档次!
- JavaCV最小依赖
- html怎么设置取消隐藏,怎么取消wifi隐藏_取消隐藏wifi设置方法-192路由网
- Python中的多线程是假的多线程?
- #444 沸腾客厅:在播客里温暖一个冬天
- 利用群发短信进行精准高效的会员营销