第二章:一些重要的数据结构
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 |