这里写目录标题

  • 一、关于进程的内存空间划分
  • 二、虚拟内存地址空间的机制
  • 三、内存管理的相关函数
    • 3.1 关于malloc()和free()函数
    • 3.2 关于sbrk()和brk()
    • 3.3 关于mmap()和munmap()
    • 3.4 关于系统调用

一、关于进程的内存空间划分

程序是代码编译链接的产物,是存储在硬盘上的文件,程序是静止的。
运行起来的程序就是进程,是内存中运行的程序,也就是把程序加载到了内存中,是运行的。
CPU(中央处理器)不能直接访问硬盘,但能直接访问内存。程序必须加载到内存中(变成了进程)才能真正运行起来。
但有些时候会把进程也叫程序,主要是为了沟通方便。
一个程序可以有多个进程。

进程的内存空间划分为以下几个部分:
1代码区-用于存放代码(函数)的区域,特点是只读区。函数指针就是指向代码区的地址
2全局区-用于存放全局变量(定义函数外的变量)和static的局部变量
3BSS段-用于存放未初始化的全局变量的区域。
4栈区(堆栈区stack)-用于存放局部变量的区域,包括:函数的参数和非static的局部变量。系统自动管理栈区内存。
5堆区(heap)- 也叫自由区,程序员唯一可以控制的区域。通常内存分 配、回收都是在堆区malloc()、free()都是在堆区。堆区的内存内存,系统完全不管,程序员全权管理堆区内存。
6只读常量区-存放字符串字面值(即""括起来的字符串)和const修饰的全局变量。这个区域也是只读区,很多书都是把只读常量区并入代码区。
只读常量区的一个例子:

char* s1 = "abc";
s1[0] = '1';//这是不可以的,因为"abc"是字符串字面值

编程示例:

include <stdio.h>
include <stdlib.h>int i1 = 10;//全局
int i2;//BSS
static int i3 = 10;//全局
const int i4 = 10;//只读
void fa(int i5){//fa在代码区,i5在栈区int i6;//栈const int i7 = 10;//栈static int i8 = 10;//全局int* pi = malloc(4);//堆区char* s1 = "acb";//指向只读常量区。也就是s1一直指向"abc",不可修改char s2[] = "abc";//指向栈,s2对应着一片存储区而不是只读区,//存储区里面是数组类型的变量是可以修改的。//char* s1 = "acb"、char* s2 = "acb";s1和1s2一个地址,这是因//为只读区里只会存一个"abc"//char s1[] = "acb"、char s2[] = "acb";s1和1s2不一个地址printf("i5=%p,i6=%p,i7=%p,i8=%p,s1=%p,s2=%p\n,pi=%p\n",&i5,&i6,&i7,&i8,s1,s2,pi);free(pi);
}
int main() {printf("i1=%p,i2=%p,i3=%p,i4=%p\n",&i1,&i2,&i3,&i4);return 0;fa(1);printf("fa=%p\n",fa);//函数名就是函数指针
}

二、虚拟内存地址空间的机制

UNIX/LInux使用虚拟内存地址的方式管理内存,比如:
int a = 10;
printf("%p\n",&a);//得到的那个地址就是虚拟地址。每个进程先天都有0 - 4G-1的虚拟内存地址空间,本质上就是整数(编号)。虚拟内存地址本身不能存储任何的数据,必须映射到物理内存或硬盘上的文件后才能存储数据,否则引发段错误。
对虚拟地址空间举个例子,你买房买了2单元402房,房子还没建好,等真正房子建好了才能住进去。
这里补充一下,为啥是4G-1的空间:
32位系统最多能有多少个内存地址?4G个
2的10次方 -> 1024 -> 1k
2的20次方 -> 10241024 -> 1M
2的30次方 -> 1024
1024*1024 -> 1G
2的32次方 -> 4G
关于虚拟地址空间和实际物理内存的关系,如下图:

虚拟内存地址是分为用户层和内核层,用户层是0 - 3G,内核层是3G - 4G(可以设置)。
用户层不能直接访问内核层,通过系统函数可以。内存的基本单位是字节,但是内存映射的基本单位不是一个字节,是4096个字节(4K),叫一个内存页。Linux内核管理物理内存将内存划分为无数4k大小页,这样的好处是增加了效率。一般都是4k,有的不是,像查一查的话,用函数getpagesize()可以查看内存页的大小。

进程的内存空间排列次序:(地址从小到大)
代码区、只读常量区、全局区、BSS段、堆、…栈

Linux系统中,几乎一切做成了文件。目录是文件,内存也是文件,各种设备都是文件。
内存中的每个进程,都在/proc目录下建立一个目录,目录名就是ID(PID),进程结束目录就消失。这个目录就是进程的文件方式。cat命令查看/proc/进程ID/maps就可以看到进程的内存分配情况。函数getpid()可以获取当前进程的ID。
printf("%d\n",getpid());//结果可能是2900这种地址

注意,两个进程之间的虚拟地址是没有关系的。我们写两个程序说明。
写一个a.c文件,其中有代码:
int a = 10;
printf("%p\n",&p);//比如打印结果0xbff5702001
让a.c运行着,再打开一个终端,写一个b.c文件,其中有代码:
int *pi = 0xbff5702001;
printf("%d\n",*pi);//结果不是10,而是会出现段出错,这是因为0xbff5702001这个地址是a.c这个进程里映射的地址,但是对于b.c这个进程来说,这个地址是一个没有映射的地址

段错误的引发原因:
1使用没有映射的虚拟内存地址存储/获取数据。
2对内存区域进行没有权限的操作。比如:修改只读区。
int* pi = NULL;
*pi 就会出现段错误。因为向pi所指存储数据了。

关于虚拟地址和物理内存的关系,我这里按我自己的理解说一下。你的各个进程都在虚拟地址里面。CPU在某以纳秒级切换到你这个进程的时候,会把你这个进程里面的变量(虚拟地址)里面的给映射(可以理解为复制)到物理内存。映射的好处在于,在纳秒级时间里,把进程变量的值放进物理内存然后释放,实现了多进程并行。

三、内存管理的相关函数

用户层的系统调用函数是靠内核层的函数来实现的,举例堆内存分配函数说明如下:

STL -> 全自动管理
|
C++ -> new分配,delete回收内存
|
C语言-> malloc分配,free()回收内存
|
Unix系统函数 -> sbrk()/brk() 分配和回收内存
|
Unix系统函数 -> mmap()映射物理内存/硬盘文件
munmap()解除映射
(用户层函数到此为止)


Unix内核层函数 -> kmalloc()/vmalloc()(内核层,嵌入式)

3.1 关于malloc()和free()函数

malloc()申请的是虚拟地址(在堆中)。
malloc() -> 传入分配内存的大小(字节),返回分配的首地址。内存分配的函数干两件事:
分配虚拟地址 -> 所有情况下
映射物理内存/硬盘文件 -> 第一次映射,后面用完了再映射
malloc()申请小块内存时一次映射33个内存页,用完后继续映射。申请大块内存时(超过31)个内存页会映射比申请稍多一点的内存页。
malloc()函数除了分配正常的内存分配空间,还需要额外开辟一些空间,用于存储一些附加信息,比如分配的大小(就是你申请的内存有多大,得记录下载。就像数组有’\0’结束一样)。额外附加信息存在前面的4个字节。
程序示例:

#include <stdio.h>
#include <stdlib.h>int main() {int a,b,c,d;//栈printf("a=%p,b=%p,c=%p,d=%p",&a,&b,&c,&d);int *p1 = malloc(4);//分配虚拟地址,映射33个内存页。注意,malloc返回的//地址是整数型的,所以用int*int *p2 = malloc(4);//分配虚拟地址,不映射int *p3 = malloc(4);//分配虚拟地址,不映射printf("p1=%p,p2=%p,p3=%p",p1,p2,p3);//*(p1-1) = 0;清前4个字节的内容,导致附加信息没了,就会出错/**(p1+100) = 10;//未分配的内存(已映射)printf("%d\n",*(p1+100));结果是不出错的,这是因为已经映射了33个页,p1+100的地址是在这33个页之内的,既然可以不malloc分配地址都可以用,为何还要malloc呢?这是因为malloc的空间有保护作用的,比如说这里,第一次malloc的4个字节就属于p1了,你再一次malloc就属于p2了,而不会覆盖p1的空间,这就是保护*/free(p1);return 0;
}

关于虚拟地址、malloc()、物理内存的关系见下图:

经验:malloc()、free()在使用时,不需要考虑那么多,用久行。
第二点是尽量不要超范围,超出范围就失去了保护作用。比如int *p1 = malloc(4);p+100

free()函数-释放内存
free()一定会释放内存地址(malloc所得的地址),以便地址虚拟地址可以循环利用,但不一定解除映射。超过33个内存页的部分会释放,最后33个内存页不释放,直到进程结束时完全释放。

3.2 关于sbrk()和brk()

sbrk()和brk()系统的底层会维护一个位置,通过位置的移动完成内存的分配和回收。映射内存时以一个内存页作为基本单位。
void* sbrk(int increment)
参数是增量:
增量是正数时,分配内存
增量是负数时,回收内存
增量为0时,取当前的位置
返回值是返回移动移动之前的位置(可用内存的首地址),这个返回值对于增量为负数的情况没有意义。 具体原理见下图:

对上图的注释:sbrk(0)的意义在于返回当前的位置。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(){printf("pid=%d\n",getpid());void* p1 = sbrk(4);//void*表示不确定指向类型的指针.返回00,位置在04,映射1页if(p1 == (void*)-1) perror("sbrk"),exit(-1);//两个函数用一个逗号连代表一个语句//exit()函数,结束当前进程printf("p1=%d\n",p1); sleep(10);printf("sleep over\n"); int* pi1 = p1;//类型转换int* pi2 = sbrk(4);//返回04,位置在08 不做映射int* pi3 = sbrk(4);//返回08,位置在12printf("pi2=%p,pi3=%p\n",pi2,pi3);sleep(10);sbrk(-4);//位置在08,回收内存sbrk(-4);//位置在04int* pi4 = sbrk(0);//把当前位置给pi4printf("pi4=%p\n",pi4);sbrk(4093);//4093+4=4097,超了一页,映射2页sleep(10); printf("sleep over\n");sbrk(-1);//回到一页,立即释放sleep(10);sbrk(-4096);printf("全部释放\n");while(1);}

sbrk()在分配内存时很方便,但在回收内存时比较麻烦;但是brk()特性则相反。
开发中,一般用sbrk()分配内存,用brk()回收内存。
brk()的使用方式就是直接传递一个地址过来,做新的位置。
brk()必须和sbrk()结合使用,获得第一个位置。brk()无返回。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {void* p = sbrk(0);brk(p+4);//分配4字节brk(p+8);//再次分配4字节brk(p+4);//回收4字节int* p1 = p;*p1 = 100;double* p2 = sbrk(0);brk(p2+1);//移动8个字节*p2 = 1.0;char* p3 = sbrk(0);brk(p3+10);strcpy(p3,"zhangfei");//正确printf("%d,%lf,%s\n",*p1,*p2,p3);//回收brk(p3);//回收p3brk(p2);//回收p2brk(p1);//回收p1//如果直接写brk(p1);则p2p3也被直接回收了。
}

发现了,brk()分配内存不好用,但回收好用,而sbrk()相反。二者怎么互补利用呢?下面这才是最方便的、最简单的、最正确的使用sbrk()、brk()的用法。
如下代码:
改良刚才的代码,用brk()回收,用sbrk()分配内存的方式,实现一个int、一个double和一个字符串类型的存储,并打印出来。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {int* p1 = sbrk(4);*p1 = 100;double* p2 = sbrk(8);*p2 = 1.0;char* p3 = sbrk(10);strcpy(p3,"zhangfei");//正确printf("%d,%lf,%s\n",*p1,*p2,p3);brk(p3);//回收p3brk(p2);//回收p2brk(p1);//回收p1
}

3.3 关于mmap()和munmap()

mmap()和munmap()-Unix的系统函数,更贴近底层

void* maap(void* addr,size_t size,int prot,int flags,int fd,off_t offset);
参数addr可以指定映射的首地址,一般为0交给内核指定。
size就是分配内存的的大小,映射以页为单位。
prot是分配内存的权限,一般用PR0T_READ|PORT_WRITE
flags是标识,通常包括以下三个:MAP_SHARED MAP_PRIVATE :这俩用的时候二选一,指明映射的内存是否共享MAP_ANONYMOUS : 映射物理内存,默认映射文件。
fd是文件描述符,在映射文件时有用。
offset是文件的偏移量,指定1映射文件时从哪里开始。映射物理内存时,fd和offset给0即可。
关于函数返回,成功则返回首地址,失败则返回MAP_FAILED即等价于(void*)-1

经验:如果有多个权限、选项的拼接,一般的设计方式时:
每种权限/选项用一个二进制表示(一位是1,其他全0),用位或运算连起来。

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.>hint main() {void* p = maap(NULL,//用户设定首地址,NULL交给系统4,//分配大小,映射时按内存页映射,不足的会补足PROT_RADE|PROT_WRITE,//权限MAP_PRIVATE|MAP_ANONYMOUS,//私有+映射物理内存0,0)//映射物理内存的话,后两个参数0即可if(p == (void*)-1)perror("mmap"),exit(-1);int* pi = p;*pi = 100;printf("*pi=%d\n",*pi);munmap(p,4);//解除映射,释放内存return 0;
}

对于以上三对函数的选择,具体问题具体分析,不存在谁好谁坏。
malloc()free()在windows环境下用
代码确保只在UNIX环境下运行,并且是服务器,就最好不用malloc

3.4 关于系统调用

系统调用
用户层的程序 不能 直接访问内核层,系统的核心功还能必须通过内核层控制,那咋办呢?因此,系统提供了一系列的函数允许用户层的程序通过函数进入内核层,从而完成功能。这些函数叫做系统调用(System call)。
原理如下图:

sbrk是系统调用,是Unix/Linux系统提供的接口(只能在Unix/Linux系统下才能用的)。而malloc是标准c函数在,所以在Unix/Linux和windows下都能用。
ps:在Unix/Linux下,malloc底层实现就是通过系统调用sbrk实现的;在windows下malloc则是通过调用windows系统提供的接口实现。

Unix系统 - 内存管理相关推荐

  1. Linux系统内存管理之伙伴系统分析 - 旭东的博客 - 博客园

    Linux系统内存管理之伙伴系统分析 - 旭东的博客 - 博客园 Linux系统内存管理之伙伴系统分析 今天去面试,一位面试官提到了内存管理的伙伴系统,当时就懵了,因为根本就没有听说过.晚上回来在实验 ...

  2. Unix系统 - 进程管理

    写在前面:注意,本章除了讲解进程管理,还包含网络编程Socket API的知识. 这里写目录标题 一.进程 1.1基础知识 1.1.1进程ID 1.1.2查看进程 1.1.2 父子进程概念 1.1.3 ...

  3. Linux Unix内存管理,简述:Unix/Linux内存管理

    一.底层结构 采用三层结构,实际使用中可以方便映射到两层或者三层结构,以适用不同的硬件结构.最下层的申请内存函数get_free_page.之上有三种类型的内存分配函数: 1.kmalloc类型.内核 ...

  4. linux系统内存管理含义,Linux内存管理--基本概念及相关数据结构

    一.内存管理的基本概念 1.存储空间 在32位嵌入式系统中,存储空间的地址范围从0x00000000到0xFFFFFFFF.这4GB存储范围内可以包括以下几种存储空间: 设备空间(MT_DEVICE) ...

  5. 鸿蒙系统内存管理,嵌入式系统内存管理-鸿蒙HarmonyOS技术社区-鸿蒙官方战略合作伙伴-51CTO.COM...

    1.概述 操作系统的内存管理功能用于向操作系统提供一致的地址映射功能和内存页面的申请.释放操作.在嵌入式实时系统中,内存管理根据不同的系统,有不同的策略,对于有些系统支持的虚拟内存管理机制,对于另外一 ...

  6. windows7系统内存管理--Superfetch

    苦比了一天后的释然.今天给笔记本加了一条2G内存,但是开机之后win7内存占用率竟然50%左右,好生郁闷啊,最终还是找到了原因,给大家分享一下! 对于Windows Vista.windows 7的内 ...

  7. 程序猿内功—系统内存管理

    目录 内存使用的演变 物理内存的连续分配管理 单一连续存储系统 分区式存储管理 物理内存的非连续分配管理 段式存储管理 页式存储管理 页式存储和段式存储的区别 段页式存储管理   首先操作系统的内存管 ...

  8. 系统内存管理介绍,内存申请及回收流程

    目录 系统申请以及回收内存 占用块和空闲块 系统的内存管理 可利用空间表 分配存储空间的方式 空间分配与回收过程产生的问题 边界标识法内存碎片合并 分配算法 回收算法 伙伴系统管理动态内存 可利用空间 ...

  9. Linux系统内存管理实验报告,Linux 内存管理 综合实验报告.pdf

    Linux 内存管理 综合实验报告 计算机与通信学院 Linux 内存管理 综合实验报告 指导老师:孙建华 组员 :夏槟 20040810720 段翼真 20040810503 米晓亮 2004081 ...

最新文章

  1. #技术分享# “乐高”内核的诞生
  2. SQL Relay 0.47 发布,SQL 中间层
  3. 【Flask】ORM高级操作之分组、过滤和子查询
  4. 来吧!我教你画真正的流程图
  5. oracle导数的数据乱码,Oracle10g导数据时中文乱码相关处理
  6. 安全系列之一:如何利用IPSec保证远程桌面的安全性!(上)
  7. $.type 怎么精确判断对象类型的 --(源码学习2)
  8. 想搬去苏州生活了。。
  9. 计算机键盘换挡键,换挡键alt电脑键盘上的用处有哪些
  10. Ubuntu下使用外置USB无线网卡
  11. 恒指赵鑫:04.11今日恒指早盘思路
  12. Elasticsearch快速初始化数据
  13. Google Map API 使用总结
  14. 【linux基础】18、进程管理工具
  15. 服务器掉包的原因?103.219.36.x
  16. cp2102 usb驱动官网
  17. 智能计数器控制板的功能及应用有哪些?
  18. python re正则表达式提取含有某些关键词的句子,findall()查找含有关键词的句子
  19. c语言红外解码程序,[转载]红外遥控和C语言51红外遥控解码程序设计实例
  20. 中国十大IC设计公司和十大最有潜力IC公司2010

热门文章

  1. Excel实战入门笔记
  2. STM32 之一 HAL库、标准外设库、LL库(STM32 Embedded Software)
  3. System.map解析
  4. 【与】、【或】、【异或】运算
  5. 系统盘(c盘)空间清理方法总结
  6. 广州南沙新区规划发布 面积约803平方公里-广州-南沙新区-面积
  7. 虚拟机提示“虚拟设备在启动时将处于断开连接状态”
  8. 双十二电容笔啥牌子好?十大电容笔知名品牌
  9. Python——n-gram实现
  10. 深入理解字符串的不可变性[java]