Most SKBs are stored on a list, whose head is implemented by 'struct sk_buff_head', which is quite simple:

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

	__u32		qlen;
	spinlock_t	lock;

Like the sk_buff, the first two members implement the doubly linked list handling. The third member, 'qlen', keeps track of how many packets are on this list. The fourth member, 'lock', is used for SMP protection.

There are many operations one can perform on SKBs wrt. list tracking. Here are the primary interfaces:

int skb_queue_empty(const struct sk_buff_head *list);

Is the given list empty?

struct sk_buff *skb_peek(struct sk_buff_head *list_);

Return, but do not remove, the first SKB on the list, else NULL. You should only use this function on an SKB list you have exclusive access to, else a thread in another context could remove the SKB this function returns.

struct sk_buff *skb_peek_tail(struct sk_buff_head *list_);

Exactly like skb_peek(), except it returns the last SKB on the list.

__u32 skb_queue_len(const struct sk_buff_head *list_);

Return the number of packets on the given list.

void skb_queue_head_init(struct sk_buff_head *list);

Initialize an sk_buff_head object. If you dynamically allocate a data structure with an sk_buff_head member in it, you should pass it to this function before trying to use it.

Next we have the queue management functions. You use these to add and remove SKBs from a given list. Each routine has two variants, the main routine and a sister version which has the same name with two underscores ("__") prepended to the name. This latter variant does no SMP locking for you, and must be used only in contexts where you have made it such that you have exclusive access to the SKB list. This can be accomplished via a seperate lock in your data structure, for example. If in doubt, call the non-underscore variant. This is what most of the datagram protocols do.

/* Remove and return the first SKB on 'list'.  */
struct sk_buff *skb_dequeue(struct sk_buff_head *list);

/* Remove and return the last SKB on 'list'.  */
struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list);

/* Insert 'newsk' before 'old' on the list that 'old' is on.  */
void skb_insert(struct sk_buff *old, struct sk_buff *newsk);

/* Insert 'newsk' after 'old' on the list that 'old' is on.  */
void skb_append(struct sk_buff *old, struct sk_buff *newsk);

/* Unlink 'skb' from the list that it is on.  */
void skb_unlink(struct sk_buff *skb);