00:00 深入理解操作系统的关键概念
Jason介绍了口袋操作系统系列视频,基于威斯康星大学麦迪逊分校的开源书籍《Operating systems sweet easy pieces》。讨论了程序运行的基本模型,从硬件层面的CPU和应用程序到应用程序在内存中的机器代码执行过程。进一步阐述了操作系统在硬件与应用程序之间的中介作用,通过系统调用提供抽象的计算机基础能力,以及操作系统内部重要机制如CPU虚拟化、内存虚拟化和持久化并发,强调了这些机制对上层应用程序运行模型的影响。

02:40 操作系统标准库与系统调用的原理
详细解释了操作系统中标准库和系统调用的工作机制。标准库,如open函数,为上层应用提供了便捷的文件操作接口,而这些底层操作实际上是通过系统调用来实现的。系统调用的内部定义在操作系统源码中,例如在Linux的X8664平台上,通过系统调用表来统一管理这些系统调用,上层代码通过指定表中的位置来调用特定的系统调用。

04:14 操作系统机制在应用程序层面的体现
讨论了CPU和内存虚拟化在操作系统中的实现方式及其对应用程序的影响。CPU虚拟化使得每个进程看似在独立的CPU上运行,互不影响,实现方式通过代码示例展示了一个持续运行并打印进程ID的程序。内存虚拟化则让每个进程都感到自己有独立的内存空间,实际上操作系统通过虚拟地址空间管理,使得多个进程共享物理内存资源而不相互干扰。

07:00 深入理解计算机持久化存储与多线程编程
本文探讨了计算机底层持久化存储技术,包括硬盘、SSD等硬件设备,以及操作系统提供的文件系统,如FFSLFS、journaling NFS等,展示了如何利用这些技术进行数据的持久化存储。随后,文章转向多线程技术的讲解,重点介绍了如何通过多线程技术更好地利用多核CPU带来的并行计算能力,同时警告了不合理的多线程编程可能导致的并发问题,如竞态条件,并通过生产者消费者问题等经典案例深入剖析了多线程编程模型。此外,文章还简要说明了操作系统作为应用程序与硬件之间资源抽象层的作用,体现了操作系统在虚拟化底层资源和管理硬件资源方面的重要性。

09:28 操作系统设计与演进
现代操作系统需兼顾性能、安全性、跨平台兼容性与节能环保。早期操作系统的功能简单,主要为特定任务服务,未具备多任务处理能力。随时间推移,UNIX等系统引入了多任务处理、内存保护与文件系统,使得操作系统功能更加完善。个人计算机与图形用户界面的出现,进一步增强了操作系统的用户交互能力。分布式操作系统的发展则促进了分布式服务的使用。系统调用的引入提高了安全性,允许用户代码在较低权限的用户态运行,而系统调用则在具有较高权限的内核态执行。

00:00 深入理解操作系统的关键概念



02:40 操作系统标准库与系统调用的原理

04:14 操作系统机制在应用程序层面的体现

07:00 深入理解计算机持久化存储与多线程编程


09:28 操作系统设计与演进

欢迎来到epic lab,我是Jason。口袋操作系统是epic lab的第一个年度系列视频。在这个系列中我们将以威斯康星大学麦迪逊分校的开源书籍Operating systems sweet easy pieces the内容为主线,来为你由浅入深的介绍组成操作系统的各种关键概念。有关这本书的更多详细内容,你可以参考视频简介。

Ok废话不多说,开始我们的低段旅程。程序运行的本质是什么?在屏幕左侧我们有一台计算机,如果以最简单的方式建模,这台计算机会有两部分组成。硬件层面,CPU为计算机提供了最核心的计算能力,而应用程序则运行其上,它控制着CPU完成某种特定任务。如果我们进一步来看他们的运行时状态,应用程序对应的机器代码会被加载到计算机内存中,已被后用。

而CPU的任务则可以被归纳为三个阶段,取值、一码执行。在取值阶段CPU会从内存中取出下一个需要被执行的机器指令。密码阶段,CPU会对机器指令进行解码,并将它转换为相应的控制信号。在最后的执行阶段,CPU会将这些控制信号发送给不同的内部处理单元,并控制他们完成指令对应的任务。

当然这个计算机最简模型掩盖了很多事物,如果我们将操作系统也放到这个模型中,你会发现事情变得复杂了起来。真实的计算机不仅由CPU这样的核心硬件组成,作为输入输出设备的一部分,内存、键盘、硬盘以及显示器等设备通常也必不可少。而在这些硬件之上,操作系统占据了软件部分的核心位置。它让我们可以更方便的与底层硬件交互,而不再需要单独与各类芯片上的IO端口直接通信的。

在操作系统的组成中,标准库位于最上层,它为我们提供了覆盖各类功能的众多可移植编程接口。这些接口通常拥有统一的调用方式,可以在不同的编程语言中使用。在标准库下方,操作系统通过系统调用的形式将整台计算机的基础能力进行了抽象。他们是应用程序可以与操作系统进行的最直接交互。而位于系统调用之下的便是操作系统内部的一些重要机制。比如CPU虚拟化、内存虚拟化、持久化并发等等。这些机制决定了上层应用程序的实际运行模型。他们也是我们将在这个系列中为你介绍的重点。

接下来让我们再深入看看组成操作系统的这几个部分。对于标准库相信你一定很熟悉。比如屏幕中展示的open函数,它便是c posix标准库中提供的接口,可用于打开去定路径下的文件。而在这个接口的内部实现中,针对文件的实际上打开操作则是由相应的系统调用来完成的。

比如针对X8664平台,我们可以在linux操作系统源码中找到这样一段用于定义sis open系统调用的代码。这段代码在操作系统层面定义了文件打开这个操作的具体行为。而通过将它封装为系统调用,上层代码可以以黑盒的形式丝滑的使用这些系统能力。需要注意的是,标准库和应用程序并不是以函数调用的形式来使用系统调用的,操作系统提供的系统调用会被统一注册在相应的系统调用表这个内核结构中。而上层代码在使用时则需要以偏移位置的方式来指定表中的某个单元格,以便调用这个单元格对应的系统调用代码。

比如在X8664体系上,我们可以通过这样简单的两行汇编指令来调用sis open这个系统调用。这里在第一行汇编指令中给出的数字2,表示我们想要使用系统调用表中第三个单元格对应的系统调用,而这个系统调用便可用于文件打开。Ok现在你对标准库与系统调用已经有了大致了解。

我们继续来看操作系统内部的这几种重要机制是如何在应用程序层面体现的。首先是CPU虚拟化,来看这样一段C代码。这里你只需要关注红框内幂函数的实现细节。代码第20行speed函数可以让程序进入空转并持续一段时间。这里我们让程序等待一秒钟,然后在紧接着的代码第21行,我们向控制台打印程序运行时传入的参数。借助代码第十九行的while循环,程序会持续重复执行上述的两行代码逻辑。直到我们按下肯加C键,观察屏幕右侧的命令行窗口,你可以看到程序从编译到运行的整个的过程。

我们使用GCC编一左侧的源代码,尝试运行程序,可以看到如期输出的字符A接着我们尝试同时创建四个应用程序进程,并给予他们不同的输入内容。这里命令行中使用的and符号可以加将标记的进程置于后台运行。毫无疑问,进程运行后,我们可以得到持续输出的字符ABCD。当然他们的顺序可能稍有不同。这里无论是在单核CPU上创建同样的多个进程,还是进程代码中存在死循环逻辑操作系统都会让我们产生这样一种假象,即每一个进程都是由独立的CPU进行处理的,任何一个进程的运行状态都不会影响到其他进程,这便是CPU虚拟化。而内存虚拟化也与此类似。

我们同样来看一段代码,这里你只需要关注红框内的代码。代码第28行,我们定义了一个局部变量stack v并给予它初始值0。代码第三十行,我们打印了当前进程的ID及stack v变量的所在地址。在代码第31至35行的死循环结构内,每隔一秒钟我们将stack v变量的值累加,一并将得到的结果值连同当前进程的ID1同的打印到要控制台,编译并运行程序。

按同样的方式,我们一次性创建四个应用程序进程。此时你会发现,虽然每个进程都拥有了不同的进程ID但在这些进程内部便让stack v的值都被存放在相同的地址上。而这就是操作系统的内存虚拟化,它会给我们制造这样一种假象,即每个进程都享有完全独立的内存资源。而实际上在操作系统内部,每个进程所享有的并不是全部的内存资源,而是自己独立的虚拟地址空间。当然了,更多细节我们会在后续的群众为你介绍。

持久化,让我们能够将数据长时间的保留在计算机上。比如屏幕中这段代码,我们通过调用前面提到的open接口来创建文件,并将指定内容写入其中。这是是计算机为我们提供的最基础能力。它的功能虽然简单直观,但所涉及的概念却十分复杂。计算机在硬件层面可以使用多种持久化存储设备,比如常见的硬盘SSD。而在软件层面,操作系统为我们提供了完备的文件系统来管理这些持久化资源。在这一部分内容中,我们将涉及众多概念,比如read FFSLFS、journaling NFS等等。

最后我们再来看看并发,并发在应用程序层面的最直接体现就是多线程。多线程技术让我们得以将进程中的任务进行更细致的划分,以更好的利用多核CPU带来的任务并行性。但不合理的多线程编程则可能导致程序出现异味快的运行结果。

比如屏幕中这个例子,这里我们在代码中创建了两个线程,每个线程都执行完全相同的任务,即不断更新变量counter值,并将更新后的结果值打印在代码第三十行。我们会首先判断counter的值是否小于10,而只有在这个条件成立时,counter的值才会被更新。换句话说,变量counter的值最多不会大于10。但从右侧的控制台输出中你可以看到thread一最后更新了变量counter值。那此时counter的值为11,这是不是与我们从代码中观察到的逻辑相违背呢?所以在并发这部分内容中,也有很多概念等着我们去理解。比如线程、本地变量、信号量、条件变量等等。我们会分析多线程领域的一个经典问题,honored buffer及生产者消费者问题,由此来深刻理解多线程的编程模型。

好的,让我们再回到这张图。操作系统位于应用程序与底层硬件之间向上,它为应用程序提供了虚拟化的底层资源,让应用程序可以不用考虑计算机在硬件层面的复杂性,因此我们有时候会称操作系统为虚拟机。向下,操作系统又提供了针对硬件层面的资源管理,所以有时它也会被称为资源管理器。而我们刚刚介绍的这些内容则统一组成了计算机的资源抽象,它简化了上层应用程序的开发流程。

现代操作系统在设计时需要考虑多方面因素。在性能方面,操作系统需要考虑如何最大程度的减少操作系统本身的性能损耗,以保证应用程序的性能最大化的操作系统一般需要长时间运行,因此它需要尽可能保证不会在发生意外情况时退出。操作系统管理了所有的硬件资源,因此如何保证恶意软件不执行非法操作也同样重要。而随着移动设备的普及,操作系统可能需要被部署在不同的硬件架构上,而如何保证操作系统在移动设备上的可用性,可能也需要我们关注。最后,节能减排,绿色地球,一个功耗低性能强的操作系统是我们所有人都在追求的目标。

OK在视频结束之前,我们再来看看操作系统的发展简史。最早期的操作系统仅由一些用于处理特定任务的常用历程组成。在那个时代计算机在同一时间段通常只能够执行一个程序,程序由专门的操作语言进行控制。而这些常用历程则可以方便开发者操作诸如底层IO设备等计算机硬件。接着系统调用的概念被普及,它最先出现在atlas计算机系统上。系统调用与历程类似,但它的调用会伴随着从用户态到内核态的执行环境转移过程中,默认情况下,用户代码在具有较低权限的用户态执行,而系统调用代码则在具有较高权限的内核态执行。用户代码可以通过trap指令来调用系统,调用并进入内核态,而当系统调用执行完毕后,通过return from trap指令执行环境的重新返回至用户台。这种执行环境的划分使得用户代码不再能够轻易触及内核甚至是底层硬件,这对计算机的安全性至关重要。

接下来,诸如unix等小型机系统诞生了。此时的操作系统具备了多任务处理能力,支持内存隔离与保护以及相对完备的文件系统。而随着个人计算机的普及,诸如windows、mac os等支持图形化界面的操作系统逐渐涌现。他们在小型机系统的基础能力之上又增加了更多与人的可交互能力。最后分布式操作系统让我们可以使用分布式服务。

Ok这就是本期视频的所有内容。在下期视频中我们将走入CPU虚拟化,为你介绍其中的一个关键角色进程,如果你对这个系列感兴趣,不妨点个关注或是一键三连。我是Jason,see you next time.