linux tricks 之 ALIGN解析.

时间:2022-09-17 17:17:26
-------------------------------------------
本文系作者原创, 欢迎大家转载!
转载请注明出处:netwalker.blog.chinaunix.net
-------------------------------------------

内核在某些应用中,为了实现某种机制,比如分页,或者提高访问效率需要保证数据或者指针地址对齐到某个特定的整数值,比如连接代码脚本。这个值必须是2N。数据对齐,可以看做向上圆整的一种运算。

  1. include/linux/kernel.h
  2. #define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a) - 1)
  3. #define __ALIGN_MASK(x, mask) (((x) + (mask))&~(mask))
  4. #define PTR_ALIGN(p, a) ((typeof(p))ALIGN((unsigned long)(p), (a)))
  5. #define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0)

内核提供了两个用来对齐的宏ALIGN和PTR_ALIGN,一个实现数据对齐,而另一个实现指针的对齐。它们实现的核心都是__ALIGN_MASK,其中mask参数为低N位全为1,其余位全为0的掩码,它从圆整目标值2N - 1得到。__ALIGN_MASK得到对齐值,对于数据来说直接返回即可,而对于指针则需要进行强制转换。IS_ALIGNED宏用来判断当前值是否对齐与指定的值。内核中的分页对齐宏定义如下:

  1. arch/arm/include/asm/page.h
  2. /* PAGE_SHIFT determines the page size */
  3. #define PAGE_SHIFT 12
  4. #define PAGE_SIZE (1UL << PAGE_SHIFT)
  5. include/linux/mm.h
  6. /* to align the pointer to the (next) page boundary */
  7. #define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)

PAGE_SIZE定义在体系架构相关的代码中,通常为4K。内核中提供的特性功能的对齐宏均是对ALIGN的扩展。下面提供一个代码示例,并给出结果:

  1. #include <stdio.h>
  2. ......
  3. int main()
  4. {
  5. int a = 0 ,i = 0;
  6. int *p = &a;
  7. for(; i < 6; i++)
  8. printf("ALIGN(%d, 4): %x\n", i, ALIGN(i, 4));
  9. printf("p:%p, PTR_ALIGN(p, 8): %p\n", p, PTR_ALIGN(p, 8));
  10. printf("IS_ALIGNED(7, 8): %d, IS_ALIGNED(16, 8): %d\n", IS_ALIGNED(7, 8), IS_ALIGNED(16, 8));
  11. return 0;
  12. }

对齐宏测试结果:

    1. ALIGN(0, 4): 0
    2. ALIGN(1, 4): 4
    3. ......
    4. ALIGN(4, 4): 4
    5. ALIGN(5, 4): 8
    6. p:0xbf96c01c, PTR_ALIGN(p, 8): 0xbf96c020
    7. IS_ALIGNED(7, 8): 0, IS_ALIGNED(16, 8): 1

1. 在linux内核中,经产会看到对齐ALIGN的调用,常见的如内存管理中page对齐,net_device中私有数据的获取等,本文是对ALIGN宏的一个简单分析。

1.1. 内核调用:在e100.c中,网卡irq处理函数 irqreturn_t e100_intr(int irq, void *dev_id) 调用netdev_priv(netdev)处理函数获取net_device的私有数据。

  

2105 static irqreturn_t e100_intr(int irq, void *dev_id)
2106 {
2107 struct net_device *netdev = dev_id;
2108 struct nic *nic = netdev_priv(netdev);

netdev_priv函数和ALIGN宏定义分别如下:

991 static inline void *netdev_priv(const struct net_device *dev)
992 {
993 return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
994 }

41 #define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1)
42 #define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))

1.2 用法解析:

    先说ALIGN的用法,ALIGN(x,a) 是为了使x以a为边界对齐,实现原理是给x加上一个最小的数,使x以a为边界对齐。举个例子,a = 8, x=0, ALIGN(x,a) 运算结果为0; a = 8, x = 3, 运算结果为8; a = 8, x = 11, 运算结果为16。

1.3原理分析:

    为了便于分析,假设所用到的数字都是16bit,typeof是类型定义分析的时候可以忽略,假设为了使x以8位界对齐。

a = 8, 则上面mask= 7,那么 __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask)) 用二进制表示为:

    X          xxxx xxxx

MASK                 + 0000 0111

    进一步,以上公式可以分为x低 三位全为0(加mask后无进位)和低三位不全为0(加mask后有进位)两种情况。

  1.3.1. x低三位全为0(加mask无进位)

     即 x = xxxx x000,这种情况下,__ALIGN_MASK(x,mask)运算结果仍为x,而x本身就为8的倍数,因此x以8为界对齐。

1.3.2 x低三位不全为0 (加mask有进位)

    x + mask 可理解为给x加7使得x向第四低位进位,同时低三位清零,运算后x = xxxx x000,同样为8的倍数,因此x以8为界对齐。