熟肉视频地址:
我们讨论了操作系统如何扮演裁判,魔术师和粘合剂的角色,裁判是指对于资源保护的管理;魔术师是指我们要让它看起来像我们有一套非常干净易用的资源的抽象,而不是使用实际的没有统一接口的物理资源。粘合剂是一组通用服务,它们使在操作系统上编写程序变得更容易,例如文件系统服务、网络服务等等。
今天我们要讲操作系统四个基本概念:
我们将从什么是线程(Thread)开始讨论,线程会完整地描述一个程序状态或者一个线性执行上下文,它会有一个程序计数器,寄存器,执行标志,线程栈等等。然后是地址空间(Address Space),这是程序可访问的一组内存地址。然后我们会介绍什么是进程(Process),进程包括地址空间以及一个或者多个线程。最后,我们将讨论在这门课的前期特别重要的硬件机制也就是双模式操作(dual mode operation),即一个典型的处理器至少有两种不同的模式,我们可以松散地称之为内核模式(Kernel mode)和用户模式(User mode)。
我们要学习如何编写和编译程序成为可执行文件,然后从文件系统中取出这些可执行文件后,建立运行的进程,文件被加载到内存中,我们会详细讨论那个进程运行需要提供的栈和堆。然后是转移控制(Transfer Control),意思是处理器的程序计数器将会指向该进程的用户代码中的指令,然后程序就会开始执行。操作系统需要给这些进程提供各种系统服务,同时操作系统需要保护操作系统和进程不受其他进程和其他用户的影响。
我们来回忆下 CS61B + CS61C 课程的内容: 处理器一开始有一个程序计数器(PC,Program Counter),还有一个可以读取的内存(Memory),内存里面有一组指令(Instruction)。程序计数器会指向内存中的指令,让处理器读取下一条指令。我们把指令从内存中拉出来,解码,然后把它输入到执行流水线中。我们在 CS61C 中经常谈论的是一种风险类型的处理器(risk style processor),有五个执行流水线。解码后,它们会输入一组寄存器和 ALU 来进行实际操作,然后递增程序计数器并继续下一条指令。
我们的第一个操作系统概念是一个控制线程(Thread of Control),线程实际上是一个唯一的执行上下文,它有程序计数器,寄存器,执行标志,线程栈,内存状态。线程就像上一张幻灯片中你看到的处理器的虚拟版本。一个线程正在处理器或核心上执行,顺便说一下,我现在混用处理器和核心这两个概念,后面我们就能更清楚地理解这两个概念的区别。但它是在处理器寄存器中常驻(Resident)时才会执行的。寄存器中有线程的上下文(Context)或根状态(Root State),有些东西在寄存器中,剩下的在内存中:
- 包括一个程序计数器,当前正在执行的指令,程序计数器指向内存中的下一条指令,所有的指令都存储在内存中
- 包括用于进行计算的中间值
- 有一个栈指针,它有一个指向内存中栈的顶部的指针,线程的其余部分都在内存中。
当线程的状态不在寄存器中即不是常驻的时候,线程就被挂起(suspended)或不再执行。被挂起的线程实际上是在内存中,还没有执行或者根本没有执行,有其他线程正在执行。
这是另一个关于执行过程的视图(冯·诺依曼体系)
这是一组地址,我们稍后会把它叫做地址空间,从 0 到 2^32 -1。里面有一组将要执行的指令,粉色的是你的处理器。进程有一个与之相关的保护状态,处理器包括寄存器和流水线的集合,我们的执行序列(Execution Sequence)包括:在程序计数器上获取一条指令,解码它,执行它,写回寄存器,获取下一条指令,并重复这个执行循环。
那么我们是如何产生多处理器的错觉的呢?我们上次讲过在你的笔记本电脑上做一个ps -aux或者其他的操作唤起任务管理器,如果你仔细看,你会发现有数百个进程在运行,大部分在休眠状态。但它们都可以在你当前的处理器上使用,那么它是如何工作的呢?
让我们假设只有一个物理处理器上只有一个核,在任何给定的时间在硬件上只有一个执行线程。但现在我们想要的是有多个cpu,或者说是多个cpu同时运行的假象,所以我们可以有多个线程同时运行。从程序员的视图来看是,我有一堆东西都在运行,它们都共享内存。
每个线程是一个虚拟核心,我们这里有三个线程,品红色线程,青色线程以及黄色线程,造成他们同时运行的错觉的方式是通过我们多重复用硬件,我们运行一段时间的品红色线程,然后运行一会青色线程,然后运行一会黄色线程,以此循环。
线程的内容包括什么?显然,每个线程都需要一个程序计数器和一个堆指针,还有所有寄存器状态。如果线程在运行中,寄存器状态就在处理器的寄存器中;如果不在运行中,这些状态就在内存中,被称为线程控制块(TCB,Thread Control Block)
我们举一个例子:
在 T1 的时候 vCPU1 在运行,在 T2 的时候 vCPU2 在运行,在 T1 和 T2 时,发生了上下文切换(Context Switch),在底层是一个事件,我们把所有的程序计数器,栈指针,所有其他寄存器都保存在内存中的对应线程控制块中。我们从 vcpu2 加载程序计数器、堆栈指针等,然后运行 vcpu2。
一些 Q&A:
- 每个线程是否都有自己独占的 CPU 缓存?答案是否定的,一般每个核有一个高速缓存,线程都共享同一个缓存,你可以想到如果切换太快,就没有线程能利用好缓存了。原始处理器中的缓存或 TLB 必须在切换时被刷新,更高级的处理器则不会。缓存本身通常在物理空间中,你从一个线程切换到另一个,你只是改变了页表,不需要清空缓存。
- 线程上下文切换这需要多长时间?这大概需要几微秒的时间,需要确保转换的时间不要太长,不要把大部分时间都花在转换上。
然后是什么触发了上下文切换?全局倒计时器时间到了,或自愿让出CPU等这样的事情触发的。比如某个线程要做一些 I/O,那么操作系统就会调度其他线程。