性能之巅:洞悉系统、企业与云计算——操作系统

本文深入探讨了操作系统的核心概念,包括内核的执行、进程和线程、内存管理、调度器以及中断处理。内核在CPU的内核态下管理硬件,通过系统调用来提供服务。进程是执行程序的环境,拥有独立的内存地址空间,而线程则是CPU执行的基本单位。调度器负责分配CPU时间,中断则用于设备与内核的通信。此外,文章还涉及虚拟内存、文件系统、网络协议和设备驱动,展示了操作系统如何协调硬件和软件资源以确保高效运行。

术语

  • 操作系统:指的是安装在系统上的软件和文件,使得系统可以启动和运行操作。操作系统包括内核、管理工具、以及系统库
  • 内核:内核是管理系统的程序,包括设备(硬件)、内存和CPU调度。它运行在CPU的特权模式,允许直接访问硬件,称为内核态
  • 进程:是一个OS的抽象概念,是用来执行程序的环境。程序通常运行在用户模式,通过系统调用或自陷来进入内核模式
  • 线程:可被调度的运行在CPU上的可执行上下文。内核有多个线程,一个进程有一个或多个线程
  • 任务:一个Linux的可运行实体,可以指一个进程(含有单线程),或一个多线程的进程里的一个线程,或者内核线程
  • 内核空间:内核的内存地址空间
  • 用户空间:进程的内存地址空间
  • 用户空间:用户级别的程序和库(/usr/bin、/usr/lib …)
  • 上下文切换:内核程序切换CPU让其在不同的地址空间上做操作(上下文)
  • 系统调用:一套定义明确的协议,为用户程序请求内核执行特权操作,包括设备I/O
  • 处理器:不要与进程混淆,处理器是包含有一个或多个CPU的物理芯片
  • 自陷:信号发送到内核,请求执行一段系统程序(特权操作)。自陷类型包括系统调用、处理器异常,以及中断
  • 中断:由物理设备发送给内核的信号,通常是请求I/O服务。中断是自陷的一种类型。

内核

内核管理着CPU调度、内存、文件系统、网络协议,以及系统设备(磁盘、网络接口,等等)。通过系统调用提供访问设备和内核服务的机制。
在这里插入图片描述
从图中还可以看到系统库,较之只使用系统调用,系统库提供的编程接口通常更为丰富和简单。应用程序包括所有运行在用户级别的软件,有数据库、Web服务器、管理员工具和操作系统的shell。

此处系统所画的环有一个缺口,表示应用程序是可以直接进行系统调用的(如果操作系统允许)。传统意义上,这张图的环是封闭的,表示从位于中心的内核起,特权级别逐层降低。

内核执行

内核是一个庞大的程序。内核的执行主要是按需的,例如,当用户级别的程序发起一次系统调用,或者设备发送一个中断时。一些内核线程会异步地执行一些系统维护的工作,其中可能包括内核时钟程序和内存管理任务,但是这些都轻量级的,只占用很少的CPU资源。

I/O执行频繁的工作负载,如Web服务器,会经常执行在内核上下文中。计算密集型的工作负载则会尽量地不打扰内核,因此它们不能中断地在CPU上运行。你可能会想内核是无法影响到这些工作负载的性能的,但是在许多情况下,确实会影响。最明显的例子就是CPU竞争,这时其他的线程在争夺CPU资源,而内核调度器需要决定哪个线程会运行、哪个会等待。内核还有选择线程运行在哪个CPU上,内核会选择硬件缓存更热或者对于进程内存本地性更好的CPU,以显著地提升性能。

内核态

内核是唯一运行在特殊CPU模式的程序,这一特殊的CPU模式叫做内核态,在这一状态下,设备的一切访问以及特权指令的执行都是被允许的。由内核来控制设备的访问,用以支持多任务处理,除非明确允许,否则进程之间和用户之间的数据是无法彼此访问的。

用户程序(进程)运行在用户态,对于内核特权操作(例如I/O)的请求是通过系统调用传递的。执行系统操作,执行会做上下文切换从用户态到内核态,然后用更高的特权级别执行,如下图所示。
在这里插入图片描述
无论是用户态还是内核态,都有自己的软件执行上下文,包括栈和寄存器。在用户态执行特权指令会引起异常,这会有内核来妥善处理。

在这些状态切换上下文是会耗时的(CPU周期),这对每次I/O都增加了一小部分的时间开销。有些服务,如NFS,会用内核态的软件来进行实现(而不是用户态的守护进程),这样从设备来回执行I/O的时候才无须上下文切换到用户态。

上下文切换也会发生在不同进程之间,例如CPU调度时。

栈用函数和寄存器的方式记录了线程的执行历史。使用栈令CPU可以高效地处理函数执行。

当函数被调用时,CPU当前的寄存器组(保存CPU状态)会存放在栈里,在顶部会成为线程的当前执行添加一个新的栈帧。函数通过调用CPU质量“return”终止执行,从而清除当前的栈,执行会返回到之前的栈,并恢复响应的状态。

栈检查是一个对与调试和性能分析非常宝贵的工具。栈可以显示通往当前的执行状态的调用路径,这一点常常可以解释为什么某些事情会被执行。

用户栈和内核栈

在执行系统调用时,一个进程的线程有两个栈:一个用户级别的栈和一个内核级别的栈,它们的范围如下图所示:
在这里插入图片描述
线程被阻塞时,用户级别的栈在系统调用期间并不会改变,当执行在内核上下文时,线程用的是一个单独的内核级别的栈。(此处有一个例外,信号处理程序取决于其配置,可以借用用户级别的栈。)

中断和中断线程

除了响应系统调用外,内核要响应设备的服务器请求,这称为中断,它会中断当前的执行,如下图所示:
在这里插入图片描述
中断服务程序(interrupt service routine)需要通过注册来处理设备中断。这类程序的设计要点是需要运行得尽可能快,以减少对活动线程中断的影响。如果中断要做的工作不少,尤其是还可能被锁阻塞,那么最好用中断线程来处理,由内核来调度。

中断优先级

中断优先级(interrupt priority level,IPL)表示的是当前活跃的中断服务程序的优先级。中断优先级是在中断信号发出时从处理器读取的,如果读到的级别要高于当前执行的中断,那么该中断成功;否则,该中断会排队以待之后运行。这就避免了高优先级的工作被低优先级的工作打断的问题。

进程

进程是以用户级别程序的环境。它包括内存地址空间、文件描述符、线程栈和寄存器。

进程可以让内核进行多任务处理,使得在一个系统中可以执行着上千个进程。每一个进程用它们的进程ID做标识(process ID, PID),每一个PID都是唯一的数字标识符。

一个进程包含有一个或多个线程,操作在进程的地址空间内并且共享着一样的文件描述符(标识打开文件的状态)。线程是一个可执行的上下文,包括栈、寄存器,以及程序计数器。多线程让单一进程可以在多个CPU上并发地执行。

进程创建

正常情况下进程是通过系统调用fork()来创建的。fork()用户自己的进程号创建自身进程的一个复制,然后调用系统调用exec()才能开始执行不同的程序。

系统调用fork()可以用写时拷贝(copy-on-write,COW) 的策略来提高性能。这会添加原有地址空间的引用而非把所有内容都复制一遍。一旦任何进程要修改被引用的内存,就会针对修改建立一个独立的副本。这一策略推迟甚至消除了对内存拷贝的需要,从而减少了内存和CPU的使用。

进程生命周期

下图展示的就是进程的生命周期。这是一个简化的示意图,对于现代多线程操作系统还会有线程的调度和执行,关于如何把这些映射成进程状态还有一些实现的细节。
在这里插入图片描述
on-proc状态是指进程运行在CPU上。ready-to-run状态是指进程可以运行,但还在CPU的运行队列等待CPU。I/O阻塞,让进程进入sleep状态直到I/O完成进程被唤醒。zombie状态发送在进程终止,这时进程等待自己的进程状态被父进程读取,或者直至被内核清除。

进程环境

下图展示的是进程环境,包括进程地址空间内的数据和内核里的元数据(上下文)。
内核上下文包含了各种进程的属性和统计信息:它的进程ID,所有者的用户ID,以及各种类型的时间。这些通常用ps(1) 命令来检查。还有一套文件描述符,指向的是打开的文件,这些文件为线程之间所共享(通常来说)。

在这里插入图片描述

系统调用

系统调用请求内核执行特权。可用的系统调用数目是数百个,但需要努力确保这一数目尽可能地小,以保持内核简单。

需要记住的关键的系统调用列在了下表:

系统调用描述
read()读取字节
write()写入字节
open()打开文件
close()关闭文件
fork()创建新进程
exec()执行新程序
connect()连接到网络主机
accept()接收网络连接
stat()获取文件统计信息
ioctl()设置I/O属性,或者做其他事情
mmap()把文件映射到内存地址空间
brk()扩展堆指针

虚拟内存

虚拟内存是主存的抽象,提供进程和内核,它们自己的近乎是无穷和私有的主存视野。支持多任务处理,允许进程和内核在它们自己的私有地址空间做操作而不用担心任何竞争。它们还支持主存的超额使用,如果需要,操作系统可以将虚拟内存在主存和二级存储(磁盘)之间映射。

下图显示的是虚拟内存的作用。一级存储是主存(RAM),二级存储是存储设备(磁盘)。
在这里插入图片描述
是处理器和操作系统的支持使得虚拟内存成为可能,它并不是真实的内存。多数操作系统仅仅在需要的时候将虚拟内存映射到真实内存上,即当内存首次被写入时。

内存管理

当虚拟内存使用二级存储作为主存的扩展时,内核会尽力保持最活跃的数据在主存中。有以下两个内核例程做这件事情:

  • 交换:让整个进程在主存和二级存储之间做移动
  • 换页:移动称为页的小的内存单元

调度器

UNIX及其衍生的系统都是分时系统,通过划分执行时间,让多个进程同时运行。进程在处理器上和CPU时间的调度是由调度器完成的,这是操作系统内核的关键组件。下图展示了调度器的作用,调度器操作线程(Linux中是任务(task)),并将它们映射到CPU上。
在这里插入图片描述
调度器基本的意图是将CPU时间划分给活跃的进程和线程,而且维护一套优先级的机制,这样更重要的工作可以更快地执行。调度器会跟踪所有处于ready-to-run状态的线程,传统意义上每一个优先级队列都称为运行队列。现代内核会为每个CPU实现这些队列,也可以用除了队列以外的其他数据结构来跟踪线程。当需要运行的线程多于可用的CPU数目时,低优先级的线程会等待直到轮到自己。多数的内核线程运行的优先级要比用户级别的优先级高。

调度器可以动态地修改进程的优先级以提升特定工作负载的性能。工作负载可以做以下分类:

  • CPU 密集型:应用程序执行繁重的计算,例如,科学和数学分析,通常运行时间较长。这些会受到CPU资源的限制。
  • I/O密集型:应用程序执行I/O,计算不多,例如,Web服务器、文件服务器,以及交互的shell,这些需要的是低延时的响应。当负载增加时,会受到存储I/O或网络资源的限制。

调度器能够识别CPU密集型的进程并降低它们的优先级,可以让I/O密集型工作负载(需要低延时的响应)更快地运行。计算最近的计算时间(在CPU上执行时间)与真实时间(逝去时间)的比例,通过降低高(计算)比例的进程的优先级就可以达到这一目的。这一机制更优先选择那些经常执行I/O的短时运行进程,包括与人交互的进程在内。

文件系统

文件系统是作为文件和目录的数据组织。有一个基于文件的接口用于访问,该接口通常是基于POSIX标准的。内核能够支持多种文件系统类型和实例。提供文件系统支持是操作系统最重要的作用之一。

操作系统提供了全局的文件命名空间,组织成为了一个以根目录(“/”)为起点,自上而下的括扑结构。通过挂载可以添加文件系统的树,把自己的数挂到一个目录上(挂载点)。这使得遍历文件命名空间对于终端用户是透明的,不用考虑底层的文件系统类型。

下图是一个典型的操作系统的组织图:
在这里插入图片描述
顶层的目录包括:etc放系统配置文件,usr是系统提供的用户级别的程序和库,dev是设备文件,var是包括系统日志在内的各种文件,tmp是临时文件,home是用户家目录。

VFS
虚拟文件系统(virtual file system, VFS)是一个对文件系统类型做抽象的内核界面。VFS的作用见下图:
在这里插入图片描述
VFS接口让内核添加新的文件系统时更加简单。VFS也支持全局的文件命名空间,用户程序和应用程序能透明地访问各种类型的文件系统。

I/O栈

基于存储设备的文件系统,从用户级软件到存储设备的路径被称为I/O栈。这就是整个软件栈的一个子集。一般的I/O栈如下图所示:
在这里插入图片描述

缓存

由于磁盘I/O的延时较长,软件栈中的很多层级通过缓存读取和缓存写入来试图避免这一点。可以包括的缓存如下表所示(按可以用于核对的顺序排列)。

磁盘I/O缓存层级示列

缓存实例
应用程序缓存——
服务器缓存Nginx缓存
缓存服务器redis、memcached
数据库缓存MySQL缓冲区高速缓存
目录缓存DNLC
文件元数据缓存inode缓存
操作系统缓冲区高速缓存segvn
文件系统主缓存ZFS ARC
文件系统次缓存ZFS L2ARC
设备缓存ZFS vdev
块缓存缓冲区高速缓存
磁盘控制器缓存RAID卡缓存
存储阵列缓存——
磁盘内置缓存——

网络

现代内核提供一套内置的网络协议栈,能够让系统用户网络进行通信,成为分布式系统环境的一部分。栈指的是TCP/IP栈,这个命名是源自最常用的TCP协议和IP协议。用户级别拥有程序通过称为套接字的编程端点跨网络通信。

连接网络的物理设备是网络接口,一般使用网络接口卡(network interface card, NIC)。运维工程师一个常规操作就是把IP地址关联到网络接口上,这样才能用网络进行通信。

网络协议不经常变化,但是协议的增强和选项会变化,诸如新的TCP选项和新的TCP阻塞控制算法需要内核支持。另一个可能的变化是对于不同的网络接口卡的支持,需要内核有新设备的驱动。

设备驱动

内核必须和各种各样的物理设备通信。这样的通信可以通过使用设备驱动达成。设备驱动是用于设备管理和设备I/O的内核软件。设备驱动常常由开发硬件设备的厂商提供。某些内核支持“可插拔”的设备驱动,这意味着不需要系统重启就可以装载或卸载这些设备驱动。

设备驱动提供给设备的接口有字符接口也有块接口。字符设备,也称为原始设备,提供武缓冲的设备顺序访问,往往可以是任意I/O尺寸的,也可以小到单一字符,取决于设备本身。

多处理器

支持多处理器使得操作系统可以用多个CPU实体来并行地执行。通常实现成为对称多处理结构,对所有的CPU都是平等对待的。这在技术上是很难实现的,因为并行运行的线程间访问与共享内存和CPU遇到不少问题。

CPU交叉调用

多处理器的系统,时常会出现CPU需要协调的情况,如内存翻译条目的缓存一致性(通知其他CPU,如果缓存了这一条目,现在失效了)。CPU可以通过CPU交叉调用去请求其他CPU,或者所有CPU去立即执行这类工作。交叉调用被设计成了能快速执行的处理器中断,以最小化对其他线程中断的影响。

抢占也可以使用交叉调用

抢占

支持内核抢占让高优先级的用户级别的线程可以中断内核并执行。这让实时系统成为可能——这些系统有着严格的响应时间要求。支持抢占的内核称为完全抢占的,虽然实际上还是会有少量的关键代码路径是不能中断的。

Linux所支持的一种方法是自愿内核抢占,在内核代码中的逻辑停止点可以做检查并执行抢占。这就避免了完全抢占式内核的某些复杂性,对于常见工作负载提供低延时的抢占。

资源管理

操作系统会提供各种各样可配置的控制,用于精调系统资源,如CPU、内存、磁盘以及网络等。这些资源控制,能用再跑不同应用程序的系统或云环境上来管理性能。这类的控制可以对每个进程(或者进程组)设定固定的资源使用限制,或者采用更灵活的方法——允许剩余的资源用于共享。

观测性

操作系统由内核、库和程序组成。这些程序包括观测系统活动和性能分析的工具,通常安装在/usr/bin和/usr/sbin目录下。也可以安装第三方工具到系统上以提供额外的观测。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值