长沙市网站制作公司,网站开发投标书,pk10代码网站开发,做珠宝网站1. 虚拟内存
1.1 什么是虚拟内存 虚拟内存是计算机系统内存管理的一种技术#xff0c;我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间#xff0c;并且 把内存扩展到硬…1. 虚拟内存
1.1 什么是虚拟内存 虚拟内存是计算机系统内存管理的一种技术我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间并且 把内存扩展到硬盘空间。 1.2 为什么需要虚拟内存 我们了解单片机是没有操作系统的所以每次写完代码都需要借助工具把程序烧录进去这样程序才能跑起来。另外单片机的 CPU 是直接操作内存的「物理地址」。 在这种情况下要想在内存中同时运行两个程序是不可能的。如果第一个程序在 3000 的位置写入一个新的值将会擦掉第二个程序存放在相同位置上的所有内容所以同时运行两个程序是根本行不通的这两个程序会立刻崩溃。也就是每次CPU都直接操作内存导致不能多进程 那么操作系统应该如何避免这种情况 这里关键的问题是这两个程序都引用了绝对物理地址而这正是我们最需要避免的 因此我们可以把进程所使用的地址「隔离」开来即让操作系统为每个进程分配独立的一套「虚拟地址」人人都有互不干涉。但是有个前提每个进程都不能访问物理地址至于虚拟地址最终怎么落到物理内存里对进程来说是透明的。 操作系统会提供一种机制将不同进程的虚拟地址和不同内存的物理地址映射起来。
如果程序要访问虚拟地址的时候由操作系统转换成不同的物理地址这样不同的进程运行的时候写入的是不同的物理地址这样就不会冲突了。
于是这里就引出了两种地址的概念
我们程序所使用的内存地址叫做虚拟内存地址Virtual Memory Address实际存在硬件里面的空间地址叫物理内存地址Physical Memory Address。
操作系统引入了虚拟内存进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元MMU的映射关系来转换变成物理地址然后再通过物理地址访问内存如下图所示 1.3 如何管理虚拟地址与物理地址之间的关系
内存分页 分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间我们叫页Page。在 Linux 下每一页的大小为 4KB
虚拟地址与物理地址之间通过页表来映射在分页机制下虚拟地址分为两部分页号和页内偏移。页号作为页表的索引页表包含物理页每页所在物理内存的基地址这个基地址与页内偏移的组合就形成了物理内存地址见下图 因此内存转化的步骤为
CPU将虚拟内存地址切分为页号和偏移量根据页号在页表中查询对应的物理页号页号加上前面的偏移量就得到了物理内存地址 但是如果这样给每一个程序分配一个页表去管理虚拟内存的话会出现下面的问题 在32位系统上虚拟内存大小约为4G2^32一个页的大小为4K2^12那么我们映射4G虚拟内存空间需要100 多万2^20个页一个「页表项」一个页的内容需要4个字节也就是需要4M的内存空间存储这个页表。 一个进程映射整个虚拟内存空间需要4M那么100个进程就需要400M存储页表。这是非常大的内存了更别说 64 位的环境了。 为了解决空间大小的问题提出了多级页表的方法 多级页表 通过上面的例子我们得知32位系统下要映射整个4G虚拟地址空间的页表大小为4M且这个页表有100 多万2^20「页表项」。 我们把这个 100 多万个「页表项」的单级页表再分页将页表一级页表分为 1024 个页表二级页表每个表二级页表中包含 1024 个「页表项」形成二级分页。如下图所示 此时你会发现进行二级分页去映射4G虚拟内存空间需要 4KB一级页表 4M二级页表这比之前不分多级只有一级表花费的4M还要大吗 其实每个进程都有 4GB 的虚拟地址空间而显然对于大多数程序来说其使用到的空间远未达到 4GB因为会存在部分对应的页表项都是空的根本没有分配对于已分配的页表项如果存在最近一定时间未访问的页表在物理内存紧张的情况下操作系统会将页面换出到硬盘也就是说不会占用物理内存。 如果使用了二级分页一级页表就可以覆盖整个 4GB 虚拟地址空间但如果某个一级页表的页表项没有被用到也就不需要创建这个页表项对应的二级页表了即可以在需要时才创建二级页表。假设只有 20% 的一级页表项被用到了那么页表占用的内存空间就只有 4KB一级页表 20% * 4MB二级页表 0.804MB这对比单级页表的 4MB 是不是一个巨大的节约 2. malloc原理
2.1 Linux内存分布长什么样
Linux操作系统中虚拟地址空间分为内核空间和用户空间 接下来看看32位中用户空间的具体分布情况 代码段包括二进制可执行代码数据段包括已初始化的静态常量和全局变量BSS 段包括未初始化的静态变量和全局变量堆段包括动态分配的内存从低地址开始向上增长文件映射段包括动态库、共享内存等从低地址开始向上增长栈段包括局部变量和函数调用的上下文等。栈的大小是固定的一般是 8 MB。当然系统也提供了参数以便我们自定义大小
在这 6 个内存段中堆和文件映射段的内存是动态分配的。比如说使用 C 标准库的 malloc() 或者 mmap() 就可以分别在堆和文件映射段动态分配内存。 2.1 malloc 是如何分配内存的
malloc 申请内存的时候会有两种方式向操作系统申请堆内存。
方式一如果用户分配的内存小于 128 KB通过 brk() 系统调用从堆分配内存方式二如果用户分配的内存大于 128 KB通过 mmap() 系统调用在文件映射区分配内存
原理
方式一通过 brk() 函数将「堆顶」指针向高地址移动获得新的内存空间。 方式二通过 mmap() 系统调用中「私有匿名映射」的方式在文件映射区分配一块内存也就是从文件映射区“偷”了一块内存。 2.2 malloc 分配的是物理内存吗
不是的malloc() 分配的是虚拟内存。 如果分配后的虚拟内存没有被访问的话虚拟内存是不会映射到物理内存的这样就不会占用物理内存了。 只有在访问已分配的虚拟地址空间的时候操作系统通过查找页表发现虚拟内存对应的页没有在物理内存中就会触发缺页中断然后操作系统会建立虚拟内存和物理内存之间的映射关系。 2.3 malloc(1) 会分配多大的内存
malloc() 在分配内存的时候并不是老老实实按用户预期申请的字节数来分配内存空间大小而是会预分配更大的空间作为内存池。
我们以以下代码为例看看malloc(1)究竟分配了多大内存
#include stdio.h
#include stdlib.h
#include malloc.hint main()
{printf(使用cat /proc/%d/maps查看内存分配\n,getpid());//申请1字节的内存void *addr malloc(1);printf(此1字节的内存起始地址%x\n, addr);printf(使用cat /proc/%d/maps查看内存分配\n,getpid());//将程序阻塞当输入任意字符时才往下执行getchar();//释放内存free(addr);printf(释放内存\n);//阻塞去查看内存是否归还给系统getchar();return 0;
} 执行代码 我们可以通过 /proc//maps 文件查看进程的内存分布情况。我在 maps 文件通过此 1 字节的内存起始地址过滤出了内存地址的范围。 这个例子分配的内存小于 128 KB所以是通过 brk() 系统调用向堆空间申请的内存因此可以看到最右边有 [heap] 的标识。
可以看到堆空间的内存地址范围是 561e7890c000-561e7892d000这个范围大小是 132KB也就说明了 malloc(1) 实际上预分配 132K 字节的内存。 2.4 free 释放内存会归还给操作系统吗
1. 我们以上面的程序为例申请小于128K的空间我们在free(addr)结束后再使用cat /proc/%d/maps去查看内存时候还在 释放malloc(1)的内存后在执行一次cat 可以看到通过 free 释放内存后堆内存还是存在的并没有归还给操作系统。 2. 我们这次申请大于128K的内存来看看
#include stdio.h
#include stdlib.h
#include malloc.hint main()
{printf(使用cat /proc/%d/maps查看内存分配\n,getpid());//申请1字节的内存void *addr malloc(200*1024);printf(此1字节的内存起始地址%x\n, addr);printf(使用cat /proc/%d/maps查看内存分配\n,getpid());//将程序阻塞当输入任意字符时才往下执行getchar();//释放内存free(addr);printf(释放200K内存\n);getchar();return 0;
} 查看进程的内存的分布情况可以发现最右边没有 [heap] 标志说明是通过 mmap 以匿名映射的方式从文件映射区分配的匿名内存。
然后我们释放掉这个内存看看 再次查看该 200KB 内存的起始地址 可以发现已经不存在了说明归还给了操作系统。 3. 内存满了会发生什么
应用程序通过 malloc 函数申请内存的时候实际上申请的是虚拟内存此时并不会分配物理内存。
当应用程序读写了这块虚拟内存CPU 就会去访问这个虚拟内存 这时会发现这个虚拟内存没有映射到物理内存 CPU 就会产生缺页中断进程会从用户态切换到内核态并将缺页中断交给内核的 Page Fault Handler 缺页中断函数处理。
缺页中断处理函数会看是否有空闲的物理内存如果有就直接分配物理内存并建立虚拟内存与物理内存之间的映射关系。
如果没有空闲的物理内存那么内核就会开始进行回收内存的工作回收的方式主要是两种直接内存回收和后台内存回收。
后台内存回收kswapd在物理内存紧张的时候会唤醒 kswapd 内核线程来回收内存这个回收内存的过程异步的不会阻塞进程的执行。直接内存回收direct reclaim如果后台异步回收跟不上进程内存申请的速度就会开始直接回收这个回收内存的过程是同步的会阻塞进程的执行。
如果直接内存回收后空闲的物理内存仍然无法满足此次物理内存的申请那么内核就会放最后的大招了 ——触发 OOM Out of Memory机制。 OOM机制会根据算法选择一个占用物理内存较高的进程然后将其杀死以便释放内存资源如果物理内存依然不足OOM会继续杀死占用物理内存较高的进程直到释放足够的内存位置。 申请物理内存的过程如下图