一、内存管理
- 用户层
STL 智能指针/容器 自动分配、释放 调用C++
C++ new/delete 调用C
C malloc/free 调用POSIX\Linux
POSIX brk/sbrk 调用内核
Linux mmap/munmap 调用内核
- 系统层
Kernal kmalloc/vmalloc 调用驱动
dirver get_free_page
二、进程映像
程序存储在磁盘上的可执行文件(脚本、二进制),当执行程序时,系统会把可执行文件加载到内存形成进程,一个程序可以同时加载出多个进程
进程在内存中的分布情况就是进程映像,从低地址到高地址依次是:
text 代码段: 二进制指令、常量(数值、"字符串字面值"、被const修饰过的原data的数据)。只读,强制修改会段错误
data 数据段: 初始化过的全局变量、初始化过的静态局部变量
bss 静态数据段: 未初始化过的全局变量、未初始化过的静态局部变量。程序运行前会自动清0
heap 堆: 程序员手动管理的大量数据,管理麻烦、申请和释放受控,与指针配合使用,使用不当可能会内存泄漏、内存碎片
stack 栈: 局部变量、块变量。大小有限、自动申请、释放
environ 环境变量表: 所有的环境变量。每个进程都有一份,修改不会影响系统真正的环境变量的值
argv 命令行参数: 程序执行时附带的参数
练习1:打印出各个内存段的数据的地址,与该进程的maps文件中记录的内存分布比较
maps 文件可以查看某个进程的代码段、栈区、堆区、动态库、内核区对应的虚拟地址
maps文件只能显示简单的分区,smap文件可以显示每个分区的更详细的内存占用数据
查看maps:/proc/进程ID/maps
查看进程ID:
命令:ps -aux
函数:getpid()
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int Num = 10;
int Num1;
int main(int argc,const char* argv[])
{
printf("text: %p\n",main);
printf("read: %p\n","hehe");
printf("data: %p\n",&Num);
printf("bss: %p\n",&Num1);
printf("heap: %p\n",malloc(4));
int num;
printf("stack:%p\n",&num);
extern char** environ;
printf("env: %p\n",*environ);
printf("argv: %p\n",argv[0]);
printf("gedit /proc/%u/maps\n",getpid());
for(;;);
}
三、虚拟内存
1、32位系统会给每个进程分配4G的虚拟内存空间
2、进程、用户只能使用访问虚拟内存,无法直接使用真实的物理内存
3、虚拟内存只有与物理内存进行映射后才能使用,否则会产生段错误
4、虚拟内存与物理内存的映射和对应使用都是由操作系统动态维护
5、虚拟内存技术一方面是为了让系统更加安全,可以不暴露真实的物理内存地址给用户,又可以让一个进程出错后不影响其他进程和系统的运行,另一方面可让进程使用比实际物理内存更大的空间
6、4G的虚拟内存地址分成两个部分
[0~3G) 用户空间
[3G~4G) 内核空间
7、当进程\线程运行在内核空间时,称该进程\线程处于内核态,当进程\线程运行在用户空间时,称该进程\线程处于用户态
8、在内核态下,进程运行在内核空间,此时CPU可以执行任何指令,此时运行的代码不受任何限制,可以自由访问任何有效的地址
9、在用户态下,进程运行在用户空间,此时进程不能直接访问内核空间的数据,可以通过系统调用(API 系统接口函数)的方式切换到内核态,间接地访问内核空间的数据
10、对虚拟内存越界访问(访问没有映射过的虚拟内存),导致段错误
四、映射虚拟内存与物理内存的函数
sbrk/brk/mmap/munmap
malloc分析
malloc使用:
#include <stdio.h>
#include <stdlib.h>
int main(int argc,const char* argv[])
{
int* p = malloc(4);
free(p);
}
关于 malloc 获取虚拟内存实际调用POSIX还是Linux提供的函数受到操作系统、编译器、字节数的影响,大致逻辑:
1、如果m请小于128Kb调用sbrk\brk
2、如果申请大于128Kb调用mmap\munmap
malloc 通过 brk()
方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;
malloc 通过 mmap()
方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放。
注意:strace ./a.out 可以追踪程序的底层调用(用户层)
注意:系统内存映射是以页(1页=4096字节)为单位的
注意:sbrk、brk底层维护一个映射位置指针,该指针指向虚拟内存中映射过的内存的最后一个字节的下一个位置,可以通过移动该指针来实现映射内存和取消映射的效果
sbrk
void *sbrk(intptr_t increment);
功能:通过增量increment来调整映射位置指针的位置,从而进行映射或取消映射
increment:增量(字节为单位)
0 获取映射位置指针的位置
>0 映射内存
<0 取消映射
返回值:映射指针原来的位置
实例:
#include <stdio.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
int* ptr = sbrk(4);
*(ptr+1023) = 1000;
printf("%d\n",*(ptr+1023));
sbrk(-4);
}
brk
int brk(void *addr);
功能:通过修改映射指针指向addr的地址,从而进行映射或取消映射
addr:
> 原来位置 映射
< 原来位置 取消映射
返回值:成功返回0 失败返回-1
实例:
#include <stdio.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
int* base = sbrk(0);
int ret = brk(base+1);
*base = 1234;
printf("%d %d\n",*base,ret);
}
总结:sbrk、brk都属于POSIX标准中的内存映射函数,都是可以单独进行映射、取消映射,但是配合使用最方便(sbrk映射、brk取消映射)
实例:
#include <stdio.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
int* arr = sbrk(0);
for(int i=0; i<10; i++)
{
sbrk(4);
arr[i] = i;
printf("%d\n",arr[i]);
}
// brk(arr);
sbrk(-40);
}
练习2:计算2~1000之间的素数,存储在堆内存中,要求尽可能少地浪费内存
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
bool is_prime(int num)
{
for(int i=2; i<=num/2; i++)
{
if(0 == num%i) return false;
}
return true;
}
int main(int argc,const char* argv[])
{
int* arr = sbrk(0);
int len = 0;
for(int i=2; i<=1000; i++)
{
if(is_prime(i))
{
sbrk(4);
arr[len++] = i;
}
}
for(int i=0; i<len; i++)
{
printf("%d ",arr[i]);
}
fflush(stdout);
brk(arr);
}
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_EXEC 执行权限 PROT_EXEX | PROT_READ
PROT_READ 读权限
PROT_WRITE 写权限
PROT_NONE 没有权限
flags:映射标志
MAP_FIXED 如果提供的addr无法映射,则直接失败
MAP_ANONYMOUS 指定映射虚拟内存,不映射文件,fd、offset失效
MAP_SHARED 对映射后的内存可以共享给其他进程
MAP_PRIVATE 对映射后的内存只能当前进程使用
注意:flags中必须在MAP_SHARED、MAP_PRIVATE之间二选一
fd:文件描述符 可以让文件映射到物理内存,不需要映射文件直接写0
offset:文件的偏移位置,从该位置开始映射文件
返回值:成功返回映射后的内存首地址,失败返回(void*)-1
munmap
int munmap(void *addr, size_t length);
功能:取消映射
addr:映射的内存首地址
length:取消映射的字节数
返回值:成功返回0 失败返回-1
实例:
#include <stdio.h>
#include <sys/mman.h>
int main(int argc,const char* argv[])
{
int* ptr = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,0,0);
if((void*)(-1) == (void*)ptr)
{
perror("mmap");
return -1;
}
for(int i=0; i<1024; i++)
{
ptr[i] = i+1;
printf("%d ",ptr[i]);
}
if(munmap(ptr,4096))
{
perror("munmap");
}
printf("success\n");
}
五、Linux内存管理总结
1、mmap\munmap底层不维护任何东西,直接在堆内存中选择合适的内存进行映射,返回映射成功后的内存首地址
2、sbrk\brk底层维护一个映射位置指针,该指针记录了通过sbrk\brk映射内存的末尾位置,通过改变该指针的位置来映射和取消映射
3、malloc\free底层调用了sbrk\brk 或者 mmap\munmap,虚拟内存必须与物理内存建立映射关系后才能使用
4、每个进程都有4G(32位)的虚拟内存空间
5、内存管理的重点是理解Linux对内存管理机制,而不是sbrk\brk\mmap\munmap函数的使用
标签:sbrk,映射,int,编程,内存,Linux,include,虚拟内存 From: https://www.cnblogs.com/ljf-0804/p/17689737.html