ptmalloc堆概述-多线程支持

ptmalloc堆概述-多线程支持

1 多线程支持

在原来的 dlmalloc 实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另外一个线程则必须等待直到临界区中不再有线程。这是因为所有的线程共享一个堆。在 glibc ptmalloc 2实现中,比较好的一点就是支持了多线程的快速访问。在新的实现中,所有的线程共享多个堆。

Ptmalloc2中,两个线程同时调用malloc时,每一个线程内存都会被立即分配。因为每个线程维护单独的heap segmentfreelist数据结构。

Per thread arena:为每个线程维护单独的heapfreelist数据结构的行为。

l arena的数目基于CPU核的数目

For 32 bit systems:

     Number of arena = 2 * number of cores.

For 64 bit systems:

     Number of arena = 8 * number of cores.

扫描二维码关注公众号,回复: 526952 查看本文章

多个arena的共享

Main线程和先执行malloc的线程会使用单独的arena,直到到达数目限制;

到达数目限制后,新的线程调用malloc时,会重用已有的arena

遍历可用的arenas,尝试锁定该arena

如果超过锁定,则返回该arena

后续调用malloc时,对于共享的arena,如果可用(free)则被使用,否则线程被阻塞直到共享arena变得可用(freed)

多个heap

主要有下面3种数据结构

heap_info Heap Header –一个线程arena可以有多个heaps。每个heap都有自己的header。开始时线程只有一个heap,如果没有空间则mmap到线程arean,生成新的heap(不连续区域)。


malloc_state Arena Header – 一个线程arena可以有多个heaps,但所有这些heaps只有一个arena header。包含关于bins, top chunk, last remainder chunk等信息。


malloc_chunk Chunk Header – 一个heap基于用户请求被划分为多个chunks;没有chunk都有自己的chunk header

注意:

l Main arean有没有多个heap,因此没有heap_info结构;

main arean没有空间时,会使用sbrk进行扩展,直到遇到内存映射段;

PS:后面的测试表明Ubuntu glibc 2.23中,Main arean也有多个heap,启动时就有两个heap。一个是sbrk生成的,一个是mmap生成的)

不像thread areanmain areanarean header不是heap段的一部分,而是静态变量

static struct malloc_state main_arena = {.mutex = _LIBC_LOCK_INITIALIZER,

.next = &main_arena,

.attached_threads = 1};

Pictorial view of main arena and thread arena (single heap segment):

 

Pictorial view of thread arena (multiple heap segments):

 

实例(glibc 2.23

Ubuntu 16.04 64上测试

millionsky@ubuntu-16:~/tmp$ ldd --version

ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23

Mt.c

/* Per thread arena example. */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>

void mallocWrapper( int size, const char* title)
{
         printf( "[BEFORE malloc] %s \n ", title);
getchar();
         if(size > 0){
         char* addr = ( char*) malloc(size);
                 printf( "[AFTER malloc] %s, addr=%p \n ", title, addr);
         getchar();
         free(addr);
         printf( "[AFTER free ] %s \n ", title);
         getchar();
        }
}

void* threadFunc( void* arg) {
mallocWrapper( 1000, "Thread 1");
}

int main() {
pthread_t t1;
void* s;
int ret;
char* addr;

printf( "Welcome to per thread arena example::%d \n ", getpid());
         mallocWrapper( 1000, "Main 1");
         mallocWrapper( 133* 1024, "Main 2");

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;
}

测试 

millionsky@ubuntu-16:~/tmp$ gcc -m32 mt.c -lpthread -o mt

malloc之前,默认的heap132KB(0x21000)

millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps     

08048000-08049000 r-xp 00000000 08:01 11143836                           /home/millionsky/tmp/mt

08049000-0804a000 r--p 00000000 08:01 11143836                           /home/millionsky/tmp/mt

0804a000-0804b000 rw-p 00001000 08:01 11143836                           /home/millionsky/tmp/mt

09a4d000-09a6e000 rw-p 00000000 00:00 0                                  [heap]

f75da000-f75db000 rw-p 00000000 00:00 0

分配大于128KB时,直接使用mmap进行分配

这里请求分配133KB0x21400),已存在4KB,实际分配后合并为140KB0x23000

释放后恢复原样

millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps

08048000-08049000 r-xp 00000000 08:01 11143836                           /home/millionsky/tmp/mt

08049000-0804a000 r--p 00000000 08:01 11143836                           /home/millionsky/tmp/mt

0804a000-0804b000 rw-p 00001000 08:01 11143836                           /home/millionsky/tmp/mt

09a4d000-09a6e000 rw-p 00000000 00:00 0                                  [heap]

f75b8000-f75db000 rw-p 00000000 00:00 0

进入线程函数后

Main_arena增加到8196KB

Thread_arena初始为4KB,当前不可用

millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps

08048000-08049000 r-xp 00000000 08:01 11143836                           /home/millionsky/tmp/mt

08049000-0804a000 r--p 00000000 08:01 11143836                           /home/millionsky/tmp/mt

0804a000-0804b000 rw-p 00001000 08:01 11143836                           /home/millionsky/tmp/mt

09a4d000-09a6e000 rw-p 00000000 00:00 0                                  [heap]

f6dd9000-f6dda000 ---p 00000000 00:00 0   thread_arena

f6dda000-f75db000 rw-p 00000000 00:00 0   main_arena

线程函数中调用malloc

[AFTER  malloc] Thread 1, addr=0xf6c00470

mmap重新分配了1024KB的空间,但只有前132KB可用

释放后内存布局没有变化

millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps

08048000-08049000 r-xp 00000000 08:01 11143836                           /home/millionsky/tmp/mt

08049000-0804a000 r--p 00000000 08:01 11143836                           /home/millionsky/tmp/mt

0804a000-0804b000 rw-p 00001000 08:01 11143836                           /home/millionsky/tmp/mt

09a4d000-09a6e000 rw-p 00000000 00:00 0                                  [heap]

f6c00000-f6c21000 rw-p 00000000 00:00 0     thread_arena

f6c21000-f6d00000 ---p 00000000 00:00 0     不可用

f6dd9000-f6dda000 ---p 00000000 00:00 0     thread_arena

f6dda000-f75db000 rw-p 00000000 00:00 0     main_arena

附件

结论

1. 虽然程序可能只是向操作系统申请很小的内存,但是为了方便,操作系统会把很大的内存分配给程序。这样的话,就避免了多次内核态与用户态的切换,提高了程序的效率。

2. 连续的内堆存区域称为 arena,主线程申请的内存为 main_arena

3.  arena 空间不足时,它可以通过增加brk的方式来增加堆的空间。类似地,arena 也可以通过减小 brk 来缩小自己的空间。

4. 释放内存时,其对应的 arena 并没有进行回收,而是交由glibc来进行管理。

5. 当用户请求的内存大于128KB时,并且没有任何arena有足够的空间时,那么系统就会执行mmap函数来分配相应的内存空间。这与这个请求来自于主线程还是从线程无关。

参考文档

1. https://ctf-wiki.github.io/ctf-wiki/pwn/heap/heap_overview/

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

猜你喜欢

转载自blog.csdn.net/luozhaotian/article/details/80267185