【操作系统】操作系统原理_为什么操作系统都是紧耦合-程序员宅基地

技术标签: 改行学it  

声明:本篇文章是根据bilibili上的一个视频:清华大牛终于把计算机操作系统底层原理讲透彻了
史上最全操作系统课程 总结而成的。  


#概述
基本概念和原理:中断、系统调用、内存管理、进程及线程、调度(进程线程调度、cpu调度)、同步(同步互斥的问题)、文件系统、I/O子系统
操作系统是怎么构成的?就是怎么搭建这个系统?怎么设置这个系统的环境,就是怎么建造它的开发环境?
操作系统的启动及中断。启动时是怎么对物理内存进行管理的。比如对内存进行虚拟化管理,使我们有更大更多的资源去利用。
怎么在计算机系统中同时跑多个不同的程序?而程序是以线程或者进程的形式存在的,所以有内核线程管理、用户进程管理。多个不同程序同时进行时,就涉及到这些程序如何占用cpu,就是cpu调度问题。
多个进程或者线程调度资源时,就会出现如何去协调这些进程和线程,使它们同步与互斥的来访问资源,提高资源的利用率和系统的正确性。
文档、数据、包括我们的执行程序 都以文件的形式存在,所以怎么设计我们的文件系统。

#知识储备
我们要知道计算机的组成,因为我们的操作系统是基于cpu的物理特性来设计的。本案例对应的cpu是intel 80386
我们还要知道数据结构,因为这里面涉及到数据的组织和管理,数据的组织管理是通过算法实现的。
操作系统也是一个软件,软件就设计到用什么语言去编写。目前编写操作系统主要是C语言和汇编语言。

#什么是操作系统?
操作系统是一个复杂的软件,也是一个特殊的软件,所以也叫系统软件,它起到的是一个承上启下的功能。操作系统是直接面向硬件的软件,一般的应用软件不是直接面向硬件的,一般的应用软件如果要访问计算机的某些资源,比如cpu,那么它不是直接去访问cpu的,它是直接访问我们的操作系统,操作系统再调度cpu资源给它的。特别是一些外设设备,这些设备都是操作系统统一提供资源给它运行的。操作系统是提供一些接口,这些外设设备或者应用软件直接调用这些接口来获得资源的。
从功能上看:对上,是对我们的应用程序、用户提供服务。所以1、它是一个控制程序,控制它上面的软件等应用程序如何运行,同时限制不同的应用程序来占用不同的资源(计算机里的资源有:CPU资源、内存资源、各种外设资源等),所以它也是一个资源管理器,管理外设、分配资源。2、它是一个服务程序,为应用程序提供服务。比如提供声音声卡的访问,网络网卡的访问等等,使得计算机可以方便的使用。也就是说操作系统提供一个环境,在这个环境中不同的应用软件可以和谐的工作,最大程度的利用资源,提高效率。
对下,操作系统是对计算机底层的硬件提供管理、控制、服务的功能。操作系统把CPU硬件抽象成"进程"、硬盘抽象成"文件"、内存抽象成"地址空间"来给操作系统上面的软件来使用。

操作系统层面的软件有2个不同的对外接口,一个是面向应用程序的接口叫shell,我们也叫"壳", 比如在windows中常见的就是gui,gui是一种图形化的表示方式,gui就是windows操作系统对外暴露的一个接口,通过这个接口我们的应用程序可以很好的运行,从而展现给用户更好的体验。再比如我们的linux操作系统,它的shell就是字符方式,就是命令行方式的shell,这需要我们敲一些命令来完成一些功能。所以,shell是操作系统对外提供的一种可见的服务。

另一个是面向内部,就是面向内部,管理内部资源的接口叫kernel。kernel是一个操作系统的核心。所以一个操作系统的好坏我们更多的是站在操作系统内核的角度看问题的。

#那么操作系统内部又细化一些什么东西呢?
操作系统是管理硬件资源的,那么它管理的硬件有:cpu、内存、硬盘,主要就是这三个硬件,其次还有网卡、显卡、声卡等等都是操作系统管理的对象。
cpu的管理有:cpu的调度,比如进程和线程的管理都是和cpu相关的管理。cpu管理是操作系统中非常重要的一个管理子系统。
内存的管理有:1、物理内存的管理。就是怎么把物理内存有效管理好。2、虚拟内存的管理。虚拟内存的管理主要是为上层的应用软件提供尽可能大的内存空间供它们去使用,这需要一系列技术手段在有限的物理内存之上,虚拟出更大更安全的虚拟空间。
硬盘的管理有:硬盘是以磁道作为一个基本的读写单位,数据写在磁道上,我们读写数据都是机械运动,不方便应用程序读写数据,所以我们在磁盘上抽象出文件系统,通过文件系统以文件的形式给应用程序提供一个存储、访问、永久保存数据等一个环境。这就是文件系统管理。
除了上面三大硬件管理外,还有和底层相关的有:中断处理和IO设备驱动2个部分,这2块是和底层硬件直接打交道的。虽然中断处理和IO和我们的应用程序离得比较远,但是它是操作系统中非常重要的一个功能单元,和我们硬件直接相关,也是有了它们,我们的操作系统才能更好的为上层应用软件提供更好的服务。如果没有中断处理、没有外设管理,那么操作系统很难完成前面讲的对软件提供的服务和控制的功能。

#操作系统的特征:
1、并发的特征。指并发管理,指的是在计算机中可以同时放多个正在运行的程序,而操作系统是来调度、选择哪个程序去占用cpu去运行,这叫并发管理。这里我们引出了"并发"和"并行"2个概念。并发指的是在"一段时间"内有多个程序在运行,这个一段时间是我们人为规定的,可短可长。并行是指在"一个时间点"上有多个程序同时运行。能够并行的计算机一般是有多个cpu的,这样才能够多个程序在一个时间点上同时运行。如果计算机上只有一个cpu的话,那么是它是无法完成并行运行的,就是不可能让两个程序同时在一个cpu上跑,这是不可能的。
2、共享的特征。既然操作系统是一个资源管理器,那它就要考虑如何让这些资源高效的共享给它上面的应用程序。根据硬件的不同物理特征,分为"同时"共享和互斥共享。同时共享就是让应用程序同时访问cpu、内存、IO等硬件。但同时访问有时就出现了互斥的情况,因为有的硬件在一个时间点或者一个时间段上只能一个程序访问这个资源,比如内存,在某一个时间点上只能有一个程序访问某个内存单元,但是如果这个内存我们给它分成两块,并且这两块之间是相互隔离的,那么我们就可以同时让两个程序访问不同的2个内存,这就是互斥共享。
3、虚拟的特征。操作系统直接面对的是硬件,那么操作系统就将硬件虚拟化了,比如将cpu虚拟化为进程、把硬盘虚拟化成文件、把内存虚拟化成地址空间,虚拟化后的好处就是我们的应用程序就好像自己独占一台计算机,每个应用程序都有一台计算机的资源。就是利用多道程序设计技术,让每个用户或者每个应用程序都觉得有一个计算机专门为他服务。这就是操作系统的虚拟功能,通过这个虚拟功能,每个程序每个用户都获取了计算机资源。虚拟功能是多道程序设计里最常见的一种方式,就是把一台物理计算机虚拟成多台虚拟机,在操作系统支持下就可以完成多台机器的功能了。
4、异步的特征。异步是指程序的执行不是一贯到底的,而是走走停停,向前推进的速度不可预知。在计算机系统里面通过操作系统的管理和调度可以跑多个程序,但是在一个cpu的情况下,在任何时刻只能有一个程序在跑,那下一个程序什么时候能跑,这取决于操作系统的调度,所以一个程序什么时候运行、什么时候停下来、什么时候继续运行就取决于操作系统的管理。所以我们看到的现象就是一个程序走一段停一段,走走停停,时间不确定。这里要说明的是,如果一个程序运行前的环境是相同的,那么理论上他运行完的结果也应该是相同的。如果一个程序在一个相同的环境下运行处来的结果是不同的,那就说明你这个操作系统的管理功能是失败的。因为我们希望运行结果应该是一致的。
综上,操作系统的4个特征之间是有联系和区别的,我们好好体会一下。

#为什么要学习操作系统
需要会程序设计语言,因为是一个软件,所以要编程,所以要会一门编程语言。
要会数据结构,因为它要编程,所以要写算法,写算法就要知道数据结构才能写算法
要会算法。
要知道计算机硬件结构。因为操作系统软件是和硬件打交道的。
要了解材料,因为硬件的性能就是材料的性能。
要了解操作系统的概念、原理、源代码
要了解操作系统设计和实现。
说明:上面讲的都是操作系统的原理,上面这些原理怎么和实际的东西对应起来,就是一是和代码对应起来,二是和硬件对应起来,所以除了学会原理外,我们还要学习操作系统的源代码。这样才能对应起来,才能深刻理解,上面的原理、概念、思想在操作系统中是这样实现的,而操作系统中的代码实现又是怎样作用在硬件上的。
如果有的同学对操作系统非常感兴趣,喜欢coding,想去完善操作系统的想法,都可以去编写、去扩展、甚至去重写,可以体会到复杂软件的设计思想。

#现在操作系统已经很完善了,比如windows我还需要去写?需要的,一是我们的硬件在不断的发展,就是我们的材料学在进步,不断有新的硬件推出,所以针对新性能新的硬件,操作系统也需要跟进和升级。二是操作系统在工控领域有强烈的需求!如果你对电脑操作系统非常熟悉的话,工控计算机的操作系统就更容易理解了。
所以操作系统还是很有用的,有很大的市场,但也很有挑战。因为操作系统涉及到很底层的东西。如果你非常熟悉一个计算机系统的操作系统,你就可以设计、改进、扩展这个系统,甚至你就可以控制整个计算机系统!

MIT 哈佛, 雅虎,谷歌,施乐,facebook
操作系统很大,比如windows XP有4500万行代码量,所以完全不可能完全掌握,所以我们都是找一些核心的代码去读。
操作系统并发、异步的特征,导致编写操作系统非常有挑战性,很容易出现错误,所以我们用很多巧妙的方法保证效率和不出错。此外硬件容易出现故障、应用软件容易出现非法行为等都增加了操作系统的编写难度。
操作系统是管理整个计算机系统的一个核心软件,所以它的可靠性和效率都会对应用程序带来很大的影响,所以操作系统一是必须可靠二是必须高效。操作系统出错就意味着机器出错,操作系统必须要比应用程序有更高的稳定性。一旦某个应用程序被黑客攻陷,那损失也仅是这一个应用程序,如果是计算机的操作系统被攻陷就意味着你的整个计算机系统都暴露在黑客的眼皮下。所以操作系统如果有漏洞其危害是巨大的。所以编写操作系统的思路、框架、算法的要求都比编写一般应用软件要高很多。

随着时间的推进,目前很多操作系统的书籍里的知识点有的都已经老化,因为过去我们认为有的工作是需要操作系统去完成的,但现在随着硬件材料的发展,或者其他技术的发展,有的功能是不需要操作系统去完成的。比如进程调度、磁盘IO调度等功能
都已经成为目前我们关注度比较小的点了,因为这些功能已经可以放到硬件里面而不需要操作系统来完成了。

所以操作系统需要权衡:1、时间与空间(cpu和内存之间找平衡)2、性能和可预测性,我们希望性能尽量高,希望可预测性尽量可预测,尽量可预测程序什么时候开始什么时候结束。3、公平和性能。希望整个系统中的资源能够被公平的使用、高效的使用。
操作系统是管理硬件的,所以比如中断、异常、执行上下文、ToB(cpu中的重要组成单元)、对硬件的特点进行有效处理。底层的细节清楚后,上层的概念和原理才能更好的理解。

#操作系统实例
当前我们有很多类型的操作系统,有面向桌面的操作系统,比如我们平时家里用的笔记本,有面向服务器的操作系统,比如centos,有面向移动中断,比如手机的操作系统,有工控领域跑的很多操作系统。
UNIX家族--UNIX BSD--基于UNIX BSD发展而来的操作系统有惠普、苹果IOS,
Linux家族--linux是一个学生,Linux就自己写了一个操作系统,基于linux发展而来的操作系统有红帽red hat、fedoro, ubunto等等, 谷歌推出的安卓操作系统用的就是linux内核,所以目前移动终端的操作系统绝大多数都是linux内核。目前服务器一般都是linux。
Windows家族:微软设计的,最初的是dos系统,后来微软和IBM合作,推出Windows,进入图形界面,然后再进一步发展到w7\w8,到目前xp,目前桌面领域windows是占据绝对统治地位的,在服务器和终端领域linux占据绝对统治地位。微软的图形化操作系统问世后,极大的推进了计算机的大众普及。

#操作系统的历史和演变过程
操作系统从0到有的发展历程是和计算机硬件、计算机系统结构、计算机的应用需求的发展紧密联系的。
早期,计算机是没有操作系统的,早期的计算机使用是用纸带将程序传到计算机上,计算机计算完后纸带机再把结果输出出来,整个过程由一个专门的操作员来完成。早期的计算机虽简陋但它完整的完成了一个计算的流程:输入-计算-输出。但是这个流程只能一个流程完成了再开始下一个流程。
之后,随着cpu越来越快、存储设备容量越来越大,我们就希望能同时执行多个输入-计算-输出这个流程,就出现了顺序执行与批处理,就是成批/离线处理,进入批处理阶段。此时操作系统进入孕育时代,操作系统具有并发的特征开始体现。
之后,随着内存越来越大,我们发现,不用每次cpu计算都要从外面输入程序到cpu,而是我们可以把多个程序直接提前放到内存中,此时CPU就可以并发的处理多个程序。这就是多道程序设计,多道程序设计cpu的效率就大大提升了。
多道程序设计就涉及到'中断',意思就是,例如当前cpu正在执行第一个程序read()程序,执行这个程序需要I/O设备把数据送给cpu,但是io设备要0.5秒后才能把数据送到,那么此时cpu就是没有数据运算而中断了,此时cpu的物理表现就是线路无电流或者电压为0,此时操作系统就接受到这个信号,操作系统就调第二个程序到cpu,cpu就开始执行第二个程序,当时间到了0.5秒,操作系统再让cpu继续执行第一个程序。这就是调度切换的过程。
上面的调度切换不需要人为干预,但是如果人机想交互怎么办?就出现了"分时调度"的思路,就是以后的分时系统。就是通过分时系统来保证不同的程序都有时间被cpu运算。我们现在的计算机系统是千分之一秒产生一次分时,就是一个时间片是千分之一秒,意思就是cpu执行第一个程序执行了千分之一秒,就把cpu分配给第二个程序,第二个程序执行了千分之一秒后就把cpu再分配给第三个程序执行。如此轮流切换执行。而人类的感觉就是这三个程序都在用计算机的cpu去完成功能。
所以这里面就牵扯到一个问题,如何每千分之秒就打断cpu的运算呢?这里面就牵扯到一个外设——时钟,时钟会定期的产生中断,把cpu的控制权交给操作系统,操作系统把运行的程序自动切换到下一个程序。所以就是时钟帮助操作系统来完成分时调度的作用。
之后,随着硬件的发展,和人类需求的发展,就是对计算机要做的工作越来越多,cpu可以内部集成多个核,就是多核cpu,就出现了"并行"的概念。此外随着网络的快速发展,很多工作可以不用本机来做,可以将数据或者程序通过网络放到数据中心来完成,就出现了分布式的操作系统。就是只要一个前端即可,前端就是和用户接触的一个界面,而很多数据的存储、计算放到数据中心即可,中间通过internet联系起来,这就是一个分布式的计算环境,所以我们的操作系统也要跟着改变,就出现了松、紧耦合系统,这个系统现在面临的研究课题就是1、如何通过internet进行松耦合的交互,就是如何通过Internet使得网络交互更加及时有效。2、在数据中心里面是一个紧耦合的集中系统,如何让这个集中系统集体的完成一个计算的功能,如何管理、协调等,都是目前新的研究方向。
目前,又出现大数据、物联网、云计算,以后我们会经常遇到大量的嵌入设备来提高感知、计算、服务,很多工作可以交到云计算中心来完成,实现大量的机器为每个人服务。

#操作系统的结构设计
早期的面向个人计算机的微软操作系统:MS-DOS,是部分模块的单体内核的操作系统,是用汇编语言编写的。这个操作系统的设计没有分层也没有模块化,也没有保护的设计,所以这个操作系统容易被攻击被破坏,而且很难去做进一步扩展。当时的内存只有640K或者1兆,所以当时的操作系统受制于硬件的极限而很难有突破,不可能设计层分层或者模块化的。
早期的面向科学计算的服务器计算机的操作系统:UNIX系统,就设计了层次的概念、模块化的概念,成为一个复杂的系统软件的雏形。Unix系统是用C语言来实现的,不是汇编语言,汇编语言和C有很大的区别,汇编语言是和机器具体绑定的,所以不具有可移植性。C语言和具体的机器无关,所以有可移植性,可以将C语言写的程序放到不同计算机上跑。

所以一个完整的操作系统至少要包括:文件系统、内存管理、调度、进程线程管理、对硬件管理的驱动(比如,向下有对时钟、磁盘的中断服务,和向上的有系统调用,供应用软件使用,让应用软件获得操作系统提供的服务。)。这就是一个微型的操作系统。

一个实际的OS如何在一个实际的硬件上完成一个相应的管理、协调、控制。
单体的模块化的操作系统架构设计。单体指的都是各个模块之间是通过函数调用来实现访问的。这种架构是紧耦合的架构设计。

由于我们的操作系统要实现文件系统、内存管理、调度、进程线程管理...等等很多功能,使得操作系统这个软件过于庞大,我们能不能让操作系统的内核更轻量化,那就是要把不同的功能都单独拎出来,将不同的功能变成一个外层的模块,而外层的模块之间的访问不是通过函数调用的方式实现,而是通过类似消息传递机制这种松耦的方式来实现,这样使得操作系统更加容易扩展。基于这种思路,我们就设计出基于微内核架构的设计,就是尽可能将内核功能移到用户空间。就是说,我们在操作系统内核里面只放最基本的功能,比如中断处理、消息传递等功能放到内核中完成,而文件系统、内存管理、网络协议栈等都放在外围,以进程或者程序的形式存在,或者说是一种服务的形式存在,那么服务和服务之间是通过内核的消息传递机制进行通讯。这种架构是松耦的架构,很灵活,相互之间也不会破坏,因为在内核中通过地址隔离可以确保相互之间的程序无法恶意的去破坏对方的地址空间。但是这种架构的缺点就是性能低。比如文件系统和内存管理系统之间进行交互的话,就需要把数据先倒到内核,内核再把数据导给内存管理子系统,这个过程要进行多次拷贝,和紧耦合架构中的直接通过函数调用相比,松耦合架构的开销要大很多。
目前产业界还很少有微内核的架构设计,主要就是性能无法有效的提升。

学术界还有一种极端的架构:"外核"。这种架构的思想是将操作系统的内核分成2块,一块是跟硬件打交道,主要功能是完成硬件功能的复制,这块叫extral kernel外核,另一个块叫Library OS,libraryos是建立在extralkernel之上的,libraryos和具体应用打交道,比如我们的browser,我们就专门有一个面向browser的libraryos,这个面向browser的libraryos再去访问extral kernel,再去访问硬件。再比如我们的office,就专门有一个面向office的libraryos,这个libraryos也是可以去访问extral kernel,再访问硬件的。但是面向面向browser的libraryos和面向office的libraryos,这两个libararyos是不一样的,因为对应的应用软件是不一样的,所以它们的设计上也是不一样的,但它们有一个共同的特点就是都可以去访问extral kernel。由extral kernel来统一完成对硬件的管理,使得这两个不同应用上的libos安全有效、并发的来使用硬件资源。这样的架构的好处是速度,因为OS的一大部分功能是和具体的应用软件紧密结合的,这样具体的应用和os是一个紧耦合的关系,因为针对不同应用的libarayos是根据不同应用的特点做的相应的特点设计,所以它的速度就更加快。同时通过extral kernel这么一个薄薄的一层(因为轻量化)来完成对硬件的隔离。

还有一种架构叫虚拟机监控器VMM。这种架构是在硬件和操作系统之间加了一层VMM,就是跑在我们传统的os之下。就是首先在一台物理计算机上虚拟出多台虚拟机VMs(说明:每台虚拟机都是一台完整的计算机,由cpu\内存、各种外设等),这些虚拟机给它上层的操作系统使用。我们传统的是操作系统之下就是硬件,VMM架构是操作系统之下是一层虚拟机监控器VMM,VMM之下才是硬件。所以我们OS是感知不到它下面跑的是一台真正的计算机还是一台虚拟的VMM,这样我们一台计算机就虚拟出了多台计算机。为什么要虚拟出多台计算机?因为现在我们的cpu越来越强,甚至一台物理机可以装一个多核的cpu,这样我们一台计算机一般的应用根本用不完cpu,cpu资源过剩了,此时我们就可以不让这台物理计算机只给一个人服务,而是放到数据中心去给多人服务,这就是数据中心虚拟化的思想雏形。如果给多个人服务,我们就需要通过VMM给多个人每人一台计算机供他使用,这样我们就把计算机资源的效率利用的最大。

小结:操作系统虽然是完成相同的功能,但是可以有不同的结构设计。

#启动
操作系统怎么从计算机一通电就启动到正常运行,然后让应用程序进入正常工作的状态?
操作系统是一个软件,这个软件是放在硬盘中的。所以当我们一按电源,内存中是没有os软件的,所以cpu也是无法运行os的,那电脑是如何自动启动的呢,启动就是执行一些操作,执行操作只能是cpu执行,而cpu执行的指令又只能是从内存中拿,内存在掉电时是没有数据的,那一通电的瞬间cpu是没有指令,它是如何指挥内存去硬盘中取数据呢?这变成了一个鸡生蛋蛋生鸡的问题了。其实硬盘中的os程序是在BIOS的帮助下才传到内存,cpu执行,再一系列过程才能自启动的。

BIOS:基本I/O处理系统,它是一个固化在计算机主板上的一个rom芯片上的一个小程序。掉电也不会丢失。固化在主板上,现在多用闪存存储,所以bios也是一个固件。只要电源一通就先开始加载并执行这个小程序。这个程序的主要功能是开机自检、初始化、系统加载等功能。
所以,当我们一按电源,第一个阶段是没有内存的阶段,这个阶段代码是在这个小固件上运行的,这个小固件只能运行汇编语言,因为汇编语言可以直接在rom空间上运行。而C语言运行需要栈空间,这时内存和cpu还没有数据呢。此时这个小固件上烧制的代码只能找个临时空间把自己复制到这个临时空间上,比如Cache空间上。第二阶段:此时cpu就可以执行bios指令了,执行bios就执行开机自检,自检完毕后执行从硬盘加载bootloader,bootloader加载完毕就进入第三阶段:bootloader是C语言,cpu执行bootloader就完成了加载os,os加载完毕就进入第四阶段:执行os, os也是c语言,用C语言初始化内存、初始化芯片组、cpu、主板模块等,启动设备、枚举设备、发现设备、并把设备启动之前需要依赖的节点统统打通,电脑移交os管理,第五个阶段:在os的管控下,加载其他的软件并执行。

BIOS是如何把硬盘中的操作系统软件加载到内存的?硬盘第一个主引导扇区我们叫bootloader,这个扇区的小程序就是加载操作系统软件到内存的程序,有512个字节,负责把os加载到内存,cpu就可以执行os了。
所以计算机的启动是先加载bios到内存,执行bios命令就实现了加载硬盘中的bootloader程序到内存, 执行bootloader程序就实现了指引加载os到内存,cpu执行os, os开始控制计算机。
在bios加载到内存后,执行bios时,第一个指令就是从一个特定地址开始执行,这个特定地址是CS段寄存器的地址和IP指令寄存器地址,这两个地址和在一起就是一个具体的内存地址,所以一开始加电,bios就从这个具体地址开始执行,执行完后就完成了所有外设的自检,就是检查自身有哪些设备可以正常工作,比如屏幕正常展示的显卡驱动、键盘鼠标、数据是否能存储在disk上,等都是自检,自检通过后,就开始从disk上加载bootloader到内存,x86系统就规定加载到0x7c00这个内存地址(首地址,有512个子节,需要一个连续的内存地址)。此时cup的控制权就由bootloader来掌握了,bootloader第一步是找到硬盘的操作系统的起始扇区以及操作系统的长度,负责把os在硬盘的区域加载到内存中。此时cpu的控制权就变成os了,其实就是cpu的执行权跳到os的第一行地址开始执行,os就在内存中开始各种初始化工作,开始加载应用程序去运行,这时计算机的所有硬件管理都已经是处在os的管理之下了。这个过程就是bootloader的一个启动过程。

#操作系统正常加载完毕后,操作系统如何和底层的硬件、外设、上层的应用程序打交道
操作系统与设备和程序交互,这就涉及到操作系统的interface, 操作系统的interface有三个:第一个是是面向外设的 '中断和io' 进行处理的。第二个是面向应用程序的'系统调用、异常'来提供相应的功能。就是说操作系统对外设的管理是通过中断和io来处理的,对应用程序是通过系统调用和异常来处理的。

#中断、异常、系统调用
操作系统是如何提供相应的接口来给我们的应用提供服务以及如何控制我们的外设、如何和我们的外设进行交互?这就涉及到中断、异常、系统调用等概念,以及如何实现中断、异常、系统调用这些概念的。

#系统调用system call
系统调用来源于应用程序,应用程序主动向操作系统发出服务请求,就是应用程序主动向操作系统发出的一条指令。指令明确告诉os它需要什么服务。有明确地指令,指令有明确地参数。比如说打开一个文件、关闭文件、发送网络包等都是系统调用,这些指令都是应用程序发出,操作系统去具体完成的。应用程序只需按照os定义好的接口,设置好这个接口的参数即可。这个指令的实现是操作系统去完成的,并且os执行完毕会返回给应用程序执行结果。
#异常exception
异常也是应用程序产生的,但不是应用程序主动发出的指令,是应用程序在执行过程中出现异常的情况,操作系统需要主动回复的指令。比如假如应用程序中有一行代码出现除0的操作,这条指令其实就是一条让计算机无法工作的指令,所以就需要操作系统及时去发现这种情况并且去处理。再比如一些恶意的程序,它要越过它的访问权限去访问其他程序的地址空间,而这种行为是要禁止的,其他程序的地址空间是要保护的,这时操作系统要能及时地截获这个指令,这种情况也是异常。
#中断interupt
中断来源于外设,比如来自不同的硬件设备的计时器或者是网络的中断,外设设备通过中断这种手段让操作系统知道有哪个外设发出请求给它(os)了,需要它(os)进行处理。比如具体的就是鼠标键盘等,键盘会产生键盘输入的事件,鼠标会产生移动的事件,网卡产生网络包的事件,声卡显卡等都可以产生中断事件让操作系统去处理。

小结:应用程序是不能直接访问外设的,要通过Os。os是一个特殊的软件,特殊在它可以操控整个计算机系统、可以执行特权指令、是一个可信任的软件。所以应用程序如果直接访问外设,很容易造成整个计算机系统的崩溃。此外通过操作系统可以使上层的应用软件屏蔽底层硬件的复杂性和特异性,使的应用程序的开发不用去考虑底层硬件的特点和性能,应用程序开发就变得容易很多,而且上层的应用程序也会更加通用和可移植。

#异步、同步
从处理时间的角度说:
中断属于异步事件,异步事件的意思是:当这个中断事件产生的时候,os其实是不知道不能预测这个事件是什么时候产生的。就是键盘的输入,操作系统也不知道用户什么时候要敲击键盘。这是异步事件。
异常事件,是同步事件,因为异常事件肯定是执行某条特定的指令产生的,只要执行某条指令,os就知道会不会产生异常事件。所以异常事件是一个同步事件。
系统调用事件,如果应用程序发出系统调用请求,os就去执行,就是同步事件。如果os还没返回应用系统的请求,应用系统就去干别的事情了,那么os返回的步骤或者说返回的点就是异步事件。所以系统调用要么是同步要么是异步。

#响应
从响应的状态说:
中断 是打断当前程序的正常执行。但是应用软件或者说应用程序是感觉不到的,因为os把中断的过程给屏蔽了,应用程序在执行过程中是感觉不到中断的。
异常 当出现异常,就是出现想象不到的应用程序指令,如果后果严重就是杀死这个程序或者重新执行这个程序。
系统调用 就是系统调用服务基本上就是等待执行,等待完毕后就继续执行,不会重复执行。

#中断、异常、系统调用的处理过程
中断和异常的处理有硬件方面的处理过程和软件方面的处理过程,两方面的处理os才能提供正常的服务。
硬件处理过程是:硬件给cpu发出一个中断的标记信号,让cpu知道这个硬件中断了,cpu根据这个标记信号,通过查表,就知道是谁中断了,然后生成一个中断号,并把中断号发给操作系统,os根据中断号找到对应的处理历程。
建立一个表,表的右侧是key,就是中断号或者异常号,中断号异常号就区分出来是鼠标的中断还是键盘的中断,不同外设的中断都有自己特定的编号,特定的编号又对应一个特定的地址,所以这个地址就是记录了到底是什么中断。假设os收到中断信号,os就去查找中断表,就查到中断服务历程的起始地址,然直接转跳到这个地址继续执行就ok了。此外,一旦出现中断,还要有保护和恢复机制,这样才能在中断之后继续运行。
软件方面的处理:就是os方面的处理。os一方面要保存打断的现场状态。比如一条程序正在执行被打断了,os就要保存这个程序已经执行的变量、中间数据、以及现在执行到什么地方了、执行的寄存器的内容是什么,这些都要先保存起来,便于以后这个程序还能从被打断的点继续执行到完毕。
比如一条程序正在执行,一个网卡收到一个网络包,这个网卡外设就产生了一个中断信号,os就保存现场先中断这个程序的执行,先去接受这个信号包并进行处理。当os处理完信号包后再回到上个程序的中断现场,清除中断标记,恢复之前保存的处理状态继续执行。
异常的处理过程和中断差不多,异常处理是要么杀死产生异常的程序,要么重新执行异常指令。出现异常事件,比如除0,也是会产生一个异常的id号,os也是首先要保存出现异常的执行现场:地址及当前寄存器的内容。然后os根据异常的编号去做相应的处理。比如退出执行或者重新执行。
系统调用的处理过程是:通过系统调用接口,让os为应用程序提供各种各样服务。比如我们的应用程序是一条C语言程序:printf()时,就是在屏幕上打印一个字符串,这条指令就会触发一条系统调用:write()系统调用。这个write()系统调用会带一些参数比如让哪个设备去显示这个字符串、这个字符串的内容。os获取这些参数后,os会直接访问屏幕把这个字符串给写出来。所以屏幕显示打印的东西,不是应用程序完成的,应用程序只是发出指令,操作系统接到指令后,操作系统完成这个具体打印出来的动作的。当os在屏幕上打印完毕后会发一条成功或者识别消息给应用程序,这样应用程序就可以继续后面的代码了。

事实上,应用程序不是直接进行系统调用的,是通过接口进行系统调用的,就是通过更高层次的api实现系统调用的。我们现在已经有很多用C语言定义好的api, 比如windows操作系统的win32 api, LINUX系统的POSIX API, 用于虚拟机JVM的 java API等。比如我们就可以通过win32 api使用windows 内核提供的各种系统调用服务。

对于应用程序而言,应用程序只需要知道实现这个系统调用,能完成什么样的功能、需要什么参数、接口什么、是怎么定义的就ok了。那么,os是怎么被设计能实现系统调用服务呢?os需要有system call interface, 应用程序可以直接或者间接的通过一个library库去访问os的system call interface接口,一旦应用程序访问了系统调用接口,就会触发从用户态到内核态的转换。
用户态是指一个应用程序在执行过程中,cpu所处的一个权限状态,这用户态下,cpu不能执行一些特殊的指令,比如不能访问io。内核态状态下,cpu可以执行任何一条指令,包括特权指令、访问io指令。所以在用户态下应用程序的指令是不能完全控制整体计算机的,因为不能执行特权指令。但在内核态下,操作系统是可以完全控制计算机整个系统的。

也就是说,有了system call interface后,当我们的应用程序调用一个系统调用的时候,os就从用户态转换到内核态,使得cpu的控制权从应用程序交到os,此时os会对应用程序发出的系统调用的这些参数的id号做出标识,这些标识帮助os来对应用程序的具体动作进行识别和服务。

系统调用和函数调用:当应用程序发出函数调用时,是在一个栈空间完成函数参数的传递和参数的返回。但是当应用程序发出系统调用时,应用程序的运行和操作系统的内核运行实际上是有各自的堆栈,所以当应用程序发出系统调用时,是需要去切换堆栈、还需要从用户态转换到内核态。这些堆栈的切换和状态的转换都需要一定的开销,系统调用的开销是要比函数调用的开销大得多得,但这个开销是必要的,因为这个开销才安全。

所以os要建立中断、异常、系统调用号的对应的服务例程的映射关系的初始化开销、建立内核堆栈、验证参数、内核态映射到用户态的地址空间、更新页面映射权限、内核态独立地址空间。
操作系统要有自己的堆栈,不能和应用程序的堆栈混为一谈。操作系统退出的时候要把这个堆栈保存,操作系统执行的时候要把这个堆栈恢复。同理,应用程序退出自己的堆栈也要保存退出现场,当操作系统执行完毕,应用系统继续执行要恢复它的堆栈。
操作系统是不信任应用程序的,所以要对参数进行检查。
当操作系统处理完数据后,会产生处理结果数据,这些结果数据要从内核态导到用户态,就是要拷贝结果数据,把数据结果从内核空间拷贝到用户空间。这就会引起内存状态的一个改变,比如页机制的转变、cpu的cacha和tob可能会被刷新。

#计算机体系结构及内存分层体系
操作系统一被加载到内存中,就开始对整个计算机系统进行管理和控制。而操作系统控制计算机的第一步就是:
首先进行管理和控制的就是内存,就是管理我们的物理内存。如何管理内存要知道计算机的体系结构和内存的层次结构。
第二步是'操作系统如何和我们的程序的编译、执行的过程进行交换',这里就涉及地址空间和地址生成。
第三步是操作系统如何进行有效的连续物理内存分配。

#计算机体系结构
CPU:程序执行在这里。
内存:放置程序的代码和要处理的数据。
外设(I/O):硬盘、键盘、鼠标、显示器等外设,配合程序运行,比如可以把程序输出打印到屏幕上、把程序运行的结果保存在硬盘中等。
所以此时,操作系统需要把cpu直接访问的物理内存有效管理起来。
#内存的层次结构
内存的层次结构就是cpu要访问的指令或者数据,它所在的位置是在什么地方。
cpu可以访问的数据有:寄存器、cache(这两部分是位于cpu芯片内部的,操作系统是没法对其进行直接管理的,但它们是速度极快、容量很小,能放的数据是非常有限的)、主存(又叫物理内存,用来放置操作系统本身以及当前要运行的代码,主存比寄存器和cache要大很多,但速度较寄存器和cahe要慢一些。我们可以在主存中同时存放很多个等待运行的程序,此时可能出现内存不够的情况,此时就要一些等待运行的程序放到硬盘上去,此时就需要操作系统做一些管理工作。此外因为内存是断电数据就丢失,这时就要求一些要永久保存的数据和代码要放到硬盘中,当一加电,数据还能从硬盘中读到内存中来)、硬盘(硬盘也叫虚拟内存!,硬盘的速度比主存要慢很多,但容量大很多)。
所以,有存储功能的器件是一个金字塔的形状,离CPU越近,速度越快、容量越小;离CPU越远,速度越慢、容量越大。
那对于这样的一种内存层次,我们编程人员如果既希望程序被CPU访问很快又希望更多的程序被存储,那是不可兼得的,但是如果在操作系统的帮助下,这又是可以达到的。所以我们要了解操作系统如何管理物理内存以及虚拟内存。

操作系统的内存管理目标:
1、抽象。就是希望应用程序在内存中运行的时候,由于操作系统的有效管理,这些应用程序不用考虑很多底层的细节,比如不用考虑物理内存在什么地方、不用考虑外设在什么地方,只需访问一个连续的地址空间即可。我们把这个地址空间称为逻辑地址空间。
2、保护。因为在内存中可以运行多个不同的应用程序,那就可能出现某个应用程序去访问了别的进程的地址空间,或者增删改查了别的进程的地址空间,这样就破坏了别的进程地址空间,这就需要一个有效的机制去保护不同进程之间的隔离。这种隔离机制的实现就需要操作系统去完成。
3、共享。因为进程之间除了需要隔离,有时还需要交换,就是访问相同的内存地址,就需要共享。所以操作系统还要有效的提供一个共享的空间,来使得进程之间能够安全可靠有效的进行数据的传递,这也是操作系统的进程间数据传递机制。
4、虚拟化。当我们在内存中放了很多应用程序之后,是很容易出现内存不够的情况,那么怎么让现在正在运行的程序获得足够的内存空间呢,就是把现在最需要的数据放到内存中,把以后需要访问的数据临时放到硬盘上去。这就虚拟的实现了一个大的内存空间的现象。而这种管理过程要做到对应用程序透明、还要做到满足应用程序运行需要的数据和内存空间。

例子:比如在操作系统的管理下,现在有4个程序P1、P2、P3、P4放在内存中运行,其中当前正在占用cpu运行的程序是p1, 那p2p3p4实际上就是处于等待状态,尤其是P4程序,它可能要等待一个过一会儿才发送的事情,比如等待键盘的一个输入,此时p4程序其实是没必要放在内存中的,而p1这个正在cpu上运行的程序,它可能需要更多的空间,所以此时操作系统就把p4的部分代码和数据就先导到硬盘上去了。此时P1程序就可以有效的执行了。当P1不执行了,操作系统就将cpu的控制权交给p2,p3, 这样p2p3就可以继续执行了。
这里涉及到逻辑地址空间和物理地址空间,我们的主存和硬盘是物理地址空间,而我们的运行程序开辟的空间是逻辑地址空间。
那操作系统要完成上面的4个目标,就要一些方法。
#操作系统管理内存的不同方法:
1、程序重定位
2、分段
3、分页
4、虚拟内存
5、按需分页虚拟内存
操作系统是个软件,它要完成上面的操作,是高度依赖硬件的,所以一是必须知道内存架构,二是要知道MMU(内存管理单元)就是硬件组件负责处理cpu的内存访问请求。

#地址空间与地址生成
地址空间就是物理地址空间和逻辑地址空间
物理地址空间有:以内存条为代表的主存,以硬盘为代表的另一种物理存储空间。这两种存储空间就是物理内存。物理内存的管理和控制是由硬件完成的。
逻辑地址空间是指一个运行的程序所拥有的内存空间。逻辑地址空间是一个一维的线性空间。是应用程序访问的空间。而逻辑地址空间也是落在实实在在的物理地址空间上的。

#逻辑地址空间的生成
例子,比如我们写一个c程序,比如xxx.c 文件A,这个.c文件经过编译器编译成汇编语言的文件,比如xxx.s 文件B,就是一个汇编程序;这个汇编文件再被汇编器汇编成xxx.o 文件C;.o文件再通过一个小程序叫linker链接程序,把.o文件编译成一个单一的连续的执行程序.exe 文件D;.exe文件再通过一个叫loader的载入程序,进行程序重定位,程序才载入到内存中E开始运行或者说执行这个文件。
A:这个文件中的符号比较接近人类理解的符号,这个文件中符号比如变量名、函数名,这些变量函数在硬盘中都有存储地址,所以这个文件的地址就是这个文件中变量的名字的地址、文件中函数的名字的地址,这些地址都是一种逻辑地址。而变量名函数名对应的变量值和函数体存在在硬盘中的地址是物理地址。
B:.s文件中的符号就更接近机器语言了,但它依然是用符号代表变量名和函数名。
C:.o程序中符号就几乎是机器语言了,它把.s文件中变量名和函数名都已经转换为相应的地址了,这个地址是一个从0开始的连续空间,这就是一种逻辑地址。
D:因为.o程序的执行需要依赖底层的一些程序的解释,通过linker就把.o程序转化为一条条直接可以执行的程序,比如.exe 文件,这个.exe文件是可以直接在内存中执行单仍然也可以存放在硬盘中的程序。
E:当程序进入内存中时,这个最初是.c的文件已经变成一条条地址数据了,这些地址数据就是逻辑地址。内存执行的就是这一条条地址。而这一条条地址返回去映射的就是.c文件里面的一条条代码。
上面的从C语言文件到文件被放到内存中开始执行,这全部的链条过程中不需要os,只要编译器、汇编器、linker,loader就加载到内存中了。此时我们放到内存中去的程序是一行行的逻辑地址,不是物理空间地址。
#物理地址空间的生成
应用程序-- 访问--逻辑地址--对应到物理地址,比如内存上的物理地址 
cpu运行一条应用程序(或者说运行一条指令),它只知道这条指令的逻辑地址,cpu里面有一个MMU(内存管理单元)把这个逻辑地址映射到内存中的物理地址上。这样逻辑地址就映射到物理地址上了。而这个映射就是查表,mmu就是一个映射表。通过这个映射表就把实际数据从内存的物理空间中取出来了。

当cpu运行某条指令时,cpu的alu会需要要计算的数值,alu就给mmu发出请求,这个请求就是带着逻辑地址的参数, mmu接到请求后就去它维护的表里面查找这个逻辑地址映射的物理地址,如果表里面有,你把物理地址的数据传给alu,如果没有,mmu就去内存中的map里面去找,如果在内存的map中找到了,cpu的控制器就会给主存发出请求,请求需要主存里面的某个物理地址的内容,这个内容实际上就是这条指令的内容。主存收到控制器的请求后,就把内存中的数据通过总线传递给cpu,cup拿到数据后就开始对这条指令进行执行了。

在上面的过程中,就是在cpu执行某条指令前,os先把这条指令对应的逻辑地址和物理地址之间的关系建立起来。这个对应关系可以提前放到内存中,这样就可以加快访问过程。

所以,os首先要保证每条程序它只能访问它自己的地址空间,它自己的地址空间包含一是它的起始地址,二是它的地址长度。也就是我们可以通过一个起始地址和长度规定这条程序只能访问这个内存区域,一旦这条程序访问的数据超过这个区域的话,就是一个不合法的访问。所以操作系统要建立一个合法访问的表,并且要不断去维护这个表。一旦cpu开始执行这条指令时,cpu就要查os建立的这个map,map返回cpu这条指令的逻辑地址对应的物理地址,然后把物理地址上的数值或者指令给取出来。如果这个过程cpu返回一个内存访问异常,此时os就需要进一步去处理。这实际上就是一个地址的安全检测的过程。

#连续内存分配
内存碎片问题
分区的动态分配:第一适配,最佳适配,最差适配。
压缩式碎片整理、交换式碎片整理
内存分配是os管理内存中很重要的一个环节。
os给一个程序分配内存空间的时候,会产生一些无法再次利用的内存空间,这些无法进一步利用的空间叫内存碎片。碎片分外碎片和内碎片。外碎片指分配单元之间的无法再次利用的空内存空间。内碎片是这一块连续的内存空间已经分配给了某个应用程序,但这个应用程序占用的空间要小于分配给它的空间,这就造成了内部的一些空间浪费,这叫内碎片。
当os要把程序从硬盘加载到内存中,os 就要给这个程序在内存中分配一块连续的区域。
当程序在cpu中运行的时候,cpu很可能要去访问一些数据,那os也要为这些被访问的数据分配一些内存空间。
所以os要会进行内存分配管理,如果分配管理,os是C写的,就是用C写一些算法,让算法实现有效的内存分配。而这些算法有,比如第一适配,只要找到一块连续的空间比程序可以占用的空间大就可以,就把这块连续的内存空间分配给这个程序。还比如最佳适配算法,就是要找到所有连续的空间,然后对比所有的空间和程序能占用的空间的大小,找到产生内碎片最小的空间分配即可。还比如最差适配算法,就是找到产生最大内碎片的空间去分配。
有分配就有回收,比如回收碎片区域,这样就能继续往内存中放程序了,这样就能充分利用内存了。
还有,比如建立一个列表,列表里面放空闲碎片的大小,并且从小到大排列,这样就能更快的为后面的程序分配空间了。
再比如,合并相邻的空闲分区,让碎片合并,就可能有大块的空闲块了,就有可能被利用了。

#压缩式与交换式碎片整理
把当前内存中已经分配好区域的程序,由于这些程序之间都有碎片空间,让这些程序依次挪动,让它们的空间紧挨,中间不要碎片。这个挪动compact(压缩)的过程就是内存里面的程序的一个拷贝过程。这个过程一是要考虑什么时候要对这些程序进行重定位是合适的?二是这个拷贝过程开销大不大?程序正在运行中是不能做挪动操作的,因为正在运行的程序如果挪动,那运行中用到的地址就出现错误了,就不行。
第二种方法就是换入换出swapping,交换式碎片整理,就是把硬盘当作内存的一个后备,把等待中的程序从内存中再拷贝到硬盘里面,释放部分内存,以供其他程序运行。所以现在的硬盘上有部分空间叫虚拟内存。所以os也要有虚拟内存管理工作,比如os要考虑把等待中的哪个程序先换出到虚拟内存里,啥时候换出最好,啥时候再换进主存,换入换出以程序为单位还是以更小的粒度为单位合适,因为换入换出也是有开销的,等等os需要考虑的问题。这就是操作系统如何优化虚拟内存。就是如何优化换入换出机制。

小结:编译器是把一个我们人类能看得懂的基于符号的地址空间 转化为 一个基于逻辑地址的地址空间。而os是进一步把逻辑地址映射到物理地址,就是os把逻辑地址转化为物理地址,os就是要建立要这个映射关系,使程序在内存中可以正常执行,并且要保证在内存中的不同的应用程序之间不能相互破坏,所以os还要进行地址安全检查。

上面讲的是os如何分配连续内存,下面讲如何管理非连续内存
#非连续内存:分段segmentation、分页paging、页表page table
进行非连续内存管理的好处就是,一是更好的利用内存,提高内存利用率。二是可以共享代码和数据,比如共享库等。三是支持动态加载和动态链接。
逻辑地址也就是虚拟地址,从虚拟地址到物理地址之间的转换,可以用软件方案实现,也可以用硬件方案实现。如果完全用软件方法实现,那这个开销是很大的,所以我们要考虑如何和硬件结合去实现,比如考虑和cpu中一些对内存管理的部件进行结合,共同去实现。
所以有硬件支持的非连续内存管理主要有分段机制和分页机制。

#分段机制
程序的分段地址空间
分段寻址方案
我们的程序其实是由各个'段'组成的,比如代码执行方面有主程序main program、子程序、共享的库,这些就构成代码的不同分段。比如数据也是有栈段、堆段、其他的共享数据段,不同的段是有不同的属性的。
所以分段机制就是把程序更细粒度的分开,不同粒度不同管理。
比如,一个程序,经过编译、汇编、linker、loader到内存中就是一段连续的逻辑地址,我们再把这段连续的逻辑地址继续细分为堆、运行栈、程序运行的数据、程序代码段(也叫text段,又可细分为库、用户代码等) 等等细分,然后让这些不同的部分放在不同的内存物理位置,然后不同的物理位置设置不同权限,比如可读、只写、共享等权限。
所以加载到内存中的连续逻辑地址段要一一映射到不连续的物理内存地址。我们可以把连续的逻辑地址看成一个一维的数组,然后把这个数组按照分段机制映射到各自对应的物理地址空间。而这个分段机制我们不是通过软件来完成的,因为通过软件的开销非常大,我们是通过设置硬件的方法来映射的。
也就是一个程序对应着--一个连续的一维的逻辑地址--对应着分段的物理地址。
那映射的方法就是 段寄存器+地址寄存器 的实现方案。就是将一维数组映射成一个2元组(s, addr), s表示段号segment number,addr表示段内偏移。然后把2元组的存储和内容的存储放在不同的地方,就是段寄存器+地址寄存器的方法,x86就是这种方法。
也就是把一个连续的一维逻辑地址映射到不同段的物理空间,所以需要一个'段表'segment table, 段表第一个字段放 逻辑地址对应的物理地址的段号,第二个字段放 这个段的偏移。
一个应用程序通过cpu执行每条指令,cpu首先是去寻址,寻址就是去找这条指令的数据在什么地方?代码在什么地方?cpu寻址就是去查段表看数据、代码在哪个物理地址。这个段表就记录了数据、代码所在的物理地址的首地址(段号)和偏移量(addr)。cpu从段表中找到物理地址后,cpu还会比对这个地址是否和操作系统设置的段号段长是否一致,一致就是说明这个指令是一个合法的寻址,如果不满足cpu会给os返回一个异常,就是这条寻址是一个非法访问,让os再确认,os再决定是kill这个程序还是重新再执行这个程序。如果是合法的,就从逻辑地址映射到物理地址了,然后从物理地址里把数据取出来交给cpu进一步处理。
段表是操作系统在cpu寻址之前就由os建立好了,os建好段表后,段寄存器就可以正常工作了。

#分页
现在cpu用分段机制已经比较少了,现在cpu大部分都是用的分页机制。
页号、页内偏移。分段机制里面,段的大小是可变的,而在分页机制里面,页的大小是固定不变的。页就是帧frame, 也叫页帧。所以我们要划分物理内存至固定大小的页帧。一个页的大小可以是512k, 4096k,8192k等等。
然后再划分逻辑地址空间至相同大小的页帧。
所以页帧frame表示的就是物理页,而page表示逻辑页。所以就是建立从page to frame之间的映射关系。所以会有页表、有MMU,有TLB(叫块表,来完成对页表的保存),这些都是cpu中重要的组成部分。
帧frame, 页帧,就是物理内存被分割成大小相等的帧。所以页帧表示的就是物理内存的地址,这个地址包含帧号和帧内偏移。frame number , offset.
例子,比如一个物理地址=(3,6),意思就是这个物理地址的页帧号是3,页帧内的偏移是6,那请问它具体的物理地址的位置是多少?

页寻址机制
cpu开始执行一个程序,这个程序已经是一个连续的逻辑地址串,cpu执行程序第一步就是开始寻址,根据页寻址机制,是先把逻辑地址串划分成一个个等长的page,然后生成一个page table, page table就记录了一条条page id页号和这个页对应的帧号,和offset,这样页地址就映射到帧地址了。
page table也是os建立的,os建立好 page table后,程序就可以正常跑起来了。
从页映射到帧
页是连续的虚拟内存
帧是非连续的物理内存   #A
不是所有的页都有对应的帧
A: 所以逻辑地址映射到物理地址后,物理地址可以是不连续的断断续续的碎片,这样可以充分利用内存空间。

#页表、TLB
页表
每个运行的程序都有一个页表,页表属于程序运行状态,会动态变化。PTBR,页表基址寄存器。
页表其实就是一个大数组,这个大数组的索引就是page number页号,这个索引上存的值就是frame number,就是帧号
TLB,转换后备缓冲区   #A
二级/多级页表
反向页表inverted page table
A:cpu里面有个MMU内存管理单元,这个内存管理单元里面又有个TLB, translation look-aside buffer, 这是一个缓冲cache,这个缓冲就缓冲的是页表里面的内容。内容是一个p值和一个f值,p值是key,f值是value.TLB是一个关联内存器,这种内存器具备快速访问的性能。
也就是当cpu寻址的时候,它先根据p值去查tlb,如果tlb中存在p对应的f的话,就一下就找到物理地址了,就可以去内存中找这个物理地址对应的内容了。当tlb中p值missing的时候,cpu就要去内存中查页表。如果页表中有,页表中对应的就是frame number.

#虚拟内存的起因
存储器的层次结构:把速度更快容量更小的存储器放在离cpu更近的地方,这样cpu就能更快的访问到。
所以第一层是寄存器register, 第二层是寄存器和内存之间的cache,cache是缓存内存里面的数据,cache比内存快比register慢。这样数据就可以从cache中直接被cpu读取,而不用访问主存。第三层是主存main memory.第四层是硬盘中的虚拟内存magnetic disk,第五层是magnetic tape磁带。

虚拟的大内存的感觉就是:把常用的数据、代码、程序等放到主存,不太常用的先放到硬盘,给人的感觉是这些大型的数据都是放在内存中的感觉,这种模式要cpu、mmu内存管理单元、os内核等协同才能完成。
早期,当程序太大,超过内存容量时,当时采取的是 手动的覆盖 overlay 技术,只把需要的指令和数据保存在内存当中。
后来,采取 自动的交互swapping 技术,把暂时不能执行的程序换到外存中。
现在,要在有限的内存中放入更大更多的程序,采取的是以更小的页粒度为单位装入内存,就是 自动的虚拟存储技术。
以前是程序员自己编写一些功能,完成把常用的代码放到内存中,不常用的放到硬盘上。这样就可以在小内存里面跑一些大的软件。具体的说是,程序员把一个大的程序划分为若干个小功能模块,并确定各个模块之间的覆盖关系,这样就实现overlay了。通过overlay就把外村的程序装入内存了。这种方式实际上是以时间换取空间的方法。
当多道程序在内存中时,我们要让正在运行的程序或者是需要运行的程序更够获得更多的内存资源,我们就将暂时不运行的程序送到外存,把内存空间腾出来,这就是swapping技术。在swapping技术思路下,os是把一个进程的整个地址空间的内容都换出swap out到外存中,就是写入到硬盘中去, 同理,换进swap in就是将外存中某个进程的地址空间全部读入到内存中。换入换出的内容是某个程序的整个地址空间。而这种方式也是通过程序员通过编程实现的。
swapping技术是把整个程序导进导出,其实这种开销还是很大的。后来的以页粒度为单位后,只导进导出页单位个代码或者程序即可,开销小多了。
分页、分段是通过硬件技术支持的。在分页分段的基础上再加os的有限管理,就实现了 虚拟内存 技术了。就是数据导出导入以更小的粒度进行倒换了。并且这种倒换的功能的实现不是由程序员实现的,是由os实现的。
所以,我们是在硬盘上专门划出一个区域,这个区域就叫交换区,也叫硬盘的缓存区。
换入换出的过程中,比如把一页程序从内存中的某个地址换出,当这页程序再换进的时候,进去的地址就不再是原来的内存中的地址了,此时cp去执行这个程序时寻址就会出现问题,此时就需要采用动态地址映射方法。就是如果我们程序都被分成一页一页,页和页之间是逻辑关系,不需要物理位置之间的依次排列那种关系了。所以通过地址映射机制就可以找到有逻辑关系的两页对应在没有连续紧挨的物理内存地址上了。所以只要页表要先建立起来,程序换入后的重定位就没问题了,程序照样可以正常跑。这种换入换出是由os完成的,程序员可以屏蔽这个过程。
小结:在内存不够用的情况下,可以采取覆盖技术和交换技术。但是,覆盖技术需要程序员手动把整个程序划分为若干个小的功能模块,并确定各个模块之间的覆盖关系,增加了程序员的负担。而交换技术,是以进程为交换单位的,需要把进程的整个地址空间都换进换出,增加了处理器的开销。
虚存技术,就是虚拟内存管理技术。虚拟技术借鉴覆盖技术,也是不是把程序的所有内容都放在内存中,这样内存可以运行比当前的空闲空间还要大的程序,但程序的哪些模块放内存哪些模块暂放硬盘,这都由os来完成,无需程序员干涉。同时,虚拟技术也像交换技术那样,也是把程序在内存和外存之间 换入换出,但虚存技术换入换出的不是整个进程的全部地址空间,而是借助硬件的分页分段等机制和cpu里面的mmu,os内核去建立动态地址映射表,也就是建立页表的方式去支持程序页的换入换出。
这就需要我们的程序有一定的要求,就是要求程序要具有局部性。
程序的局部性原理:principle of locality,指程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,分别局限于一定区域。
局部性的表现有时间局部性和空间局部性。时间局部性就是一条指令的执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短的时期内是最好的。空间局部性指当前指令和临近的几条指令,当前访问的数据和临近的几个数据,都集中在一个较小区域内,就是这些指令或数据加载到内存中后,它们之间的距离靠的比较近。
所以我们设计程序时,一般都会要求代码的局部性要好,代码局部性好就意味着代码的执行效率会更高些。就是访问快、执行效率高、不需要频繁读硬盘。

程序的编写方法对缺页率的影响
例子:页面大小为4k,分配给每个进程的物理页面数为1。在一个进程中,定义了如下的二维数组int A[1024][1024],该数组按行存放在内存中,每一行放在一个页面中。
程序编写方法1:            程序编写方法2:
for(j=0; j<1024; j++)        for(i=0; i<1024; i++)
for(i=0; i<1024; i++)        for(j=0; j<1024; j++)
A[i][j]=0                A[i][j]=0
说明,这个二维数组A是一个整型的数组,1024x1024这么大的空间。在32位机上,一个整型对象是4byte, 也就是这个数组的每个元素是4byte, 所以这个数组总共是4兆的空间。但是目前物理内存只有4k,也就是每次只能加载1024个整型对象到内存中。
此时上面两个程序都放内存中跑会出现什么情况?在C语言中,是按照行优先来放置二维数组的。所以a00,a01,a02,,a01023是放在一个页内,a10,a11,a12,,a11023是放在下一个页内。
当我们开始运行第一个程序:假如第一个程序已经加载到内存中,但是第一个程序要访问的数组A还在硬盘中,不在内存中。此时第一次执行这个程序会产生缺页的异常。此时os就会把A数据中的4k的数据(a00,a01,a02,,a01023)放到内存中来,因为内存给每个进程分配的物理页数是1,而一个页是4k。此时程序1会先访问A00这个数据,此时A00已经加载到内存中,所以可以正常访问。当程序1访问A10时,但A10没有在内存里,还在硬盘中呢,就又产生一个缺页的中断,os接到中断后,就会把a10,a11,a12,,a11023这些数据再加载到内存中的一个页内让程序1去访问A10数据地址,以此类推,程序1的两个for循环执行完毕,要产生1024x1024次缺页中断,光中断就有1兆的中断次数,每中断一次就要完成一次从硬盘到内存之间的读写操作,1兆次的读写操作就非常影响效率。同理,程序2会产生1024次缺页中断。
所以我们写代码一定要注意自己代码的局部性问题。
os考虑的思路是,当前执行的代码,它近邻的数据和代码最有可能被访问。然后os根据这个思路,把当前正在被执行的代码的最近邻的代码和数据放到内存中,最远邻的代码和数据先放到硬盘中。
所以,os在硬件的分段或者分页的支持下(cpu中的mmu,TLB表、内存中的页帧),来完成虚拟内存的管理。

为什么要用虚拟技术呢?比如一个cpu是32位的机器,理论上它可以访问4GB的数据,而实际中这个机器的内存只有256M的物理内存,虚拟技术可以提供更大的内存空间。这样也可以同时跑更多更大的程序。
说明:操作系统的内核是常驻内存的,是不能被换进换出的。用户程序可以根据程序的运行状态进行部分的换进换出。
由于每个程序都是部分的在内存中执行,部分的从内存中换进换出,所以给我们人类的感觉就是这些程序都是同时在执行的。就是多道程序设计。
所以,物理内存的分配是不连续的,虚拟地址空间的使用也是不连续的。

#页式内存管理
页表page table, 完成逻辑页logical pages到物理页帧physical frames的映射maps to.
#页表表项
页表里面除了有逻辑页的id、物理页页帧号frame的id、还有一个bit位叫驻留位,取值0或1,这个位是0代表不存在,就是这个虚拟地址空间对应的物理地址空间是没有的,所以这个位是0,cpu就给os发一个地址访问异常,此时异常机制就引发,这个异常就是缺页异常,也是cpu向os发送一个请求调页,os处理方法就是页面置换。
还有一个2bit的保护位:表示允许对该页做何种类型的访问,比如只读、可读写、可执行等。
还有一个bit的修改位:表示这个页是否有被修改过,就是是否有被写过。写过这个位置1,没有写过置为0. 写过就说明现在内存中的数据和以前放在在硬盘中的数据不一致了,被改写过了。此时这个页换入换出就要重新考虑该怎么处理了。因为我们要保证内存中的数据是硬盘中的数据的无错换出换入。
好友一个bit位是访问位:如果该页面被访问过,包括读操作和写操作,则设置此位为1。这主要是用于页面置换算法,如果这个位经常是0,那os就据此判断把这个页给换出去。
小结页表表项依次是:逻辑页号、访问位、修改位、保护位、驻留位、物理页帧号。

例子:假设16位的逻辑地址,有64k, 而物理内存只有32k,页面大小是4k。那么有16页逻辑页、8页物理页。
比如我们现在想把索引为0的虚拟地址上的内容(就是索引为0的逻辑页上的内容)赋给cpu的寄存器,也就是汇编语言的代码:mov reg, 0 8192
我们可以看到索引为0的虚拟地址的驻留号是1、页帧号是2,那么2*一个页的大小4K=8192位, 所以cpu要执行索引为0的地址,它执行时找的数据是在物理内存中8192位上。
再比如我们想把虚拟地址32780上的内容读到寄存器里:mov reg, 32780  
我们从页表看,这个地址在32k-36k之间,也就是在索引为8为逻辑页上,但这个逻辑页的驻留号为0,就表示cpu访问这页会产生缺页异常。
#请求调页、页面置换
基本思路:当一个用户程序要调入内存运行时,不是将该程序的所有页面都装入内存,而是只装入部分的页面,就可以启动程序运行了。
在运行的过程中,如果发现要运行的程序或要访问数据不在内存,则向os发出缺页中断请求,os在处理这个中断时,将外存中相应的页面调入内存,使得该程序可以继续运行。
随着程序的继续执行,调入内存的页面越来越多,内存不够用了,此时os将不常用的页换出,可能会常用的页换进。这就是页面置换。所以要设计一个有效的页面置换算法非常重要。

缺页中断处理过程:
当cpu load一个内存地址:cpu load M,就是当cpu执行一个指令时,第一步cpu先reference 查 page table, 如果page table中的驻留位是0,cpu就发出操作异常,此时cpu的执行权就交给了os:
os第一步先查看内存中是否有空闲的物理页,如果有,os就将硬盘中的数据以页为单位读到内存中的空闲物理页中。第二步修改页表reset page table, 就是把原来逻辑页中那个指令地址的驻留号改为1,页帧号改为刚才的空闲物理页页号。第三步让cpu跳回到刚才执行的那条指令,重新执行这条指令。这次cpu就可以正确取到这条指令地址的物理存储值了,就可以继续执行程序了。
如果os第一步检查内存中是否有空闲物理页,如果没有,此时就调用某个页面置换算法,假如根据这个页面置换算法,我们决定清空物理内存中某些页,如果被清空的页没有被修改过,我们就直接清空,因为硬盘中还有一模一样的一份,如果被清空的页有被修改过,那就写回到硬盘中,这样就清理出内存空间了,然后再换入当前正在执行的程序需要的数据页,再更改reset page table, cpu再重新执行。
这个过程就是缺页中断异常处理的一个流程的原理。

硬盘存储数据的特点:
不同的数据是采用不同的存储形式的。
第一种数据,比如数组,假如一个很大的数组,它就以一个数据文件的形式放在硬盘上,当需要访问这个数组时,不是把整个数组全部放到内存中的,是把数组分成一个个页帧,内存只读cpu当前需要的页帧。
第二种数据,比如代码,cpu执行代码其实就是执行一条条指令,这个指令数据是以可执行二进制文件的形式放在硬盘中,也就是我们硬盘中存在很多可执行程序,可执行程序文件中的指令就被当成数据被读到内存中让cpu执行。
第三种数据是动态链接库,比如我们的软件在执行过程中需要很多库,这些库实际上也是放在硬盘中的,只有cpu需要的时候才加载库的代码和数据到内存中。
第四种数据是程序在运行过程中产生的中间数据,这些中间数据是动态产生的,不像上面的三种数据就是直接保存在硬盘上的那种,这些动态产生的数据很可能占用了内存很多的空间,这些数据需要换出到硬盘中,os专门在硬盘上开辟一个区域叫做swap file, 就是换入换出分区,这个区域放这些没有与文件直接对应的这些数据。
上面的四类数据就形成了我们的后备存储backing store, 或者说二级存储,或者说二级缓存。

#最优页面置换算法
#先进先出算法
#最近最久未使用算法
#时钟页面置换算法
#二次机会法
#最不常用法
#局部页替换算法的问题、工作集模型
#两个全局置换算法
#抖动问题

#进程process的定义、组成、特点、进程控制结构
这是进程的静态描述,进程状态是进程的动态描述。
从某种程度上说,进程可以被看做一个个正在被执行的程序。一个程序被执行,它需要cpu运算、内存占用、网络占用、硬盘读写等等。也就是一个程序的执行要消耗计算机的各种资源。
为什么有进程这个说法?因为刚开始我们的计算机一次只能运行一个程序,后来cpu越来越快、内存越来越大,我们就可以在内存中放更多的运行程序,或者我们也可以一个程序运行多次,此时如何区别这些程序,就用进程这个概念来区别。就是用进程来表示一个程序的整个执行过程。

#进程的定义:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
比如,我们写一个可以完成某个功能的小程序source code, 比如用C语言写的,这个小程序被编译器编译、汇编器汇编、link程序 变成一个一个可执行文件executable file, 这个可执行文件包括代码段、数据段、其他的数据结构等(Header 、code、initialized code、、等等),这个可执行文件是以静态的方式放在我们的文件系统中,以可执行文件的形式放在文件系统中的,但它还在硬盘不在内存,cpu还没有执行它。 然后这个可执行文件被加载loader到进程的地址空间process's address space,就是说此时cpu可以执行这个文件中指令了。cpu执行这个文件的指令、处理这个文件的数据,实现这个文件的功能,这个过程就是进程。这个过程是一个动态的执行过程。但这个动态执行过程是由程序代码决定的。

#进程的组成包括:
1、程序的代码             #需要放到内存中
2、程序处理的数据        #需要放到内存中
3、程序计数器中的值,指示下一条将运行的指令     
4、一组通用的寄存器的当前值,堆、栈       #使用cpu大量的寄存器
5、一组系统资源                                       #就是一个进程要消耗很多计算机资源,比如cpu、内存、网络等
总之,一个进程包含了正在运行的一个程序的所有状态信息。
进程不等于程序。程序是一段静态的代码,进程是一个动态的执行过程。

#进程与程序的联系:
1、程序是产生进程的基础            #进程中执行的各种功能都是程序的指令要实现的功能。代码限制进程的功能。
2、程序的每次运行构成不同的进程  #A
3、进程是程序功能的体现。
4、通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。  #B
A: 程序只有一份,就是放在硬盘中的一份,但这个程序可以执行多次,每次执行产生不同的进程,因为每次执行可能用到的数据不一样,这样可能会导致产生的结果不一样。就是多次运行同一个程序,但执行结果可能会因为每次用到的数据不一样而不一样。比如一个排序程序,虽然每次排序的数据不一样,那每次排好序的数据结果自然不一样,但是都是排好序的结果。
B:有可能是多个程序合在一起去完成一个更大的功能。所以要完成一个更大的功能,要用到程序调用。

小结:进程是动态的,程序是静态的;程序是有序代码的集合;进程是程序的执行,进程有核心态/用户态。#A
进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序可长久保存。
进程与程序的组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)   #B
A:核心态是指os掌握cpu的控制权。我们写代码并没有要求os去做什么,我们写的程序都是用户态,我们去跑程序也是用户态,为什么代码执行时os要控制cpu控制权进入核心态呢?实际上,进程在执行过程中,它要完成某些特定功能,而有些特定功能只有os才能提供,比如程序写的要读一个文件,那读文件的过程是要和硬盘打交道的,而和硬盘打交道的过程是由os完成的,此时进程需要给os发出请求,os代表进程在内核中进行执行,这时,我们可以说这个进程处于核心态。
B:就是说程序中不包含数据和进程控制块等信息,而进程包含程序,程序是进程的一部分。

一个做蛋糕的例子:做蛋糕的食谱=程序     做蛋糕的人=cpu    做蛋糕的原料面粉糖奶油=数据  做蛋糕的过程=进程
所以,做蛋糕的过程要有食谱、人、原料、进程块(及时状态信息,就是做到哪一步了), 当做完一个蛋糕,一个进程就结束了,完成的功能就是我们得到一个蛋糕。
如果一个人在做蛋糕的过程中,她的电话响了,那接电话是比做蛋糕更重要的事情,做蛋糕必须要被打断,接完电话再继续做蛋糕。cpu就要从有一个进程(做蛋糕)切换到另一个进程(接电话)。

#进程的特点:
1、动态性:可动态的创建、结束进程。
2、并发性:进程可以被独立调度并占用cpu。#并发并行
3、独立性:不同进程的工作不互相影响。  #A
4、制约性:因访问共享数据/资源或进程间同步而产生制约  #B
A:就是说cpu在执行过程中,它可能是某段时间内执行了好几个进程,所以每个进程在这段时间内的执行都是被打断的,虽然可以被打断,但打断后继续再执行的正确性是要保证的。这就是os要保证的,保证一个进程在执行过程中被打断在继续执行时,它的数据、代码不被破坏,保证仍能继续正确执行。要保证这个os使用的是页表机制保证的,不同的程序放到不同的页表,不同的页表放在不同内存物理空间,不同程序只能访问自己的页表对应的物理地址空间,不能随便访问甚至改写其他程序的页表对应的物理空间。如果一个程序要去访问不是它对应的页表对应的物理内存空间,就发送缺页异常、页错误等异常,cpu就发这个异常给os, os去考虑是完善这个程序的页表继续执行还是kill掉这个程序。
B:进程和进程并不是完全独立不相互干涉的,进程和进程之间是有交互的,比如一个进程要等待另外一个进程执行到一定时间后才能执行,所以需要os按照不同进程的特点来协条这些进程的执行。这就涉及到进程之间的同步互斥的概念。
进程管理:进程状态、线程、进程间通信inter process communication、进程互斥与同步、死锁deadlock.
进程管理机制,就是os如何进行进程管理,os也是一个程序,所以os的编写也是需要算法和数据结构来完成os需要完成的功能的。
程序=算法+数据结构
描述进程的数据结构就是进程控制块,process control block, PCB, os为每个进程都维护了一个PCB,用来保存与该进程有关的各种状态信息。
#进程控制结构
进程控制块:os管理控制进程运行所用的信息集合。os用PCB来描述进程的基本情况以及运行变化的过程,PCB是进程存在的唯一标志。一旦一个进程消失了,这个进程控制块pcb也随之消失。一旦创建要给进程就创建要给pcb。

#PCB具体包含什么信息?如何组织的?进程的状态如何转换的?
内存中某以时刻会存在多个进程,PCB记录这个进程是什么时候创建的、什么时候结束的、中间是否有被切换,都是PCB要记录的。
PCB包含以下三大类信息:
1、进程标识信息。如本进程的标识(进程id);本进程的父进程标识(pid),也就是本进程的生产者标识;用户标识。
2、cpu的状态信息。pcu在执行指令的时候,都是把数据放在寄存器中,所以cpu的状态信息主要就是指寄存器的状态信息。寄存器的状态信息就是进程的运行现场信息。寄存器的状态信息有:
(1)用户程序可以使用的数据、地址等寄存器。
(2)控制和状态寄存器,如程序计数器PC、程序状态字PSW. 
(3)栈指针,过程调用/系统调用/中断处理和返回时需要用到它。              #A
3、进程控制信息:
(1)调度和状态信息,用于os调度进程并占用cpu使用。  #B
(2)进程间通信信息,为支持进程间的与通信相关的各种标识、信号、信件等,这些信息存在接收方的进程控制块中。
(3)存储管理信息,包含有指向本进程映像存储空间的数据结构  #C
(4)进程所用资源,说明由进程打开、使用的系统资源,如打开的文件等。#D
(5)有关数据结构连接信息,进程可以连接到一个进程队列中,或连接到相关的其他进程的PCB.    #E

A:这是CPU的执行过程信息,比如执行加法时会不会有溢出,有没有溢出就是状态信息,这些状态信息就放在寄存器里面。还有比如执行过程,执行到什么地方了?栈在什么地方?堆在什么地方?这就是执行的过程,过程的保存也是放在寄存器里面,比如程序计数器、栈指针都保存在寄存器。
B:让这个进程处于运行状态,就是占用cpu执行状态,还是等待的状态,还是处于就绪的状态。就是这个进程当前的执行现状。
C:进程本身是需要内存的,进程本身的代码、数据都在内存中,所以内存信息也是要管理的,比如这个进程占用了多少内存、是否要给这个进程分配新的内存、是否要回收这个进程的内存。
D:一个进程的运行除了要用到数据还可能要用到文件等,怎么组织这些数据和文件。
E:一个进程可能会有父进程、子进程,所以进程之间是有关系的,这种联系用一个List把这种关系记录下来。

#进程控制块PCB的组织方式
PCB的组织方式一般有链表的方式和数组的方式,数组的方式又叫索引表的方式。目前我们用的最多的是链表的方式,因为PCB是一个动态的过程,数组的方式组织PCB的话,动态的插入和删除所用的开销就比较大。

#进程的状态state
进程的状态是一种对进程的动态描述。包括进程的生命期管理、进程状态变化模型、进程挂起模型。
#进程的生命期管理
进程创建   #A
进程运行    #就是这个进程占用cpu正在执行,就是内核选择一个就绪的进程,让它占用cpu并执行。#B
进程等待    #C
进程唤醒    #D
进程结束     #E
A:引起进程创建的3个主要事件:
1、操作系统初始化时。就是操作系统创建第一个进程,我们称为init进程,init进程负责创建新的进程,比如用户请求创建一个新进程,os收到请求后就代用户去创建一个。
2、用户请求创建一个新进程;
3、正在运行的进程执行了创建进程的系统调用。
B:如果有多个就绪的进程,那os选择哪个进程让cpu执行呢,这就涉及到调度算法。
C:进程等待除了因为现在cpu正在被其他进程占用,还有就是因为这个正在被执行的进程出现阻塞了,所以需要等待。比如当前进程请求并等待系统服务而无法马上完成,比如当前进程启动了某种操作而无法马上完成,比如当前进程需要的数据没有到达等等。要说明的是,进程只能自己阻塞自己,因为只有进程自身才能知道何时需要等待某种事件的发生。所以出现阻塞的进程os就让这个进程进入等待状态,让就绪状态的其他进程占用cpu。而进程等待这个事件的触发是由这个进程本身触发的,就是进程本身发起进程等待这个事件。
D:处于进程等待的进程才会有被唤醒的事件,唤醒的原因有比如被阻塞的进程需要的资源被满足了,比如,被阻塞进程等待的事件到达了,比如将该进程的pcb插入到就绪队列。要说明的是,进程只能被别的进程或os唤醒。进程被唤醒就是进程从等待状态转换到就绪状态。
E:进程的所有功能完成进程就正常退出。
或者进程的功能没有完成但出现了错误,程序主动退出。
或者进程的功能没有完成但出现了致命的错误,比如要访问它不该访问的地址空间,os就强制它退出了。
或者该进程没有完成功能呢但被其他进程所杀,也是强制性的杀死。比如一些管理上的进程觉得这些应用进程占用资源太多了,破坏了整个系统的安全和可靠性,就强制杀死了这个应用进程。

#进程状态变化模型
进程在生命结束前处于且仅处于三种基本状态之一:
运行状态running: 当一个进程正在cpu上运行时
就绪状态ready: 一个进程获得了除cpu外的一切所需资源,一旦得到cpu就即可运行。
等待状态blocked,又称阻塞状态:一个程序正在等待某一事件而暂停运行。如等待某资源,等待输入输出完成。
但是,不同系统设置的进程状态数目不同。上面只是三种基本状态。
running-->blocked
running-->ready    #为什么有running到ready呢?#A
blocked-->ready
ready-->blocked
A:我们现在计算机都是并发执行的,就是有好几个程序都是处于就绪状态的,就是我们的内存不断扩大,可以同时放好几个程序都在就绪状态,这时cpu就是分时进行计算的,当一个进程的占用时间到了,它就得转为就绪状态,让其他就绪进程占用cpu.
除了上面的三种状态,进程还有:
创建状态:new, 一个进程正在被创建,还没被转到就绪状态之前的状态。
结束状态:exit,一个进程正在从系统中消失时的状态,就是正在消失但还没有完全消失,这时这个进程的PCB还存在,只有PCB彻底消失后才表示这个进程结束。
NULL-->New:一个新进程被产生出来执行一个程序。就是生成了一个PCB.
New-->ready:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态。就是PCB里面的所有数据都初始化完毕了,这时进程就可以被执行了。PCB初始化的过程是非常块的。
running-->ready: 处于运行状态的进程在其运行过程中,由于分配给它的cpu时间片用完了而让出cpu。这个步骤由os的调度器来执行。这就涉及到调度算法。os还管理着时钟,所以os可以知道什么时候时间到了。
ready-->running:处于就绪状态的进程被进程调度程序选中后,就分配到cpu上来运行。
running-->exit: 当进程表示它已经完成或者因出错,当前运行进程会由操作系统做结束处理。
running-->blocked: 进程请求某样东西且必须等待时。比如要读或者要写一个文件。
blocked-->ready:当进程要等待的某事件来到时,它从阻塞状态变到就绪状态。
这些进程状态的转变都是由操作系统完成的。

#进程挂起
进程挂起就是说这个进程没有占用内存空间,这个进程还是映射在硬盘上的进程,此时我们说这个进程是处于挂起状态。
进程是在内存空间上被执行的,也就是进程是在内存中被创建、执行、等待、就绪、阻塞的,那进程挂起,就是进程被换入换出到硬盘中啦,所以叫进程被挂起。
挂起状态有2种:
阻塞挂起状态blocked-suspend:进程被swap换出到外存中等待某事件的出现。
就绪挂起状态ready-suspend: 进程在外存,只要进入内存即可运行。

与挂起相关的状态转变:
阻塞到阻塞挂起:没有进程处于就绪状态或者就绪进程要求更多的内存资源时,会进行这种转换,以提交新进程或运行就绪进程。
就绪到就绪挂起:当有高优先级阻塞(系统认为会很快就绪的)进程和低优先就绪进程时,系统会选择挂起低优先级就绪进程。
运行到就绪挂起:对抢先式分时系统,当有高优先级阻塞挂起进程因事件出现而进入就绪挂起时,系统可能会把运行进程转到就绪挂起状态。

在外存时的状态转换:
阻塞挂起到就绪挂起:当有阻塞挂起进程因相关事件出现时,系统就会把阻塞挂起进程转换为就绪挂起进程。此时进程的程序、数据和进程本身还是在硬盘上的。我们只是把状态变了一下。

解挂/激活activate:把一个进程从外存转到内存:
就绪挂起到就绪:没有就绪进程或挂起就绪进程优先级高于就绪进程时,会进行这种转换。
阻塞挂起到阻塞:当i个进程释放足够内存时,系统会把一个高优先级阻塞挂起(系统认为会很快出现所等待的事件)进程转换为阻塞进程。

问题:os怎么通过PCB和定义的进程状态来管理PCB,帮助完成进程的调度过程?
以进程为基本结构的os, 上面一层为一组各式各样的进程,下面最底层时cpu调度程序(包括中断处理等)
os从上面看到的是用户进程、服务进程、终端进程、磁盘管理进程。。。。等等。
os的主要任务就是选择哪个进程占用cpu、如何把一个进程的状态从一个状态换到另一个状态。
状态管理--状态队列
就是由操作系统来维护一组队列(就是很多队列),用来表示系统当中所有进程的当前状态。
不同的状态分别用不同的队列来表示,比如就绪队列、各种类型的阻塞队列。并且每种队列可以有多队伍,比如就绪队列1就绪队列2就绪队列3.
每个进程的PCB都根据它的状态加入到相应的队列当中,当一个进程的状态发生变化时,它的pcb从一个状态队列中脱离出来,加入到另外一个队列。

#为什么使用线程
线程shread管理。自从60年代提出进程概念以来,在操作系统中一直都是以进程作为独立运行的基本单位,直到80年代中期,人们又提出了更小的能独立运行的基本单位——线程。
例子,编写一个MP3播放软件,核心功能模块有三个:一是从Mp3音频文件中读取数据。二是对数据进行解压缩。三是把解压缩后的音频数据播放出来。
单进程的实现方法:
main()
{
    while(TRUE)
    {
    Read();
    Decompress();
    play();
    }
}
Read() {....}
Decompress()  {....}
play() {....}

上面就是用三个函数依次执行一遍就可以了,这种就是单进程实现方式。但这种方式在性能上有问题:问题1播放出来的声音能否连贯?比如读的时候是不是要读半天才能解压和播放。问题2各个函数之间不是并发执行,影响资源的使用效率。
多进程的实现方法:
程序1                程序2            程序3
main()                main()            同理略
{                {
    while(TRUE)            while(TRUE)
    {                {
    Read();                Decompress();
    }                }
}
Read() {....}            Decompress()  {....}
把读数据写一个进程,解压写一个进程,播放写一个进程,写三个进程,当read等数据的时候,cpu就可以执行别的进程。当一开始的多读一些数据,后面解压和播放的进程就可以有机的进行了。但是也有一个问题:进程和进程之间是隔离的,但这三个进程之间是要通信的、要共享数据的?进程1的执行结果数据要传给进程2,进程2传给进程3.
上面就是进程并发执行,并发执行时如果进程A和进程B之间有通信,就是进程A要访问进程B的地址空间,进程A要通过操作系统来做一个进程间通讯,就是os要把进程A的数据传到进程B中去,这个开销也非常大。
此外,维护进程的系统开销较大:创建进程时,分配资源、建立PCB,撤销进程时回收资源、撤销PCB,进程切换时保存当前进程的状态信息等开销。
这些开销怎么解决?于是就有一种新的实体:线程,来解决进程之间可以并发的执行,进程之间共享相同地址空间。

#线程
线程可以并发的执行,但线程之间可以共享相同的地址空间。
线程的定义:线程是进程当中的一条执行流程。
从两个方面来重新理解进程:
从资源组合的角度:进程把一组相关的资源组合起来,构成了一个资源平台(环境),包括地址空间(代码段、数据段)、打开的文件、访问的网络等各种资源。  #A
从运行的角度:代码在这个资源平台上的一条执行流程(线程)。
A:所以进程就是管理资源的,而进程中执行的功能就被分离出来了,执行的功能就交给线程来管理。所以,线程是进程的一个重要组成部分,进程管理资源,线程来完成执行过程。
在一个进程中可以存在多个线程。多个线程共享进程提供的资源平台。属于同一个进程的所有线程共享这个进程的所有资源。就是进程中的所有线程可以直接访问进程中的代码、数据、内存、文件等等。
而每个线程都有自己的TCB , thread control block, TCB只负责跟执行流程相关的信息,包含PC程序寄存器、SP堆栈、执行状态信息(因为有不同的控制流,不同控制流都有自己的寄存器表示自己的执行状态)。
线程=进程的所有管理-共享资源的管理 , 线程就是控制流的管理。
线程的优点:一个进程中可以同时存在多个线程;各个线程之间可以并发的执行;各个线程之间可以共享地址空间和文件等资源。
线程的缺点:一个线程崩溃,会导致其所属的进程的所有线程崩溃。因为崩溃的线程会用到共享的进程资源,那这个线程把数据破坏了,其他线程就无法使用这个数据了,所以其他线程也崩溃了。所以整个进程就崩溃了,整个进程就结束了。

#进程与线程
一个程序,读入内存,全是01构成
从内存读入到cpu计算,这个时候要通过总线
怎么区分一段01的数据到底是数据还是指令?总线分为三种:控制线、地址线、数据线
一个程序的执行,首先把可执行文件放到内存,找到起始(main)地址,逐步读出指令和数据,进行计算并写回到内存。
一个程序进入内存,被称之为进程。一个QQ.exe可以运行多份
同一个进程内部:有多个任务并发执行的需求(比如,一边计算一边接受网络数据一边刷新界面)。
多进程的情况下,进程和进程之间除了隔离外,还有就是需要共享,所以当需要共享时就很容易出现一个进程改写了别的进程共享的数据,就是搞死别的进程。所以线程概念诞生:共享空间、不共享计算。
进程是静态的概念:程序进入内存,分配对应资源:内存空间、静态变量、各种数据等,进程进入内存,同时产生一个主线程。
所以进程是分配资源的基本单位。
当一个进程里面分配好数据后,这个进程里面的所有线程是可以共享这个进程里面的所有数据的。但不能共享其他进程里面的数据。
线程是动态的概念:是可执行的计算单元(任务),是cpu任务调度的基本单元。cpu里面执行的都是线程、不是进程。cpu和线程是一一对应的。
一个ALU同一个时间只能执行一个线程。
同一段代码为什么可以被多个线程执行?比如我们写一段代码main函数下面的代码,这段代码load到内存中后,这段代码不仅可以被cpu1执行也可以被cpu2执行。所以一个代码段是可以同时被多个cpu执行的。

一段指令比如一个线程已经放到内存上了,os找到这段指令的main函数所在的地址,就把这条指令读到cpu的控制器A,控制器判断这条指令是要干什么的,比如这条指令是要做加法的,接下来cpu就把这条指令下面的指令取出来放到寄存器里,然后alu开始做运算,运算完毕再写回内存。
A:pc, program control, 也叫指令寄存器,也叫程序计数器。功能就是记录下面该执行哪条指令了。
线程的切换
保存上下文、保存现场
单cpu也是有必要进行多线程并行运算的,因为,比如现在有两个线程在等待执行,一个是接受网络数据,一个线程是刷新UI,当接受网络数据的线程需要等待网络数据时,此时cpu就是处于空闲的,此时cpu就可以运行刷新界面的线程。

#超线程
一个alu对应多个pc| registers , 所谓的四核八线程。

暂时这么多了,后面没有再写,因为越来越难了。。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/friday1203/article/details/134715249

智能推荐

netty实现websocket聊天并发送文件_websocket实现聊天,图片,文件发送-程序员宅基地

文章浏览阅读1.6k次。1.实现了发送文字聊天 用户上下下线自动更新聊天列表 网页保存聊天记录2.发送文件 最大支持30兆 ,代码中可修改3.浏览器通知,https下发送文件的实现思路是在文件前200个字节拼接发送人的信息以及文件名 不够200字节用&填补后台代码在w3cschool的基础上修改的效果图如下启动成功后访问http://localhost:7633/chat.htm..._websocket实现聊天,图片,文件发送

习题8:编写一个控制台应用程序,完成下列功能,并写出运行程序后输出的结果。_编写一个控制台应用程序调用getservbyport()查询并显示本机上所有的使用udp协议的服务的-程序员宅基地

文章浏览阅读8k次,点赞3次,收藏20次。 编写一个控制台应用程序,完成下列功能,并写出运行程序后输出的结果。1) 创建一个类A,在A中编写一个可以被重写的带int类型参数的方法MyMethod,并在该方法中输出传递的整型值加10后的结果。2) 再创建一个类B,使其继承自类A,然后重写A中的MyMethod方法,将A中接收的整型值加50,并输出结果。3) 在Main方法中分别创建类A和类B的对象,并分别调用MyMethod_编写一个控制台应用程序调用getservbyport()查询并显示本机上所有的使用udp协议的服务的名称及端口号

使用cmd命令,运行java正常,无法运行javac_cmd只能运行javac不能运行java-程序员宅基地

文章浏览阅读3.9k次。今天在装jdk时,出现java能正常运行,而显示javac不是内部或外部命令,也不是可运行的程序或批处理文件。原因是我在装jdk时将jdk和jre装到了一个文件夹,卸载之后重新装了一遍就好了。..._cmd只能运行javac不能运行java

代码管理之结合repo和git搭建代码服务器_repo和git 配合使用-程序员宅基地

文章浏览阅读2.2k次。在管理android项目的时候,如果使用一个git仓库来管理整个android的源码的话,感觉总是不爽,所以谷歌引入了repo,说白了,repo其实就是一个python写的脚本,用于管理多个git仓库的代码,使得我们可以同时下载多个git仓库的代码,而这些git仓库的代码可以组成我们最终想要的整个大的工程的全部代码。总结一句话,repo是用来管理多个git仓库的工具。那么如何搭建repo工_repo和git 配合使用

python QT 图片缩放,移动_qtdesigner中怎么让图片随着控件缩放而变化-程序员宅基地

文章浏览阅读5.4k次,点赞28次,收藏93次。利用python的QT库搭建图片显示界面,实现图片的拖拽、以鼠标中心缩放等功能。_qtdesigner中怎么让图片随着控件缩放而变化

Powerdesigner逆向工程从现有数据库生成PDM _powerdesigner 从数据库反向生成pdm-程序员宅基地

文章浏览阅读432次。在数据建模过程中,我们建立概念数据模型,通过正向工程生成物理数据模型,生成数据库建库脚本,最后将物理数据模型生成关系数据库,现在反过来,通过逆向工程将关系数据库,生成物理数据模型。 优点: 在丢失数据模型或者数据库模型同现有的数据库不一致,可以通过该方法生成使用中数据库的模型 缺点: 还原回的模型中,可能会没有中文注释,没有表外键对应关系(字段还有,索引关系没了) 前提: _powerdesigner 从数据库反向生成pdm

随便推点

基于Android的VoIP系统实现原理_android voip forward-程序员宅基地

文章浏览阅读4.2k次。VoIP(Voice over Internet Protocol)即首先数字化语音信号并压缩成帧,转换为IP数据包在网络上传输,以此完成语音通话的业务,是一种利用IP协议传输语音数据的、新兴的通信技术。  随着我国三网融合的推进,VoIP与IPTV(Interactive Personality TV)一起成为这一庞大工程的重要标志。而目前手机中,VoIP的解决方案并不是很多,特别是_android voip forward

XShell连接虚拟机输入命令卡顿问题解决_redhat9连接xshell连接虚拟机容易卡顿-程序员宅基地

文章浏览阅读2.3k次,点赞20次,收藏6次。第一步:打开会话管理器第二步:选中正在使用的会话,鼠标右键选择属性第三步:定位到【连接】->【ssh】->【隧道】,选择【隧道】第四步:将【转发X11连接到(X)】复选框取消掉最后一步:点击确定,重新连接图片:..._redhat9连接xshell连接虚拟机容易卡顿

如果第一次启动使用ZooKeeper时,没注意用root用户启动了,生成的zookeeper.out文件权限就会改变,如果之后再使用普通用户去启动的话就会没有权限-程序员宅基地

文章浏览阅读226次。ZooKeeper JMX enabled by defaultUsing config: /usr/local/zookeeper-3.4.13/bin/../conf/zoo.cfgStarting zookeeper ... /usr/local/zookeeper-3.4.13/bin/zkServer.sh: line 140: ./zookeeper.out: Permission deniedSTARTED_zookeeper.out文件

南阳理工OJ_题目91 阶乘之和_南阳理工oj91-程序员宅基地

文章浏览阅读629次。//用n从9的阶乘开始向前减 #include using namespace std;int main(){ int T; int a[] = {1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; cin >> T; while(T--) { int n; cin >> n;_南阳理工oj91

B2B,B2C,C2C,C2B,B2G_c2b网站代表-程序员宅基地

文章浏览阅读2.8w次,点赞4次,收藏7次。B2B、C2C、B2C B2B:B2B(Business To Business)是指一个市场的领域的一种,是企业对企业之间的营销关系。电子商务是现代B2B marketing的一种具体主要的表现形式。它将企业内部网,通过B2B网站与客户紧密结合起来,通过网络的快速反应,为客户提供更好的服务,从而促进企业的业务发展。 B2B模型 简介 目前基于互联网的B2B_c2b网站代表

物联网概论(IoT)_Chp5 物联网通信 Zigbee/蓝牙/UWB/WLAN/WiMax_公用电信网可以划分为哪三部分?-程序员宅基地

文章浏览阅读6.5k次。Chp5 物联网通信公用电信网可划分为三个部分,即长途网(长途局以上的部分)、中继网(长途局与市话端局之间、市话端局与市话端局之间的部分)和接入网(端局与用户之间的部分)。目前国际上倾向于将长途网和中继网合在一起称为核心网,相对于核心网的部分就是接入网。在物联网中,接入网技术是物联网通信的关键技术,接入网和核心网共同构成了物联网通信的体系架构。依通信覆盖范围的不同,无线网络从小到大依次为无线个域网(WPAN)、无线局域网(WLAN)、无线城域网(WMAN)和无线广域网(WWAN)。无线接入技术能实现真_公用电信网可以划分为哪三部分?

推荐文章

热门文章

相关标签