- 论坛徽章:
- 0
|
内容:Memory
1. 几个基本概念,page、frame、paging、segment。
进程分配内存的两种模式,一个使用 exec 系列函数,一个使用 programmatically(malloc 等函数)。
重要的 segment 有 text segment(存放代码等等,一般在进程的生命周期中不变)、data segment
(存放数据,可以用一些函数来调整大小,但是低位端位置不变)、stack segment(随着使用的堆栈
变大而变大,但不变小...)
2. 内存的静态分配和自动分配。前者是对于 static 变量或者全局变量,一旦开始就分配,即一直存在到最后。
后者是临时变量,如调用函数。值得注意的是:
In GNU C, the size of the automatic storage can be an expression that
varies. In other C implementations, it must be a constant.
3. 内存的动态分配不为 C 语言本身支持,不像 C++。基本方法是
void * malloc (size_t size)
分配到的内存没有初始化(calloc 会做清零,clear allocate),因此可以用 memset 来
进行初始化。分配后应检测返回指针是否为 NULL。malloc 返回的一个块多数情况下对齐
(这样可以存储任意类型的数据)过了,地址为 8 的倍数(64 位系统里面是 16 的倍数),
在一些特殊情况下(page 的边界)可以利用 memalign、posix_memalign、vlign 来返回
对齐(2 的幂次)的内存块。free 的内存很少被返还给操作系统,多数情况被留作后面
malloc 使用。如果需要调整已经 malloc 的块的大小,使用 realloc。glibc 不会将分配的块
对 2 的幂次进行向上取整。
4. 一次性分配很大的内存(大于一个 page)会使用向 2 的幂次取整的策略,使用 mmap
相关的函数 mallopt,这种分配得到的内存在 free 时会返回给操作系统。
5. 如何调整 malloc 的行为?使用 mallopt 调整一些参数的值(malloc.h),如
M_TRIM_THRESHOLD(返回给 OS 的内存的一个阈值)、M_MMAP_THRESHOLD
(大于此值的内存分配请求使用 mmap 系统调用)等等。
6. 分配的内存来自堆(heap),可以用
int mcheck (void (*abortfn) (enum mcheck_status status))
来检查分配内存的一致性,调用在 malloc 之前。
enum mcheck_status mprobe (void *pointer)
为特定的一块内存做检查。这都是 GNU extension。(mcheck.h)
7. 为了方便调试,glibc 为用户提供了 malloc 等等函数的钩子(hook),如 __malloc_hook
对应的是一个函数指针,
void *function (size_t size, const void *caller)
其中 caller 是调用 malloc 返回值的接受者(一个指针的地址)。另外有 __malloc_initialize_hook
函数指针,仅仅会调用一次(第一次分配动态内存时)。(malloc.h)
8. 一些使用 malloc 的统计量(SVID 扩展)可以用 struct mallinfo 储存,
可调用
struct mallinfo mallinfo (void)
获得。
9. 如何检测 memory leakage?glibc 提供了一个函数
void mtrace (void)
及其反作用
void muntrace (void)
这时会依赖于一个环境变量 MALLOC_TRACE 所指的文件,把一些信息记录在该文件中
用于侦测 memory leakage,其本质是安装了前面提到的 hook。一般将这些函数用
#ifdef DEBUGGING 包裹以便在非调试态下减少开销。产生的文件据说不建议自己去读,
而使用 mtrace 程序(perl 脚本来进行分析)。下面用一个简单的例子说明这个过程,这是
源程序:
#include
#include
#include
int
main( int argc, char *argv[] )
{
int *p, *q ;
#ifdef DEBUGGING
mtrace( ) ;
#endif
p = malloc( sizeof( int ) ) ;
q = malloc( sizeof( int ) ) ;
printf( "p = %p\nq = %p\n", p, q ) ;
*p = 1 ;
*q = 2 ;
free( p ) ;
return 0 ;
}
很简单的程序,其中 q 没有被释放。我们设置了环境变量后并且 touch 出该文件
执行结果如下:
p = 0x98c0378
q = 0x98c0388
该文件内容如下
= Start
@ ./test30:[0x8048446] + 0x98c0378 0x4
@ ./test30:[0x8048455] + 0x98c0388 0x4
@ ./test30:[0x804848f] - 0x98c0378
可以知道带有 + 的表示 malloc 了内存,而 - 表示释放,后面的 0x4 是分配的内存大小
可见正常情况 + - 数目应该一样多,现在不一样表明出现了 leakage。接着我们看看前面的
地址样的东西是啥,objdump 之得
08048424 :
...
8048441: e8 d2 fe ff ff call 8048318
8048446: 89 45 f4 mov %eax,0xfffffff4(%ebp)
8048449: c7 04 24 04 00 00 00 movl {fckeditor}x4,(%esp)
8048450: e8 c3 fe ff ff call 8048318 ;
8048455: 89 45 f8 mov %eax,0xfffffff8(%ebp)
8048458: 8b 45 f8 mov 0xfffffff8(%ebp),%eax
804845b: 89 44 24 08 mov %eax,0x8(%esp)
804845f: 8b 45 f4 mov 0xfffffff4(%ebp),%eax
8048462: 89 44 24 04 mov %eax,0x4(%esp)
8048466: c7 04 24 54 85 04 08 movl {fckeditor}x8048554,(%esp)
804846d: e8 c6 fe ff ff call 8048338
8048472: 8b 45 f4 mov 0xfffffff4(%ebp),%eax
8048475: c7 00 01 00 00 00 movl {fckeditor}x1,(%eax)
可见是调用函数开始地方的地址。下面使用 mtrace 命令行工具进行分析,其基本调用方式为
mtrace binary tracefile。当使用 gcc -DDEBUGGING -g 编译才能获得最好的效果,会报告出
哪一行上的没有被释放掉。
10. obstack 是什么?
可以看做任意“对象”的 stack,用户可以建立多个 obstack,其使用类似于一个 stack
相关的定义在 obstack.h 中。使用一个 obstack 是通过一个结构 struct obstack 实现的。
obstack 中的东西放在 chunk 中,chunk 使用用户自定义的函数(其实是个 macro )分配
后来的东西是用一些接口函数放到 obstack 中,真是标准的 C 实现啊... 比较特别的是一种可以
grow 的对象的放入。建立一个 obstack 只需要弄一个结构,然后用
static struct obstack myobstack;
obstack_init (&myobstack);
当然,malloc 一个也行的。不过首先应该告诉编译器用什么分配 chunk -.-
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
分配的 chunk 大小是用下面的 macro 决定的
int obstack_chunk_size (struct obstack *obstack-ptr)
调用方式如下:
obstack_chunk_size (obstack_ptr) = new-chunk-size;
如果 trunk 分配失败,则会调用 obstack_alloc_failed_handler 对应的函数指针。
下面看看如何将一些东西放进去。最直接的就是通过分配一块地方
void * obstack_alloc (struct obstack *obstack-ptr, int size)
然后手动 memcpy 好了... 这样麻烦,因此有个
void * obstack_copy (struct obstack *obstack-ptr, void *address, int size)
减少工作量,对于字符串呢还有更简单的
void * obstack_copy0 (struct obstack *obstack-ptr, void *address, int size)
这样最后的 0x0 会自动被添加。如果想释放,可以调用
void obstack_free (struct obstack *obstack-ptr, void *object)
这时 obstack 中在此 object 之后添加的都会被释放掉(这才是 stack 嘛~)注意,如果 object == NULL,此时
就不仅仅释放了所有的 obstack 中的东西,obstack 自己也被恢复到未经初始化的状态了。
下面看看如何添加可以 grow 的对象,最基本的
void obstack_blank (struct obstack *obstack-ptr, int size)
分配空间,但不初始化,
void obstack_grow (struct obstack *obstack-ptr, void *data, int size)
让这部分空间变大(size 可以为负,那就是变小,但不会缩过头 @@),为了方便字符串等等数据类型
可以使用下面系列函数
void obstack_grow0 (struct obstack *obstack-ptr, void *data, int size)
void obstack_1grow (struct obstack *obstack-ptr, char c)
void obstack_ptr_grow (struct obstack *obstack-ptr, void *data)
void obstack_int_grow (struct obstack *obstack-ptr, int data)
grow 完了最后要 finish 一下
void * obstack_finish (struct obstack *obstack-ptr)
可以在 finish 之前测一下这个家伙有多大
int obstack_object_size (struct obstack *obstack-ptr)
不过因为这样做每 grow 一点都会检查是否需要分配新的 chunk 因此较慢,当有连续的
grow 产生而对 chunk 的把握正确的时候可以用对应的 fast grow 系列函数,如
void obstack_1grow_fast (struct obstack *obstack-ptr, char c)
void obstack_blank_fast (struct obstack *obstack-ptr, int size)
为了方便之前检查 chunk 剩余空间是否够用可以用
int obstack_room (struct obstack *obstack-ptr)
下面是一些其他的与 obstack 函数
void * obstack_base (struct obstack *obstack-ptr)
返回下一个 object 的地址(栈顶),如果有 growing object 则是该 object 地址。
如果需要进行对齐,下面的 macro 设置其掩膜
int obstack_alignment_mask (struct obstack *obstack-ptr)
使用类似设置 chunk size。
11. 一些 macro 调用的问题
obstack 是在老的 C 编译器上可能不好正常工作,特别是对利用 maco 产生了重定义的函数不能取址
但是遵循 ISO C 编译器可以,但是不保证在使用宏调用时参数会被多次利用产生的后果,如
obstack_alloc (get_obstack (), 4);
中 get_obstack () 或者 *obstack_list_ptr++ 这类参数。但是 GNU C 下这类参数不会被多次展开。
12. 可变大小自动释放类型,一个是 BSD extension,在 stdlib.h 中定义,使用 alloca 分配的
int
open2 (char *str1, char *str2, int flags, int mode)
{
char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
stpcpy (stpcpy (name, str1), str2);
return open (name, flags, mode);
}
这样一个好处在于 longjmp() 时不需要再手工释放这部分内存。并且使用 alloca 分配的内存是
统一管理,不会造成内存碎片化。但是非 GNU 系统可能不支持,如果分配的内存太大会使程序崩溃
另外,还可以用 GCC 的方式:
int open2 (char *str1, char *str2, int flags, int mode)
{
char name[strlen (str1) + strlen (str2) + 1];
stpcpy (stpcpy (name, str1), str2);
return open (name, flags, mode);
}
两种方式并不相同,后者可能仍然在栈内分配的内存,因此作用域结束即释放,而前者在程序结束
才释放。前者可用于循环体内,后者不可。
13. brk() 和 sbrk() 用于调整 data segment 的 high end,有什么用?
int brk (void *addr)
int sbrk (ptrdiff_t delta)
名字由来是原来进程里面 data segment 和 stack 对着干,一个从上向下长,一个从下向上长
中间隔开它们的是 break -.-b
14. root 可以调用一些相关函数将某个 page 锁住,这样不能被 paged out,目的是避免交换出去
造成再读入的开销。相关函数在 sys/mmem.h
int mlock (const void *addr, size_t len)
int munlock (const void *addr, size_t len)
int mlockall (int flags)
int munlockall (void)
这些是 POSIX.1b 的标准。
(http://www.fanqiang.com)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/30686/showart_261477.html |
|