操作系统内存管理相关

news2024/10/7 20:33:56

1. 虚拟内存

1.1 什么是虚拟内存

        虚拟内存是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且 把内存扩展到硬盘空间。        

1.2 为什么需要虚拟内存?

        我们了解单片机是没有操作系统的,所以每次写完代码,都需要借助工具把程序烧录进去,这样程序才能跑起来。另外,单片机的 CPU 是直接操作内存的「物理地址」

        在这种情况下,要想在内存中同时运行两个程序是不可能的。如果第一个程序在 3000 的位置写入一个新的值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,这两个程序会立刻崩溃。(也就是每次CPU都直接操作内存导致不能多进程

那么操作系统应该如何避免这种情况?

(这里关键的问题是这两个程序都引用了绝对物理地址,而这正是我们最需要避免的)

        因此我们可以把进程所使用的地址「隔离」开来,即让操作系统为每个进程分配独立的一套「虚拟地址」,人人都有,互不干涉。但是有个前提每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的。

操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。

如果程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。

于是,这里就引出了两种地址的概念:

  • 我们程序所使用的内存地址叫做虚拟内存地址Virtual Memory Address
  • 实际存在硬件里面的空间地址叫物理内存地址Physical Memory Address)。

操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,如下图所示:

1.3 如何管理虚拟地址与物理地址之间的关系?

内存分页

        分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫Page)。在 Linux 下,每一页的大小为 4KB

虚拟地址与物理地址之间通过页表来映射,在分页机制下,虚拟地址分为两部分页号页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图:

因此内存转化的步骤为:

  1. CPU将虚拟内存地址切分为页号和偏移量
  2. 根据页号在页表中查询对应的物理页号
  3. 页号加上前面的偏移量,就得到了物理内存地址

但是如果这样给每一个程序分配一个页表去管理虚拟内存的话会出现下面的问题:

        在32位系统上,虚拟内存大小约为4G(2^32),一个页的大小为4K(2^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.h>

int 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.h>

int 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会继续杀死占用物理内存较高的进程,直到释放足够的内存位置。

申请物理内存的过程如下图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1059778.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SLAM从入门到精通(用python实现机器人运动控制)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在ROS下面&#xff0c;开发的方法很多&#xff0c;可以是c&#xff0c;可以是python。大部分接口操作类的应用&#xff0c;其实都可以用python来开…

(五)激光线扫描-位移台标定

线激光属于主动测量方式,但是由于线激光的特性,我们只能通过提取激光中心线获取这一条线上的高度信息,那么要进行三维重建的话,就需要通过平移或者是旋转的方式,来让线激光扫描被测物体的完整轮廓,也就是整个表面。激光线的密度越高还原出来的物体越细腻,但由于数据量大…

计算机中丢失vcomp140.dll解决方案,可以使用这几个最新方法来修复

今天早上&#xff0c;当我打开电脑时&#xff0c;突然看到一个提示窗口&#xff0c;显示找不到 vcomp140.dll 文件。我一下子懵了&#xff0c;不知道这是怎么回事&#xff0c;也不知道如何解决这个问题。于是&#xff0c;我开始了寻找答案的旅程。 首先&#xff0c;我了解到 v…

<C++> 异常

C语言传统的处理错误的方式 传统的错误处理机制&#xff1a; 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查找对应的错误。如系统的…

Autosar诊断实战系列21-UDS连续帧(CF)数据接收代码级分析

本文框架 前言1. 长帧数据的连续帧接收2. 连续帧的处理前言 在本系列笔者将结合工作中对诊断实战部分的应用经验进一步介绍常用UDS服务的进一步探讨及开发中注意事项, Dem/Dcm/CanTp/Fim模块配置开发及注意事项,诊断与BswM/NvM关联模块的应用开发及诊断capl测试脚本开发等诊…

计算机类毕业设计选题60套!太全了!快收藏!

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

基于MDK-Keil环境如何把STM32程序直接下载到SRAM运行

1. 前言 对于 Cortex-M 内核的微控制器&#xff0c;它们都可以支持在 RAM 中执行程序&#xff0c;有些非 ARM 的微控制器是不支持的。 在内部 SRAM 执行程序&#xff0c;有基于以下几方面的原因&#xff1a; 1、所使用的设备可能具有OTP&#xff08;One-time Programmable&a…

《CPU设计实战》第四章lab3记录找bug

修bug之路 1. debug_wb_pc 一个信号一个信号找下去&#xff0c;发现ID_stage.v中load_op未赋值 assign load_op inst_lw; 代码解释 module decoder_5_32(input [ 4:0] in,output [31:0] out ); //这个循环被命名为 gen_for_dec_5_32。 genvar i; generate for (i0; i<…

c++中的动态内存管理

目录 1.内存分布 2.c语言动态内存管理 3.c动态内存管理 4.operator new 与operator delete 函数 5.定位new 6.malloc/free 与 new/delete 的区别 1.内存分布 首先我们需要了解一下数据在内存中的分布&#xff0c;请看以下代码&#xff1a; int globalVar 1; static in…

Day 04 python学习笔记

Python数据容器 元组 元组的声明 变量名称&#xff08;元素1&#xff0c;元素2&#xff0c;元素3&#xff0c;元素4…….&#xff09; &#xff08;元素类型可以不同&#xff09; eg: tuple_01 ("hello", 1, 2,-20,[11,22,33]) print(type(tuple_01))结果&#x…

基于Java的4S店汽车商城系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

C++17中头文件filesystem的使用

C17引入了std::filesystem库(文件系统库, filesystem library)&#xff0c;相关类及函数的声明在头文件filesystem中&#xff0c;命名空间为std::filesystem。 1.path类&#xff1a;文件路径相关操作&#xff0c;如指定的路径是否存在等&#xff0c;其介绍参见&#xff1a;http…

STM32复习笔记(四):看门狗

目录 &#xff08;一&#xff09;简介 &#xff08;二&#xff09;IWDG IWDG的CUBEMX工程配置 IWDG相关函数&#xff08;非常少&#xff0c;所以直接贴上来&#xff09;&#xff1a; &#xff08;三&#xff09;WWDG &#xff08;一&#xff09;简介 看门狗分为独立看门…

【Java】微服务——Nacos注册中心

目录 1.Nacos快速入门1.1.服务注册到nacos1&#xff09;引入依赖2&#xff09;配置nacos地址3&#xff09;重启 2.服务分级存储模型2.1.给user-service配置集群2.2.同集群优先的负载均衡 3.权重配置4.环境隔离4.1.创建namespace4.2.给微服务配置namespace 5.Nacos与Eureka的区别…

[C++随想录] 优先级队列的模拟实现

优先级队列的模拟实现 底层结构初始化向下调整 && 向上调整push && poptop && empty && size源码 底层结构 namespace muyu {template <class T, class Continer std::vector<T>, class Compare less<T> >class priority_…

C#停车场管理系统

目录 一、绪论1.1内容简介及意义1.2开发工具及技术介绍 二、总体设计2.1系统总体架构2.2登录模块总体设计2.3主界面模块总体设计2.4停车证管理模块总体设计2.5停车位管理模块总体设计2.6员工管理模块总体设计2.7其他模块总体设计 三、详细设计3.1登录模块设计3.2主界面模块设计…

力扣:119. 杨辉三角 II(Python3)

题目&#xff1a; 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09…

【Linux】ping命令详解

目录 一、ping概述 二、Ping用法 三、ping参数详解 四、使用 五、Wireshark抓取ICMP请求应答消息 一、ping概述 ping 命令用于测试与目标主机之间的连接。它向目标主机发送一个ICMP&#xff08;Internet Control Message Protocol&#xff09;Internet控制报文协议回显请求…

fetch前后端通信

fetch 前后端通信&#xff1a;ajax。 4步走&#xff1a; let xhrnew XMLHttpRequest() xhr.open(get,http://xxx) xhr.onreadystatechange xhr.send() 常用库&#xff1a; jQuery -> 回调地狱 axios fetch&#xff1a;网络获取资源 与Ajax的不同&#xff1a; 网络故障或请求…

激光雷达中实现F-P标准具高热稳定性的帕尔贴精密温控解决方案

摘要&#xff1a;法布里-珀罗标准具作为一种具有高温度敏感性的精密干涉分光器件&#xff0c;在具体应用中对热稳定性具有很高的要求&#xff0c;如温度波动不能超过0.01℃&#xff0c;为此本文提出了相应的高精度恒温控制解决方案。解决方案具体针对温度控制精度和温度均匀性控…