学习Linux 堆内存管理

学习链接

https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/

http://www.cnblogs.com/alisecurity/p/5486458.html

http://www.cnblogs.com/alisecurity/p/5520847.html

首先还是看看linux内存的布局

实验演示

代码:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
void* threadFunc(void* arg){
printf("Before malloc in thread 1\n");
getchar();
char* addr = (char*) malloc(1000);
printf("After malloc and before free in thread 1\n");
getchar();
free(addr);
printf("After free in thread 1\n");
getchar();
}
int main(int argc, char const *argv[]){
pthread_t t1;
void* s;
int ret;
char* addr;
printf("Welcome to per thread arena example::%d\n", getpid());
printf("Before malloc in main thead\n");
getchar();
addr = (char*) malloc(1000);
printf("After malloc and before free in main thread\n");
getchar();
ret = pthread_create(&t1, NULL, threadFunc, NULL);
if (ret){
printf("Thread creation error\n");
return -1;
}
ret = pthread_join(t1, &s);
if (ret){
printf("Thread join error\n");
return -1;
}
return 0;
}

我用的是kali1.1 32位系统

编译一开始报

pthread1.c:5:22: fatal error: sys/types.h: No such file or directory

安装一下

sudo apt-get install build-essential flex libelf-dev libc6-dev-amd64 binutils-dev libdwarf-dev

继续

/tmp/ccpy3gZb.o: In function `main':
pthread1.c:(.text+0xda): undefined reference to `pthread_create'
pthread1.c:(.text+0x10d): undefined reference to `pthread_join'

解决方法

 pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。

最终:

gcc pthread1.c -o pthread1 -l pthread

开始运行

Before malloc in main thead

root@kali:~/learn/learnHeap# ./pthread1 
Welcome to per thread arena example::22219
Before malloc in main thead

看看内存,是没有heap段的,栈段是有的,当然线程栈也是没有

root@kali:/# cat /proc/22219/maps
08048000-08049000 r-xp 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
08049000-0804a000 rw-p 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
b7551000-b7553000 rw-p 00000000 00:00 0 
b7553000-b76f7000 r-xp 00000000 08:01 1311582    /lib/i386-linux-gnu/i686/cmov/libc-2.19.so
b76f7000-b76f9000 r--p 001a4000 08:01 1311582    /lib/i386-linux-gnu/i686/cmov/libc-2.19.so
b76f9000-b76fa000 rw-p 001a6000 08:01 1311582    /lib/i386-linux-gnu/i686/cmov/libc-2.19.so
b76fa000-b76fd000 rw-p 00000000 00:00 0 
b76fd000-b7715000 r-xp 00000000 08:01 1311574    /lib/i386-linux-gnu/i686/cmov/libpthread-2.19.so
b7715000-b7716000 r--p 00017000 08:01 1311574    /lib/i386-linux-gnu/i686/cmov/libpthread-2.19.so
b7716000-b7717000 rw-p 00018000 08:01 1311574    /lib/i386-linux-gnu/i686/cmov/libpthread-2.19.so
b7717000-b7719000 rw-p 00000000 00:00 0 
b7733000-b7737000 rw-p 00000000 00:00 0 
b7737000-b7739000 r--p 00000000 00:00 0          [vvar]
b7739000-b773b000 r-xp 00000000 00:00 0          [vdso]
b773b000-b775a000 r-xp 00000000 08:01 1311723    /lib/i386-linux-gnu/ld-2.19.so
b775a000-b775b000 r--p 0001f000 08:01 1311723    /lib/i386-linux-gnu/ld-2.19.so
b775b000-b775c000 rw-p 00020000 08:01 1311723    /lib/i386-linux-gnu/ld-2.19.so
bfba5000-bfbc6000 rw-p 00000000 00:00 0          [stack]

以免占用太多的篇幅,将后面不相关的删掉了

After malloc and before free in main thread

08048000-08049000 r-xp 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
08049000-0804a000 rw-p 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
09d74000-09d95000 rw-p 00000000 00:00 0          [heap]

可以看到出现了heap段,而且是132KB,我们程序只申请了1000(B)字节

blob.png

那下次再malloc,直接从这里搞就行了

如果这132KB都用完了,就增大这个heap段的大小,调整program break(可看上面内存布局图片)的位置就行了

After free in main thread

可以看到内存是没有马上给回操作系统

而是由glibc 的malloc库函数加以管理。它会将释放的chunk添加到main arenas的bin(这是一种用于存储同类型free chunk的双链表数据结构),记录空闲空间的freelist数据结构称之为bins。之后当用户再次调用malloc申请堆空间的时候,glibc malloc会先尝试从bins中找到一个满足要求的chunk,如果没有才会向操作系统申请新的堆空间。

08048000-08049000 r-xp 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
08049000-0804a000 rw-p 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
09d74000-09d95000 rw-p 00000000 00:00 0          [heap]

Before malloc in thread 1

thread1的堆还没分配,但栈空间已经分配了

08048000-08049000 r-xp 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
08049000-0804a000 rw-p 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
09d74000-09d95000 rw-p 00000000 00:00 0          [heap]
b6d11000-b6d12000 ---p 00000000 00:00 0 
b6d12000-b7514000 rw-p 00000000 00:00 0          [stack:24056]

After malloc and before free in thread 1

root@kali:/# cat /proc/24050/maps 
08048000-08049000 r-xp 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
08049000-0804a000 rw-p 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
09d74000-09d95000 rw-p 00000000 00:00 0          [heap]
b6c00000-b6c21000 rw-p 00000000 00:00 0 
b6c21000-b6d00000 ---p 00000000 00:00 0 
b6d11000-b6d12000 ---p 00000000 00:00 0 
b6d12000-b7514000 rw-p 00000000 00:00 0          [stack:24056]

可以看到,比没malloc前多了这两行

b6c00000-b6c21000 rw-p 00000000 00:00 0 
b6c21000-b6d00000 ---p 00000000 00:00 0

同时从这个区域的起始地址可以看出,它并不是通过brk分配的,而是通过mmap分配

因为地址是b6c21000,地址是比较高的,而brk分配的内存的地址是比较低的,就在程序后面一点的地方

那么main函数的堆区是brk分配,线程的就mmap分配

总共的话是1MB,算一下就知道

blob.png

但是实际根据内存属性有分成了两端,还是只有132KB可读写的

blob.png

这里应该也是一次性分配多一点,那不可读不可写的给可能其他线程(如果有的话)malloc的时候用的?或者当前thread1的132KB不够用的时候用的吧

笔记:作者还说,如果一次性申请超过128字节( lets say malloc(132*1024)),而且没有足够的arena能够满足用户的需求时,系统使用的是mmap系统调用来申请内存,而不管是从main arena还是thread arena申请的

After free in thread 1

最后跟main的释放是一样的,释放只是给了thread arenas bin

root@kali:/# cat /proc/24050/maps 
08048000-08049000 r-xp 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
08049000-0804a000 rw-p 00000000 08:01 1598728    /root/learn/learnHeap/pthread1
09d74000-09d95000 rw-p 00000000 00:00 0          [heap]
b6c00000-b6c21000 rw-p 00000000 00:00 0 
b6c21000-b6d00000 ---p 00000000 00:00 0 
b6d11000-b6d12000 ---p 00000000 00:00 0 
b6d12000-b7514000 rw-p 00000000 00:00 0          [stack:24056]

Arena

arena数量

在上面的例子中main thread和thread1有自己独立的arena,那么是不是无论有多少个线程,每个线程都有自己独立的arena呢?这样太浪费内存了。事实上,arena的个数是跟系统中处理器核心个数相关的

国内的翻译是给作者的公式+1了,不过根据下面的描述也对

For 32 bit systems:
     Number of arena = 2 * number of cores + 1.
For 64 bit systems:
     Number of arena = 8 * number of cores + 1.

Multiple Arena

那假如我的程序有4个线程(main thread(主线程) 和 3 个 user thread),我运行在32位的单核系统上,那我这系统只能有2+1=3个arena ,那user thread就只能共享arena咯

那怎么共享的呢

首先,主线程首次malloc的话,直接给他arena,不需要其他条件

thread 1 和 thread 2也是第一次申请的话,也是创建一个新的arena给他,这时候各个线程和arena是一一对应的

假如thread 3也是第一次申请,算一下,arena已经达到上限,只能跟前面的共用咯,那前面3个用哪个呢?

    首先循环可用的arena,并且再循环的时候尝试锁住它,如果成功就返回那个arena给用户(比如说是main arena),但如果失败就阻塞,知道有可用的arena为止

现在thread3第二次malloc,首先先尝试上一次拿到额共享的arena,有空闲的就直接用,否则就阻塞,直到共享的 arena有空闲的空间为止

Multiple Heaps

作者在‘glibc malloc’源码中看到有3种数据结构

heap_info

就像是heap Header,一个thread arena可以有多个heap,所以为了便于管理就都有一个heap header

那么在什么情况下一个thread arena会包含多个heaps呢?在当前heap不够用的时候,malloc会通过系统调用mmap申请新的堆空间,新的堆空间会被添加到当前thread arena中,便于管理。

typedef struct _heap_info
{
  mstate ar_ptr; /* Arena for this heap. */
  struct _heap_info *prev; /* Previous heap. */
  size_t size;   /* Current size in bytes. */
  size_t mprotect_size; /* Size in bytes that has been mprotected
                           PROT_READ|PROT_WRITE.  */
  /* Make sure the following data is properly aligned, particularly
     that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
     MALLOC_ALIGNMENT. */
  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;

malloc_state

通俗来说就是arena Header

一个线程可以有多个堆,但是这个线程的所有这些堆,只有一个arena Header

这个Header包含了bins,top chunk,last remainder chunk…

struct malloc_state
{
  /* Serialize access.  */
  mutex_t mutex;
  /* Flags (formerly in max_fast).  */
  int flags;
  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];
  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;
  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;
  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];
  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];
  /* Linked list */
  struct malloc_state *next;
  /* Linked list for free arenas.  */
  struct malloc_state *next_free;
  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

malloc_chunk

即chunk Header,一个heap被分成很多个chunk,一个chunk的大小是跟用户malloc的大小相关的,每个chunk都有自己的Header

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

作者笔记:

main arena没有多个heap,因此没有heap info 这个结构,但它需要更多空间就通过brk申请更多,直到memory mapping段

和thread arena不同, main arena的arena header不是brk的heap段的一部分,他是一个全局变量,在libc的data段中

复制一下main arena和thread arena只有单个堆的示意图

可以看到不同的是main arena的arena Header(malloc state)是在libc的data段

而且main arena没有heap info结构

thread arena则在堆的地址上 

那么有多个heap段的Thead arena呢

blob.png

thread arena只含有一个malloc_state(即arena header),却有两个heap_info(即heap header)。

可以看到malloc state和heap info形成一个循环单向链表

原来那个堆的top chunk变成了free chunk

后面那些其实做了笔记的,但由于意外,没保存,所以就这样吧

其实动态调试去理解更好

打赏作者
喜欢本博客,打赏让博客永久运行,多少你说了算

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注