最近研究了linux内核的网络子系统上的网络分组的接收与发送的流程,发现这个叫sk_buff的东西无处不在,内核利用了这个结构来管理分组,在各个层中传递这个结构,因此sk_buff可以说是linux内核网络子系统的基石,所以我决定在这篇文章中好好扒一扒这个sk_buff。
下面列出我我认为比较重要的sk_buff中的成员变量:
内核是利用一个双链表来管理sk_buff的,不过不使用内核的标准双链表而是自己实现了双链表:
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sk是sk_buff关联的socket
ktime_t tstamp是分组到达的时间
struct net_device *dev是相关联的网络硬件设备
struct dst_entry *dst这个成员很重要,分组在ip层处理时sk_buff会被传入一个ip_route_input函数中,这个函数会判断这个分组是要在本地接收呢还是要转发出去,这时函数就会填充这个dst成员让其内的成员来代表目的地,例如:
struct dst_entry
{
……
int (*input)(struct sk_buff*);
int (*output)(struct sk_buff*);
}; 在接收分组时input被填充为ip_local_deliver或ip_forward,在发送分组时被填充为ip_output
内核会分配一块内存来存储sk_buff,head和end指示了这块内存的头和尾,data和tail则指示了这块内存中具体的数据的头和尾
mac_header、network_header和transport_header分别指向了分组的mac、ip和tcp(或udp)首部
内核在利用sk_buff管理分组时是利用了sk_buff里的指针成员来指向分组的不同部分,这样有一个好处就是在传递该分组时不用复制整个分组而是在整个分组发送与接收过程中传递sk_buff指针即可,节省了复制分组的开销。再一个就是内核在解包和组包时只需要调整sk_buff的指针就可以了,例如要在TCP首部前增加IP首部只需要在预分配的内存里拿出一小块内存在存放IP首部,再将network_header指向那个小块内存即可,注意拿出的小块内存是在TCP首部内存之前的,两者紧密相连。
最后来看看内核管理sk_buff的双链表吧:
sk_buff_head为链表的头,链表为双向循环链表,next指向了下一个sk_buff,prev指向了最后一个sk_buff,qlen保存了链表的sk_buff个数