操作系统——内存管理(学习笔记)

文章详细介绍了操作系统如何通过虚拟内存机制管理内存,包括分页、分段和段页式管理,讨论了Linux中的内存划分、malloc函数的工作方式以及内存回收策略。此外,还阐述了Linux文件系统的结构,如索引节点(inode)、目录项、文件存储方式和不同类型的文件I/O操作。
摘要由CSDN通过智能技术生成

前言

  本文章为操作系统内存管理篇的学习笔记,文章中的图片,文字部分引用小林coding阿秀的学习笔记知识星球如有侵权,请联系删除。

虚拟内存

  操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来( 通过CPU 芯片中的内存管理单元(MMU)的映射关系转换的)可以使得进程对运行内存超过物理内存大小,这样程序访问虚拟地址的时候操作系统会转换成不同的物理地址,就不会产生冲突(如果有冲突运行中的程序会崩溃,单片机没有操作系统所以单片机只能运行一个进程),这是通过不同进程中都会有自己的页表,所以每个进程的虚拟内存空间都是相互独立的,防止了多进程冲突的地址问题,程序使用的内存是虚拟内存地址,硬件空间中的地址是物理内存地址。
  操作系统是根据内存分段和内存分页来管理虚拟地址和物理地址之间的关系的。
  内存分段:分段机制下虚拟地址由段选择因子和段内偏移量组成,段选择子就保存在段寄存器里面。里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。分段的好处就是能够产生连续的内存空间;但是内存分段存在着一些问题比如内存碎片(会出现外部内存碎片,因为段的长度不固定,多个段未必能使用完所有的内存空间,可以使用内存交换解决)和内存交换效率低(因为有外部内存碎片所以会频繁的进行内存交换)。
  内存分页:分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小,把连续并且尺寸固定的内存空间叫做页,虚拟地址和物理地址之间通过页表来映射,页表是存储在内存。采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。但是页的大小是固定的,所以有时候程序不足一页,会出现内部内存碎片的现象(内存分段不会有内部内存碎片);如果内存不够会把最近不使用的内存页面释放掉,写进磁盘称为换出,需要的时候再加载进来称为换入,所以每次换入少数几个页内存交换的效率就会比较高,并且只有在虚拟内存和物理内存映射时(程序运行中,需要用到对应虚拟内存页里面的指令和数据时)才进行加载,可以进一步减小内存交换的频率。分页机制下是通过页号和页内偏移来实现虚拟地址和物理地址的映射的。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址。但是使用单页的模式会占用大量内存空间,所以引入了多级页表减小内存空间的占用(运用了局部性原理去分配内存,未使用的内存不需要分配)。
  段页式内存管理:先将程序根据分段机制划分为多个段,再把每段分为多个页,地址结构由段号,段内地址,和页内位移组成。访问时先通过段表得到页表起始地址,再访问页表得到物理页号,将物理页号和页内位移组合得到物理地址。
  Linux中的内存划分:在Linux中每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。在用户空间中从低到高总共有6种不同的内存段,分别是代码段(放可执行的二进制代码),数据段(包括已初始化的静态常量和全局变量),BSS段(未初始化的静态变量和全局变量),堆段(动态分配的内存,从低地址开始增长),文件映射段(动态库、共享内存),栈段(局部变量和函数调用的上下文,栈是固定大小一般8MB),在代码段下面还有文件保留区(因为较小数值的地址被认为不合法,比如我们附指针NULL)。
  malloc函数:malloc申请的是虚拟内存,并不会分配物理内存,当程序读到这块虚拟内存,CPU才会去访问,这时会发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler (缺页中断函数)处理。malloc 通过 brk() 方式(用户分配的内存小于128KB,分配在堆段)申请的内存(均为虚拟内存),free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;malloc 通过 mmap() 方式(用户分配的内存大于128KB,分配在文件映射段)申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放,使用mmap()方式会发生缺页中断,和运行态的切换,会导致CPU的消耗变大。
  内存回收:后台内存回收,在物理内存紧张的时候,会唤醒 kswapd 内核线程来回收内存,这个回收内存的过程异步的,不会阻塞进程的执行。
  直接内存回收:如果后台异步回收跟不上进程内存申请的速度,就会开始直接回收,这个回收内存的过程是同步的,会阻塞进程的执行。
  OOM机制:选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存依然不足,OOM Killer 会继续杀死占用物理内存较高的进程,直到释放足够的内存位置。
  回收的文件主要有两种分别是文件页,文件页分为干净页就是可以直接回收和脏页,脏页是指被应用程序修改过,并且暂时还没写入磁盘的数据,需要先写回磁盘后再释放内存;还有一种是匿名页是指没有实际载体,比如堆栈数据,这部分数据可能还要再次访问,所以会通过Linux的Swap机制(触发场景有两个内存不足或内存闲置),Swap 会把不常访问的内存先写到磁盘中,然后释放这些内存。
  页面置换算法:当出现缺页异常,需调入新页面而内存已满时,选择被置换的物理页面,有最佳页面置换算法:置换在未来最长时间不访问的页面;先进先出置换算法:选择在内存驻留时间很长的页面进行置换;最近最久未使用的置换算法:选择最长时间没有被访问的页面进行置换;时钟页面置换算法:把所有的页面都保存在一个类似钟面的「环形链表」中,一个表针指向最老的页面。当发生缺页中断时,算法首先检查表针指向的页面:如果它的访问位位是 0 就淘汰该页面,并把新的页面插入这个位置,然后把表针前移一个位置;如果访问位是 1 就清除访问位,并把表针前移一个位置,重复这个过程直到找到了一个访问位为 0 的页面为止;最不常用算法:当发生缺页中断时,选择「访问次数」最少的那个页面,并将其淘汰。

文件系统

  Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),它们主要用来记录文件的元信息和目录层次结构。
  索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点是文件的唯一标识,它们之间一一对应,也同样都会被存储在硬盘中,所以索引节点同样占用磁盘空间。
  目录项用来记录文件的名字,索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。
  目录项要与目录区分开来,目录是一个文件,持久化存储在磁盘,而目录项是内核的一个数据结构,缓存在内存。
  文件系统读取文件的时候把多个扇区组成一个逻辑块,每次读写的最小单位就是逻辑块(数据块),文件系统的基本操作单位是数据块。
  文件的存储:文件数据在磁盘的存储主要有两种方式,分别是连续空间存放方式,非连续空间存放方式,非连续空间存放方式又可以分为「链表方式」和「索引方式」。连续空间存放:文件存放在磁盘「连续的」物理空间中,文件的数据都是紧密相连,读写效率很高,因为一次磁盘寻道就可以读出整个文件。但是存放的前提必须要知道文件的大小,所以文件头里需要指定「起始块的位置」和「长度」;但是会有磁盘空间碎片和文件长度不易扩展的缺陷。非连续空间存放方式中链表方式指:存放是离散的,不用连续的,于是就可以消除磁盘碎片,可大大提高磁盘空间的利用率,同时文件的长度可以动态扩展,链表可分为「隐式链表」和「显式链接」两种。这种方式的缺点就是不能有效支持直接访问,而索引的方式可以解决这个问题。索引的实现是为每个文件创建一个「索引数据块」,里面存放的是指向文件数据块的指针列表,文件头需要包含指向「索引数据块」的指针。这种方式的优点是文件的创建、增大、缩小很方便,不会有碎片的问题,支持顺序读写和随机读写。缺陷就是存储索引所带来的开销。
  目录的存储:普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息。在目录文件的块中,最简单的保存格式就是列表,但是如果目录中文件很多,存储格式就会改成哈希表。
  软链接和硬链接:可以用软连接或是硬链接的方式来给某个文件系统取个别名,硬链接是多个目录项中的「索引节点」指向一个文件,但是索引节点不可能跨越文件系统的,所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。
  文件I/O:根据「是否利用标准库缓冲」,可以把文件 I/O 分为缓冲 I/O非缓冲 I/O:缓冲 I/O,利用的是标准库的缓存实现文件的加速访问,而标准库再通过系统调用访问文件。非缓冲 I/O,直接通过系统调用访问文件,不经过标准库缓存。根据是「否利用操作系统的缓存」,可以把文件 I/O 分为直接 I/O非直接 I/O,直接 I/O,不会发生内核缓存和用户程序之间数据复制,而是直接经过文件系统访问磁盘。非直接 I/O,读操作时,数据从内核缓存中拷贝给用户程序,写操作时,数据从用户程序拷贝给内核缓存,再由内核决定什么时候写入数据到磁盘。阻塞I/O和非阻塞I/O阻塞I/O等待的是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程。非阻塞I/O在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区。但是在轮询的过程中程序只是在循环等待,所以有了I/O多路复用技术,它是通过 I/O 事件分发,当内核数据准备好时,再以事件通知应用程序进行操作。同步I/O和异步I/O:同步I/O可能会在数据的准备过程或者数据从内核空间拷贝到用户进程缓冲区的过程阻塞,而异步I/O都不会阻塞。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值