文章插图
图片
- Brk()的参数设置为新的brk上界地址 , 成功返回1 , 失败返回0;
- Sbrk()的参数为申请内存的大小 , 返回heap新的上界brk的地址 。
文章插图
图片
mmap()系统调用用于在进程的虚拟地址空间中创建新的内存映射 。内存分配器通常使用这个系统调用来创建私有匿名映射 , 以分配内存 。内核会按照页面大小的倍数(通常为4096字节)来分配内存 , 函数原型如下:
#include <unistd.h>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
【Malloc技术原理解析以及在转转搜索业务上的实践】一旦建立了这种映射关系 , 进程就可以通过指针的方式来读写这段内存 , 而系统会自动将脏页(被修改的页)回写到相应的磁盘文件上 。这意味着进程可以通过直接访问内存来完成对文件的操作 , 而无需再调用read、write等系统调用函数 。除了减少read、write等系统调用外 , mmap()还可以减少内存拷贝的次数 。例如 , 在使用read调用时 , 典型的流程是操作系统首先将磁盘文件内容读入页缓存 , 然后再将数据从页缓存复制到read传递的缓冲区中 。然而 , 使用mmap()后 , 操作系统只需将磁盘数据读入页缓存 , 然后进程可以直接通过指针方式操作mmap映射的内存 , 从而减少了从内核态到用户态的数据拷贝 。
3 ptmalloc目前大部分服务端程序使用GNU Libc的内存分配器ptmalloc , 起源于Doug Lea的malloc , 进而由Wolfram Gloger改进得到可以支持多线程 。ptmalloc的设计是在性能和内存放大之间做了权衡 , 为了降低锁冲突 , 设置了多arena机制 , 却间接增加了内存碎片 , 从而导致物理内存的消耗增加 。下面将对服务器常用的centos7上使用的ptmalloc2的技术细节进行介绍和分析 。
3.1 整体架构主分配区(mAIn_area)和非主分配区(no_main_area):当多个线程同时使用malloc分配内存时 , 内存分配器会采取一些策略来解决线程之间的锁竞争问题 。分配器将内存分配区分为两种:主分配区和非主分配区 。这两种分配区被组织成一个环形链表 , 以便进行有效管理 。每个分配区都使用互斥锁来确保线程对其的访问是互斥的 。每个进程只有一个主分配区 , 但可以有多个非主分配区 。ptmalloc根据系统对分配区的竞争情况动态调整分配区的大小 , 一旦增加了分配区的数量 , 就不会再减少 。主分配区可以使用brk和mmap来分配内存 , 而非主分配区只能使用mmap来映射内存块 。在分配小内存时 , 可能会导致内存碎片问题 , 因此ptmalloc在整理内存时需要对分配区进行加锁操作 。当线程需要使用malloc分配内存时 , 它首先检查自己的私有变量中是否已经有一个分配区 。如果有 , 它会尝试对其进行加锁操作 , 如果成功 , 就使用该分配区分配内存 。如果失败 , 它会遍历循环链表 , 寻找一个未加锁的分配区 。如果整个链表中都没有未加锁的分配区 , 那么malloc将创建一个新的分配区 , 将其加入全局循环链表并加锁 , 然后使用该分配区进行内存分配 。
在释放内存时 , 线程会先获取待释放内存块所在分配区的锁 。如果其他线程正在使用该分配区 , 线程必须等待其他线程释放该分配区的互斥锁 , 然后才能进行释放内存的操作 。这些策略有助于确保内存分配的线程安全性和高效性 。
文章插图
图片
ptmalloc使用chunk结构体来描述内存块 , 其中包含了大小、前后chunk指针、前一个chunk是否在使用中以及前一个chunk的大小等成员信息 。这些成员在内存块的管理和合并操作中起着关键作用 。
其中 , p字段主要用于内存块的合并操作 , 具体表示如下: