malloc底层原理 brk,sbrk,mmap

发布于:2025-02-11 ⋅ 阅读:(14) ⋅ 点赞:(0)

brk() 系统调用

brk()Linux 系统调用,用于 调整进程的堆(heap)大小。它用于管理进程的 数据段(Data Segment),从而影响 malloc()free() 的底层实现


1. brk() 的作用

  • 增加或减少堆的大小
  • 用于 sbrk(),它是 malloc() 申请堆内存的底层机制
  • 影响 malloc()free(),但不适用于 mmap() 分配的内存

示例

#include <unistd.h>
#include <stdio.h>

int main() {
    void *heap1 = sbrk(0); // 获取当前堆顶
    printf("Heap start: %p\n", heap1);

    brk(heap1 + 4096); // 增加 4KB
    void *heap2 = sbrk(0);
    printf("Heap after brk(): %p\n", heap2);

    return 0;
}

输出

Heap start: 0x55aebc756000
Heap after brk(): 0x55aebc757000

brk() 增加了 4KB 的堆空间。


2. sbrk()brk() 的关系

  • sbrk() 调用 brk(),以相对方式调整堆
  • brk(new_end) 直接设置堆顶,而 sbrk(size) 相对移动堆顶。

示例

#include <unistd.h>
#include <stdio.h>

int main() {
    void *heap1 = sbrk(0);
    printf("Heap start: %p\n", heap1);

    sbrk(4096); // 增加 4KB
    void *heap2 = sbrk(0);
    printf("Heap after sbrk(4096): %p\n", heap2);

    sbrk(-4096); // 归还 4KB
    void *heap3 = sbrk(0);
    printf("Heap after sbrk(-4096): %p\n", heap3);

    return 0;
}

输出

Heap start: 0x5634b2712000
Heap after sbrk(4096): 0x5634b2713000
Heap after sbrk(-4096): 0x5634b2712000

sbrk(4096) 增加了 4KB,sbrk(-4096) 归还 4KB。


3. brk()malloc() 中的作用

malloc() 可能使用 brk() 来分配内存:

  1. 检查 free list(空闲块链表),如果没有足够的空闲块:
  2. 调用 sbrk(size)brk(new_end) 增加堆空间:
    void* malloc(size_t size) {
        if (size == 0) return NULL;
        void* new_heap = sbrk(size);
        if (new_heap == (void*)-1) return NULL; // 分配失败
        return new_heap;
    }
    

4. brk() vs mmap()

方法 适用场景 分配方式 是否可释放
brk() / sbrk() 小块分配(≤128KB) 调整堆大小 ❌ 只能扩展,不能归还
mmap() 大块分配(>128KB) 匿名映射内存 munmap() 释放

示例

void* bigAlloc = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  • mmap() 适用于大块内存分配,而 brk() 适用于 小块动态分配

5. 为什么 free() 不归还内存?

  • free() 将内存块加入 free list,但不会调用 brk(-size)
  • 只有当释放的内存块正好在堆顶free() 可能调用 brk() 归还内存。

示例

void* p1 = malloc(1000);
void* p2 = malloc(1000);
free(p2);  // 可能归还
free(p1);  // 不能归还,因 p2 仍在堆中

6. 总结

  • brk() 直接设置堆顶,sbrk() 以相对方式移动堆顶。
  • malloc() 可能使用 brk() 扩展堆,但释放时不会立刻归还。
  • mmap() 适用于大块分配,而 brk() 适用于小块管理。

mmap() 系统调用

mmap() 是 Linux 提供的一种 内存映射 机制,它可以 直接将文件或匿名内存映射到进程地址空间,用于 高效的大块内存分配


1. mmap() 的作用

  • 文件映射:将 文件 映射到进程的虚拟地址空间,可用于 文件 I/O 加速
  • 匿名映射:分配一块 无需关联文件 的内存,类似 malloc(),但 支持 munmap() 释放
  • 共享内存:多个进程可使用 mmap() 共享一块内存,实现 进程间通信(IPC)
  • 大块内存分配malloc() 可能使用 mmap()大于 128KB 的分配 申请内存。

2. mmap() 的函数原型

#include <sys/mman.h>
void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
参数 作用
addr 建议映射的地址(通常填 NULL,由系统分配)
length 映射的大小(字节数)
prot 保护权限,如 PROT_READ(可读)、PROT_WRITE(可写)
flags 控制标志,如 MAP_PRIVATE(私有映射)、MAP_SHARED(共享映射)
fd 文件描述符(-1 表示匿名映射)
offset 文件偏移量(fd=-1 时填 0

返回值

  • 成功:返回 映射的起始地址
  • 失败:返回 MAP_FAILED(void*)-1

3. mmap() 用于匿名内存分配

mmap() 可用于代替 malloc() 申请内存,并且可以 释放,而 brk() 只能扩展堆,无法释放。

示例:使用 mmap() 申请 1MB 内存

#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    size_t size = 1024 * 1024;  // 1MB
    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (ptr == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    printf("Memory allocated at: %p\n", ptr);

    // 使用 mmap 分配的内存
    *(int*)ptr = 42;
    printf("Value at ptr: %d\n", *(int*)ptr);

    // 释放内存
    munmap(ptr, size);
    return 0;
}

输出

Memory allocated at: 0x7f9c3c12d000
Value at ptr: 42

mmap() 成功 分配 1MB 内存,并能 munmap() 释放


4. mmap() 用于文件映射

可以将 文件内容映射到内存,实现 高效文件 I/O,避免 read()/write() 系统调用开销

示例:映射文件到内存

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    struct stat st;
    fstat(fd, &st); // 获取文件大小
    size_t size = st.st_size;

    // 将文件映射到内存
    void* ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    printf("File content:\n%.*s\n", (int)size, (char*)ptr);

    munmap(ptr, size); // 释放映射
    close(fd);
    return 0;
}

✅ 读取 test.txt 直接映射到内存,避免 read() 系统调用,提高 I/O 性能


5. mmap() 的常用 flags 选项

标志 作用
MAP_ANONYMOUS 匿名映射(不与文件关联,fd=-1 时使用)
MAP_PRIVATE 私有映射(修改不会影响原文件)
MAP_SHARED 共享映射(多个进程共享)
MAP_FIXED 固定映射地址(如果地址不可用,失败)

6. mmap() vs. malloc()

特性 mmap() malloc()
适用场景 大块内存(>128KB 小块内存
底层实现 mmap() 系统调用 sbrk() 调整堆
释放方式 munmap(ptr, size) free(ptr)
是否影响 brk() ❌ 不影响堆 ✅ 影响 brk()
性能 适合大对象,减少碎片 适合小对象,管理灵活

示例

void* largeAlloc = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE,
                        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

mmap() 适合 大块分配(如 1MB)。


7. mmap() vs. brk()

特性 mmap() brk() (sbrk())
适用场景 大块内存分配(如 1MB) 小块内存管理(如 malloc()
是否可释放 ✅ 可 munmap() 释放 ❌ 只能扩展,不能收缩
线程安全 ✅ 线程安全 ⚠️ sbrk() 影响全局堆,不推荐多线程使用

大对象用 mmap(),小对象用 malloc()


8. mmap() 共享内存(IPC)

多个进程可以通过 mmap() 共享一块内存,实现 进程间通信(IPC)

示例

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    int fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);
    ftruncate(fd, 1024); // 设置共享内存大小

    void* ptr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    strcpy(ptr, "Hello from mmap!");
    printf("Shared Memory Written: %s\n", (char*)ptr);

    munmap(ptr, 1024);
    close(fd);
    shm_unlink("/my_shared_memory"); // 删除共享内存
    return 0;
}

✅ 进程 A 写入共享内存,进程 B 读取共享数据


9. 结论

  • mmap() 用于 高效文件 I/O、匿名内存、共享内存
  • mmap() 适合大块内存,而 malloc() 适合小对象。
  • mmap() 可以释放内存,而 brk() 不能收缩。
  • 高性能应用(数据库、服务器)常用 mmap() 代替 malloc()