我们可以通过GPT来详细地图解Linux上的C内存分配。这个过程可以进一步细化,只要你愿意。
最小的 C 代码示例
以下代码使用了标准 C 库函数 malloc 分配一块内存:
#include <stdlib.h>
#include <stdio.h>int main() {int *ptr = (int *)malloc(sizeof(int) * 10); // 分配10个整数大小的内存if (ptr == NULL) {perror("Memory allocation failed");return 1;}free(ptr); // 释放内存return 0;
}
系统调用的分叉情况
brk 和 sbrk 系统调用接口
brk
和 sbrk
是用于调整数据段(heap)大小的系统调用。以下是它们的接口说明:
brk
#include <unistd.h>int brk(void *end_data_segment);
end_data_segment
: 指向新的数据段末尾的指针。- 返回值: 成功时返回 0,失败时返回 -1 并设置 errno。
sbrk
#include <unistd.h>void *sbrk(intptr_t increment);
increment
: 增加或减少数据段大小的字节数。- 返回值: 成功时返回之前的程序 break 地址,失败时返回 (void *) -1 并设置 errno。
直接从 C 代码调用 brk 和 sbrk 的示例
#include <unistd.h>
#include <stdio.h>int main() {void *initial_brk = sbrk(0); // 获取当前的程序 break 地址printf("Initial program break: %p\n", initial_brk);// 增加数据段大小if (sbrk(1024) == (void *) -1) {perror("sbrk failed");return 1;}void *new_brk = sbrk(0); // 获取新的程序 break 地址printf("New program break: %p\n", new_brk);// 恢复原来的数据段大小if (brk(initial_brk) == -1) {perror("brk failed");return 1;}printf("Program break reset to: %p\n", sbrk(0));return 0;
}
mmap 系统调用接口
mmap
是用于映射文件或设备到内存的系统调用。以下是它的接口说明:
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.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_ANONYMOUS
等。fd
: 文件描述符,若使用匿名映射则为 -1。offset
: 文件映射的偏移量,通常为 0。- 返回值: 成功时返回映射区域的指针,失败时返回
MAP_FAILED
并设置 errno。
直接从 C 代码调用 mmap 的示例
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {size_t length = 4096; // 映射 4KB 内存void *addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (addr == MAP_FAILED) {perror("mmap failed");return 1;}printf("Memory mapped at address %p\n", addr);// 使用映射的内存int *array = (int *)addr;for (int i = 0; i < length / sizeof(int); i++) {array[i] = i;}// 取消映射if (munmap(addr, length) == -1) {perror("munmap failed");return 1;}return 0;
}
综合,malloc内部的分叉逻辑:
- 小内存块(例如几 KB):通过 brk 调整堆的大小。
- 大内存块(通常超过一定阈值,如 128 KB):通过 mmap 分配匿名内存区域。
用户态到内核态流程的 Mermaid 图
graph TDA[User Application] -->|malloc| B[glibc]B -->|small allocation| C[brk/sbrk]B -->|large allocation| D[mmap]C -->|System Call| E[Kernel: Adjust Heap]D -->|System Call| F[Kernel: Map Memory Pages]E --> G[Allocate Physical Memory]F --> G[Allocate Physical Memory]
内存分配的 Mermaid 图示
graph TDA[Heap Start] -->|Expand by brk/sbrk| B[Heap End]C[Anonymous Memory Region] -->|mmap| D[Virtual Address Space]E[Kernel Memory Manager] -->|Page Allocation| F[Physical Memory]
解释:
- Heap Start 和 Heap End 表示由 brk/sbrk 管理的堆区域。
- Anonymous Memory Region 是通过 mmap 分配的虚拟内存。
- Kernel Memory Manager 负责将虚拟地址映射到物理内存页。
最小程序加载后的内存分布示意图(演示brk/sbrk直接在Heap区域分配小内存)
graph TDH[Kernel Space] --> G[Stack]G --> F[Unmapped Memory]F --> E[Heap End]E -->|brk/sbrk| D[Heap Start]D --> C[Uninitialized Data Segment]C --> B[Initialized Data Segment]B --> A[Program Static Code Segment]
解释:
- Kernel Space:内核空间。
- Stack:存储函数调用的栈帧和局部变量。
- Unmapped Memory:未映射的内存区域。
- Heap Start 和 Heap End:由 brk/sbrk 管理的堆区域。
- Uninitialized Data Segment:存储未初始化的全局和静态变量(BSS段)。
- Initialized Data Segment:存储已初始化的全局和静态变量。
- Program Static Code Segment:存储程序的代码。
mmap 分配匿名内存区域的示意图
- Anonymous Memory Region 是通过
mmap
分配的虚拟内存。 mmap
分配的内存区域不在 HEAP 区域内,因为mmap
直接在虚拟地址空间中分配内存,而不依赖于堆的增长。堆的增长是通过brk/sbrk
系统调用来管理的,而mmap
则用于分配较大的内存块或特定用途的内存区域,如共享内存或文件映射。这样可以避免堆和mmap
分配的内存区域相互干扰,提高内存管理的灵活性和效率。
graph TDH[Kernel Space] --> G[Stack End]G[Stack End] --> F[Stack Start]F[Stack Start] --> E[Unmapped Memory]E --> I[Anonymous Memory Region]I --> J[Unmapped Memory]J --> D[Heap End]D --> C[Heap Start]C --> B[Uninitialized Data Segment]B --> A[Program Static Code Segment]
解释:
- Stack Start 和 Stack End:表示栈的起始和结束位置。
- Anonymous Memory Region 是通过
mmap
分配的虚拟内存,位于栈和堆之间的未映射内存区域中。 - Heap Start 和 Heap End:由
brk/sbrk
管理的堆区域。 - Uninitialized Data Segment:存储未初始化的全局和静态变量(BSS段)。
- Initialized Data Segment:存储已初始化的全局和静态变量。
- Program Static Code Segment:存储程序的代码。