《现代操作系统》读书笔记

第一次打开《现代操作系统》这本书都是去年的事儿了,中间断断续续地翻几页,最近终于把前9章的内容读完了(后面几章是操作系统实例解析),因此决定记录阅读过程中学到的一些关于操作系统的概念。

<!--more-->

根据章节目录,归纳每一章的大致内容。其中的多媒体系统和多处理机系统只是草草翻阅,因此并没有记录了。(实际上其他章节也只是囫囵吞枣,大概过了一遍而已~)

PS:起初确实被密密麻麻的排版给下了一跳,阅读过程中却发现了作者引入的不少有趣的梗,2333...

1. 引论

计算机由软件和硬件组成,为了管理硬件,需要为用户程序提供一个简单清晰的计算机模型,因此计算机安装了一层被称为操作系统的软件。

随着硬件的发展和软件的需求,操作系统也在逐步发展,第一代正儿八经的操作系统始于(1945-1955),迄今为止的第四代操作系统广泛应用在个人计算机上。

操作系统与运行该操作系统的硬件密切相关。根据冯.诺依曼模型,将计算机分成了4个子系统:存储器,算术逻辑单元,控制单元和输入/输出单元(这在《计算机科学导论》中了解到)。

大多数的操作系统都使用了某些基本概念和抽象,比如进程地址空间文件等,这本书的后续章节基本上就是围绕着这些基本概念展开的。

  • 进程本质上是一个正在执行的程序(或者说是容纳运行一个程序所需要的所有信息的容器),与每一个进程相关的是进程的地址空间。为了让用户感觉到可以同时运行多个程序,操作系统周期性地挂起一个进程然后启动运行另一个进程
  • 操作系统的另一项主要功能隐藏磁盘和其他I/O设备的细节特性,为程序员提供独立于设备的、良好清晰的抽象文件模型
  • 操作系统需要管理获取输入和产生输出的物理设备
  • 操作系统需要保护计算机中用户的信息

这里还提到了一个“个体重复系统发育”的概念,引导我们思考为什么一个概念会过时,而什么样环境变化又会启用“过时概念”,我觉得这个挺有趣的。

2. 进程与线程

2.1. 进程

进程与程序之间的区别是很微妙的,一个进程是某种类型的一个活动,他有程序、输入、输出和状态。从技术上来看,新进程都是由于一个已存在的进行执行了一个用于创建进程的系统调用而创建的(UNIX下就是fork命令)

现代计算机会在“同一时间”做很多事情,这里的同一时间有两个概念:

  • 并发,两个或多个事件在同一时间间隔发生,在某个瞬间,CPU只执行一个进程,并发通过CPU快速切换进程实现。
  • 并行,指两个或者多个事件在同一时刻发生

这里暂时只关注并发的相关知识。一个进程有三种状态:

  • 运行态(此刻该进程实际占用CPU)
  • 就绪态(可运行,但因为其他进程正在运行而暂停)
  • 阻塞态(除非某种外部事件发生,否则进程不能运行,比如等待输入)

其中运行态和就绪态通过调度程序进行切换。为了实现进程模型,操作系统维护着一张进程表,该表包含了每个进程的状态,用于当进程重其他状态切换到运行时恢复原本状态,仿佛该进程重未被中断过一样。

2.2. 线程

每一个进程有一个地址空间和一个控制线程。 而引入多线程的原因是:在应用中同时发生着许多活动,某些活动随着时间推移而被阻塞,将程序分解成可以准并行运行的多个顺序线程,程序设计模型会变得更简单。多个线程可以共享同一个(进程)地址空间内和所有可用数据的能力(多进程无法实现因为每个进程的地址空间都不相同)。此外,与进程相比,线程更加轻量,速度更快,效率更高。 高级程序语言提供了多线程编程的能力(比如Java),多线程编程是一个比较复杂和庞大的问题,在Android开发中略有接触,还需要进一步掌握。

2.3. 调度

通常会有多个进程或线程同时竞争CPU,调度程序决定应当允许哪一个程序、何时运行以及他应该运行多长时间。调度程序使用的算法成为调度算法,常见的算法有先来先服务最短作业等。 具体的细节实在是过于深奥~

3. 存储管理

内存是计算机需要管理的重要资源,操作系统负责管理内存,记录哪些内存是正在使用、哪些内存是空闲的,在进程需要时为其分配内存,在进程结束后释放内存。

3.1. 地址空间

地址空间是一个进程可用于寻址内存的一套地址集合,每个进程都有自己独立的地址空间(貌似这句话出现的次数不少)。这里的问题是如何为每个进程分配自己的地址空间,使用基址寄存器和界限寄存器可以将程序装在到内存中连续的空闲位置而无须重定位,但是每次访问地址都需要进行加法和比较运算,因此会显得很慢。

另外的一个问题是:所有进程所需的内存往往比计算机提供的内存要大得多,有两种解决内存超载的问题:

  • 交换技术,把进程调入内存,执行一段时间,然后把他存回磁盘,因此,空闲进程都存储在磁盘上
  • 虚拟内存,这个下面再提

许多程序语言都允许从堆中动态的分配内存,当进程空间试图增长时,就必须注意分配的内存不足的问题。为了减少因内存不足而引起的进程交换和移动的开销,一种解决办法是加载进程时为其额外分配一些内存。此外操作系统必须对动态分配的内存进行管理。

3.2. 虚拟内存

虚拟内存的基本思想是把进程的地址空间分割成多个块(称为页或者页面),每一页有连续的地址范围,由于并不是必须所有的页在内存中才能运行程序,因此程序可以引用一部分在虚拟内存中的地址空间,由硬件执行必要的映射。 虚拟内存系统基于分页的技术,该技术有下面几个比较重要的概念:

  • 虚拟地址,由程序产生的地址,构成了虚拟地址空间
  • 分页速度,虚拟地址到物理地址的映射必须非常快
  • 页面置换算法,因为只有一部分页面真正运行在内存中,所以相关的页面算法就显得十分重要,所有的页面算法都是为了实现虚拟内存页面的覆盖需求

4. 文件系统

进程运行时在内存中保留信息,在其终止时,他在内存中保存的信息也随之丢失。因此需要在计算机中长期存储信息(保存在磁盘上),操作系统使用文件的概念来解决这个问题。

4.1. 文件

文件也是一种抽象,它提供了一种在磁盘上保留信息且方便以后读取的方法。

文件命名 我们熟悉的文件操作方式是:从某个文件夹中找到某个文件名。不同的操作系统对于文件的具体命名规则是不一样的

  • Windows支持文件扩展名,为文件扩展名绑定相关的程序(双击就可以启动)
  • Unix的文件扩展名只是一种约定,并不强迫采用他们

文件结构和类型 我们熟悉的文件采用的是树形的文件目录结构管理的,但是操作系统所见到的只是字节,把文件当作字节序列为操作系统提供了最大的灵活性。

操作系统支持多种文件类型,目录是管理文件系统结构的系统文件。此外,文件都有文件名和数据,此外操作系统还保存着其他与文件相关的信息,如创建日期、文件大小等。

4.2. 目录

文件系统通常提供目录或者文件夹记录文件。可以用目录把很多文件进行分组,这样就组成了目录树,目录树的顶部被称为根目录。

使用目录树组织文件系统时,需要通过绝对路径或相对路径指定文件名,此外在不同的操作系统下,目录的分割符也是不一样的:

  • window使用\
  • unix使用/

每个目录下有两个特殊的目录项,可以从某个文件开始查找目录树中的其他文件:

  • .指向当前目录
  • ..指向父目录,

4.3. 实现

文件系统实现的关键问题在于记录每个文件分别用到了哪些磁盘块

  • 连续分配,把每个文件作为一个连续数据块存储在磁盘上,缺点是随着时间推移,磁盘会产生很多碎片
  • 链表分配,为每个文件构造磁盘块链表,缺点是随机读取文件效率缓慢
  • 在内存中采用表的链表分配,缺点是需要占据很大的内存
  • i节点,为每个文件赋予一个成为i节点的数据结构,其中列举了文件属性和文件块的磁盘地址

5. 输入/输出

5.1. I/O设备

操作系统需要控制计算机的所有I/O设备。I/O设备一般由机械部件和电子部件(又被成为设备控制器或适配器)组成,而每个控制器有几个寄存器用来与CPU进行通讯,通过写入这些寄存器,操作系统可以命令设备发送数据、接受数据、关闭开启或执行某些其他操作。

在设计I/O软件时一个关键的概念是设备独立性,即可以访问任意的设备而无须事先指定设备(不要再代码里面写死了)。

每个连接到计算机上的I/O设备都需要某些设备特定的代码来对其进行控制,这样的代码被成为设备驱动程序(一般由厂家提供)。大多数操作系统都定义了一个所有块设备都必须支持的标准接口,驱动程序需要实现这些接口以便设备能够正常工作。

5.2. 时钟

当把一块石英晶体使当地切割并且安装在一定的压力之下时,他就可以产生非常精确的周期性信号,即可以用来定时。

时钟对于多道程序设计系统的操作是至关重要的

  • 防止进程超时
  • CPU记账

6. 死锁

操作系统的一个主要任务就是为应用程序分配资源。在系统中有很多独占性的资源,在任意时刻他们只能被一个进程使用;另一方面,很多应用甚至需要排他性地访问若干种资源。因此必须考虑死锁的问题。

如果一个进程集合中的每个进程都在等待只能有盖进程集合中的其他进程才能引发的时间,那么,该进程的集合就是死锁的

死锁有下面四个必要条件,当死锁发生时,下面的四个条件一定是同时满足的:

  • 互斥条件,每个资源要么分配给了一个进程,要么就是可用的
  • 占有和等待条件,已经得到资源的进程可以再次请求其他资源
  • 不可抢占条件, 已分配给某个进程的资源不能强制性被抢占,而只能被该进程显式地释放
  • 环路等待条件,死锁发生时必定存在某个环路,该环路的每个进程都等待着下一个进程占有的资源。

6.1. 资源

大部分死锁都和资源有关。

  • 当进程请求资源失败,他们会进入休眠状态;
  • 当进程需要两个或者更多资源时,他们可以顺序获取。

结合这两点,如果进程以相同的顺序请求资源,则后请求资源的进程会由于资源被锁而被阻塞;但是如果以不同的顺序请求资源,则可能会发生死锁。可以通过有向图对死锁进行建模

6.2. 解决方案

有四种处理死锁的策略:

  • 忽略该问题
  • 检测死锁并恢复
  • 仔细对资源进行分配,动态避免死锁
  • 通过破坏死锁的四个必要条件之一,避免产生死锁

上面的每个策略都有一系列对应的算法,如单个资源的银行家算法等。

7. 计算机安全

计算机安全这一块挺有趣的。计算机安全比较主要的三个方面是:

  • 威胁的实质
    • 数据的保密性,即未经授权的人不能查看相关信息(对应read操作)
    • 数据的完整性,即未经授权的人不能修改相关信息(对应write操作)
    • 目标系统可用性,指没有人可以扰乱系统使之瘫痪
  • 入侵者的本质
    • 非恶意用户
    • 恶意用户
  • 数据的意外遗失等

7.1. 密码学

加密的目的是将明文(即原始信息或文件),通过某种手段变为密文,通过这种手段,只有进过授权的人才知道如何将密文回复为明文。加密算法是公开的,而加密的安全性由独立于加密算法之外的密钥决定。(这在《HTTP权威指南》的https有了解过,在本章开头提到的“操作系统安全与网络安全之间有着不可分离的关系”)

私钥加密技术 私钥加密技术采用加密密钥明文进行加密。采用解密密钥对密文进行解密。这在理解上十分合理的,但是需要发送者和接受者同时拥有密钥,且必须有物理上的接触,才能传递密钥(否则无法保证密钥本身的安全性)

公钥加密技术 大数乘法比对大数进行因式分解要容易得多。当使用公钥加密技术时,每个人将自己的公钥公布出去,发送方使用对方的公钥进行加密,而接受方使用自己的私钥进行解密。

数字签名 发送者使用自己的私钥对文档散列值进行计算并附在文档后,接收方使用发送者的公钥对签名进行运算用以恢复原有的散列值,如果传输过程发生了更改,则双方的散列值无法对应。为了满足签名的要求,公钥和私钥的计算是可交换的(因为需要恢复到原本的散列值)

7.2. 保护机制

计算机中有许多需要保护的对象,每一个对象都有用于调用的单一名称和允许进程运行的有限的一系列操作。域是一对(对象、权限)组合,每一对组合指定一个对象和一些可在其上运行的操作子集(每个用户对该对象的操作权限可能是不一样的)。操作系统负责跟踪和确定哪个对象属于哪个域。 为了记录每个用户的权限,操作系统要求所有的用户在登陆时进行身份认证。随着而来的一个安全问题是保证口令的安全,并防止黑客通过暴力和撞库的方式进入系统

7.3. 恶意软件

恶意软件包括木马,病毒,蠕虫,间谍软件等

8. 最后

操作系统最重要的部分包括:进程与线程、内存管理、文件管理,这些知识不是单单看一遍理论就能弄清楚的。这些概念也与日常开发关系密切,因此需要在今后的学习中逐步掌握(现在只是简单了解概念而已)。

操作系统的其他知识点也有趣的很,比如计算机安全是我非常感兴趣的一个地方(/斜眼)。

除此之外,还需要学习使用常见的操作系统,包括Windows,Mac OS和Linux等,这是接下来需要工作。另外这篇笔记是在看完书之后回头整理的,因此比较草率,也跳过了一些比较晦涩的地方,还需要回头进行补充和调整。

使用Guzzle并发请求接口从Phaser中了解游戏的几个概念