在计算机中,进程是程序的执行实例。
当我们运行一个程序时,操作系统会为这个程序创建一个新的进程,然后在该进程的上下文中运行可执行目标文件(即程序的二进制代码)。
每个进程都有自己的内存空间、寄存器、文件描述符等资源,这些资源在进程之间是隔离的,以确保它们不会相互干扰。
应用程序执行的假象
当我们运行一个程序时,就好像这个程序独自占了整台电脑资源一样,但这实际上是一种假象。
计算机的处理器(也就是电脑的大脑)会轮流为不同的程序提供服务。也就是说一个程序运行一会儿,然后让出处理器控制权,让其他程序有机会运行。
这个过程不断交替进行,就好像每个程序都在独立使用处理器一样,但实际上处理器在不同程序交错使用着。
模式位与进程切换
处理器通过「模式位」来提供一种功能:模式位告诉处理器当前运行的程序有多大特权,可以执行哪些操作,以及可以访问哪些计算机内存区域。
当程序在「内核模式」下运行时,它可以执行指令集里的任何命令,访问所有的内存区域。
当程序在「用户模式」下运行时,它的特权受到限制,不能执行一些特殊的操作,比如关闭服务器、改变处理器的模式、进行 I/O 操作或者访问计算机的核心数据。
为了执行这些特殊操作,程序必须通过一种叫做「系统调用」的接口来请求内核帮助,我们之前文章《计算机系统「异常」处理机制》详细介绍过,这是一种间接方式来访问内核代码和数据。
程序一开始在用户模式下启动,只有当发生特殊情况,比如「中断、故障、系统调用」时,处理器才会切换到内核模式。当内核完成任务后,处理器会再次切换回用户模式,让程序继续运行。
进程上下文都有什么?
在操作系统中,每个进程都有一个称为上下文的特殊数据集。这个上下文包含了许多重要的内容,例如目标寄存器(用于存储数据的位置)、程序计数器(用于跟踪下一条指令的位置)、用户栈(用于保存函数调用信息)、状态寄存器(包含处理器的各种状态信息)、内核栈(用于处理内核级别的操作)、地址空间页表(用于管理内存访问权限)、当前进程的信息(包括进程状态、标识等)以及已经打开文件的信息(用于跟踪文件操作)。
在进程的执行过程中,操作系统可以根据需要决定抢占当前正在执行的进程,并切换到先前被抢占的进程,这个决策过程被称为调度,由内核中的一个称为调度器的代码模块来管理和执行。
上下文切换流程:
一、保存当前进程的上下文
当操作系统决定要切换到另一个进程时,首先会保存当前正在执行的进程的上下文。
二、恢复原先被抢占的进程上下文
操作系统会选择要执行的下一个进程,并将其保存的上下文恢复回处理器的寄存器和状态中。
三、控制传递给新恢复的进程
一旦新进程的上下文被恢复,处理器就会从程序计数器指示的地址开始执行新进程的代码。这样,控制权就转移到了新的进程,它可以继续执行自己的任务。
进程切换示例
让我们来看一个具体的例子,说明进程切换是如何工作的。考虑两个进程,进程A和进程B,它们之间发生上下文切换的情况。
- 进程 A 开始执行:初始化,进程 A 处于用户模式下,执行一些计算任务。然后,它需要从磁盘读取数据,因此执行了一个系统调用(read)。这个系统调用需要内核的帮助,所以进程 A 将切换到内核模式。
- 内核处理系统调用:在内核模式下,内核代码开始处理进程 A 的系统调用请求。它与磁盘控制器通信,请求数据传输。由于磁盘读取需要一段时间(可能几十毫秒),内核不会让进程A一直等待,而是决定进行上下文切换,以充分利用计算机资源。此时,内核代表进程 A 在内核模式下执行命令。
- 上下文切换到进程 B:在上下文切换的第一部分中,内核暂停了代表进程 A 的执行,然后开始代表进程 B 在内核模式下执行指令。切换完成后,进程 B 在用户模式下开始运行。
- 进程 B 执行一段时间:进程 B 在用户模式下运行一段时间,执行一些计算工作,直到磁盘控制器发出中断信号,表示数据已经从磁盘传送到内存。
- 上下文切换回进程 A:内核判断进程 B 已经运行了足够长的时间(或者根据其他调度策略),于是它执行一个上下文切换,将控制权从进程 B 切换回进程 A。此时,进程 A 在用户模式下继续执行系统调用 read 之后的命令。
- 进程 A 继续执行:进程 A 继续执行它的任务,直到下一次发生异常或需要进行其他系统调用。
这个过程可以一直循环下去,不同的进程之间交替执行,以实现多任务处理。
上下文切换允许操作系统有效地管理多个进程,确保它们在计算机上公平共享资源,同时避免了进程长时间等待,从而提高了系统的效率和响应能力。
内容来源:《深入理解计算机系统》
如果您对本篇文章中提到的问题有任何疑问或想法,请在评论区留言,我将尽力回复。
微信公众号「小道研究」,获取更多关于前端技术的深入分析和实践经验。