03-网络协议接口层
16.1.1 网络协议接口层
网络协议接口层最主要的功能是给上层协议提供了透明的数据包发送和接收接口。当上层ARP或IP需要发送数据包时,它将调用网络协议接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向struct sk_buff数据结构的指针。dev_queue_xmit()函数的原型为:
dev_queue_xmit (struct sk_buff * skb );
同样地,上层对数据包的接收也通过向netif_rx()函数传递一个struct sk_buff数据结构的指针来完成。netif_rx()函数的原型为:
int netif_rx(struct sk_buff *skb);
sk_buff结构体非常重要,定义于include/linux/skbuff.h文件,它的含义为“套接字缓冲区”,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。
当发送数据包时,Linux内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。
代码清单16.1列出了sk_buff结构体的几个关键数据成员,其中head为整个缓冲区的头指针,data为有效数据的头指针,tail为其尾部位置,而transport_header、network_header、mac_header分别为传输层、网络层和MAC层的包头位置。
代码清单16.1 套接字缓冲区sk_buff
1 struct sk_buff {
2 ...
3 unsigned int len,
4 data_len;
5 __u16 mac_len,
6 hdr_len;
7 ...
8 sk_buff_data_t transport_header;
9 sk_buff_data_t network_header;
10 sk_buff_data_t mac_header;
11 ...
12 sk_buff_data_t tail;
13 sk_buff_data_t end;
14 unsigned char *head,
15 *data;
16 };
下面我们来分析套接字缓冲区涉及的操作函数,Linux套接字缓冲区支持分配、释放、变更等功能函数。
(1)分配。
Linux内核用于分配套接字缓冲区的函数有:
struct sk_buff *alloc_skb(unsigned int len, gfp_t priority)
struct sk_buff *dev_alloc_skb(unsigned int len);
alloc_skb()函数分配一个套接字缓冲区和一个数据缓冲区,参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对于ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。
(2)释放。
Linux内核用于释放套接字缓冲区的函数有:
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);
上述函数用于释放被alloc_skb()函数分配的套接字缓冲区和数据缓冲区。
Linux内核内部使用kree_skb()函数,而网络设备驱动程序中则最好用dev_kfree_skb()、dev_kfree_skb_irq()或dev_kfree_skb_any()函数进行套接字缓冲区的释放。其中,dev_kfree_skb()函数用于非中断上下文,dev_kfree_skb_irq()函数用于中断上下文,而dev_kfree_skb_any()函数则在中断和非中断上下文中皆可采用。
(3)变更。
Linux内核中可以用如下函数在缓冲区尾部增加数据:
unsigned char skb_put(struct sk_buff skb, unsigned int len);
它会导致skb->tail后移len,而skb->len增加len的大小。通常,设备驱动的收数据处理中会调用此函数。
Linux内核中可以用如下函数在缓冲区开头增加数据:
unsigned char skb_push(struct sk_buff skb, unsigned int len);
它会导致skb->data前移len,而skb->len增加len的大小。与该函数完成相反功能的函数是skb_pull(),它可以在缓冲区开头移除数据。
对于一个空的缓冲区而言,调用如下函数可以调整缓冲区的头部:
static inline void skb_reserve(struct sk_buff *skb, int len);
它会将skb->data和skb->tail同时后移len。