侧边栏壁纸
博主头像
qiuker

常态咸鱼,偶尔动弹

  • 累计撰写 7 篇文章
  • 累计创建 11 个标签
  • 累计收到 0 条评论

操作系统

qiuker
2022-03-08 / 0 评论 / 0 点赞 / 49 阅读 / 11,262 字

一.引论

1.概述

操作系统(Operating System,OS)是配置在计算机硬件上的第一层软件,现代几乎所有的计算机都配备了操作系统,所以学习操作系统是很有必要的。

我们的计算机由上到下大致可以分为三个层次:

  1. 应用软件:如QQ、微信、QQ音乐、Codeblocks、钉钉之类的
  2. 系统软件:即操作系统,如Windows、Linux、Mac之类的
  3. 硬件:如CPU、内存、I/O等等,是计算机中的一些物理装置

操作系统实例:

  • Unix家族:如IOS、MacOS
  • Linux家族:如Ubuntu、Centos、安卓,在服务器端和终端占据统治地位
  • Windows:大部分个人电脑上安装的操作系统

操作系统一般都是用C语言编写的

2.操作系统的功能

①操作系统是用户和计算机硬件之间的接口

用户使用计算机的三种方式:

  • 命令行:通过操作系统提供的一些命令来操作计算机,典型的如Windows的dos命令

  • 图形化界面:通过图标、窗口、鼠标这些图形化的方式来操作计算机

  • 应用程序:OS提供了系统调用的接口,用户操作应用软件,应用软件则去调用接口

用户使用计算机的三种方式,都是由操作系统所提供的

其中命令行、图形化界面都是操作系统的外壳(shell)

应用程序的一切行为都需要通过操作系统来完成:

  • 一方面,因为操作系统是可以被信任的程序,而应用程序可能存在恶意
  • 另一方面,操作系统可以使得应用程序不用关注计算机硬件的细节,便于开发、可移植

解释一下系统调用:

C语言中,为什么printf("Hello World")能在控制台上显示出对应的语句呢?

printf 函数是存在 stdio.h 库中的,而如果去研究 stdio.h 库就会发现它其实调用了很多操作系统所提供的API,即 stdio.h 通过系统调用实现了在控制台上输出、获取键盘输入等功能,并将其再次封装为了scanf、printf 等函数供用户使用。

总而言之,C语言的标准类库如 stdio.h 是通过调用操作系统所提供的API(系统调用)来实现的。

②操作系统是计算机资源的管理者:

计算机的硬件、软件资源可以大致分为四类:处理机、存储器、I/O设备以及文件(包括数据和程序)。

OS的主要功能就是对这四类资源进行有效的管理

注:处理机是一个比CPU稍微大点的概念,可以理解为负责处理任务的部分

③OS实现了对计算机资源的抽象:

操作系统将CPU抽象为若干进程,磁盘抽象为一个文件系统,内存抽象为地址空间,便于使用。

文件、进程、套接字等都是操作系统提供的对象,并不是物理上存在的概念。

总结:OS的主要功能是

  1. 向下管理计算机资源以提高计算机系统资源利用率
  2. 向上提供接口便于用户使用

3.操作系统的特征

①并发

  • 并发:在计算机中同一个时间段内有多个程序在运行

  • 并行:在计算机中同一个时间点有多个程序在运行

一个CPU在一个时间点只能处理一段程序

例1: 0~15ms让A程序运行,15~20ms停下来让B程序运行,20~35ms再让A程序运行,二者在一个很小的时间段内交替运行就称为并发。

计算机中若干个任务非常频繁地在交替运行,在人眼中就好像在同时进行。

例2: 同时下载视频、听歌、发qq,实际上这几个任务在交替运行,只是交替速度非常快

并发的最大意义在于提高CPU的利用率

例3: 假设CPU要执行这样一段由C语言写的程序

char s[255];
scanf("%s",s);
printf("%s",s);

如果不给CPU设计并发,那当CPU在执行这段程序时就得一直等着人输入字符串,浪费了大量时间。

I/O设备的速度远低于CPU的速度,所以如果只能一个个处理任务CPU就被大大浪费了

例4: 一个3核的处理机系统,若有5个用户进程,则同一时刻最多有(3)个正在运行的进程

一台计算机中同时存在多个CPU,才能实现并行

②共享

共享指的是计算机系统中的资源可供内存中多个并发执行的进程共同使用

计算机中的资源是很可能少于内存中多道程序的需求的,比如打印机。如果A进程要求打印“Hello”,B进程要求打印“World!”,两个进程在并发交替进行时如果都可以使用打印机,A进程打印了两个字母、再换B打印两个字母、再换A打印,最后就会出现混乱。

  • 互斥共享: 计算机中的某些资源,如打印机。应当规定一段时间内只允许一个进程访问,直到这个进程使用完成并释放资源后才能让别的进程来访问。这种资源被称为临界资源(比如不能在打开一个文件的情况下把这个文件删了或重命名,因为文件属于临界资源)。
  • 同时访问: 另一类资源,一段时间内多个进程都可以访问。如:磁盘系统。每个进程都可以向磁盘中写入/读取文件,除非它们访问的是同一个文件

变量(内存中的某个地址)也属于临界资源

例5: 有两个并发执行的进程P1和P2,共享初值为1的变量x。P1对x加1,P2对x减1。加1和减1操作的指令序列分别如下所示,两个操作完成后,x的值( )。

进程P1:
①Load R1,x //取x到寄存器R1中    
②Inc Rl //令R1中的数加1         
③Store x,R1 //将R1的内容存入x 

进程P2:
④Load R2,x //取x到寄存器R1中
⑤dec R2	//令R1中的数加1 
⑥Store x,R2 //将R1的内容存入x 

正确答案:0或1或2

解析:因为进程可能交替进行,因此很可能进程P1和P2的指令会交替执行。

如果执行的顺序为①②③④⑤⑥,则x为1

如果执行的顺序为①④②⑤③⑥,则x为0

如果执行的顺序为①④②⑤⑥③,则x为2

综上,变量也应当作为临界资源,不允许多个进程在一个时间段内同时访问。

③虚拟

  • 时分复用:为每个程序建立进程,让多道程序并发执行。此时虽然还是只有一个CPU,但每个程序都好像独立运行在一台计算机上
  • 空分复用:令一个100MB的程序在30MB的内存上运行,实质上每次都只把程序的一部分调入内存运行,运行完了就移出内存。

④异步

因为并发执行的特点,一个进程的执行往往是“走走停停”的。

进程以不可预知的速度向前推进。何时执行、何时暂停、何时完成都是未知的,这就造成了系统的异步性。

4.操作系统的启动

程序必须要在进入内存之后执行,操作系统是系统软件,也属于程序,计算机开机后操作系统从硬盘加载到内存的过程就是操作系统的启动。

操作系统的启动过程:

  • BIOS自检:CPU在默认位置执行BIOS程序,进行开机自检和对本地设备的初始化。
  • 系统引导:将操作系统内核从外存读入内存
  • 启动内核并初始化系统

BIOS程序执行过程:

BIOS:Basic Input/Output System,即基本输入/输出系统。为了在关机后使 BIOS不会丢失,BIOS 程序会被固化在主板的ROM 中(一般内存中的程序在断电后就丢失了)。

因此接通电源后,CPU首先运行的就是BIOS程序,这段程序主要完成的工作是:

  1. POST,Power On Self Test,即开机自检/加电自检,判断键盘、鼠标、显卡、硬盘等计算机中的基本设备是否正常工作,发现错误会停机或给出声音报警信号
  2. 对本地设备进行初始化,以硬盘的初始化为例,BIOS会读取硬盘中第一个扇区(MBR),并执行其中的代码

系统引导:

BIOS读取MBR后,就将执行MBR中的内容

MBR,Master Boot Record,主引导记录,指的就是硬盘中的第一个扇区

MBR中存放着一段开机管理程序(bootloader),bootloader 的最主要功能就是加载OS(将OS从外存读入)

5.中断处理机制

中断指的是CPU暂停执行一项任务,而去处理新的任务,是实现多道程序所必需的。

中断分为三类:

  • 中断:又称外中断
    • 例:外设发出I/O申请、时间片到了等,来自于CPU外部
    • 对用户来说是透明的,感受不到的
  • 异常:又称内中断
    • 例:程序出错(除以0、越界访问等),来自于CPU内部
    • 异常程序会被杀死(退出)或重新执行
  • 系统调用:system call
    • 应用程序主动向操作系统发出的服务请求
    • 执行完系统调用后系统可能会等待调用结果或直接去进行其它程序
    • 例:C语言中的printf()语句是通过调用write()来完成的

系统调用本质是操作系统所提供的一些函数,如write()。应用程序只需要了解系统调用的参数和功能就可以使用,而不需要去关注其背后的实现细节。

注:操作系统本身也是用C语言写的

CPU是通过CS:IP这两个寄存器来读取指令(高级编程语言会被编译为一条条机器语言指令)的,CS寄存器的最低两位表明当前指令的特权级:

  • 若为0,表示内核态,可以访问内存的所有数据,包括外围设备,例如硬盘,网卡
  • 若为3,表示用户态,只能受限的访问内存,且不允许访问外围设备

用户态和内核态是CPU的两种状态,当发生中断时,CPU就由用户态切换为内核态,且中断是CPU由用户态切换为内核态的唯一途径

二.进程

1.概述

程序就是指令序列(或者说代码,代码和程序本质应该是一个东西,但是不同场景下使用的词汇),存放在硬盘(外存)之中。想要运行一个程序,就需要把程序(代码)加载进内存,即将文件从硬盘复制到内存中(实际过程比这个复杂)。

注:内存和外存是不同的存储器,都可以用来存储内容。内存速度快、容量小、且断电就丢失数据;外存速度慢、容量大、且数据永久保存。CPU是从内存中获取程序和数据的。

早期的单道系统中,计算机的所有资源同时只为一个程序服务;为了提升CPU使用率,计算机使用了多道程序技术,从而出现了进程的概念。进程是操作系统所抽象出来的,是在多道环境系统下用来管理、控制内存中的不同程序所创建的一个概念。

进程由进程控制块(PCB)、程序段、数据段三部分构成。其中PCB最为重要,它描述了进程的基本情况和活动过程,是程序进入内存后操作系统所创建的。

具体而言,PCB的作用有:

  • 储存程序运行的情况,是并发的前提(程序中断,再次执行时恢复之前的执行进度)
  • 记录数据、程序在内存、外存中的位置
  • 记录进程的状态
  • 实现进程通信

2.进程的生命周期

进程具有三种基本状态:

  1. 就绪状态(Ready):此时进程获得了其运行所需的所有计算机资源,只要再获得CPU就可以立即执行
  2. 执行状态(Running):程序正在运行的状态
  3. 阻塞状态(Block):正在执行的进程因为某些事件(如发出I/O申请)而无法继续执行的状态

状态的转换:

  1. 处于就绪状态的进程获得CPU后转变为执行状态
  2. 处于执行状态的进程因为时间片用完转变为就绪状态
  3. 处于执行状态的进程遭遇某些事件无法继续进行(如进程发出I/O申请)转换为阻塞状态
  4. 处于阻塞状态的进程解决了事件转换为就绪状态(如I/O完成)

进程还具有两种状态:

  1. 创建状态:进程正在创建的状态,假如某项计算机资源无法获得就会导致无法转为就绪状态,比如内存不够
  2. 终止状态:进程正在终结的状态,进程不能再被执行,但是PCB还需要清空,或者别的进程需要收集信息

操作系统还引入了一个对进程的重要操作————挂起操作,执行中的进程被挂起后将暂停执行,就绪状态的进程被挂起后将暂时不接受调度。与挂起操作对应的是激活操作。

被挂起的进程将进入静止状态,与之对应的是活动状态,可细分如下:

  • 静止就绪:进程不再被调度
  • 静止阻塞:当进程解决事件,满足运行条件后将转化为静止就绪状态
  • 活动就绪
  • 活动阻塞

挂起操作可能由用户执行,希望暂停程序运行;也可能由操作系统自己执行,以希望减轻工作负荷或检查资源使用情况等。

3.进程同步

进程同步指让多个并发进程能按照一定规则(顺序/时序)来共享系统资源,其实也就是解决临界资源的问题。

生产者-消费者问题:

生产者进程负责生产产品,并将产品供给消费者进程来消费,为了使生产者进程和消费者进程能并发执行,设置了一个具有n个缓冲区的缓冲池,生产者将产品放入缓冲池,消费者从缓冲池中拿走产品来消费。该问题中需要避免两个bug:1.消费者进程从空的缓冲池中取产品 2.生产者向满的缓冲池放产品

假设用一个变量count表示缓冲区中的产品个数。

count变量是由生产者进程和消费者进程所共享的,生产者生产时count++,消费者消费时count--,两个进程分开执行不会有问题,但并发执行时却会出现问题。所以变量count应该被视为临界资源,两个进程互斥共享

临界区:

人们把每个进程中访问临界资源的那段代码称为临界区,每个进程在进入临界区前应先对欲访问的临界资源进行检查,看它是否被访问。若临界资源正在被其它进程访问,则本进程不能进入临界区。

在临界区前应当增加一段用于进行上述检查的代码,即进入区,相应地临界区后面也应当加上一段退出区来将临界区刚刚访问的资源恢复成未被访问的标志。

同步机制的原则:

  • 空闲让进:看到资源空着直接上

  • 忙则等待:资源被占了就等着

  • 有限等待:但要避免让某进程一直死等

  • 让权等待:进程等待资源时应该让出CPU

信号量机制:

用一个变量S来记录某个资源的可用数量,比如空闲的打印机数量。只允许用两个原子操作来访问这个变量,即P、V操作(荷兰文中通过叫passeren,释放叫vrijgeven),P是让S--,V是让S++

记录型信号量中P、V操作的具体实现:

首先将所有正在等待临界资源的进程用链表串起来,用list表示

P{
S--; //表示占用一个资源
if(S<0) block(list); //block是原语,可以使进程进入阻塞状态
}

V{
S++;
if(S<=0) wakeup(list); //wakeup也是原语,唤醒进程
}

解释:S为非负数时表示临界资源的可用数量,S为负数时表示有|S|个进程还在等待临界资源的释放,若在V操作释放一个临界资源后S<=0,表示在释放前S<0,还有进程因为等待资源阻塞中,所以需要唤醒

4.进程通信

进程间必然会存在需要通信的情况,比如将图库中的某个照片分享给qq好友。

进程的同步、互斥因为也需要在进程间交换一定的信息,所以可以视作一种低级进程通信。

OS提供了高级的通信工具,用于进程间的数据传送:

  • 共享内存:进程间本身是没有共享内存的,但操作系统可以在内存中划出一片区域供进程来交换信息。最有效,但是无法用于网络通信(不同计算机的进程肯定无法划分出共享内存)。

  • 管道通信:用一个pipe文件,写进程向pipe文件中写入数据,读进程从pipe文件中读取数据即可。这个文件也是在内存中的,可以分为匿名管道和有名管道。缺点是只能单向通信,且匿名管道只能用于父子进程等有亲缘关系的进程。

  • 消息队列:以消息为单位进行通信,操作系统提供一组原语让进程来通过消息队列传递消息。具体的通信细节是由操作系统控制的。计算机网络中的报文就是一种消息。效率较共享内存来说较低,但可以在任意进程间通信。

  • 套接字(Socket): 是不同计算机进程间的主流通信机制。

5.线程

进程是操作系统进行资源调度/分配的最小单位,但由于进程的切换需要付出较大的时空开销,所以就需要设法将拥有资源和CPU调度分配这两个属性分开来,即进程是拥有资源的基本单位,而线程是CPU进行调度分派的基本单位。

线程是能独立运行的基本单位,在线程切换时只需要保存和设置少量寄存器内容,代价远比进程切换小。同一个进程中的线程互相切换不会引起进程切换,但从一个进程的线程切换到另一个进程的线程会引起进程切换。

同一个进程的不同线程,为了减小切换代价、提高并发性,共享进程的内存地址空间和资源,所以独立性相较于不同进程要低得多。

线程的状态和进程状态的转换是一样的

三.处理机调度

1.概述

内存中的进程数目往往是多于处理机数目的,这就要求操作系统根据某种算法来动态地将处理机分配给处于就绪状态的进程。除此之外,有时候会为了提高内存利用率和系统吞吐量而把暂时不能运行的程序调到外存中等待(挂起),这也是一种调度。

系统吞吐量:指的是单位时间内完成的作业数,如果只追求系统吞吐量则会贪心地先解决短作业,不是一种合理的算法。

2.进程调度算法

3.死锁

现系统中有一台扫描仪和一台刻录机,有两个进程P1,P2都希望先使用扫描仪扫描文档,再将文档刻录到光盘上,进程P1先申请到了扫描仪,而进程P2先申请到了刻录机,此时两个进程都希望对方释放资源,都不能获得自己所需的资源来运行(即阻塞状态)从而无法释放占有的资源,并且会一直处于这样的状态从而导致死锁。

可抢占性资源:进程获得资源后可被其它进程或系统抢占资源,如优先级较高的进程可以抢占低优先级进程的CPU,如内存紧张时将不重要的进程调到外存。可见内存和CPU都是可抢占性资源,不会引起死锁。

不可抢占性资源:一旦系统将该类资源分配给某进程,就不能再收回,只能等进程用完后释放。文件、光盘、磁盘、打印机都属于不可抢占性资源。

简而言之,死锁就是两个进程互相抢占了对方所需要的资源,产生的僵持局面。

死锁的定义: 一组死锁进程中,每组进程都在等待其它死锁进程所占用的资源,但由于这些进程都缺少资源无法运行,所以谁也不会释放资源,最终导致所有进程都被阻塞且不会被唤醒。

死锁产生的必要条件:

  • 互斥:资源必须是不可共享,需要互斥访问的

  • 请求和保持:进程在得不到自己想要的资源从而阻塞时,也不释放自己手中的资源

  • 不可抢占:进程的资源是不可抢占性的

  • 循环等待:形成一个循环链

死锁的解决办法:

  • 预防死锁:采取一些限制方法,破坏死锁的四个必要条件中的至少一个

    • 互斥条件是资源决定的,没法破坏

    • 破坏请求和保持条件:规定进程必须在开始运行前一次性申请所需的全部资源,缺点是浪费资源,资源利用率低;更好的方法是进程获取到部分资源就可以开始运行,在运行过程中逐步释放用完的资源。

    • 破坏不可抢占条件:规定到进程占用一些不可抢占资源,提出新的资源请求却得不到满足时要释放已经保持的资源。但这种方法代价很大,比如打印机如果在使用一段时间后被抢占则之前的工作去拿不失效。

    • 破坏循环等待条件:给所有资源分配一个编号,规定进程必须按序号递增的顺序来请求资源,比如规定必须先申请扫描仪再申请打印机就不会出现一开始的死锁问题。

  • 避免死锁:和预防的区别在于不事先采取措施破坏死锁的必要条件,而是在资源分配中采用一定的办法防止进入死锁:

    系统判断若存在某种进程执行顺序不会导致死锁发生才分配资源。如银行家算法

  • 检测死锁:发生死锁后检测出来,从而能采取适当的措施解脱

  • 解除死锁:

    • 从其它进程中抢占足够的资源分配给死锁进程,从而解除死锁状态

    • 终止/撤销发生死锁的一个/多个进程,直到打破循环

四.内存管理

1.存储器的层次结构

冯·诺依曼结构型计算机由五大部分组成:控制器、运算器、存储器、输入设备、输出设备;

现代计算机(电脑)由以下几部分组成:

  • CPU:中央处理单元,集成了控制器和运算器的功能,同时还拥有寄存器(一种存储器)

  • 内存:存储器的一种,又称主存。进程运行时的程序、数据都存放在内存中。

  • 主板:整合电脑的所有配件,具有CPU、内存、各种外设的接口,笔记本的主板就在键盘下方

08f790529822720e1891f5f672cb0a46f21fab5a.jpg

  • 外设:即输入/输出设备,没有外设计算机也是可以运行的,只不过无法和人交互了。声卡、显卡、显示屏、鼠标、键盘等都属于外设

这些设备都属于操作系统的管辖范围

存储器分为很多种,处理速度越快的存储器,价格越贵,容量也越小

这篇文章讲了为什么寄存器速度快、容量小。

CPU是从存储器中读取指令、数据来进行运算的,由于CPU处理速度非常快,如果存储器的处理速度跟不上CPU就会导致CPU性能大大浪费;但令一方面,速度越快的存储器造价贵且容量小,为了同时满足容量、价格和速度的需求,现代存储器往往采取多层结构:

越上层的存储器速度越快、容量越小、价格越贵

  1. 寄存器处理速度最快,完全可以匹配CPU的运算速度,因此是直接放在CPU中的。每个寄存器通常只有8位,大部分计算机中有几十到几百个寄存器。

  2. 主存储器就是俗称的内存,进程运行时的程序和数据都是存放在内存当中的。CPU运算时从内存中读取数据放到数据寄存器中,读取指令放到指令寄存器中;输出时也是将数据放到内存中。可以将内存理解为CPU和外界沟通的桥梁。内存的容量普遍是数GB。

  3. 高速缓存位于寄存器和主存储器之间。因为主存储器的速度和CPU执行指令的速度相比太慢了,因此才引入高速缓存(Cache),它的主要功能是备份主存中常用的数据,避免频繁访问主存。容量比内存小两三个数量级

  4. 磁盘缓存位于主存和磁盘之间,它的主要功能是备份磁盘中常用的数据和信息,减少访问磁盘次数。但和高速缓存不同,磁盘缓存并不是实际存在的一种存储器,而是从主存中划出的部分空间

  5. 磁盘又称硬盘/外存,容量特别大,但是访问速度很慢

内存的一大特点是无法永久储存信息,断电后信息丢失;而硬盘/磁盘可以永久储存信息

寄存器、高速缓存都是位于CPU芯片内部的,不是操作系统的管辖范围。外存(磁盘)的主要用途是存储文件,在文件管理中再介绍,这一章只讲内存管理。

2.内存基本概念

内存是一种存储器,是可以存放数据的硬件。

程序在被执行前需要先放到内存中才能被CPU处理。

计算机会给每一个物理存储单元分配一个号码,叫作“编址”。如果计算机按字节编址,则每个存储单元大小为1字节;如果按字来编址,则每个存储单元大小为1个字(即4个字节)

例:观察一个非常简单的程序x=x+1;是如何执行的

语句被编译成机器语言:

image.png

解释:机器指令由一个三元组构成,每一部分是一个8位的二进制数。第一部分表示指令,其中00101100表示数据传送指令,10010010表示加法指令。第二和第三部分是指令所需的操作数。

x=x+1;指令对应三条机器指令来实现:

  • x的值传入寄存器:其中00000011表示寄存器地址;01001111表示变量x所对应的内存单元地址
  • 对寄存器中的值进行加1操作
  • 将寄存器中的值传入x所在内存单元

实际的程序当然远比x=x+1复杂得多,但执行流程仍然是这样的:程序被编译成机器指令后进入内存 -> CPU从内存中读取指令并执行 -> CPU从内存中读取数据并进行运算

3.程序的装入和链接

程序进入内存的步骤:

  1. 编译,将源程序编译形成若干目标模块
  2. 链接,将目标模块和各自所需库函数链接在一起,形成装入模块
  3. 装入,将装入模块放进内存中

目标模块:即目标文件,对C语言来说一个源文件编译后就产生一个目标文件(一段程序往往包含多个源文件)。我们都知道C语言程序最后会形成以.exe为后缀的可执行文件(Windows系统下),但其实编译完之后只是生成以.o.obj为后缀的目标文件,还需要经过链接才能变成.exe文件。

所以严格意义上述1、2步只是程序变为可执行文件的过程;第3步(即点击可执行文件后发生的事)才真正地让程序进入内存开始运行。

逻辑地址,指的是每一道程序都把自己的起始位置设为0,程序中的所有地址都是和起始地址相对的相对地址。但由于内存中存在多道程序,所以程序的实际物理地址和逻辑地址不对应,二者的映射关系由操作系统完成

装入其实就是考虑要把可执行文件放在内存的哪个位置。单个目标模块装入内存有三种方式

绝对装入方式:

程序中的地址就是其在内存中的实际物理地址。只适用于上古时代的单道操作系统。

可重定位装入方式:

又称静态重定位。编译时就令每个程序的起始位置都是0,程序中所有的地址都表示相对于起始位置的相对位置(即逻辑地址),和实际上的物理地址不同。

比如说程序中的一条指令Load 1 2500表示将地址为2500的数据移入寄存器1中。这道程序进入内存后,操作系统发现前9999的内存单元已经被占了,就给程序分配了起始地址为10000,那这上面的指令就会被翻译成Load 1 12500,仍能保持正确。

显然这种方式只需要在程序进入内存后把程序中的所有相对地址修改为绝对地址就好了,所以称为静态重定位。

动态运行时的装入方式:

静态重定位不支持程序在内存中移动位置,实际上内存中的程序很有可能在没被轮到执行的时候放进硬盘来给其它需要很多内存的程序让位,这样就会导致程序频繁的换位。

解决办法是在程序快要运行的时候才把逻辑地址换成物理地址,即进入内存时的地址仍是逻辑地址。

链接指源程序经过编译后形成若干目标模块,模块和库函数进行链接,根据链接的时间不同分为三类:

**静态链接:**装入前将目标模块和库函数链接

**装入时动态链接:**边装入边链接

**运行时动态链接:**程序执行时才链接,执行时发现一个模块未装入内存立刻由OS去找到该模块。这么做的好处是使得那些执行过程中未被用到的目标模块不被调入内存中(比如报错用到的函数,如果程序运行不出错就不会被调用;比如某些喜欢在写程序前引入一大堆头文件的行为)

多个目标模块最后会组装成一个装入模块,其中必然涉及相对地址的改写。

操作系统在内存管理中起到的主要作用有:

  • 管理逻辑地址和物理地址的映射关系
  • 确保内存中的程序不互相干扰,限制和约束程序不进行越界访问。不能让程序A访问到划分给程序B的地址

没有操作系统,就没办法实现多道程序、并发

4.连续内存分配

内存分配就是给内存中的每个程序分配一定的内存空间,操作系统的目标是减少内存碎片:

  • 外部碎片:分配单元间的空隙
  • 内部碎片:一个分配单元中未被使用到的内存

连续分配是最早的一种内存分配方式,应用于上世纪60~80年代,每个程序占据一段连续的内存空间

  1. 单一连续分配:单道程序环境中,将内存分为系统区和用户区,系统区运行OS,用户区装有一道程序
  2. 固定分区分配:
    • 将内存分为若干大小相等的分区,每个分区放一道程序;显然这种方式不灵活、非常浪费
    • 将内存分为若干大小不等的分区,根据程序的大小为其分配分区,更灵活些
    • 固定分区分配是最早出现的用于多道程序的内存管理方式,但分区大小固定必然会造成内存的浪费
  3. 动态分区分配:又称可变分区分配,根据进程的需要为其动态分配内存

动态分区分配保证不存在内部碎片,但会有外部碎片;

单一连续分配和固定分区分配都不存在外部碎片,但有内部碎片存在。

我们需要抱着操作系统只不过是一段非常大的C语言程序来学习它

在固定分区分配中,操作系统中有一个数据结构来记录每个分区的使用情况

image.png

用一个结构体数组就可以了,非常好实现。

在动态分区分配中,常用两种数据结构来记录内存中的分区使用情况:

  1. 空闲分区表:同上
  2. 空闲分区链:就类似链表的结构,一个分区即一个节点。我感觉这在动态分区分配中可能更常用一点,因为动态分区分配必然经常涉及到分区的增加和删除,用链表更好点。

空闲分区的回收问题:修改空闲分区表/链中分区信息即可。

动态分区分配算法:

一开始我还有点傻了,没想明白动态分区分配算法为什么会有空闲分区。其实很简单,比如某时刻内存中A占据了10M空间,紧跟着B占据了10M空间, 紧跟着C占据了10M空间,然后B先执行完退出内存后就产生了空闲分区。

算法算法思想分区排列顺序优点缺点
首次适应算法FF从头到尾寻找第一个大小满足程序需求的分区按地址递增排列综合性能最好,算法开销小
最佳适应算法BF优先使用容量较小的分区按容量递增排列更能满足大进程的需求算法本身开销大(感觉用小根堆维护复杂度不大233);会产生很多难以利用的小碎片
最坏适应算法LF优先使用容量较大的分区按容量递减排序减少小碎片算法本身开销大;不利于大进程

为什么最佳适应算法会产生小碎片:比如一个9M的程序,最佳适应算法找到了10M的空闲分区,割出9M来存放,则浪费了1M(大概率不能再被用到了);最坏适应算法可能就会找到100M的空闲分区,割出91M。

覆盖技术:

将程序分为多个段,常用的常驻内存;不常用的需要时调入内存。从而解决程序大小超过内存空间大小的问题

交换技术:

将暂时不能运行的程序先移出内存,从而获得更多空闲的内存空间

5.分页存储管理

连续内存分配总会不可避免地产生碎片,如果能将程序分散地装入不同分区,就可以消除碎片问题。

这种方式称为离散分配方式,分为:

  1. 分页存储管理:将程序的地址空间分为若干个页,将内存地址空间分为若干页框。页和页框大小相同,程序的任一页可以放进任一页框,从而实现离散分配。
  2. 分段存储管理:将程序的地址空间分为若干个段,每段定义一组相对完整的信息
  3. 段页式存储管理:目前使用较广的一种方式

分页存储管理用页表来记录程序的每个页所在的具体位置

五. 文件管理

0

评论区