操作系统-14-操作系统是如何运行的

操作系统是如何启动的
我们已经知道了,操作系统其实就是一个大的C程序,本质上和我们自己写的C程序没什么区别,用户程序要想运行起来需要被操作系统从磁盘加载到内存中,那么操作系统是如何运行起来的呢?

我们可以自己想一下这个问题,计算机在关机状态下也就是不通电的状态下内存是不能保存内容的,因此一般情况下操作系统和我们的程序一样都是保存在磁盘当中(没有磁盘的计算设备,例如嵌入式设备,会保存在ROM中,稍后会有解释),那么操作系统要想运行起来必然需要被什么东西加载到内存当中。

当我们按下计算机的开机按键后,这时的计算机什么都做不了,因为这时操作系统还没有运行起来,内存中什么都没有。由于现在内存中没有任何程序供CPU执行,因此CPU只能跳转到一种特殊的存储介质中,这种特殊的存储介质就是ROM(Read-Only Memory),这种存储介质在断电后保存的内容不会丢失,CPU开始执行ROM中保存的程序,这个程序就是大家熟悉的BIOS(Basic Input/Output System),基本输入输出系统。

BIOS之所以称之为基本输入输出系统,就是因为BIOS中有管理硬件设备的程序,类似硬件的驱动程序,如果你重装过操作系统使用过BIOS就很容易理解了,你在设置BIOS时就可以使用键盘鼠标来操作,但是请注意此时操作系统还没有运行起来,因此是BIOS来管理的这些硬件设备,当操作系统运行起来后会使用自己的驱动程序来进行设备管理,此时就不再依赖BIOS了。

BIOS程序运行后就开始在磁盘中搜索操作系统映像(Operating System Image,也就是操作系统),可以搜索的位置包括floppy disk,磁盘,CD-ROM等,搜索的顺序基于BIOS的设置,这个大家应该比较清楚,我们在重装操作系统时要设置BIOS的搜索顺序。

一般情况下像Windows、Linux、MacOS这样的大型操作系统其映像都是放在磁盘当中的(就像我们写程序一样,编译成可执行程序后存放在磁盘当中)。当BIOS找到可以使用的操作系统映像后,开始把操作系统映像加载到内存当中,就好比操作系统把我们的程序从磁盘加载到内存一样,加载完毕后调用操作系统初始化函数,比如启动的是Linux系统,那么调用start_kernel()函数,该函数对Linux内核进行初始化,之后操作系统就运行起来了,因此作为程序员如果你好奇操作系统是如何初始化的,那么该阶段完成的就是操作系统的初始化任务。

以上就是操作系统启动的完整过程,如下图所示:
在这里插入图片描述
这里选择从CD-ROM中加载操作系统,但是请注意这里省略了很多细节。在很多教材中这个过程被称之为bootstrap,在接下来的内容中我们同样使用boostrap来表示操作系统的启动过程。

操作系统什么时候才会运行
在《程序员应如何理解系统调用》以及《程序员应如何理解中断》这两节中,我们已经知道了两种运行操作系统的方法:

首先是系统调用,当用户程序调用系统调用时,CPU从用户模式切换到内核模式,操作系统开始运行;其次是中断,外设向CPU发出中断信号后,和系统调用一样,CPU从用户模式切换到内核模式,跳转到提前定义好的内存地址,运行操作系统,通过调用中断handler来进行设备管理。我们已经知道了这个提前定义好的内存地址中保存的是异常表(Exception Table)。我们已经提到过,这张表就可以认为是操作系统的总入口,如图所示:

在这里插入图片描述
这张表中的处理函数有两类,一种是系统调用,一种是中断;其实这里还有一种,被称之为错误(Fault),比如除0错误,当CPU执行除0指令后,同样会跳转到这张表中,执行相应的异常处理代码。如果你用C/C++语言写一个除0的程序,编译运行后就会出现程序崩溃,就是因为操作系统运行执行异常处理函数后终止掉了该进程。还有一类比较重要的错误类型,那就是Page Fault,该类型错误涉及到了虚拟内存,我们会在后面的章节中详细讲解。此外,用户程序企图访问只有在内核模式下才能访问的内存地址(Segment Fault,段错误的原因)以及企图执行只有在内核模式下才可以被执行的机器指令都属于错误这一类型。

我们可以认为,计算机中只有两种软件,一种是用户程序一种是操作系统,CPU不是在用户模式下执行用户程序就是在内核模式下执行操作系统,因此作为程序员,我们需要知道在以下三种情况下,如果当前用户程序正在运行,那么暂定该进程,CPU切换到内核模式,操作系统开始运行:

1.用户程序调用系统调用
2.外设向CPU发送中断信号
3.CPU执行机器指令过程中产生的错误(Fault)
如图所示:

在这里插入图片描述

操作系统的本质是事件驱动(Event Driven)
我们在这里将这三种情况统称为异常,有的教材中将系统调用称之为“软中断(Software Interrupt)”,将设备产生的中断称之为“硬中断(Hardware Interrupt)”,将CPU执行指令过程中产生的错误称之为“异常”。不管这几种的称呼如何,你只要知道这几类的来源以及产生的原因就可以了。我们还会继续沿用系统调用,中断以及错误这种命名方式。

在有的文献资料中将系统调用、中断、错误统称为事件(Event),操作系统的作用无非就是当出现这些event时作出适当的处理,因此从这个角度上看操作系统本质上其实是事件驱动的(Event Driven),如图所示,你也可以形象的把事件驱动理解为“有事做事,没事一边呆着”,操作系统也是如此。

在这里插入图片描述

操作系统重获控制权:定时器(Timer)
我们已经知道了有三种情况下操作系统会开始运行重新获取计算机的控制权。

第一种是系统调用,也就是说程序员在写代码时明确的指定要进行系统调用,当CPU执行系统调用时,操作系统会开始运行;第二种是外设产生的中断信号,比如鼠标晃动,键盘按键,网卡接收网络数据,这些都会产生中断信号,之后操作系统开始运行;第三种是CPU执行指令过程中产生的错误,比如除0,这时操作系统也会运行。

接下来,我们想一个问题,那就是如果我们的计算机没有联网也就是说不会有网卡产生中断,不去晃动鼠标不去按键盘不去进行文件操作,我们写的程序中也没有除0这样的代码,也不会去进行系统调用,就像下面这样:

include<stdio.h>

int main() {
int i = 0;
while(1)
++i;
}
这段程序非常简单,不断的对变量进行+1操作,那是不是可以说当这段程序开始运行后操作系统就永远没有机会运行了?因为此时没有网卡、鼠标键盘中断、没有系统调用、没有CPU错误,会是这样吗?

要想知道答案,我们来做一个简单的实验,在单CPU计算机中编译运行这个简单的程序,不管你用的是Linux还是Windows,然后切断网络并连续两次运行这个程序。在Linux下你可以开启两个终端运行该程序,使用top命令(或者其它你熟悉的方法);在Windows下双击运行后再双击运行一次或其它你熟悉的运行方式,然后打开资源管理器观察。如果真的像我们假设的那样,那么这两个程序中会有一个完全独占CPU运行,另一个没有机会运行(因为操作系统无法运行的话就没有办法进行进程调度)。

但是如果你真的做实验就会发现这两个进程其实是在同时运行,这就证明了在这种情况下操作系统还是运行起来了,那么操作系统在这种情况下到底是如何运行起来的呢?

原来在计算机中还有一类特殊的硬件,叫做定时器(Timer),定时器会以固定的频率(比如一秒钟1000次中断)产生中断信号(Timer Interrupts),正是在定时器的帮助下操作系统才得以固定的频率不断重新获得掌控计算机的机会,而不会出现CPU被用户进程独占的情况,操作系统也正是利用这种方式来记录时间走过了多少。

注意定时器对于操作系统的进程管理来说及其重要,正是有了定时器我们才可以在即使只有一个CPU的计算机上一边写代码一边听音乐。定时器产生的中断给了操作系统不依赖系统调用、CPU错误、鼠标键盘网卡等中断而再一次运行的机会,正是以这种机制,操作系统实现了多进程的并发运行。

定时器对操作系统的作用就好比我们给自己设置了一个闹钟一样,每隔半小时响一次,每次闹钟响后,我们就会想“我现在是不是应该做点其它事情了,是不是该放下手机了,是不是该去学习了等等”。基本上操作系统也是一样,每次定时器产生中断后,操作系统就会开始运行并检查“是不是当前运行的进程使用CPU太多了,要不要开始运行另外一个进程等等”。正是以这种方式,操作系统实现了进程调度以及时间管理。

因此作为程序员,我们需要知道定时器对于操作系统的重要意义。当你打开资源管理器看所有当前正在运行的进程时,你要意识到正是定时器产生的中断信号才使得操作系统在有限的CPU上同时运行几十甚至成百上千个进程成为可能。

接下来我们就从CPU的视角来看看操作系统是如何工作的。

操作系统是如何工作的:基于CPU时间维度
在这一节我们同样假设计算机中只有一个CPU,以下就是从CPU时间角度来看整个计算机系统是如何运行的,如图所示:

在这里插入图片描述
在这里注意一点,除去启动过程bootstrap之外,CPU不断在进程管理、中断处理、进程三者之间进行切换,其中进程管理与中断处理是操作系统的一部分,工作在内核模式;进程1、进程2、进程3属于用户程序工作在用户模式。bootstrap属于引导程序用于启动操作系统,和操作系统一样工作在内核模式下。

接下来,我们讲解一下可能运行场景,注意这只是其中一种可能的情况,该运行场景基于上图,以下是该过程:

1.boostrap将操作系统加载到内存当中并对操作系统进行初始化,操作系统开始运行。
2.操作系统的进程管理模块初始化并创建进程1。
3.进程1开始运行。
4.进程1暂停运行,进程管理模块初始化并创建进程2。
5.进程2开始运行。
6.进程2暂停运行,进程管理模块初始化并创建进程3。
7.进程3开始运行。
8.产生中断信号,进程3暂停运行,中断handler开始运行。
9.中断处理完毕后,进程管理模块进行进程调度,开始恢复进程2。
10.进程2开始运行。
11.产生中断信号,进程2暂停运行,中断handler开始运行。
12.中断处理完毕后,进程管理模块进行进程调度,开始恢复进程1。
13.进程1开始运行。
14.产生中断信号,进程1暂停运行,中断handler开始运行。
15.中断处理完毕后,进程管理模块进行进程调度,开始恢复进程3。
16.进程3开始运行。
17.产生中断信号,进程3暂停运行,中断handler开始运行。
18.中断处理完毕后,进程管理模块进行进程调度,开始恢复进程2。
19.进程2开始运行。
注意,在该例子只有一个CPU,你会发现其实我们的进程和操作系统都是交错执行的,正是通过不断的将CPU进行重新分配,操作系统才实现了多任务处理,只不过在只有一个CPU的情况下任意时刻实际上只有一个进程在运行,只有多CPU的情况下才能实现多进程的并行运行。

如下图所示:2,4,6属于进程创建,9,12,15,18属于进程调度;这些都是我们在接下来的章节中重点要讲解的内容。

在这里插入图片描述

此外,8,11,14,17属于中断处理,我们在上一节中已经详细讲解过了。在三个进程运行期间产生了四次中断信号,CPU真正执行用户程序是在3,5,7,10,13,16,19,也就是说CPU其实不是一直都在运行我们的程序的。

在这里插入图片描述
因此从这里例子中也可以看到,作为程序员我们是没有办法控制进程运行时间长短的,进程随时可能会因为中断信号的产生而被暂停执行。如果写一个测试程序去监测代码的运行时间你就会发现,程序每次的运行时间都不是固定的,就是因为我们的程序会因为各种情况(系统调用,中断,CPU执行错误)而被暂停执行,被暂停后操作系统开始重新获取计算机控制权,此时操作系统可能会选择其它进程运行,比如进程A,只有进程A暂停运行后操作系统才有可能恢复运行我们的程序,这一切都是由计算机的运行状态来决定的。

作为程序员,我们应该知道,就像多个进程可以交替运行一样,操作系统其实也是和进程交替运行的。

总结
在这一节中我们详细讲解了操作系统的运行原理。在系统调用,中断以及CPU错误这三种情况下我们的程序被暂停执行,操作系统开始运行,也因此操作系统其实是事件驱动(Event Driven)着来运行的,同时事件驱动也是程序员开发过程中常用的一种编程模式。此外我们也了解了定时器的重要作用,正是定时器使得操作系统可以有机会重新运行并对进程进行调度管理,多任务处理正是基于此实现的。最后,我们以CPU运行时间的视角详细讲解了整个计算机的运行过程,操作系统和我们的程序交替运行,同时我们也没有办法控制一段代码的执行时间。

至此,我们从全局的角度了解了操作系统的运行方式,这对于理解接下来的课程至关重要,在后面的课程中我们将会详细的讲解操作系统几个重要的模块,操作系统是如何管理进程的,如何实现虚拟内存的等等。

让我们适当休息一下,接下来的章节就是操作系统中的重点内容了,让我们先来详细了解一下操作系统是如何管理进程的。

本文来自《码农的荒岛求生》

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发如雪-ty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值