一、内存管理的层次关系
![](https://i-blog.csdnimg.cn/blog_migrate/f046526d084827e70013cf9e5ee71d93.png)
二、进程映像
程序和进程:
程序是存储在磁盘上的可执行文件,当运行程序时系统就会把可执行文件加载到内存中运行,正在运
行的程序叫进程,一个程序可以同时被执行多次,形成多个进程,每个进程互相独立,由操作系统管
理。
什么是进程映像:
进程在内存空间中的布局就是进程映像,从低地址到高地址依次为:
![](https://i-blog.csdnimg.cn/blog_migrate/8393896058c02a2fb933ea42a2900aa3.png)
各内存段分别存储了什么数据:
代码区
(text)
:
里面存储的是二进制指令、字面值常量、被
const
修饰过的原
data
内存段的变量,权限为
r--
或
r-x
。
数据区
(data)
:
里面存储的是初始化过的全局变量和静态变量。
BSS
区:
里面存储的是未初始化的全局和静态局部变量,进程一经加载此内存段的所有字节就会被清理为
0
,所以全局变量和静态变量的默认值是
0
。
堆区
(heap)
:
会被动态分配出去,从低地址向高地址扩展,在
C
语言中需要调用
malloc
系列函数进行管理,而
malloc
底层又调用了系统的
API
接口
(brk/sbrk/mmap/munmap)
。
栈区
(stack)
:
里面存储的是非静态局部变量、块变量,包括函数的参数和返回值,从高地址向低地址扩展。
堆区和栈区之间存在一块间隙,一方面为堆和栈的增长预留空间,同时共享库、共享内存等亦位于
此。
命令行参数与环境变量表:
里面存储的是命令行参数和环境变量。
#include <stdio.h>
#include <stdlib.h>
const int const_global = 0; // 常全局变量
int init_global = 0; // 初始化全局变量
int uninit_global; // 未初始化全局变量
int main(int argc, char *argv[])
{
const static int const_static = 0; // 常静态变量
static int init_static = 0; // 初始化静态变量
static int uninit_static; // 未初始化静态变量
}
三、虚拟内存
我们首先要理解一点就是操作系统不能把物理内存直接分配给进程使用,如果操作系统把物理内存直
接分配给进程,这样就会面临严重的安全问题,进程可以根据自己获取到的内存地址,对指针进行运算
从而访问其它进程的内存空间进行破坏,甚至影响操作系统的安全,所以操作系统的设计引入的虚拟内
存的概念。
什么是虚拟内存:
虚拟内存是操作系统内核为了对进程地址空间进行管理(
process address space management
)而
精心设计的一个逻辑意义上的内存空间概念。
在
32
位系统下,操作系统规定每个进程拥有
4G
的虚拟内存,但这
4G
的内存空间并不能直接使用,也
就是操作系统给进程画的饼,当进程真的需要使用内存来存储数据时,操作系统会把一部分的虚拟内存
与物理内存进行映射,映射后的虚拟内存才能正常使用。
const int const_local = 0; // 常局部变量
int prev_local; // 前局部变量
int next_local; // 后局部变量
int *prev_heap = malloc(sizeof(int)); // 前堆变量
int *next_heap = malloc(sizeof(int)); // 后堆变量
const char *literal = "literal"; // 字面值常量
extern char **environ; // 环境变量
printf("---- 命令行参数与环境变量 ---- <高>\n");
printf(" 环境变量:%p\n", environ);
printf(" 命令行参数:%p\n", argv);
printf("-------------- 栈 ------------\n");
printf(" 常局部变量:%p\n", &const_local);
printf(" 前局部变量:%p\n", &prev_local);
printf(" 后局部变量:%p\n", &next_local);
printf("-------------- 堆 ------------\n");
printf(" 后堆变量:%p\n", next_heap);
printf(" 前堆变量:%p\n", prev_heap);
printf("------------- BSS ------------\n");
printf(" 未初始化全局变量:%p\n", &uninit_global);
printf(" 未初始化静态变量:%p\n", &uninit_static);
printf("------------ 数据 ------------\n");
printf(" 初始化静态变量:%p\n", &init_static);
printf(" 初始化全局变量:%p\n", &init_global);
printf("------------ 代码 ------------\n");
printf(" 常静态变量:%p\n", &const_static);
printf(" 字面值常量:%p\n", literal);
printf(" 常全局变量:%p\n", &const_global);
printf(" 函数:%p\n", main);
printf("------------------------------ <低>\n");
printf("查看/proc/%u/maps,按<回车>退出...", getpid());
getchar();
return 0;
}
用户空间与内核空间:
每个进程的
4G
虚拟内存,根据使用者的不同分为两个部分。
用户空间:
[0x00000000,0xC0000000)
部分,有
3GB
大小,在经过操作系统的映射后,在应用程序中使用,程序
中不能直接访问内核空间中的代码和数据,但可以通过系统调用进入内核态,间接地与系统内核交互。
内核空间:
[0xC0000000,0xFFFFFFFF]
部分,有
1GB
大小,只有操作系统才能使用,里面存储着操作系统为该进程服务或者与该进程交互所需要的相关数据,内核空间由操作系统内核进行管理,不会随进程切换而改变。
用户空间对应进程,进程一切换,用户空间即随之变化,每个进程的内存空间完全独立。不同进程之间交换虚拟内存地址是毫无意义的
进程使用虚拟内存好处有哪些:
1
、操作系统为每个进程分配一个
4G
虚拟内存空间,可以对进程进行隔离,避免进程之间互相影响、
破坏
2
、操作系统把
4G
虚拟内存划分为用户态和内核态,可以对进程和操作系统进行隔离,避免进程对操
作系统的影响破坏。
3
、操作系统还可以把硬盘上的文件与虚拟内存进行映射,当物理内存不够用时,还可以使用硬盘代
替,虽然速度慢了些。
注意:如果一个程序可以影响、破坏其它进程和操作系统,它就病毒了。
四、内存映射
自动映射:
当程序执行时,操作系统把它加载到内存形成进程后,会自动给该进行映射
text
、
data
、
bss
、
statck
、命令行参数表、环境变量表进行内存映射。
手动映射:
在程序中首次使用
malloc
申请内存时,此时
malloc
手里没有堆内存可分配,也就是没有映射过的内存可供分配,malloc
会向操作系统申请映射
33
页虚拟内存,这
33
页内存就归
malloc
管理了,之后再向malloc申请内存时,
malloc
会从这映射好的
33
页内存中分配给调用者,而
malloc
函数底层调用了操作系统API
接口完成了映射操作。
如果程序要使用堆内存不多,达不到
33
页,那么使用
malloc
申请内存就会造成内存浪费,可以直接调
用了操作系统
API
接口,进行更精确的内存映射。
int main(int argc, char *argv[])
{
int num = 123456;
printf("%lu",(unsigned long)&num);
getchar();
}
// 程序B
int main(int argc, char *argv[])
{
int* ptr = NULL;
scanf("%lu",(unsigned long*)&num);
printf("%d",*ptr);
}
五、Linux系统内存管理API
遵循POSIX标准的内存管理API:
#include <unistd.h>
// brk和sbrk在内部维护一个指针p,指向当前堆内存最一个字节的下一个位置地址。
void *sbrk(intptr_t increment);
功能:根据参数来调整p的位置(p+/-increment),既可以映射虚拟内存也可以取消映射。
返回值:调整前的p的位置。
int brk(void *addr);
功能:根据指针参数修改p的位置(把p设置为addr)。
返回值:成功返回0,失败返回-1。
// 注意:brk和sbrk都可以单独分配/释放内存,但一般配合使用,sbrk用户分配,brk用于释放。
// 注意:brk和sbrk只能用于对虚拟内存映射,无法做到精细化管理。
举例:计算出
100
到
100000
所有的素数,存储到内存中。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <math.h>
bool is_prime(int num)
{
int end = sqrt(num);
for(int i=2; i<=end; i++)
{
if(0 == num % i)
return false;
}
return true;
}
int main(int argc,const char* argv[])
{
int* base = sbrk(0);
for(int i=100; i<10000; i++)
{
if(is_prime(i))
{
int* p = sbrk(4);
*p = i;
}
}
int *p = base, *end = sbrk(0);
while(p < end)
{
printf("%d ",*p++);
}
printf("\n");
brk(base);
return 0;
}
Linux系统的内存管理:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
功能:
1
、用户空间的虚拟内存与物理内存建立映射关系。
2
、用户空间的虚拟内存与文件建立映射关系。
addr
:想要映射虚拟内存的首地址,如果是
NULL
则操作系统自动计算。
length
:要映射的字节数,
prot
:映射的内存权限
PROT_EXEC
执行权限
PROT_READ
读权限
PROT_WRITE
写权限
PROT_NONE
无权限
flags
:
MAP_SHARED
共享映射,映射的内容对其它进程是可见的,对共享区的写入,相当于输出到文件。
MAP_PRIVATE
私有映射,映射的内容对其它进程不可见的
MAP_DENYWRITE
拒绝其它文件写入操作
MAP_ANON
映射的是内存而非文件
fd
:
文件描述符,类似于文件指针
FILE
offset
:
映射文件时使用到的偏移值
返回值:映射成功后的虚拟内存地址,如果失败返回值为
0xffffffff
int
munmap
(
void *
addr
,
size_t
length
);
功能:取消映射
addr
:映射内存的首地址
length
:内存的字节数
返回值:成功返回
0
,失败返回
-
1
。
//
注意:系统的内存映射是以页为单位的
(4096byte)
#include <stdio.h>
#include <sys/mman.h>
int main(int argc,const char* argv[])
{
FILE* fp = fopen("test.txt","r+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
int fd = fileno(fp);
char* str = mmap(NULL, 100, PROT_WRITE|PROT_READ, MAP_SHARED|MAP_FILE, fd,
0);
printf("%s\n",str);
sprintf(str,"Hello World\n");
munmap(str,100);
fclose(fp);
return 0;
}