admin管理员组

文章数量:1579915

文章目录

  • 一、操作系统简介
    • 思维导图
    • 1.什么是操作系统
    • 2.操作系统结构
      • 内核
    • 3.计算机的启动
    • 4.系统调用
  • 二、内核深入
    • 操作系统篇2-内核深入
      • 内核(kernel)
      • 1.基本介绍
      • 2.内核分类
        • 单内核
        • 微内核
        • 混合内核
        • 外内核
        • 单内核与微内核的比较
      • 3.内核优点
        • 抽象隐藏
        • 源代码管理
        • 并行开发
        • 代码覆盖分析
        • 大量信息
  • 三、内存管理
      • 1、虚拟内存
      • 2、内存分段
      • 3、内存分页
      • 4、简单分页
      • 5、多级页表
      • 6、页表缓存
      • Linux内存管理
  • 四、线程和进程
      • 1、进程
        • 1)进程的状态
        • 2)五状态模型
        • 3)七状态模型
        • 4)进程的控制结构
        • 5)进程的切换
        • 6)线程
        • 7)线程的上下文切换
        • 8)进程调度
        • 9)什么时候调度进程
      • 2、进程调度算法
        • 1)先来先服务
        • 2)时间片轮转调度
        • 3)最短作业优先
        • 4)最短剩余时间优先
        • 5)优先级调度
        • 6)多级反馈队列调度
        • 4)进程间通信
        • 5)管道
        • 6)消息队列
        • 7)共享内存
        • 8)信号量
        • 9)信号
        • 10)Socket
      • 3、总结
  • 五、文件间的通信
      • 1、文件系统的功能规划
        • 1)文件的用户视角
      • 2、文件系统的基本组成
        • 1)一切皆文件
        • 2)文件数据在磁盘的存储方式
        • 3)虚拟文件系统
      • 3、文件的物理结构
        • 1)文件块
        • 2)文件分配方式
        • 3)连续分配
      • 4、非连续空间存放方式
        • 1)链式分配
        • 2)隐式链接
        • 3)显式链接
        • 4)索引分配
        • 5)空闲空间的管理
        • 6)文件系统的结构
      • 5、目录的存储
  • 六、设备管理 & 网络系统
      • 1、设备控制器
      • 2、I/O控制方式
      • 3、设备驱动程序
      • 4、通用块层
      • 5、存储系统I/O软件分层
      • 6、键盘敲入字母时,期间发生了什么?
      • 7、网络系统
      • 8、发送数据包
      • 9、零拷贝
        • 为什么要有DMA技术
      • 10、传统的文件传输有多糟糕?
      • 11、如何优化文件传输的性能?
      • 11、如何实现零拷贝
      • 12、sendfile
      • 13、PageCache有什么作用?
      • 14、大文件传输用什么方式实现?

一、操作系统简介

思维导图

1.什么是操作系统

现代计算机系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各种输入输出设备构成。然而,程序员不会直接和这些硬件打交道,在硬件的基础之上,计算机安装了一层软件,这层软件能够通过响应用户输入的指令达到控制硬件的效果,从而满足用户需求,这种软件称之为操作系统,它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型。(说白了就是能直接与硬件打交道而让用户能方便使用电脑的一种软件 )

我们一般常见的操作系统主要有Windows、Linux、FreeBSD或OsX,这种带有图形界面的操作系统被称为图形用户界面(Graphical User Interface,GUI),而基于文本、命令行的通常称为Shel1。

操作系统简化图如上,最下面的是硬件,在硬件之上是软件。驱动是从操作系统中细化出来的,操作系统通过驱动和硬件进行交互。大部分计算机有两种运行模式:

内核态(也称为管态和核心态)和用户态,操作系统运行在内核态中,具有硬件的访问权,可以执行机器能够运行的任何指令。软件的其余部分运行在用户态下。

2.操作系统结构

Windows和Linux可以说是我们比较常见的两款操作系统的。Windows基本占领了电脑时代的市场,商业上取得了很大成功,但是它并不开源,所以要想接触源码得加入Windows的开发团队中。对于服务器使用的操作系统基本上都是Linux,而且内核源码也是开源的,任何人都可以下载,并增加自己的改动或功能,Linux最大的魅力在于,全世界有非常多的技术大佬为它贡献代码。这两个操作系统各有千秋,不分伯仲。操作系统核心的东西就是内核

内核

计算机是由各种外部硬件设备组成的,比如内存、CPU、硬盘等,如果每个应用都要和这些硬件设备对接通信协议,那这样太累了。所以,这个中间人就由内核来负责,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。

内核能力

现代操作系统,内核一般会提供4个基本能力:

●管理进程、线程,决定哪个进程、线程使用CPU,也就是进程调度的能力;

(PS:进程一般指各个打开的应用,而线程是指打开应用里面的各个页面,如Google浏览器运行是一个进程,而Google浏览器里的各个页面则是线程)

●管理内存,决定内存的分配和回收,也就是内存管理的能力;

●管理硬件设备,为进程与硬件设备之间提供通信能力,也就是硬件通信能力;

●提供系统调用,如果应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。

内核具有很高的权限,可以控制CPU、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,把内存分成了两个区域:

●内核空间,这个内存空间只有内核程序可以访问;

●用户空间,这个内存空间专门给应用程序使用;

用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。

因此,当程序使用用户空间时,我们常说该程序在用户态执行,而当程序使用内核空间时,程序则在内核态执行。

应用程序如果需要进入内核空间,就需要通过「系统调用」,下面来看看系统调用的过程:

内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后,CPU会中断当前在执行的用户程序,转而跳转到中断

处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把CPU执行权限交回给用户程序,回到用户态继续工作。

Liux内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等。

当今Windows7、Windows10使用的内核叫Nindows NT,NT全称叫New Technology。

(PS:这里大家需要知道一点就是Liux和window的执行文件不一样,因为他们的内核、机制不一样。)

加油ヾ(◍°∇°◍)ノ゙ 加油ヾ(◍°∇°◍)ノ゙你已经看到一半了!坚持!

3.计算机的启动

主板一般为矩形电路板,上面安装了组成计算机的主要电路系统,一般有BIOS芯片、I/O控制芯片。在主板上,有一个东西叫ROM (Read Only Memory,只读存储器)。这和咱们平常说的内存RAM (Random Access Memory,随机存取存储器)不同。

咱们平时买的内存条是可读可写的,这样才能保存计算结果。而ROM是只读的,上面早就固化了一-些初始化的程序, 也就是BIOS(Basic Input and Output System,基本输入输出系统)。

如果你自己安装过操作系统,刚启动的时候,按某个组合键,显示器会弹出一个蓝色的界面。能够调整启动顺序的系统,就是我说的BIOS,然后我们就可以先执行它。

然后操作系统会询问BIOS获取配置信息。对于每个设备来说,会检查是否有设备驱动程序。如果没有,则会向用户询问是否需要插入CD-ROM 驱动(由设备制造

商提供)或者从Internet上下载。一旦有了设备驱动程序,操作系统会把它们加载到内核中,然后初始化表,创建所需的后台进程,并启动登录程序或GUI。

4.系统调用

你会发现,一个项目要想顺畅进行,需要用到公司的各种资源,比如说盖个公章、开个证明、申请个会议室.打印个材料等等。这里有个两难的权衡,一方面,资源毕竟是有限的,甚至是涉及机密的,不能由项目组滥职滥用:另方面,就是效率,咱是一个私营企业,保证项目申请资源的时候只跑一次,这样才能比较高效。

为了平衡这一点, 一方面涉及核心权限的资源,还是应该被公司严格把控,审批了才能用;另外一方面,为了提高效率,最好有个统一的办事大厅, 明文列出提供哪些服务,谁需要可以来申请,然后就会有回应。在操作系统中,也有同样的问题,例如多个进程都要往打印机上打印文件,如果随便乱打印进程,就会出现同样一张纸, 第一行是A进程输出的文字,第二行是B进程输出的文字,乱套了。所以,打印机的直接操作是放在操作系统内核里面的,进程不能随便操作。但是操作系统也提供一个办事大厅, 也就是系统调用(System Cl).系统调用也能列出来提供哪些接口可以调用,进程有需要的时候就可以去调用。这其中,立项是办事大厅提供的关键服务之一。同样,任何一个程序要想运行起来,就需要调用系统调用,创建进程。如果一个进程在用户态下运行用户程序,例如从文件中读取数据。那么如果想要把控制权交给操作系统控制,那么必须执行一个异常指令或者 系统调用指令。操作系统紧接着需要参数检查找出所需要的调用进程。

二、内核深入

操作系统篇2-内核深入

内核(kernel)

发源时间1991年10月

内核是操作系统最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。内核的分类可分为单内核双内核以及微内核。严格地说,内核并不是计算机系统中必要的组成部分。

1.基本介绍

内核,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程内存设备驱动程序文件网络系统,决定着系统的性能和稳定性。

现代操作设计中,为减少系统本身的开销,往往将一些与 紧密相关的(如中断处理程序、设备驱动程序等)、基本的、公共的、运行频率较高的模块(如时钟管理、进程调度等)以及关键性数据结构独立开来,使之常驻内存,并对他们进行保护。通常把这一部分称之为操作系统的内核。

内核体系结构如下图:

2.内核分类

单内核

单内核(Monolithic kernel),是个很大的进程。它的内部又能够被分为若干模块(或是层次或其他)。但是在运行的时候,它是个单独的二进制大映象。(二进制文件是可以直接执行的文件,映像文件是需要仿真的文件。)其模块间的通讯是通过直接调用其他模块中的函数实现的,而不是消息传递。

单内核结构在硬件之上定义了一个高阶的抽象界面,应用一组原语(或者叫系统调用来实现操作系统的功能,例如进程管理,文件系统,和存储管理等等,这些功能由多个运行在核心态的模块来完成。

尽管每一个模块都是单独地服务这些操作,内核代码是高度集成的,而且难以编写正确。因为所有的模块都在同一个内核空间上运行,一个很小的bug都会使整个系统崩溃。然而,如果开发顺利,单内核结构就可以从运行效率上得到好处。

很多现代的单内核结构内核,如Linux和FreeBSD内核,能够在运行时将模块调入执行,这就可以使扩充内核的功能变得更简单,也可以使内核的核心部分变得更简洁。

微内核

微内核(Microkernelkernel)结构由一个非常简单的硬件抽象层和一组比较关键的原语或系统调用组成,这些原语仅仅包括了建立一个系统必需的几个部分,如线程管理,地址空间和进程空间通信等。

微核的目标是将系统服务的实现和系统的基本操作规则分离开来。例如,进程的输入/输出锁定服务可以由运行在微核之外的一个服务组件来提供。这些非常模块化的用户态服务器用于完成操作系统中比较高级的操作,这样的设计使内核中最核心的部分的设计更简单。一个服务组件的失效并不会导致整个系统的崩溃,内核需要做的,仅仅是重新启动这个组件,而不必影响其它的部分。

微内核将许多OS服务**(操作系统 Operating System,简称OS)**放入分离的进程,如文件系统,设备驱动程序,而进程通过消息传递调用OS服务。微内核结构必然是多线程的,第一代微内核,在核心提供了较多的服务,因此被称为’胖微内核’,它的典型代表是MACH。它既是GNU HURD也是APPLE SERVER OS的核心,可以说,蒸蒸日上.第二代为微内核只提供最基本的OS服务,典型的OS是QNX(QNX是一个微内核实时操作系统,其核心仅提供4种服务: 进程调度 、 进程间通信 、底层 网络通信 和 中断处理 ,其进程在独立的 地址空间 运行。 所有其它OS服务,都实现为协作的用户进程,因此QNX核心非常小巧,QNX4.x大约为12Kb,而且运行速度极快),QNX在理论界很有名,被认为是一种先进的OS。

微内核只提供了很小一部分的硬件抽象,大部分功能由一种特殊的用户态程序:服务器来完成。微核经常被用于机器人和医疗器械的嵌入式设计中,因为它的系统的关键部分都处在相互分开的,被保护的存储空间中。这对于单核设计来说是不可能的,就算它采用了运行时加载模块的方式。

微内核的例子:AIX,BeOS,L4微内核系列。

混合内核

混合内核它很像微内核结构,只不过它的的组件更多的在核心态中运行,以获得更快的执行速度。

混合内核实质上是微内核,只不过它让一些微核结构运行在用户空间的代码运行在内核空间,这样让内核的运行效率更高些。这是一种妥协做法,设计者参考了微内核结构的系统运行速度不佳的理论。然而后来的实验证明,纯微内核的系统实际上也可以是高效率的。大多数现代操作系统遵循这种设计范畴,微软公司开发的Windows操作系统就是一个很好的例子。另外还有XNU,运行在苹果Mac OS X上的内核,也是一个混合内核。

混合内核的例子: BeOS 内核 ,DragonFly BSD,ReactOS 内核Windows NT、Windows 2000、Windows XP、Windows Server 2003以及Windows Vista等基于NT技术的操作系统。

外内核

外内核系统,也被称为纵向结构操作系统,是一种比较极端的设计方法 。

外内核这种内核不提供任何硬件抽象操作,但是允许为内核增加额外的运行库,通过这些运行库应用程序可以直接地或者接近直接地对硬件进行操作。

它的设计理念是让用户程序的设计者来决定硬件接口的设计。外内核本身非常的小,它通常只负责系统保护和系统资源复用相关的服务。

传统的内核设计(包括单核和微核)都对硬件作了抽象,把硬件资源或设备驱动程序都隐藏在硬件抽象层下。比方说,在这些系统中,如果分配一段物理存储,应用程序并不知道它的实际位置。

而外核的目标就是让应用程序直接请求一块特定的物理空间,一块特定的磁盘块等等。系统本身只保证被请求的资源当前是空闲的,应用程序就允许直接存取它。既然外核系统只提供了比较低级的硬件操作,而没有像其他系统一样提供高级的硬件抽象,那么就需要增加额外的运行库支持。这些运行库运行在外核之上,给用户程序提供了完整的功能。

理论上,这种设计可以让各种操作系统运行在一个外核之上,如Windows和Unix。并且设计人员可以根据运行效率调整系统的各部分功能。

外核设计还停留在研究阶段,没有任何一个商业系统采用了这种设计。几种概念上的操作系统正在被开发,如剑桥大学的Nemesis,格拉斯哥大学的Citrix系统和瑞士计算机科学院的一套系统。麻省理工学院也在进行着这类研究。

单内核与微内核的比较

(ps:最重要的没有说明,单内核和微内核最重要的区别就是单内核是所有模块高度聚合的,编写难度高,但效率也高,局部错误整体就会错误,而微内核的模块是分散的,编写难道简单一些,效率稍低,局部不影响整天。百度百科给的比较偏历史发展,相关度不高,可略过 ヽ(ー_ー)ノ )

加油加油!已经看了一大半了!知识的力量汹涌澎湃!(✧◡✧)


单内核结构是非常有吸引力的一种设计,由于在同一个地址空间上实现所有复杂的低阶操作系统控制代码的效率会比在不同地址空间上实现更高些。

20世纪90年代初,单内核结构被认为是过时的。把Linux设计成为单内核结构而不是微内核,引起了无数的争议。

单核结构正倾向于设计不容易出错,所以它的发展会比微内核结构更迅速些。两个阵营中都有成功的案例。

尽管Mach是众所周知的多用途的微内核,人们还是开发了除此之外的几个微内核。L3是一个演示性的内核,只是为了证明微内核设计并不总是低运行速度。它的后续版本L4,甚至可以将Linux内核作为它的一个进程,运行在单独的地址空间。

QNX是一个从20世纪80年代,就开始设计的微内核系统。它比Mach更接近微内核的理念。它被用于一些特殊的领域;在这些情况下,由于软件错误,导致系统失效是不允许的。例如航天飞机上的机械手,还有研磨望远镜镜片的机器,一点点失误就会导致上千美元的损失。

很多人相信,由于Mach不能够解决一些提出微内核理论时针对的问题,所以微内核技术毫无用处。Mach的爱好者表明这是非常狭隘的观点,遗憾的是似乎所有人都开始接受这种观点。

3.内核优点

抽象隐藏

内核提供一种硬件抽象的方法来完成对硬件操作,因为这些操作是非常复杂的,硬件抽象隐藏了复杂性,为应用软件和硬件提供了一套简洁,统一的接口,使程序设计更为简单。

源代码管理

历史上,从来没有出现过用于Linux内核的正式的源代码管理或修正控制系统。实际上,很多开发者实现了他们自己的修正控制器,但是并没有官方的LinuxCVS档案库,让LinusTorvalds检查加入代码,并让其他人可以由此获得代码。修正控制器的缺乏,常常会使发行版本之间存在“代沟”,没有人真正知道加入了哪些改变,这些改变是否能很好地融合,或者在即将发行的版本中哪些新内容是值得期待的。通常,如果更多的开发者可以像了解他们自己所做的改变一样了解到那些变化,某些问题就可以得到避免。

非常有必要使用一个实时的、集中的档案库来保存对Linux内核的最新更新。(防止 源代码更新混乱,所以有一个档案库来保存新的代码,跟游戏补丁一样,需要新版本就下载新版本或者其他功能的补丁)每一个被内核接受的改变或者补丁都被作为一个改变集被追踪。终端用户和开发者可以保存他们自己的源文件档案库,并根据需要可以通过一个简单的命令用最新的改变集进行更新。对开发者来说,这意味着可以始终使用最新的代码拷贝。测试人员可以使用这些逻辑的改变集合来确定哪些变化导致了问题的产生,缩短调试所需要的时间。甚至那些希望使用最新内核的用户也可以直接利用实时的、集中的档案库库,因为一旦他们所需要的部件或缺陷修复加入到内核中,他们就可以马上进行更新。当代码融合到内核时,任何用户都可以提供关于这些代码的即时反馈和缺陷报告。

并行开发

随着Linux内核的成长,变得更加复杂,而且吸引更多开发者将注意力集中到内核的特定方面的专门开发上来,出现了另一个开发Linux方法的有趣改变。在2.3内核版本的开发期间,除了由LinusTorvalds发行的主要的一个内核树之外,还有一些其他的内核树。

在2.5的开发期间,内核树出现了爆炸式的增长。由于使用源代码管理工具可以保持开发的同步并行进行,这样就可能实现开发的部分并行化。为了让其他人在他们所做的改变被接受之前可以进行测试,有一些开发需要并行化。那些保持自己的树的内核维护者致力于特定的组件和目标,比如内存管理、NUMA部件、改进扩展性和用于特定体系结构的代码,还有一些树收集并追踪对许多小缺陷的纠正。

这种并行开发模型的优点是,它使得需要进行重大改变的开发者,或者针对一个特定的目标进行大量类似改变的那些开发者可以自由地在一个受控环境中开发,而并不影响其他人所用内核的稳定性。当开发者完成工作后,他们可以发布针对Linux内核当前版本的补丁,以实现到此为止他们所完成的改变。这样,社区中的测试人员就可以方便地测试这些改变并提供反馈。当每一部分都被证明是稳定的之后,那些部分可以单独地,或者甚至同时全部地,融合到主要Linux内核中。

代码覆盖分析

新工具为内核提供了代码覆盖分析的功能。覆盖分析表明,在一个给定的测试运行时,内核中哪些行代码被执行。更重要的是,覆盖分析提示了内核的哪些部分还根本没有被测试到。这个数据是重要的,因为它指出了需要再编写哪些新测试来测试内核的那些部分,以使内核可以得到更完备的测试。

大量信息

在为将来的2.6Linux内核进行开的过程中,除了这些自动化的信息管理方法之外,开放源代码社区的不同成员还收集和追踪了数量惊人的信息。

例如,在KernelNewbies站点上创建了一个状态列表,来保持对已经提出的内核新部件的追踪。这个列表包含了以状态排序的条目,如果它们已经完成了,则说明它们已经包含在哪个内核中,如果还没有完成,则指出还需要多长时间。列表上很多条目的链接指向大型项目的Web站点,或者当条目较小时,链接指向一个解释相应部件的电子邮件信息的拷贝。

下面是我用康奈尔笔记法总结的部分截图,康奈尔笔记法在上篇文章有介绍过,真的好用的,大家可以尝试一下!(≖ᴗ≖)✧

三、内存管理

1、虚拟内存

先来了解一下单片机,单片机是没有操作系统的,所以每次写完代码,都需要借助工具把程序烧录进去,这样程序才能跑起来。另外,单片机的CPU是直接操作内存的物理地址。在这种情况下,要想在内存中同时运行两个程序是不可能的。因为第一个程序在2000的位置写入一个新的值,将会擦掉第二个程序存放在相同位置上的所有内容。

我们可以把进程所使用的地址隔离开来,即让操作系统为每个进程分配独立的一套虚拟地址,人人都有,大家可以在自己的地址玩,互不干涉。但是有个前提每个进程都不能访问物理地址,虚拟地址由操作系统安排到物理内存里。

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

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

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

● 我们程序所使用的内存地址叫做 虚拟内存地址 (Virtual Memory Address)

● 实际存在硬件里面的空间地址叫 **物理内存地址 ** (Physical Memory Address)。

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

操作系统管理虚拟内存和物理地址之间的关系主要有两种方式,分别是内存分段和内存分页,分段是比较早提出的,我们先来看看内存分段。

2、内存分段

(这是一个比较早的解决方案)

程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation) 的形式把这些段分离出来。

分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量。(下图看不懂可跳,知道地址是分成一段一段的就行(ŎдŎ;))

段选择子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等

● 虚拟地址中的 段内偏移量 应该位于0和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。

在上面,知道了虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成4个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址,如下图:

如果要访问段3中偏移量500的虚拟地址,我们可以计算出物理地址为,段3基地址7000 +偏移量500 = 7500。

分段的办法很好,解决了程序本身不需要关心具体的物理内存地址的问题,但它缺点也明显:

● 第一个就是内存碎片的问题。

● 第二个就是内存交换的效率低的问题。

接下来,说说为什么会有这两个问题。

我们来看这样一个例子。一台电脑,有1GB(1024M)的内存。我们先启动一个图形渲染程序,占用了512MB的内存,接着启动一个Chrome浏览器,占用了128MB 内存,再启动一个Python 程序,占用了256MB 内存。这个时候,我们关掉Chrome,于是空闲内存还有1024 - 512 - 256 = 256MB。按理来说,我们有足够的空间再去装载一个200MB的程序。但是,这256MB的内存空间不是连续的,而是被分成了两段128MB的内存。因此,实际情况是,我们的程序没办法加载进来。

如上图所示,两处白色的“空余内存”,看出内存时不连续的,会拒绝一些稍大的内存占用程序的进入(比如图中192M的程序)

当然,这个也有办法解决。解决的办法叫内存交换(Memory Swapping)。

我们可以把Python程序占用的那256MB内存写到硬盘上,然后再从硬盘上读回来到内存里面。不过读回来的时候,我们不再把它加载到原来的位置,而是紧紧跟在那已经被占用了的512MB内存后面。这样,我们就有了连续的256MB内存空间,就可以去加载一个新的200MB的程序。如果你自己安装过Linux操作系统,你应该遇到过分配一个swap硬盘分区的问题。这块分出来的磁盘空间,其实就是专门给Linux操作系统进行内存交换用的。

虚拟内存、分段,再加上内存交换,看起来似乎已经解决了计算机同时装载运行很多个程序的问题。不过,这三者的组合仍然会遇到一个性能瓶颈。硬盘的访问速度要比内存慢很多,而每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以,如果内存交换的时候,交换的是一个很占内存空间的程序,这样整个机器都会显得卡顿。

于是,为了解决内存分段的内存碎片和内存交换效率低的问题,就出现了内存分页

3、内存分页

分段的好处就是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。

要解决这些问题,那么就要想出能少出现一些内存碎片的办法。另外,当需要进行内存交换的时候,让需要交换写入或者从磁盘装载的数据更少-点这样就可以解决问题了。这个办法,也就是内存分页(Paging) 。

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

虚拟地址与物理地址之间通过页表来映射,如下图:

页表实际上存储在CPU的内存管理单元(MMU) 中, 于是CPU就可以直接通过MMU,找出要实际要访问的物理内存地址。

而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

分页怎么解决分段的内存碎片、内存交换效率低的问题?

由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,这正是分段会产生内存碎片的原因。而采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。

如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swop Out)。一旦需要的时候,再加载进来,称为换入(Swapln) 。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。

更进一步地,分页的方式使得我们在加载程序的时候,不再需要一次性都把程序加载到物理内存中。 我们完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

在分页机制下,虚拟地址分为两部分,页号页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图。(ps:与内存分段类似的原理)

说白了就是,对于一个内存地址转换,其实就是这样三个步骤:

● 把虚拟内存地址,切分成页号和偏移量;

● 根据页号,从页表里面,查询对应的物理页号;

● 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

下面举个例子,虚拟内存中的页通过页表映射为了物理内存中的页,如下图:

4、简单分页

(上面图表介绍的就是简单分页)

简单分页有空间上的缺陷。

因为操作系统是可以同时运行非常多的进程的,那这不就意味着页表会非常的庞大。

在32位的环境下,虚拟地址空间共有4GB,假设一个页的大小是 4KB (2^12) ,那么就需要大约100万(2^20) 个页, 每个页表项需要4个字节大小来存储,那么整个4GB空间的映射就需要有4MB的内存来存储页表。

这4MB大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。

那么,100 个进程的话,就需要400MB的内存来存储页表,这是非常大的内存了,更别说64位的环境了。(ps:单单是一张映射表就要占这么多空间,所以肯定要用算法啥的来优化咯,所以出现了下面的多级页表,层层套娃来了,o(╥﹏╥)o)

5、多级页表

要解决上面的问题,就需要采用的是一种叫作多级页表(Multi-Level Page Table)的解决方案。

在前面我们知道了,对于单页表的实现方式,在32位和页大小4KB 的环境下,一个进程的页表需要装下100多万个页表项,并且每个页表项是占用4字节大小的,于是相当于每个页表需占用4MB大小的空间。

我们把这个100多万个页表项的单级页表再分页,将页表(一级页表)分为1024个页表(二级页表),每个表(二级页表)中包含1024个页表项,形成二级分页。如下图所示:

你可能会问,分了二级表,映射4GB地址空间就需要4KB (一级页表) + 4MB (二 级页表)的内存,这样占用空间不是更大了吗?

当然如果4GB的虚拟地址全部都映射到了物理内上的,二级分页占用空间确实是更大了,但是,我们往往不会为一个进程分配那么多内存。

其实我们应该换个角度来看问题,例如计算机组成原理里面无处不在的局部性原理

每个进程都有4GB的虚拟地址空间,而显然对于大多数程序来说,其使用到的空间远未达到4GB,因为会存在部分对应的页表项都是空的,根本没有分配,对于已分配的页表项,如果存在最近一定时间未访问的页表, 在物理内存

本文标签: 程序员学习笔记操作系统