posix_memalign函数()
/*
* 背景:
* 1)POSIX 1003.1d
* 2)POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于
* 任何的C类型来说都是对齐的
* 功能:由posix_memalign分配的内存空间,需要由free释放。
* 参数:
* p 分配好的内存空间的首地址
* alignment 对齐边界,Linux中,32位系统是8字节,64位系统是16字节
* size 指定分配size字节大小的内存
*
* 要求:
* 1)要求alignment是2的幂,并且是p指针大小的倍数
* 2)要求size是alignment的倍数
* 返回:
* 0 成功
* EINVAL 参数不满足要求
* ENOMEM 内存分配失败
* 注意:
* 1)该函数不影响errno,只能通过返回值判断
*
*/
memalign()函数与 posix_memalign 的不同是其将分配好的内存块首地址做为返回值
封装 posix_memalign,如果是 Solaris 则封装 memalign
#if (NGX_HAVE_POSIX_MEMALIGN) void * ngx_memalign(size_t alignment, size_t size, ngx_log_t *log) { void *p; int err; err = posix_memalign(&p, alignment, size); if (err) { ngx_log_error(NGX_LOG_EMERG, log, err, "posix_memalign(%uz, %uz) failed", alignment, size); p = NULL; } ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0, "posix_memalign: %p:%uz @%uz", p, size, alignment); return p; } #elif (NGX_HAVE_MEMALIGN) void * ngx_memalign(size_t alignment, size_t size, ngx_log_t *log) { void *p; p = memalign(alignment, size); if (p == NULL) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "memalign(%uz, %uz) failed", alignment, size); } ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0, "memalign: %p:%uz @%uz", p, size, alignment); return p; } #endif
参考:Nginx源码完全注释(1)ngx_alloc.h / ngx_alloc.c
=========================
对齐
数 据的对齐(alignment)是指数据的地址和由硬件条件决定的内存块大小之间的关系。一个变量的地址是它大小的倍数的时候,这就叫做自然对齐 (naturally aligned)。例如,对于一个32bit的变量,如果它的地址是4的倍数,-- 就是说,如果地址的低两位是0,那么这就是自然对齐了。所以,如果一个类型的大小是2n个字节,那么它的地址中,至少低n位是0。对齐的规则是由硬件引起 的。一些体系的计算机在数据对齐这方面有着很严格的要求。在一些系统上,一个不对齐的数据的载入可能会引起进程的陷入。在另外一些系统,对不对齐的数据的 访问是安全的,但却会引起性能的下降。在编写可移植的代码的时候,对齐的问题是必须避免的,所有的类型都该自然对齐。
预对齐内存的分配
在 Linux中,由这两个函数获得的内存都可以通过free( )释放。但在别的Unix系统却未必是这样,一些系统并没有提供一个足够安全的机制去释放这些内存。考虑移植性的程序不得不放弃使用这些接口来获得动态内 存。Linux程序员最好只在考虑对老系统的兼容性时才使用它们;posix_memalign( )更加强大。只有在malloc( )不能提供足够大的对齐时,这三个接口才需要使用。
其它和对齐有关的
这种错误在现实中的普遍程度超出我们的想象,现实世界的例子虽看上去没有这么愚蠢,但亦更难以觉察了。
数据段的管理
特意地,POSIX和C都没有定义这些函数。但几乎所有的Unix系统,都提供其中一个或全部。可移植的程序应该坚持使用基于标准的接口。
匿名存储器映射
跟 变戏法似的,glibc的malloc( ) 能用用数据段来满足小的分配,用存储器映射来满足大的分配。临界点是可被设定的(看后面的高级内存分配),也有可能一个glibc版本是这样,另外一个就 不是了。目前,临界点一般是128KB:比128KB小的分配由堆实现,相应地,更大的由匿名存储器映射来实现。
创建匿名存储器映射
perror ("munmap");
想复习一下mmap( ), munmap( ),和一般的映射,请翻开第四章。
映射到/dev/zero
其 它Unix系统,就像BSD,并没有MAP_ANONYMOUS标记。作为替代,它们用一个特殊的设备文件/dev/zero实现了一个类似的解决方法。 这个设备文件提供了和匿名存储器语义上一致的实现。一个映射包含了全0的写时复制页面;所以行为上和匿名存储器一样。Linux一直有一个/dev /zero设备,可以由映射这个文件来获得全0的内存块。实际上,在引入之前MAP_ANONYMOUS,Linux的程序员就是这样做的。为了对早期的 Linux版本提供向后兼容性,或者对其他Unix系统的可移植性,程序员仍然可以将映射/dev/zero作为匿名映射的替代。这个映射其他文件的映射 是不一样的:
void *p;
int fd;
/* open /dev/zero for reading and writing */
fd = open ("/dev/zero", O_RDWR);
if (fd < 0) {
perror ("open");
return -1;
}
/* map [0,page size) of /dev/zero */
p = mmap (NULL, /* do not care where */
getpagesize ( ), /* map one page */
PROT_READ | PROT_WRITE, /* map read/write */
MAP_PRIVATE, /* private mapping */
fd, /* map /dev/zero */
0); /* no offset */
if (p == MAP_FAILED) {
perror ("mmap");
if (close (fd))
perror ("close");
return -1;
}
/* close /dev/zero, no longer needed */
if (close (fd))
perror ("close");
/* 'p' points at one page of memory, use it... */
在这种情况下映射的存储器当然也是用munmap( )来取消映射的。
这种实现引入了附加的打开和关闭文件的系统调用。所以,匿名映射是个更快的解决方法。
高级存储器分配
本章所涉及的许多存储分配操作都是为内核的参数所控制和限制的,但程序员可以修改这些参数。要这么做,可以使用mallopt( )调用:
#include <malloc.h>
int mallopt (int param, int value);
一个mallopt( )的调用将制param确定的存储管理相关的参数设为value。成功时,调用返回一个非0值;失败时,返回0.要注意的是mallopt( )不设置errno。虽然它往往都成功返回,但是别过于乐观,要好好检查返回值。
Linux目前支持param的六个值,所有都被定义在了<malloc.h>:
M_CHECK_ACTION
环境变量MALLOC_CHECK_的值(将在下一节讨论)。
系统用来满足动态存储器请求的最大存储器映射数。当映射数达到了限制,数据段将被用来满足所有的分配,知道已有的映射中的某个被取消。值为0时将禁止匿名映射用于动态存储的分配。
M_MMAP_THRESHOLD
决定该用匿名映射还是用数据段来满足存储器分配请求的临界值的大小(以字节为单位)。要注意的是,有时候系统为了慎重起见,就算是比临界值小,也有可能用匿名映射来满足动态存储器的分配。值为0时会启用匿名映射来满足所有的分配,而不再使用数据段来满足请求。
M_MXFAST
Fast bin的最大大小(以字节为单位)。Fast bins是堆中特殊的内存块,永远不和临近的内存块合并,也永远不归还给系统,以碎片的增加为代价来满足高速的内存分配。值为0时,fasy bin将不被启用。
M_TOP_PAD
为 适应数据段的大小而使用的填充(padding)的大小(以字节为单位)。无论何时,在使用brk( )来使数据段变大的时候,为了以后少点调用brk( ),glibc总会请求更多的内存。相似地,但glibc收缩数据段的时候,它会保持一些多余的内存,而不是将所有的归还给系统。这多余的部分就叫做填 充。值为0时会取消填充的使用。
Table 8-1. mallopt( ) parameters
Parameter | Origin | Default value | Valid values | Special values |
M_CHECK_ACTION | Linux-specific | 0 | 0 – 2 | |
M_GRAIN | XPG standard | Unsupported on Linux | >= 0 | |
M_KEEP | XPG standard | Unsupported on Linux | >= 0 | |
M_MMAP_MAX | Linux-specific | 64 * 1024 | >=0 | 0 disables use of mmap( ) |
M_MMAP_THRESHOLD | Linux-specific | 128 * 1024 | >=0 | 0 disables use of the heap |
M_MXFAST | XPG standard | 64 | 0 – 80 | 0 disables fast bins |
M_NLBLKS | XPG standard | Unsupported on Linux | >= 0 | |
M_TOP_PAD | Linux-specific | 0 | >=0 | 0 disables padding |
程序在使用malloc( )或其它申请动态存储分配的接口之前是不能使用mallopt()的。用法很简单:
/* use mmap( ) for all allocations over 64 KB */
ret = mallopt (M_MMAP_THRESHOLD, 64 * 1024);
if (!ret)
fprintf (stderr, "mallopt failed!\n");
===================================
memalign
在GNU系统中,malloc或realloc返回的内存块地址都是8的倍数(如果是64位系统,则为16的倍数)。如果你需要更大的粒度,请使用memalign或valloc。这些函数在头文件“stdlib.h”中声明。
在GNU库中,可以使用函数free释放memalign和valloc返回的内存块。但无法在BSD系统中使用,而且BSD系统中并未提供释放这样的内存块的途径。
函数:void * memalign (size_t boundary, size_t size)
函数memalign将分配一个由size指定大小,地址是boundary的倍数的内存块。参数boundary必须是2的幂!函数memalign可以分配较大的内存块,并且可以为返回的地址指定粒度。
函数:void * valloc (size_t size)
使用函数valloc与使用函数memalign类似,函数valloc的内部实现里,使用页的大小作为对齐长度,使用memalign来分配内存。它的实现如下所示:
void *
valloc (size_t size)
{
return memalign (getpagesize (), size);
}