###1. struct sk_buff

sk_buff是Linux网络代码中最重要的结构体之一。它是Linux在其协议栈里传送的结构体,也就是所谓的“包”,在其中包含了各层协议的头部的指针,比如ethernet, ip ,tcp ,udp等等,linux中还定义了一堆sk_buff的相关操作函数

###2. sk_buff 成员

struct sk_buff {

	/* These two members must be first. */
	struct sk_buff		*next;			// 用于串联struct sk_buff, 形成双向链表
	struct sk_buff		*prev;			// 用于串联struct sk_buff, 形成双向链表

	ktime_t			tstamp;			// 数据到达的时间戳, 通常由 netif_rx() 调用 net_timestamp() 设置

	struct sock		*sk;			// 该数据对应的socket
	struct net_device		*dev;			// 发送数据包时的出口网络设备, 目的地址为本地的数据包使用“lo”接口

	......

	unsigned int		len,			// 实际数据的长度
				data_len;			// 数据的长度
	__u16			mac_len,			// 链路层数据头的长度
				hdr_len;			// clone的sk_buff的可写数据头的长度
	
	......

	__be16			protocol;			// 链路层帧的协议类型,如ETH_P_ARP, 由driver标识
	
	......

	int			skb_iif;			// 接收到该数据包的网络接口的编号

	......

	sk_buff_data_t		inner_transport_header;	//内部的传输层协议头
	sk_buff_data_t		inner_network_header;	//内部的网络层协议头
	sk_buff_data_t		inner_mac_header;		//内部的链路层协议头
	sk_buff_data_t		transport_header;		//传输层协议头
	sk_buff_data_t		network_header;		//网络层协议头
	sk_buff_data_t		mac_header;		//链路层协议头

	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t		tail;			//tail 指针,下一节详细解释
	sk_buff_data_t		end;			//end 指针,下一节详细解释
	unsigned char		*head,			//head 指针,下一节详细解释
				*data;			//data 指针,下一节详细解释
	unsigned int		truesize;			//data buffer和sk_buff的总大小
	atomic_t			users;			//sk_buff的引用计数
};

###3. sk_buff的分配

linux kernel中提供两种api用于 sk_buff和数据的分配

struct sk_buff *alloc_skb(unsigned int size, gfp_t priority);
struct sk_buff *dev_alloc_skb(unsigned int length);

sk_buff和数据缓冲区是2种不同的实例, 因此, 涉及到2次内存分配: 从告诉缓存中分配sk_buff; 分配对应的数据缓冲区

alloc_dev() 的priority参数用于指定内存分配的方式:

  • GFP_ATOMIC:分配内存优先级高,不会睡眠, 用于中断上下文
  • GFP_KERNEL:常用的方式,可能会阻塞

dev_alloc_skb()默认使用 GFP_ATOMIC 的方式来分配内存, 尽量不要在非中断上下文中使用

####3.1 skb_shared_info结构

在数据缓存区的末尾,即end指针所指向的地址起紧跟着有一个skb_shared_ info结构,保存了数据块的附加信息

struct skb_shared_info {
	unsigned char	nr_frags;
	__u8		tx_flags;
	unsigned short	gso_size;
	/* Warning: this field is not always filled in (UFO)! */
	unsigned short	gso_segs;
	unsigned short  gso_type;
	struct sk_buff	*frag_list;
	struct skb_shared_hwtstamps hwtstamps;
	__be32          ip6_frag_id;

	/*
	 * Warning : all fields before dataref are cleared in __alloc_skb()
	 */
	atomic_t	dataref;

	/* Intermediate layers must ensure that destructor_arg
	 * remains valid until skb destructor */
	void *		destructor_arg;

	/* must be last field, see pskb_expand_head() */
	skb_frag_t	frags[MAX_SKB_FRAGS];
};

其中

  • dataref : 数据缓冲区的引用计数, 例如skb_clone会克隆一个sk_buff, 和原始的sk_buff指向同一个数据缓冲区

####3.2 sk_buff 高速缓存

在skb_init()中申请了两个用于分配sk_buff (不包含数据缓冲区)的高速缓存

void __init skb_init(void)
{
	skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
				      sizeof(struct sk_buff),
				      0,
				      SLAB_HWCACHE_ALIGN|SLAB_PANIC,
				      NULL);
	skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
					(2*sizeof(struct sk_buff)) +
					sizeof(atomic_t),
					0,
					SLAB_HWCACHE_ALIGN|SLAB_PANIC,
					NULL);
}

两个高速缓存:

  • skbuff_head_cache : 一般情况下, 从该缓存分配 sk_buff, 每次分配的大小为 sizeof(sk_buff)
  • skbuff_fclone_cache : 若知道sk_buff可能或被克隆, 则从该缓存来分配sk_buff, 每次分配两个sk_buff, 大小为 2*sizeof(sk_buff) + sizeof(atomic_t), 额外的一个引用用于标识这一对sk_buff有几个被使用, 其值可以为 0,1,2

###4. sk_buff的释放

linux kernel提供2个api用于sk_buff的释放

void kfree_skb(struct sk_buff *skb);
#define dev_kfree_skb(a)	consume_skb(a)
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);	

kfree_skb() 和 dev_kfree_skb() 都是调用 __kfree_skb()来完成sk_buff的释放,他们会修改sk_buff的引用计数sk_buff.users,只有当 sk_buff.users为0时, 才完成真正的释放工作

  • linux kernel 里面使用 kfree_skb(),
  • 设备驱动里面最好使用 dev_kfree_skb(),
  • 中断上下文中使用 dev_kfree_skb_irq()
  • dev_kfree_skb_any() 在中断和非中断上下文中都可使用, 内部会根据需要自己决定调用 dev_kfree_skb() 或者 dev_kfree_skb_irq()

###5. sk_buff 的数据缓冲区指针

分配sk_buff时, 会涉及到两次内存分配, 一块用于存放 sk_buff, 另一块数据缓冲区用于存放数据包, sk_buff中有4个成员指针(head, end, data, tail)会指向数据缓冲区

先看上层协议发送数据这种情况

sk_buff and data

  1. 如图左, 首先, 分配一个sk_buff以及data buffer
    • sk_buff.head 指向data buffer的起始位置, 并且这一指针是固定不变的
    • sk_buff.end 指向data buffer的起始位置, 并且这一指针是固定不变的
    • sk_buff.data 指向实际存放的数据的起始位置, 因现在没有存放数据,和sk_buff.head相同
    • sk_buff.tail 指向实际存放的数据的结束位置, 因现在没有存放数据,和sk_buff.head相同
    • sk_buff.len 实际存放数据的长度, 因现在没有存放数据, 故为0
  2. 如图右, 在data buffer中, 预留一部分空间给下层协议头, 然后填入数据
    • sk_buff.data 指向实际存放的数据的起始位置, sk_buff.head和sk_buff.data之间是为下层协议预留的空间
    • sk_buff.tail 指向实际存放的数据的结束位置
    • sk_buff.len 实际存放数据的长度

如上面所示的方式, sk_buff在各层之间传递时, 通过修改 sk_buff.data 和 sk_buff.tail 就可以索引各协议层的数据, 同样有一套api用于修改sk_buff.data和sk_buff.tail指针(下一小节会详细说明)

在收包时, 每一层的协议栈, 在完成本层的处理之后, 在将sk_buff 发给上层之前, 会将 sk_buff.data指向本层协议的协议头的尾部, 即上层协议收到sk_buff时, sk_buff.data指向该层的协议头, 发送的过程则与此相反

另外, 需要注意的是, sk_buff中几个标识长度的成员

  • sk_buff.len : 标识实际数据的长度, 即 sk_buff.tail - sk_buff.data, 这一值在各协议层传递时会有变化
  • sk_buff.data_len : data buffer的长度,在分配好之后, 这一值不会变化
  • sk_buff.truesize : sk_buff结构体和data buffer的总长度,在分配好之后, 这一值不会变化

####5.1 修改 sk_buff的数据缓冲区指针

linux kernel 中有一套api用于修改sk_buff.data和sk_buff.tail指针

sk_buff api

unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
inline void skb_reserve(struct sk_buff *skb, int len);
  • skb_push() : 用于扩展实际的数据区的尾部, 即增加 sk_buff.tail 的值,同时修改sk_buff.len, 然后返回旧的 sk_buff.tail的值
  • skb_push() : 用于扩展实际的数据区的首部, 即减小 sk_buff.data 的值,同时修改sk_buff.len, 然后返回修改后的 sk_buff.data的值, 常用于发送数据时, 数据包从上层协议传递到下层时, 添加下层的协议头
  • skb_pull() : 用于扩展实际的数据区的首部, 即减小 sk_buff.data 的值,同时修改sk_buff.len, 然后返回修改后的 sk_buff.data的值,常用于接收数据时, 数据包从下层协议传递到上层时, 剥离下层的协议头
  • skb_reserve() : 用于在实际数据区的首部预留空间, 即同时增加 sk_buff.data 和 sk_buff.tail, 常用在于在填充协议数据前, 为下层协议头预留空间

####5.2 获取sk_buff的数据缓冲区指针

static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)			//获取sk_buff.end
static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)			//获取sk_buff.tail
static inline void skb_reset_tail_pointer(struct sk_buff *skb)				//sk_buff.tail = sk_buf.data
static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)		//设置 sk_buff.tail 到 sk_buff.data的偏移

static inline unsigned int skb_headroom(const struct sk_buff *skb)			//sk_buff.head到sk_buff.data之间的空间大小
static inline int skb_tailroom(const struct sk_buff *skb)				//sk_buff.tail到sk_buff.end之间的空间大小
static inline int skb_availroom(const struct sk_buff *skb)				//sk_buff.tail到sk_buff.end之间由sk_stram_alloc()分配的空间的大小

###6. sk_buff的协议头指针

sk_buff.xxx_header保存了各协议层的header的指针

  • mac_header : 链路层帧头指针
  • network_header : 网络层数据包头
  • transport_header : 传输层数据报头
  • inner_mac_header : 内部的链路层帧头指针, 用于处理网络隧道
  • inner_network_header : 内部的链路层帧头指针, 用于处理网络隧道
  • inner_transport_header : 内部的传输层数据报头, 用于处理网络隧道

接收数据时, 由于第n层协议从第n-1层协议接收到 sk_buff时, sk_buff.data指向第n层的协议头, 因此第n层的协议负责将该层的协议头指针初始化为 sk_buff.data, 例如, ip层(网络层)在接收到sk_buff后负责将sk_buff.network_header 初始化为 sk_buff.data

linux kernel中提供了相关的api来获取/设置这些 xxx_header 指针

static inline unsigned char *skb_mac_header(const struct sk_buff *skb)			//获取k_buff.mac_header
static inline unsigned char *skb_network_header(const struct sk_buff *skb)		//获取sk_buff.network_header
static inline unsigned char *skb_transport_header(const struct sk_buff *skb)		//获取sk_buff.transport_header

static inline void skb_set_mac_header(struct sk_buff *skb, const int offset		//设置 sk_buff.mac_header 到 sk_buff.data的偏移
static inline void skb_set_network_header(struct sk_buff *skb, const int offset)		//设置 sk_buff.network_header 到 sk_buff.data的偏移
static inline void skb_set_transport_header(struct sk_buff *skb,const int offset)		//设置 sk_buff.transport_header 到 sk_buff.data的偏移

static inline void skb_reset_mac_header(struct sk_buff *skb)				//skb->mac_header = skb->data
static inline void skb_reset_network_header(struct sk_buff *skb)			//skb->network_header = skb->data
static inline void skb_reset_transport_header(struct sk_buff *skb)			//skb->transport_header = skb->data

static inline int skb_mac_header_was_set(const struct sk_buff *skb)			//检查是否初始化了skb->mac_header
static inline bool skb_transport_header_was_set(const struct sk_buff *skb)		//检查是否初始化了skb->transport_header


static inline unsigned char *skb_inner_mac_header(const struct sk_buff *skb)		//获取sk_buff.inner_mac_header
static inline unsigned char *skb_inner_network_header(const struct sk_buff *skb)		//获取sk_buff.inner_network_header
static inline unsigned char *skb_inner_transport_header(const struct sk_buff *skb)	//获取sk_buff.inner_transport_header

static inline void skb_set_inner_mac_header(struct sk_buff *skb, const int offset)	//设置 sk_buff.inner_mac_header 到 sk_buff.data的偏移
static inline void skb_set_inner_network_header(struct sk_buff *skb, const int offset)	//设置 sk_buff.inner_network_header 到 sk_buff.data的偏移
static inline void skb_set_inner_transport_header(struct sk_buff *skb, const int offset)	//设置 sk_buff.inner_transport_header 到 sk_buff.data的偏移

static inline void skb_reset_inner_mac_header(struct sk_buff *skb)			//skb->inner_mac_header = skb->data
static inline void skb_reset_inner_network_header(struct sk_buff *skb)			//skb->inner_network_header = skb->data
static inline void skb_reset_inner_transport_header(struct sk_buff *skb)			//skb->inner_transport_header = skb->data

static inline void skb_reset_inner_headers(struct sk_buff *skb)				//reset  inner_mac_header, inner_network_header, inner_transport_header
static inline void skb_reset_mac_len(struct sk_buff *skb)

当然, 还有一些包装好的函数可以直接获取各层的协议头

static inline struct ethhdr *eth_hdr(const struct sk_buff *skb)
{
	return (struct ethhdr *)skb_mac_header(skb);
}

static inline struct iphdr *ip_hdr(const struct sk_buff *skb)
{
	return (struct iphdr *)skb_network_header(skb);
}

static inline struct icmphdr *icmp_hdr(const struct sk_buff *skb)
{
	return (struct icmphdr *)skb_transport_header(skb);
}

static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb)
{
	return (struct tcphdr *)skb_transport_header(skb);
}

static inline struct udphdr *udp_hdr(const struct sk_buff *skb)
{
	return (struct udphdr *)skb_transport_header(skb);
}

......

其它的sk_buff 指针相关的api

static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)			//检测sk_buff中的数据是否有指定的长度
static inline void skb_pop_mac_header(struct sk_buff *skb)				//skb->mac_header = skb->network_header
static inline void skb_probe_transport_header(struct sk_buff *skb, const int offset_hint)	//若skb->transport_header 未初始化则初始化它
static inline void skb_mac_header_rebuild(struct sk_buff *skb)

###8. sk_buff 的数据缓冲区添加/删除/拆分

static inline int skb_add_data(struct sk_buff *skb, char __user *from, int copy)
void skb_trim(struct sk_buff *skb, unsigned int len)
static inline int pskb_trim(struct sk_buff *skb, unsigned int len)
void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len)
  1. skb_add_data() : 在缓冲区尾部(sk_buff.tail所指的位置)添加数据, 其中
    • from : 源数据的起始位置
    • copy : 添加的数据的长度
  2. skb_trim() : 删除sk_buff数据区尾部的数据(即 移动sk_buff.tail 向 sk_buff.data 靠拢), 只能处理线性存储数据的sk_buff
    • len : 截取后, 新的数据区长度, 若小于原始长度则不执行操作
  3. pskb_trim() : 同skb_trim(), 但是pskb_trim()还能够处理非线性存储数据的 sk_buff
  4. skb_split() : 根据指定长度拆分skb,使得原skb中的数据长度为指定的长度len,而剩下的数据保存到拆分得到的skb1中, 当拆分数据的长度小于线性数据长度时比较容易处理,直接拆分线性数据区即可,若拆分数据的长度大于线性数据长度,则需要拆分非线性区域中的数据
    • skb : 原始的skb
    • skb1 : 保存拆分后, 剩余数据的skb
    • len : 拆分后, 原始skb中应当保留的数据长度

###9. sk_buff 的克隆和复制

如果一个SKB会被不同的用户独立操作(例如一个接收包程序要把该包传递给多个处理函数或者网络模块),而这些用户可能只是修改SKB描述符中的某些字段值,例如 mac_header, data等, 内核没有必要为每个用户复制一份完整的SKB描述及其相应的数据缓存区,而会为了提高性能,只复制SKB描述符,同时增加数据缓存区的引用计数,以免共享数据被提前释放, 这一过程由 skb_clone() 完成

struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)

克隆后, 原始的sk_buff.cloned和克隆的sk_buff.cloned都会被设置为1,克隆的sk_buff.users值会被置为1,这样在第一次释放时就会释放掉,同时将数据缓存区引用计数dataref递增1,因为又多了一个克隆的sk_buff指向它

可以使用 skb_cloned() 来检查一个sk_buff是否被clone

static inline int skb_cloned(const struct sk_buff *skb)

一个sk_buff被克隆后,该sk_buff对应的数据缓存区中的内容就不能再被修改,这也意味着访问数据的函数没有必要加锁

当一个函数不仅要修改sk_buff,而且还要修改数据缓存区中的数据时,就需要同时复制数据缓存区, 这一过程可以使用 skb_copy() 和 pskb_copy() 来完成

struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
static inline struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)

当sk_buff为非线性存储数据时, 则只能使用 skb_cpoy()

###10. sk_buff 双向链表

sk_buff被串联成一个复杂的双向链表,由struct sk_buff_head 引用, 在它们中有next和prev指针,分别指向链表的下一个节点和前一个节点

struct sk_buff_head {
	/* These two members must be first. */
	struct sk_buff	*next;
	struct sk_buff	*prev;

	__u32		qlen;
	spinlock_t	lock;
};

struct sk_buff {
	/* These two members must be first. */
	struct sk_buff		*next;
	struct sk_buff		*prev;
	......
};

###11. sk_buff 队列操作

linux kernel为 sk_buff_head 指向的双向链表提供了一套队列操作接口:

初始化一个sk_buff队列, 即 sk_buff_head.len = 0, 并将 sk_buff_head.next 和 sk_buff_head.prev 指向 sk_buff_head 自身

static void skb_queue_head_init(struct sk_buff_head *list)

添加 sk_buff 到队列头/尾

void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk);
void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk);

从 sk_buff 队列头/尾取出一个sk_buff

struct sk_buff *skb_dequeue(struct sk_buff_head *list)
struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list);

在队列中插入sk_buff

skb_insert(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list);

将队列变为空(事实上, 循环对队列调用 skb_dequeue(), 然后对每一个sk_buff调用kfree_skb() )

void skb_queue_purge(struct sk_buff_head *list);

遍历队列中指定的sk_buff及其之后的sk_buff

skb_queue_walk(queue, skb)
skb_queue_walk_safe(queue, skb, tmp)
skb_queue_walk_from(queue, skb)
skb_queue_walk_from_safe(queue, skb, tmp)

遍历队列中指定的sk_buff及其之前的sk_buff

skb_queue_reverse_walk(queue, skb)
skb_queue_reverse_walk_safe(queue, skb, tmp)
skb_queue_reverse_walk_from_safe(queue, skb, tmp)

连接两个sk_buff队列

/* 将 list 所指的sk_buff 链表插入到 head 所指的链表的头部 */
void skb_queue_splice(const struct sk_buff_head *list, struct sk_buff_head *head)

/* 同 skb_queue_splice() 将 list 所指的sk_buff 链表插入到 head 所指的链表的头部, 另外将 list 链表清空 */
void skb_queue_splice_init(struct sk_buff_head *list, struct sk_buff_head *head)

/* 将 list 所指的sk_buff 链表插入到 head 所指的链表的尾部 */
void skb_queue_splice_tail(const struct sk_buff_head *list, struct sk_buff_head *head)

/* 同 skb_queue_splice_tail() 将 list 所指的sk_buff 链表插入到 head 所指的链表的尾部, 另外将 list 链表清空 */
void skb_queue_splice_tail_init(struct sk_buff_head *list, struct sk_buff_head *head)

其它操作

__u32 skb_queue_len(const struct sk_buff_head *list_)
int skb_queue_empty(const struct sk_buff_head *list)
bool skb_queue_is_last(const struct sk_buff_head *list, const struct sk_buff *skb)
bool skb_queue_is_first(const struct sk_buff_head *list, const struct sk_buff *skb)
struct sk_buff *skb_queue_next(const struct sk_buff_head *list, const struct sk_buff *skb)
struct sk_buff *skb_queue_prev(const struct sk_buff_head *list, const struct sk_buff *skb)

###12. linux中的sk_buff队列

在linux中,为每一个cpu都维护了一个sk_buff队列(sk_buff_head指向的双向链表),由于是每个cpu都有一个队列,因此在不同的cpu之间我们就不需要任何锁来控制sk_buff队列 的并发处理

struct softnet_data {
	......
	struct sk_buff		*completion_queue;
	struct sk_buff_head	process_queue;
	......
	unsigned int		dropped;
	struct sk_buff_head	input_pkt_queue;
	struct napi_struct	backlog;
};

其中

  • completion_queue : 发送完成后的sk_buff放在这一队列中等待被回收
  • input_pkt_queue : 用于保存待处理的接收到的sk_buff
  • dropped : 被丢弃的数据包的计数
  • process_queue : 用于保存正在处理的接收到的sk_buff

可以通过如下的方式获取per-cpu的 softnet_data, 第二个参数为cpu的id

per_cpu(softnet_data, cpu);

####13 sk_buff在sk_buff队列中的流动

网络设备驱动填充好sk_buff后, 调用 netif_rx() 将sk_buff交给linux的网络协议栈来处理

netif_rx()
	enqueue_to_backlog()

int enqueue_to_backlog(struct sk_buff *skb, int cpu, unsigned int *qtail)
{
	......
	sd = &per_cpu(softnet_data, cpu);
	......

	if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {
		if (skb_queue_len(&sd->input_pkt_queue)) {
	enqueue:
			__skb_queue_tail(&sd->input_pkt_queue, skb);
			......
			return NET_RX_SUCCESS;
		}
		......
		____napi_schedule(sd, &sd->backlog);
		......
		goto enqueue;
	}

	sd->dropped++;
	......
	atomic_long_inc(&skb->dev->rx_dropped);
	......
	return NET_RX_DROP;
}
  1. 首先, 获取per-cpu的softnet_data
  2. 若 input_pkt_queue 队列中 sk_buff数目小于netdev_max_backlog(通常为1000)
    • 若 input_pkt_queue 队列非空, 则直接将sk_buff入队到input_pkt_queue队列的尾部然后返回
    • 若 input_pkt_queue 队列为空, 则先唤醒软中断NET_RX_SOFTIRQ来处理input_pkt_queue中的包,然后将sk_buff入队到input_pkt_queue队列的尾部再返回
  3. 如果input_pkt_queue队列中sk_buff数目大于netdev_max_backlog(通常为1000), 则直接丢弃sk_buff, 同时
    • 增加 softnet_data.dropped 计数
    • 增加 skb->dev->rx_dropped 计数

____napi_schedule()用于触发软中断 NET_RX_SOFTIRQ 来处理 per-cpu 的 softnet_data.input_pkt_queue 队列中的skb, 该软中断的注册过程为

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

因此, 其处理函数为 net_rx_action()

void net_rx_action(struct softirq_action *h)
{
	struct softnet_data *sd = &__get_cpu_var(softnet_data);
	......
	int work, weight;
	struct napi_struct *n;
	.....
	n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
	......
	work = n->poll(n, weight);
	......
}

对于不使用NAPI的net_device 来说, napi_struct.poll 指向 process_backlog(), 而 使用NAPI的驱动则需要自己注册poll 函数, 但是最终还是会调用 process_backlog()

int process_backlog(struct napi_struct *napi, int quota)
{
	......
	while (work < quota) {
		......
		while ((skb = __skb_dequeue(&sd->process_queue))) {
			......
			__netif_receive_skb(skb);
			......
		}

		......

		qlen = skb_queue_len(&sd->input_pkt_queue);
		if (qlen)
			skb_queue_splice_tail_init(&sd->input_pkt_queue, &sd->process_queue);
		......
	}
	......
}

在 process_backlog()中 :

  1. 先将 process_queue 队列中的sk_buff依次出队, 通过__netif_receive_skb(skb)发送给协议栈,依次进行 链路层/网络层/传输层 等的处理
  2. 再将 input_pkt_queue 队列添加到process_queue队列的尾部, 然后清空 input_pkt_queue 队列

###14. softnet_data.completion_queue

有些时候, 需要延后释放sk_buff (例如在中断上下文中, 需要快速返回),可以将需要释放的的sk_buff添加到 softnet_data.completion_queue 链表中, 在 net_device 的软中断NET_TX_SOFTIRQ 的处理函数 net_tx_action()中, 会释放softnet_data.completion_queue中的所有 sk_buff

例如, 在dev_kfree_skb_irq()中,并不执行实际的释放工作, 只是将需要释放的的sk_buff添加到 softnet_data.completion_queue 链表中, 然后触发 NET_TX_SOFTIRQ 软中断来处理