【nginx流程分析之内存对齐和分配】
浅谈内存对齐
首先我们来简单说一下内存对齐,以及为什么要进行内存对齐。
首先现在我们的电脑是64位,也就是8个字节。对于我们cpu来说,并不是一个字节一个字节从内存中获取数据,因为这样效率太低,都是8个字节和8个字节去拿,所以分配的地址也尽量保证是8的整数倍,当然这个c语言底层已经帮助我们做好了,会有一个自然的对齐的策略,尤其在结构体中,当然存在内存的浪费,当然这个是在允许的范围内,如下:
struct align{
char b;
long long a;
int c;
char d;
};
int main()
{
struct align align1;
printf("&a = %p\n", &align1.a);
printf("&b = %p\n", &align1.b);
printf("&c = %p\n", &align1.c);
printf("&d = %p\n", &align1.d);
printf("sizeof(align) = %lu\n", sizeof(struct align));
return;
}
/*
&a = 0x16f4275c0
&b = 0x16f4275b8
&c = 0x16f4275c8
&d = 0x16f4275cc
sizeof(align) = 24
*/
我们以这个为例子来简单分析一下,刚开始很多人会认为sizeof(struct align) 会是 1 + 8 +4 + 1 = 14。其实不然,我们从地址看应该是如下:
一共占用 3 * 8 =24 个字节。之所以char b 占用8个字节,是为了满足内存对齐的要求,而后面的char d 只占用4个字节,是因为可以和变量c int 一起占用8个字节,起到了节省内存的作用。
当然还有其他情况,这边我们就不细说,感兴趣的可以搜一下。
判断是否对齐
在上一篇中,我们可以看到,在ngx_crc32_table_init可以看到,如下代码
//ngx_crc32_table_short 是ngx_crc32_table16的首地址
uint32_t *ngx_crc32_table_short = ngx_crc32_table16;
//判断 ngx_crc32_table_short的地址是否是 按照ngx_cacheline_size的对齐的 详见
if (((uintptr_t) ngx_crc32_table_short & ~((uintptr_t) ngx_cacheline_size - 1))
== (uintptr_t) ngx_crc32_table_short)
{
return NGX_OK;
}
这里就是为了判断ngx_crc32_table_short的地址是否和ngx_cacheline_size对齐。
ngx_crc32_table_short可以看出来,取的是ngx_crc32_table16的首地址。
然后 & 和 ~ 都是c语言中的位操作。& 是 and的意思 ,~ 就是取反。
我们以二进制举例子。比如 01 & 11 = 01; ~01= 10;
所以集合起来, &~ 就是低位清空,
然后这边可以看到ngx_cacheline_size是32/64, 就以64为例子,那么ngx_cacheline_size-1=63;同时在前面加上了uintptr_t的类型转换为8字节。
64的二进制 10000000,64-1= 63 的二进制 00111111, 然后取反是 11000000,8字节前面还是都是一样的,就省略了。
所以如果是64的备注,其实就是 64 <<n, 那么和 11000000(63的取反) 取&的,一定是一样的,因为11000000 前面都是1,有点绕,多想想就好了。
然后就是判断是否是64的倍数,如果是64的倍数那么就返回,如果不是那么接下来就要继续走。就是重新分配ngx_crc32_table16的地址了
分配内存
我们先看下分配内存的代码
static uint32_t ngx_crc32_table16[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
p = ngx_alloc(16 * sizeof(uint32_t) + ngx_cacheline_size, ngx_cycle->log);
if (p == NULL) {
return NGX_ERROR;
}
首先是16 * sizeof(uint32_t) 比较好理解,因为ngx_crc32_table16有16个类型为uint32_t的成员变量。然后ngx_cacheline_size就是要对齐的长度,之所以加这个长度,是因为malloc也不一定是ngx_cacheline_size的倍数,所以要加上这个长度。
然后就是调用ngx_alloc,然后这边是 16 * 4 + 64 = 128 字节。
获取符合ngx_cacheline_size的地址
上面我们可以看到我们已经申请了128字节的内存,然后就是获取开始为64倍数的地址了,然后我们看一下nginx是怎么做的:
//地址按照ngx_cacheline_size 进行对齐
p = ngx_align_ptr(p, ngx_cacheline_size);
#define ngx_align_ptr(p, a)
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
这边p就是我们调用malloc返回的地址,然后a就是64,然后有了上面的说明就比较好理解了,其实((uintptr_t) § + ((uintptr_t) a - 1)),就是把地址 达到64位向左边移动了一个,然后配合& ~((uintptr_t) a - 1)移除地位达到目的。
赋值
先看一下代码,
//把ngx_crc32_table16 赋值给p
ngx_memcpy(p, ngx_crc32_table16, 16 * sizeof(uint32_t));
//再把 p的地址赋值给ngx_crc32_table_short
ngx_crc32_table_short = p;
其实这边就是比较好理解了,就是把ngx_crc32_table16赋值给p变量,因为p此时已经是64位对齐的了,然后ngx_crc32_table_short的地址和p保持一致。