前言
操作系统是计算机专业中非常核心也是很基础的课程,但是很多人在学习编程的过程中却忽略了对它的学习,我当时因为疫情在家上网课,所以没有认真学习这门课程。近日有些空闲时间,于是重温了哈工大李治军老师的《操作系统》课程,以此留下学习记录。
一.概述
1.1 什么是操作系统?
操作系统就是一个管理计算机硬件资源的一种软件。它的位置在硬件之上,用户软件之下,可以帮助我们的应用软件更好的进行调度系统硬件资源。
1.2 操作系统的发展过程
- 上古神机IBM7094,造价在250万美元以上。
一台计算机只用做一件事情,批处理操作系统,专注于计算,没有多进程的概念。
- OS/360(1965-1980)
需要让一台计算机做多件事情,有多个程序运行。作业之间的调度和切换称为核心,多进程结构和进程管理开始萌芽。
- MULTICS
每个人启动一个作业,作业之间快速切换。出现了分时系统的概念,核心仍然是线程之间的切换。
- UNIX(1980-1990)
个人计算机的普及,让操作系统的出现更加必要。Ken Thompson,Dennis Ritchi两人开发。
- Linux(1990-2000)
Linus Torvalds编写了Linux.
1.3 操作系统能够解决的问题?
- 屏蔽掉硬件调用的接口,为应用程序员提供了调用硬件资源更好,更简单,更清晰的模型(系统调用接口)。
- 管理计算机的CPU,让CPU能够更加高效的进行工作(多进程切换运行)。
- 管理计算机的内存,让内存的使用更加充分(虚拟内存)。
二.操作系统管理CPU
管理CPU的核心就是要CPU运行的更加的高效。那cpu是如何工作的呢?
2.1 cpu的工作方式
基本知识:
程序在我们的计算机中是如何进行存储的呢?就是一段一段的指令!而计算机运行程序也就是cpu把这个程序的指令从磁盘中加载到内存中,然后读取程序的指令并运行的过程!
CPU执行过程:
CPU的执行就是不断的取指令运行的过程,如果在取指令的过程中存在一条读文件的操作,那么就需要访问磁盘,进行I/O操作。由于磁盘IO的速度和CPU执行的速度相比非常慢,所以CPU在这种情况下就会空闲,直到磁盘IO完成,从而导致CPU的利用率低下。
这种情况下指令是一直保持顺序执行的
所以操作系统就引入了一个多进程执行的概念,这样来执行提高CPU的效率。
等到程序1的磁盘io结束以后,会发出一个中断请求,这时cpu又从程序2切换回程序1继续执行,因为cpu没有处于等待的状态,一直在运行,这样就大大减少了cpu空闲的时间,提高了cpu的效率!
问题1. pc切换进程的时候,如何保存当前进行的执行信息呢?
使用PCB进程控制块,该进程运行的所有信息都会保存在这个结构当中,当切换到这个进程的时候,就会通过保存的PCB恢复进程的运行信息。
问题2. 多进程有哪些调度的方式
1. 先来先服务算法(FCFS)
用一个队列来维护任务列表,按照顺序依次运行。
2. 时间片轮转算法
每隔一段固定的时间进行线程的切换。
3. 短任务优先算法
核心思想还是不通的进程有不同的优先级,而短任务的优先级要比长任务的优先级更高,而我们总是安排高优先级的任务运行。
4. 优先级调度算法
给每一个进程分配一个优先级,每次需要进程切换时,找一个优先级最高的进程进行切换。
问题3. 进程和线程的关系
- 一个进程往往有多个线程,一个线程只属于一个进程。
- 同一进程的多个线程可以共享进程的资源。
- 进程是操作系统分配资源的最小单位,线程是CPU执行的最小单位。
问题4. 内核级线程和用户级线程的关系
每一个线程都有自己的执行栈,这样当执行线程跳转的时候不会出现跳转到其他线程中的错误。当用户级线程经过调用而陷入内核的时候,就会调用核心级线程。
核心级线程也对应着一个栈,但是核心级线程还必须关联着它在用户级下的线程。当核心线程切换的时候,用户线程也必须跟着进行切换。
用户级线程:不需要内核的支持而在用户程序中实现的线程,它的内核的切换时用户态程序自己控制内核的切换,不需要内核的干涉,但是它不能像内核级线程一样更好的运用多核CPU,内核对用户级线程不能感知。
内核级线程:切换由内核控制,当线程进行切换的时候,会从用户态转换为内核态,切换完毕又要从内核态转变为用户态。可以很好的运用多核CPU。
三. 操作系统管理内存
如何让内存使用起来?
我们都知道,程序都是存储在磁盘中的。当我们要运行一个程序的时候,就会把程序加载到内存中,然后cpu取指运行程序。磁盘的容量是很大的,可以完整的存储程序,但是我们内存的容量是非常有限的,并且当我们运行一个程序的时候往往只需要其中的一些功能,而不是全部功能,所以需要把程序的全部代码都加载到内存中吗?
3.1 分段
在实际中,对程序员来说一个完整的程序往往被分为多个不同的段(如数据段,变量集,函数等等)。在程序载入内存的时候,程序以段为单位载入内存,每个段都有一个pc执行单元向下执行。
那这些段是载入到程序的哪些区域呢?如何找到指令所在的地址呢?
这就涉及到了相应的内存分配算法:1. 首先适配 2. 最佳适配 3.最差适配。但是这种分配算法存在缺陷,就是会产生大量的内存碎片,从而当需要进行大数据存放的时候没有完整的内存空间进行分配。
找到指令的地址也简单呀,给每一个段加一个段地址,每条指令有一个偏移地址,此时该条指令的物理地址=段地址+偏移地址 。
但是现在分区又存在一个问题,当空闲内存区域的长度不够分配怎么办?比如现在有一个进程需要100k内存,但是现在空闲内存只有50k和80k,两个合起来就够用,但是分开就不够用,造成空间的浪费,所以引入了一个分页的概念。
3.2分页
为了解决分段所造成的内存碎片问题,我们引入了分页概念。就是将内存作为固定的大小如4k再分为一页一页的形式,在分配程序内存的时候,计算它需要多少页内存,直接分配给对应大小的页即可,使用一张分页表来记录每个页分配的情况,并提供了从虚拟内存到物理内存的映射。
这样分页也会存在问题,如果每一页的内存设置的小的话那么确实减少了内存的浪费,但是极大的增加了页表所占的空间。
解决方案:多级页表。 这样的话空间利用率就高了,但是时间利用率也不是很高,所以又使用快表(TLB)。作用就是页表的cache中存储了当前最有可能被访问到的页表项。只有在TLB无法完成地址翻译任务时,才会到内存中去查询页表,这样就减少了页表查询导致的处理器性能下降。
3.3段页结合(虚拟内存)
在实际使用中我们都是使用的段页结合的方式,在段和内存之间引入虚拟内存的概念。实际内存的分配对用户是不可见的,段的分配对用户来说是可见的。
3.4页面换入换出
当用户访问一个页时,MMU会去查询一个操作系统维护的页表,看看用户访问的页是否存在于内存当中,如果存在,就直接调用;如果没有,就需要开启中断,执行页错误处理程序,在磁盘中找到相应的数据页,并在内存中找到一块空闲,将其载入进来,然后中断返回,执行用户程序,在去访问内存中用户需要的页。
当我们不断地将页面换入内存当中,内存肯定会不足的,我们需要选择一些页面进行换出,为新的数据腾出空间,但是我们应该选择那些页面换出呢?
3.5页面置换算法
FIFO算法: 将最先进入的页面置换出去,让新的页面加载进来。
LRU(Least Recently Used)算法 : 将最近最少使用的页面置换出去,让新的页面加载进入。