OS

现代操作系统原理与实现-进程与线程

Progress and Threads

Posted by PYQ on June 12, 2023

阅读《现代操作系统原理与实现》一书的笔记。

陈海波老师的这本书写的很好,在学习xv6后想阅读一下这本书看看所谓的现代操作系统(Real World)相较于xv6这种教学操作系统的异同。

只是笔记和摘抄,不是系统全面的介绍相关知识(因为已经了解或者掌握很多了

Introduction

每个进程都对应一个运行中的程序。应用程序在运行时仿佛拥有了整个CPU,进程的管理、CPU资源的分配等任务则交给操作系统。

为了使多个进程能同时执行,操作系统进一步提出了上下文切换(context switch),通过保存和恢复进程在运行过程中的状态(上下文),使进程可以暂停、切换和恢复,从而实现CPU资源的共享。

针对进程间数据不易共享、通信开销高等问题,操作系统在进程内部引入了更加轻量级的执行单元,也就是线程。

由于上下文切换需要进入内核,开销较大,后续又引入了纤程(fiber)这一抽象,允许上下文之间在用户态切换。

Progress

进程的状态(xv6中的状态是UNUSED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE)

image-20230612121532528

进程的内存空间布局(只介绍内核部分

  • 进程地址空间最顶端的是内核内存,每个进程的虚拟地址空间都映射了相同的内核内存。进程在用户态运行时,内核内存对其不可见;只有当进程进入内核态时,才能访问内核内存。与用户态相似,内核部分也有内核需要的代码和数据段,当进程由于中断或系统调用进入内核后,会使用内核的栈。

image-20230612122056313

子进程和父进程虽然具有几乎一模一样的状态,但fork一旦结束,它们就是两个独立的进程,在操作系统调度器的视角是两个完全独立的个体。因此,父进程和子进程的执行顺序是不确定的,完全取决于调度器的决策。

Windows的进程创建:不同于Linux,Windows采用的进程创建接口为CreateProgress系列,与fork相比,CreateProgress的设计更加直观:它会从头创建进程,载入指定的二进制文件,并根据其他参数设定的配置直接开始执行指定的二进制代码。CreateProgress需要对进程的运行参数进行大量配置,在提供灵活性的同时也使接口变得异常复杂。

COW(copy on write):(只是简单介绍)早期的fork实现简单粗暴,直接将父进程的物理内存完全拷贝一份,并映射到子进程的内存空间中。这种方式很多情况是不必要的,极其浪费。

  • 共享代码库、代码段等虚拟内存是只读的,对它们进行拷贝是一种浪费。
  • 进程有时在调用fork后会立即调用exec以载入新的可执行文件,重置地址空间,之前的内存拷贝就完全失去了意义。

对于只读虚拟页来说,父子进程能直接共享这些页,减小了拷贝的开销。对于堆、栈这种容易发生变化的页,如果出现了写操作,就会触发写时拷贝。COW的出现提升了fork的性能,又能降低进程占用的系统资源

进程树:Linux中所有的进程都是fork创建的,所有的进程都是init进程(OS编写人员手动写的)直接或间接创建出来的。

进程组和会话:主要用于shell环境的进程管理。

插一个不相关的笔记,临时想到的问题(

Terminal和Shell是两个不同的概念。

Terminal(终端)是一种计算机用户界面,它提供了一个命令行界面,用户可以通过键盘输入命令,并查看命令输出。在Linux和Unix系统中,Terminal通常是一个模拟终端窗口,用户可以通过该窗口连接到计算机的命令行界面。

Shell是计算机操作系统中的一个命令解释器,它提供了一种与操作系统交互的方式。用户可以通过Shell输入命令,并由Shell将命令传递给操作系统内核执行。在Linux和Unix系统中,常见的Shell包括Bash、Zsh、Ksh等。

在使用Terminal时,用户可以选择使用不同的Shell来解释命令。用户可以通过设置默认Shell或在Terminal中切换Shell来选择使用不同的Shell。同时,Terminal也提供了一些常用的命令行工具,如ls、cd、mkdir等,用户可以通过这些工具来管理文件系统、安装软件等。

image-20230612145443742

讨论:fork过时了吗

fork的优点:

  • 简洁。fork和exec的组合将进程创建过程进一步解耦,程序可以在两者之间对子进程进行各种设定。
  • fork强调了进程之间的联系(原有进程的拷贝),这种联系为进程的管理提供了很多便利。

fork的局限性:

  • 尽管fork的接口保持着简洁的风格,但随着操作系统支持的功能越来越多,fork的实现也越来越复杂。另外由于fork的实现与进程、内存管理等模块的耦合度太高,因此不利于内核的代码维护。
  • fork的性能太差。大内存应用越来越普遍,尽管COW已经大大减小了内存拷贝,但对于这类应用来说,建立内存映射都需要耗费大量时间,fork的效率已经满足不了需要了。
  • fork存在潜在安全漏洞(父进程和子进程之间的联系,BROP攻击)。
  • 扩展性差
  • 与异质硬件不兼容
  • 线程不安全

针对fork的多种替代方案:

  • posix_spawn:类似fork和exec的组合,性能明显优于fork+exec
  • vfork:子进程和父进程共享同一地址空间
  • rfork/clone:fork的“精密控制”版

Threads

为什么需要线程

image-20230612153810385

多线程的地址布局空间

image-20230612154831074

线程可分为用户态线程和内核态线程。用户态线程更加轻量,创建开销更小,功能也较为受限,与内核态相关的调用(如系统调用)需要内核态线程协助才能完成。内核态线程由内核创建,受操作系统调度器直接管理。多线程模型中一对一模型是当今主流的。

image-20230612155300060

线程本地存储(TLS):可以实现“一个名字,多份拷贝(与线程数量相同)”的全局变量。

线程的基本接口省略,进程/线程的调度和切换也省略,和xv6差不多,思想一致都是中断->用户态->内核态->内核态->用户态

Fiber

Fiber可以叫纤程,也可以叫协程。操作系统的支持叫做纤程,程序语言提供的支持叫协程,叫法不同罢了。

Fiber是一种轻量级的用户级线程,也被称为协程。与操作系统级线程不同,Fiber是由应用程序自己管理的,因此可以更高效地切换上下文,避免了操作系统级线程的上下文切换开销。Fiber通常用于实现高并发、高吞吐量的网络应用程序,如Web服务器、消息队列等。

相较于抢占式多任务处理,纤程的调度方式为合作式多任务处理