从陈老哥的博文中,获得一种新体验。在学习kernel代码的时候,不仅要注重其内在的流程和架构,也可以多多关注一下代码的实现细节,从小的地方去学习编程之道。
今天以一个小函数compare_ether_addr_64bits,作为一个尝试。
static inline unsigned compare_ether_addr_64bits(const u8 addr1[6+2],
const u8 addr2[6+2])
{
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
unsigned long fold = ((*(unsigned long *)addr1) ^
(*(unsigned long *)addr2));
if (sizeof(fold) == 8)
return zap_last_2bytes(fold) != 0;
fold |= zap_last_2bytes((*(unsigned long *)(addr1 + 4)) ^
(*(unsigned long *)(addr2 + 4)));
return fold != 0;
#else
return compare_ether_addr(addr1, addr2);
#endif
}
该函数用于比较ethernet 网卡地址,即MAC地址是否相同。MAC为6字节长度,为什么这个函数的参数声明为[6+2]呢?并且作为一个形参的数组,其实际上就是一个指针类型。那么这里的6+2并没有什么实际意义。难道是为了体现对齐?
这个函数的目的是为了取代memcmp(addr1, addr2, ETH_ALEN)的用法。因为按照这个函数的实现,是不需要有任何branch的。
看条件编译宏CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS。当平台对于不对齐的内存访问,性能不错时,该宏为enabled的;否则为disabled。
先看CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS enable的时候。将addr1和addr2强制转换为unsigned long型,并进行异或运算。看到这里,应该就可以明白为什么参数addr1和addr2要声明为[6+2]的字节数组了。因为64位的机器上,long为8个字节。所以addr1和addr2需要有8个有效的字节。尽管其实质为一个指针地址,但是从形参的定义上看,可以告诉读者,这个addr1和addr2是需要有8个合法可访问的字节的,尽管有用的只有6个。所以才写成了6+2的形式。当得到异或的结果fold后,下面判断sizeof(fold)是否为8。如果为8,那么调用zap_last_2bytes函数舍去后面的两个字节,再判断结果是否为0。如为0,表明该MAC地址相等。如果sizeof(fold)为不为8,那就是为4了。就再次对后面4个字节做异或,并舍去最后2个字节。相当于将MAC地址分为两部分分别判断,最后将结果相或。这里虽然有一个sizeof的判断,但是由于这里的sizeof的值是在编译期间可以确定的值,因此编译器肯定会对其进行优化。因此在最终的二进制代码中,不会有branch的跳转代码。
那么当CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS disable的时候,进入函数compare_ether_addr
static inline unsigned compare_ether_addr(const u8 *addr1, const u8 *addr2)
{
const u16 *a = (const u16 *) addr1;
const u16 *b = (const u16 *) addr2;
BUILD_BUG_ON(ETH_ALEN != 6);
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) != 0;
}
这个代码非常简单。将addr1和addr2转换为u16*的类型,然后每两个字节做异或,最后将结果或起来,检查是否为0。光看这个代码,其实我认为有一点问题。这里的addr1和addr2为u8*类型,然后强制转换为u16*类型。对于某些平台,如果addr1和addr2不是2字节对齐的话,这样的强制转换会引起问题,甚至crash。不过我想,因为这里的比较都是针对mac地址的比较,可以保证其为2字节对齐的。所以这里既没有注释,也没有字节对齐的处理。在compare_ether_addr_64bits的注释中,反而说明了参数要求2字节对齐。
下面列一下不同情况,compare_ether_addr_64bits需要的操作:
1. CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS enable且64位机:一次异或,一次移位,一次比较;
2. CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS enable且32位机:两次异或,一次移位,一次或运算,一次比较;
3. CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS disable:三次异或,两次或运算,一次比较;
本文转载自:http://blog.chinaunix.net/uid-23629988-id-3248661.html 作者:gfree.wind@gmail.com