momo zone

调核人的blog

kfree_skb 中的疑惑

代码如下

void kfree_skb(struct sk_buff *skb)
{
	if (unlikely(!skb))
		return;
	if (likely(atomic_read(&skb->users) == 1))
		smp_rmb();
	else if (likely(!atomic_dec_and_test(&skb->users)))
		return;
	trace_kfree_skb(skb, __builtin_return_address(0));
	__kfree_skb(skb);
}

这其实是一个包裹函数,先检查skb的引用计数,符合调节的才会真正执行__kfree_skb去释放。
那么引用计数是几才会真正释放?
首先如果user=1是会被释放的,然后后面可以解释为
if(atomic_dec_and_test()) 
__kfree_skb();

说明user=1的时候也会被释放。user为其他值的时候则只是递减然后返回。那么前面那个atomic_read还要他干嘛?我觉得第一个判断atomic_read()是为了优化,比直接用atomic_dec_and_test节省几条指令,而且少了dec的写操作可以避免cacheline更新同步操作,在网络负载重的情况下能够提升速度。

但这里还有个值得注意的地方就是atomic_read后面有个smp_rmb()。这是一条运行时内存屏障(实际在X86上同时也是编译时内存屏障,详见后面介绍)。内存屏障的作用有两个:

1. 禁止指令重排

2. 强制刷新一次缓存,也就是多核间的缓存同步

第一个作用是否是针对x86的我不太确认,第二个增加了一次cacheline同步我觉得就和之前的atomic_read优化相矛盾。总之这里有一些疑问,我试图去找最早提交该补丁的commit log,发现在这里:

http://oss.sgi.com/archives/netdev/2005-02/msg00070.html

发现是来自SGI的,可以肯定这个不是针对X86的,然后时间大概是在05年初,可以确定是在Linux-2.6.12-rc2之前,看了下代码也确实是这样。mail loop讨论的问题是arp_queue进行__skb_unlink出队操作后紧接着进性kfree_skb会发生问题,代码如下:

			if (skb_queue_len(&neigh->arp_queue) >=
			    neigh->parms->queue_len) {
				struct sk_buff *buff;
				buff = neigh->arp_queue.next;
				__skb_unlink(buff, &neigh->arp_queue);
				kfree_skb(buff);
                        }

__skb_unlink操作中有skk->list=NULL,然后__kfree_skb有这句:

	if (skb->list) {
	 	printk(KERN_WARNING "Warning: kfree_skb passed an skb still "
		       "on a list (from %p).\n", NET_CALLER(skb));
		BUG();
	}

mail 提到一种情况(没有上smp_rmb时):

        cpu 0                   cpu 1
        skb_get(skb)
        unlock(neigh)
                                lock(neigh)
                                __skb_unlink(skb)
                                kfree_skb(sb)
        kfree_skb(skb)

cpu1 进行__skb_unlink后kfree_skb中走的是atomic_dec_and_test分支(因为前面user+=1了,且有自旋锁操作),因此cpu1此时看到了所有的cpu0对skb的操作。cpu1执行完kfree_skb后该轮到cpu0的操作(或者与此同时cpu0也去操作),因为atomic_dec_and_test也会同步缓存,所以cpu0 看到的user 也是更新的值,cpu0再去kfree_skb也不会产生问题。
接下来换一个场景(没有上smp_rmb时):

       cpu 0                   cpu 1
                               __skb_unlink()
                               kfree_skb()
       kfree_skb()

和之前的一样cpu1先出队,然后进行kfree_skb走的是atomic_read分支,那么这个skb就真正被释放掉了。假如在此刻同时cpu0也执行kfree_skb,由于之前的atomic_read没有缓存同步操作,导致cpu0看到的skb数据可能都是旧的。在__kfree_skb时skb->list是非空,导致panic。换句话说,cpu1没有kfree_skb也会有问题。也就是如果在一个cpu上进行__skb_unlink,却在另一个cpu上执行kfree_skb就是很容易出问题的。

这是我对这个补丁的理解,同步缓存似乎是主要理由。

现在的内核skb结构已经大改了,__skb_kfree也大变。但这个smp_rmb还在我觉得就是用来防止多个cpu同时kfree_skb一个skb(user==1)时发生问题。

另外一个话题,我原来以为X86不会对写操作进行指令重排,现在透过这个补丁得知我原来的理解是错的。举例看一下:

int ready;
int critical;

void br(int *i)
{
	critical = 4444*(*i);
	ready = 100;
}

gcc -O3 -S mbr.c -o mbr.s
可以看到

	.file	"mbr.c"
	.text
	.p2align 4,,15
	.globl	br
	.type	br, @function
br:
.LFB18:
	.cfi_startproc
	movl	(%rdi), %eax
	movl	$100, ready(%rip)
	imull	$4444, %eax, %eax
	movl	%eax, critical(%rip)
	ret
	.cfi_endproc
.LFE18:
	.size	br, .-br
	.comm	t,8,4
	.comm	critical,4,16
	.comm	ready,4,16
	.ident	"GCC: (SUSE Linux) 4.7.1 20120723 [gcc-4_7-branch revision 189773]"
	.section	.comment.SUSE.OPTs,"MS",@progbits,1
	.string	"Ospwg"
	.section	.note.GNU-stack,"",@progbits

发现ready变量赋值被提前了。因为critial赋值来自一个指针,该操作需要load操作,微架构的指令调度单元认为ready放前面合适。如果禁止编译时指令重排:barrier()肯定可以,mb,rmb,wmb如何呢,测试了一样也可以(又改变了我之前的认识)。atomic_read呢?试了一下竟然也可以。当然这一切都是GCC做的。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: