【操作系统原理】—— Linux内存管理

简介: 【操作系统原理】—— Linux内存管理

实验原理与内容

     操作系统的发展使得系统完成了大部分的内存管理工作,对于程序员而言,这些内存管理的过程是完全透明不可见的。因此,程序员开发时从不关心系统如何为自己分配内存,而且永远认为系统可以分配给程序所需的内存。在程序开发时,程序员真正需要做的就是:申请内存、使用内存、释放内存。其它一概无需过问。

内存管理

     Linux 内存管理是操作系统对计算机内存进行分配、保护和回收的一系列机制。这些机制确保了程序能够访问到合适的内存空间,同时防止程序越界访问或滥用系统资源。 以下是 Linux 内存管理的一些关键方面:

     虚拟内存: Linux 使用虚拟内存机制,将物理内存和进程地址空间分隔开来。每个进程都有自己的虚拟地址空间,而不必担心物理内存的具体位置。这允许多个进程共享相同的代码,同时保持各自的独立地址空间。

     分页机制: 内存被划分为固定大小的页面,通常是4KB。虚拟地址空间和物理地址空间被分割成相同大小的页面,这样就可以按页进行映射。分页机制有助于提高内存的利用率和管理效率。

     页表: 为了实现虚拟地址到物理地址的映射,Linux 使用页表。页表记录了每个页面的虚拟地址到物理地址的映射关系。当进程访问某个虚拟地址时,操作系统通过页表查找相应的物理地址。

     分段机制: 除了分页,Linux 还使用分段机制。这将虚拟地址空间分为多个段,每个段都有不同的权限。例如,代码段、数据段、堆、栈等。

     内存保护: Linux 使用页面保护位(read, write, execute)来控制对内存的访问权限。这有助于防止程序越界访问和滥用内存。

     内存分配器: Linux 提供了多种内存分配器,如 malloc 和 free 函数使用的 glibc 库中的 ptmalloc。此外,Linux 内核也提供了 kmalloc 和 kfree 等用于内核空间的内存分配函数。

     交换空间: 为了支持虚拟内存,Linux 使用了交换空间(swap space),它是物理磁盘上的一部分,用于存储不常用的页面。当物理内存不足时,操作系统可以将不常用的页面交换到磁盘上,以释放物理内存。

     OOM Killer: 当系统内存不足时,Linux 内核可能会触发 Out Of Memory Killer(OOM Killer),它会杀死某些进程以释放内存。OOM Killer的目标是维护系统的稳定性,但它也可能导致未预期的程序中断。

     这些是 Linux 内存管理的基本原理和机制。内存管理在操作系统中扮演着至关重要的角色,它直接影响到系统的性能、稳定性和安全性。

实验要求

     练习一:用 vim 编辑创建下列文件,用 GCC 编译工具,生成可调试的可执行文件,记录并分析执行结果,分析遇到的问题和解决方法。

     练习二:用 vim 编辑创建下列文件,用 GCC 编译工具,生成可调试的可执行文件,记录并分析执行结果。

     练习三:用 vim 编辑创建下列文件,用 GCC 编译工具,生成可调试的可执行文件,记录并分析执行结果。

     改编实验中的程序,并运行出结果。

在虚拟机中编写好以下程序:

#include <stdio.h> 
#include <string.h> 
#include <malloc.h>
int main(void)
{
  char *str;
  /* 为字符串申请分配一块内存 */
  if ((str = (char *) malloc(10)) == NULL)
  {
      printf("Not enough memory to allocate buffer\n");
      return(1); /* 若失败则结束程序 */
  }
  /* 拷贝字符串“Hello”到已分配的内存空间 */
  strcpy(str, "Hello");
  /* 显示该字符串 */
  printf("String is %s\n", str);
  /* 内存使用完毕,释放它 */
  free(str);
  return 0; 
}

     调试过后得出的结果截图如下:

#include <stdio.h>
#include <malloc.h>
#include <string.h>
int main(void)
{
  char *str; /* 为字符串申请分配一块内存 */
  if ((str = (char *) malloc(10)) == NULL)
  {
    printf("Not enough memory to allocate buffer\n");
    return(1); /* 若失败则结束程序 */
  } /* 复制 "Hello" 字符串到分配到的内存 */
  strcpy(str, "Hello"); /* 打印出字符串和其所在的地址 */
  printf("String is %s\n Address is %p\n", str, str); /* 重分配刚才申请到的内存空间, 申请 增大一倍 */
  if ((str = (char *) realloc(str, 20)) == NULL) { 
    printf("Not enough memory to allocate
    buffer\n"); return(1); /* 监测申请结果, 若失败则结束程序,养成这个好习惯 */ 
  } /* 打印出重分配后的地址 */
  printf("String is %s\n New address is %p\n", str, str); /* 释放内存空间 */ free(str);
  return 0;
}

调试过后得出结果截图如下:

#include <stdio.h>
#include <alloca.h>
void test(int a)
{
  char *newstack;/*  申请一块内存空间 */
  newstack = (char *) alloca(len);
  if (newstack)/*  若成功,则打印出空间大小和起始地址 */
    printf("Alloca(0x%X) returned %p\n”, len, newstack);
  else/*  失败则报告错误, 我们是做实验, 目前无需退出 */
  printf("Alloca(0x%X) failed\n",len);
} /*  函数退出, 内存自动释放, 无需干预 */
void main()
{
/*  申请一块 256 字节大小的内存空间,观察输出情况 */
  test(256);
/*  再申请一块更大内存空间,观察输出情况 */
  test(16384);
}

调试结果截图如下:

     对上述程序改写, 将程序中的“Hello”改为“Myname is 自己的名字!”,调试并观察结果。再将程序中的“10”和“20”分别改成“20”和“40”再进行调试,观察结果,并对比说明原因。

实验设备与软件环境

安装环境:分为软件环境和硬件环境

硬件环境:内存ddr3 4G及以上的x86架构主机一部

系统环境:windows 、linux或者mac os x

软件环境:运行vmware或者virtualbox

软件环境:Ubuntu操作系统

实验内容

练习一:

     用 vim 编辑创建下列文件,用 GCC 编译工具,生成可调试的可执行文件,记录并分析执行结果。

运行代码及截图:

结果分析:

     练习一的代码通过malloc(10)分配10字节的内存,通过(char*)进行强制类型转换,因为malloc函数的返回类型是void*,需要强制转换成需要的类型(char)。如果成功为字符串申请分配一块内存,程序则通过strcpy()函数拷贝字符串"Hello"到已分配的内存空间里面,接着程序进行输出,最后通过free函数释放内存空间。

如果没有成功为字符串申请分配一块内存,程序会输出报错信息"Not enough memory to allocate buffer"并结束程序。

改编实验中的程序,并运行出结果。

改编内容:将程序中的“Hello”改为“My name is 自己的名字!”

改编后的程序的运行代码及截图:

结果分析:

     改编后的练习一的代码通过malloc(10)分配10字节的内存,通过(char*)进行强制类型转换,因为malloc函数的返回类型是void*,需要强制转换成需要的类型(char)。如果成功为字符串申请分配一块内存,程序则通过strcpy()函数拷贝字符串"My name is 姓名!"到已分配的内存空间里面,接着程序进行输出,最后通过free函数释放内存空间。

     如果没有成功为字符串申请分配一块内存,程序会输出报错信息"Not enough memory to allocate buffer"并结束程序。

     修改前后的程序不同的地方就是strcpy()函数拷贝的内容不同。


练习二:

     用 vim 编辑创建下列文件,用 GCC 编译工具,生成可调试的可执行文件,记录并分析执行结果。

运行代码及截图:

结果分析:

     练习二的代码通过malloc(10)分配10字节的内存,通过(char*)进行强制类型转换,因为malloc函数的返回类型是void*,需要强制转换成需要的类型(char)。如果成功为字符串申请分配一块内存,程序则通过strcpy()函数拷贝字符串"Hello"到已分配的内存空间里面,接着程序进行输出,打印出字符串和其所在的地址。

如果没有成功为字符串申请分配一块内存,程序会输出报错信息"Not enough memory to allocate buffer"并结束程序。

     接着通过realloc函数将数组扩容,对于刚才申请到的内存空间进行重分配,申请增大一倍的内存容量(分配20字节的内存),通过检测判断是否重分配成功,若失败则结束程序,反之则打印出字符串和重分配后的地址,最后通过free函数释放内存空间。

     值得注意的是通过重分配后的地址和重分配前的地址是一样的。这是因为如果当前连续内存块足够realloc进行分配的话,只是将str所指向的空间扩大,并返回它的指针地址。 这个时候重新分配前后指向的地址是一样的。

改编实验中的程序,并运行出结果。

改编内容1:将程序中的“10”和“20”分别改成“20”和“40”再进行调试

运行代码及截图:

结果分析:

     改编后的练习二的代码和之前不同的地方在于程序则通过strcpy()函数拷贝字符串"Hello"变为了"My name is 姓名!"。

     但是值得注意的是通过重分配后的地址和重分配前的地址还是一样的。,只是和上一次的地址不一样,因为每次分配的地址都是随机的。

改编内容2:将程序中的“10”和“20”分别改成“20”和“40”再进行调试

运行代码及截图:

结果分析:

     再次改编后的练习二的代码通过malloc(10)分配20字节的内存,通过(char*)进行强制类型转换,因为malloc函数的返回类型是void*,需要强制转换成需要的类型(char)。如果成功为字符串申请分配一块内存,程序则通过strcpy()函数拷贝字符串"My name is 姓名!"到已分配的内存空间里面,接着程序进行输出,打印出字符串和其所在的地址。

     如果没有成功为字符串申请分配一块内存,程序会输出报错信息"Not enough memory to allocate buffer"并结束程序。

     接着通过realloc函数将数组扩容,对于刚才申请到的内存空间进行重分配,申请增大一倍的内存容量(分配40字节的内存),通过检测判断是否重分配成功,若失败则结束程序,反之则打印出字符串和重分配后的地址,最后通过free函数释放内存空间。

     值得注意的我们通过对比可以发现修改后的通过重分配后的地址和重分配前的地址是不一样的。这是因为如果当前连续内存块不够长度的时候,需要再找一个足够长的地方来分配一块新的内存,并将原本指向的内容copy到新地址,返回新地址。并将str所指向的原来的内存空间删除。

     这样也就是说realloc()函数有时候会产生一个新的内存地址,有的时候不会。所以在分配完成后我们需要判断下空间是否等于可以进行再分配,并做相应的处理。


练习三:

     用 vim 编辑创建下列文件,用 GCC 编译工具,生成可调试的可执行文件,记录并分析执行结果。

运行代码及截图:

结果分析:

     练习三的代码写了一个void test(int a)函数,通过这个函数可以申请一块内存空间,如果成功申请到内存空间,则打印出空间大小和起始地址,如果申请失败则报告错误。

     在主函数我们申请了两块内存不同大小的空间,一块256字节,一块更大的16384字节。

     通过运行结果我们可以看到两次申请都成功分配了空间,而且对应的空间大小和起始地址都不同。和预期效果一致。

问题与解决方法

问题一:

     编译练习三的时候出现报错,下面为报错信息:

解决方法:

     通过检查代码发现问题为写成了中文的引号,以及练习所给的代码没有对于len进行定义。通过修改后再次运行便没有问题了。

问题二:

     为什么通过重分配后的地址和重分配前的地址有的时候是一样的,有的时候是不一样的。

解决方法:

     如果当前连续内存块足够realloc进行分配的话,只是将str所指向的空间扩大,并返回它的指针地址。 这个时候重新分配前后指向的地址是一样的。

     如果当前连续内存块不够长度的时候,需要再找一个足够长的地方来分配一块新的内存,并将原本指向的内容copy到新地址,返回新地址。并将str所指向的原来的内存空间删除。

     这样也就是说realloc()函数有时候会产生一个新的内存地址,有的时候不会。所以在分配完成后我们需要判断下空间是否等于可以进行再分配,并做相应的处理。


目录
相关文章
|
机器学习/深度学习 存储 算法
NoProp:无需反向传播,基于去噪原理的非全局梯度传播神经网络训练,可大幅降低内存消耗
反向传播算法虽是深度学习基石,但面临内存消耗大和并行扩展受限的问题。近期,牛津大学等机构提出NoProp方法,通过扩散模型概念,将训练重塑为分层去噪任务,无需全局前向或反向传播。NoProp包含三种变体(DT、CT、FM),具备低内存占用与高效训练优势,在CIFAR-10等数据集上达到与传统方法相当的性能。其层间解耦特性支持分布式并行训练,为无梯度深度学习提供了新方向。
1011 1
NoProp:无需反向传播,基于去噪原理的非全局梯度传播神经网络训练,可大幅降低内存消耗
|
Ubuntu 物联网 Linux
从零安装一个Linux操作系统几种方法,以Ubuntu18.04为例
一切就绪后,我们就可以安装操作系统了。当系统通过优盘引导起来之后,我们就可以看到跟虚拟机中一样的安装向导了。之后,大家按照虚拟机中的顺序安装即可。 好了,今天主要介绍了Ubuntu Server版操作系统的安装过程,关于如何使用该操作系统,及操作系统更深层的原理,还请关注本号及相关圈子。
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
431 0
|
12月前
|
缓存 监控 Linux
Linux内存问题排查命令详解
Linux服务器卡顿?可能是内存问题。掌握free、vmstat、sar三大命令,快速排查内存使用情况。free查看实时内存,vmstat诊断系统整体性能瓶颈,sar实现长期监控,三者结合,高效定位并解决内存问题。
1057 0
Linux内存问题排查命令详解
|
Ubuntu Unix Linux
操作系统的最强入门科普(Unix/Linux篇)
下期文章,小枣君会重点聊聊Windows和macOS那条线。敬请关注! 如果大家觉得文章不错,还请帮忙多多转发!谢谢!
|
缓存 Linux 数据安全/隐私保护
Linux环境下如何通过手动调用drop_caches命令释放内存
总的来说,记录住“drop_caches” 命令并理解其含义,可以让你在日常使用Linux的过程中更加娴熟和自如。
2085 23
|
Java 关系型数据库 MySQL
在Linux操作系统上设置JDK、Tomcat、MySQL以及J2EE后端接口的部署步骤
让我们总结一下,给你的Linux操作系统装备上最强的军队,需要先后装备好JDK的弓箭,布置好Tomcat的阵地,再把MySQL的物资原料准备好,最后部署好J2EE攻城车,那就准备好进军吧,你的Linux军团,无人可挡!
686 18
|
监控 Ubuntu Linux
什么Linux,Linux内核及Linux操作系统
上面只是简单的介绍了一下Linux操作系统的几个核心组件,其实Linux的整体架构要复杂的多。单纯从Linux内核的角度,它要管理CPU、内存、网卡、硬盘和输入输出等设备,因此内核本身分为进程调度,内存管理,虚拟文件系统,网络接口等4个核心子系统。
1177 0
|
Unix 物联网 Linux
都什么年代了,你还不懂啥是Linux操作系统
至于华为鸿蒙操作系统是不是独树一帜,这个留给各位阅读本文的网友们来讨论
656 0
|
Web App开发 缓存 Rust