0.进程地址空间
进程地址空间不是物理地址,是一种虚拟地址,由操作系统提供。进程地址空间本质是进程看待内存的方式,抽象出来的一个概念,内核中用一个结构体mm_struct表示,这样每个进程都认为自己独占系统内存资源。
在进程控制块task_struct中有一个mm_struct结构体指针,指向一个mm_struct结构体,这个结构体里面完成对各个数据区域的划分,然后通过页表映射到物理内存上。
1.用户空间
根据内存空间分配方式的不同,可以将内存分为动态内存和静态内存。
动态内存与静态内存是两种不同的分配内存的方式,那么它们在分配方式上存在什么样的区别呢?
- 静态内存实际上是由编译器完成的、在栈空间的、按计划分配,其释放由作用域决定;
- 动态内存是由程序员完成的、在堆空间的、按需分配,需要程序员手动释放。
计算机的内存空间都是通过指针进行访问的,而指针对于正确地分配动态内存空间来说又是十分重要的。关于动态内存的分配所使用的操作函数,在这里主要介绍malloc(),calloc(),realloc()和memset()函数的基本用法。
1.1 malloc()函数
函数原型:void* malloc(size_t size);
其中,size
参数是你希望分配的内存字节数。
- 申请一块连续长度(虚拟内存连续)的内存空间,如果分配成功,
malloc()
返回一个指向这块内存的指针。 - 如果分配失败(例如,内存不足),
malloc()
返回NULL
。 - 分配的内存区域在调用
free()
函数之前都是可用的,调用free()
会释放该内存区域,使其可以被操作系统重新分配。
1.2 calloc()函数
函数原型:void* calloc(size_t num, size_t size);
其中,num
是你想要分配的元素的数量,size
是每个元素的大小(以字节为单位)。calloc()
会分配 num * size
字节的内存,并将这块内存的内容初始化为零。
- 如果分配成功,
calloc()
返回一个指向这块内存的指针。 - 如果分配失败(例如,内存不足),
calloc()
返回NULL
。 - 与
malloc()
一样,使用calloc()
分配的内存区域需要通过调用free()
函数来释放。
使用示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
// 请求分配 10 个整数的空间,并初始化为零
int* numbers = (int*)calloc(10, sizeof(int));
if (numbers == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// 使用分配的内存
for (int i = 0; i < 10; i++) {
numbers[i] = i;
}
// 打印分配的内存中的内容
for (int i = 0; i < 10; i++) {
printf("%d ", numbers[i]);
}
// 释放内存
free(numbers);
return 0;
}
1.3 realloc()函数
函数原型:void* realloc(void* ptr, size_t new_size);
realloc()
是 C 语言中用于重新分配之前已分配内存的函数。如果你已经使用 malloc()
或 calloc()
分配了一块内存,但后来发现这块内存不够大(或太大了),你可以使用 realloc()
来调整它的大小。
其中,ptr
是指向之前已分配内存的指针,new_size
是你想要的新内存大小(以字节为单位)。
- 如果
new_size
大于原来的大小,realloc()
会尝试在原有内存之后扩展内存空间,并返回指向新内存区域的指针。 - 如果
new_size
小于原来的大小,realloc()
会收缩内存区域,并可能移动内存块以释放多余的空间,同样返回指向新内存区域的指针。 - 如果内存调整失败(例如,没有足够的内存来扩展),
realloc()
返回NULL
,并且原来的内存块保持不变。
重要的是,如果 realloc()
成功,它可能会返回一个新的内存地址(即使大小没有变化)。因此,你总是应该使用 realloc()
的返回值来更新你的指针,而不是依赖原始的指针。
1.4 memset()函数
memset()
是 C 语言中的一个库函数,用于设置内存区域的内容。这个函数通常用于初始化内存区域,将其设置为一个特定的值。它的原型在 <string.h>
头文件中定义。
void *memset(void *ptr, int value, size_t num);
参数说明:
ptr
:指向要设置的内存区域的指针。value
:要设置的值。这个值以 int 形式提供,但函数实际上按照 unsigned char 来解释它,并且只使用该值的低 8 位(即一个字节)。num
:要设置的字节数。
memset()
函数会将 ptr
指向的内存区域的前 num
个字节设置为 value
的低 8 位所表示的值。这意味着,如果 value
是一个整数,它将被解释为一个无符号字符,并且该字符值将被复制到内存区域的每个字节中。
2. 内核空间
2.1 kmalloc函数
2.2 kcalloc函数
2.3 krealloc函数
2.4 kfree函数
vmalloc/vfree
kmem_cache_create
__get_free_page/__get_free_pages
alloc_page/alloc_pages/free_pages
brk、sbrk
mmap/munmap
alloca
在开发板上编程测试这些函数的使用方法。理解记忆。
内存空间分布,每个区代表的意思,上述函数分别都是在哪个区申请的。
通过学习上述函数的内核代码实现,理解每个函数的特征及应用场景。
理解伙伴系统,理解页表,理解内存管理机制。