7.10 操作系统的启动

   这里,我们简单看看操作系统的启动问题,主要是了解,操作系统如何建立一个运行环境。

   首先有一个基础的问题,就是内存断电后数据是无法保存的。所以,设备刚上电时,内存里一无所有,CPU无法从内存开始执行。基于此,操作系统需要保存在磁盘类设备中。但是磁盘同CPU速度差距太大,所以,也不能直接使用磁盘来执行程序。为解决这个问题,现在通用的做法都是,放一段引导程序到磁盘类设备中(需要注意,这里只是说磁盘类设备,并非磁盘。用于存放引导程序的存储设备还有一个隐含的先决条件,那就是不需要驱动。这是为满足CPU可以直接读取数据到内存执行而要求的。不过,现在有些CPU,特别是SOC,可以内置一个简单的程序到内部固件,以实现安全启动),并将该设备的开始地址设置为零地址。开机后,CPU从零地址开始执行,从而执行开机引导程序。在开机引导程序中,将操作系统代码从磁盘加载到内存中,这样内存中就有了系统代码。之后,将CPU的PC寄存器设置为内存存放操作系统代码的开始地址处,这样就实现了运行跳转,之后,操作系统代码才真正开始执行。基本流程如下图:

 

   其实,技术是一直发展的。上面所述也只是一般情况。关键是了解其中的原理和限制条件,而非单纯的死记结果。

   上述过程只是完成了第一步,即操作系统的引导加载问题。

   对于引导加载程序,这里需要多说一点。引导程序不仅可以将操作系统代码从磁盘加载到内存,也可以将自身搬移到内存的别的地方执行,如果理解了这一点,对引导程序的实现就不会再有疑问了。当然了,操作系统的开始代码也可能干这件事。比如,如果操作系统代码被搬移到内存开始处,其执行过程中,很可能将自己搬移到别的安全地方,以便能够把开始一段内存地址预留出来给中断向量表。这个搬移自身的过程,也可以理解为自举的过程,也就是不需要借助别的程序帮助,自身就可以完成。那如何来理解指令搬移自身呢?

   我们以完全在内存中执行为例来说明。首先,这段特殊的程序,需要被引导程序加载到内存中。之后,重置CPU的PC寄存器,跳转到这段程序执行。我们要清楚,代码是在CPU里面执行的,所以我们可以将正在执行代码的CPU想像成我们的大脑。这样再来理解正在执行的指令将自己放到内存的别的地方。

   第一条指令:从代码开始处搬移四个字节到内存空白地址处;

   第二条指令:是否完成搬移?如果没有,则执行下面的指令,否则执行第六条指令;

   第三条指令:开始地址加四个字节;

   第四条指令:目的地址加四个字节;

   第五条指令:跳转到第一条指令执行;

   第六条指令:跳转到新的地址处执行。

   通过上面六条指令,我们就可以完成代码的搬移,这个搬移过程,也搬移了上述六条指令本身。只不过,第六条指令跳转的地址,一定是要搬移后重新计算的正确地址。这些在写代码时,一定要特别清醒。上述过程,可以图示如下:

 

指令搬移自己的示意图

   这就是有点绕的地方。其实完成这个过程,只需要知道地址即可,即自己当前在什么地址处,这对于开机刚运行的程序来讲,并不是什么难的问题。也正是因为引导程序知道这些地址,所以才可以做到乾坤大挪移。如果系统运行起来了,要做到这一点就不那么容易了。因为运行过程的随意性,加之用户可能有一些操作,而内存的分配又与用户操作十分相关,此时,要想知道内存的完整使用情况,即使是操作系统的设计者,也是难上加难。

   对于搬移本身来讲,同样没有什么困难,难的还是在于理解起来有点绕。指令是在CPU内部运行的,内存只是存储指令的。就如上图而言,第一条指令运行后,内存中的内容发生右边指向线所示的变化。而指令所在的内存,并没有发生任何改变。同样,当执行到指令5后,就回到指令1,继续将内存往目的地址搬移。整个过程即使完事了,上述六条指令所在的内存也不会发生变化,而目的地址中,则保存了该段内存的一份副本。当CPU执行指令六切换到新地址后,副本可以转正,而原来的指令所在的内存段,就可以释放,用来做他用了。

   CPU从内存开始执行操作系统代码后,一切就将按照操作系统设计的逻辑工作,控制权也从此牢牢掌握在操作系统手中。操作系统的执行过程,将完成内存环境的建立、进程环境的建立、文件系统的建立,完成设备的识别与驱动的加载等等。从宏观上来讲,操作系统的执行过程,也就是建立整个系统工作环境的过程。最终,所有的数据结构都在内存中正确的关联起来,形成一张巨大的逻辑的管理网。至此,操作系统的主体功能已经完成。

 

   这些数据结构之间的关系网错综复杂,正是依赖这些管理结构及其实例,操作系统掌握着全局,控制着一切。

   最后,我们还需要看看操作系统的收尾工作。话说操作系统起来后,接下来的工作就交给应用程序了。对于操作系统来讲,这个工作并不简单。比如,对于Linux而言,在所有初始化工作完成后,操作系统就处于怠速状态,就是一个核心任务在空转,不做具体的工作。但是,这只是在没有事情干的情况下的状态。一旦有中断,或者外部有请求,操作系统就要立马去处理,摆脱怠速状态。

   比如,我们假设执行一个用户程序。在前面部分介绍过,Linux下,新的进程创建,是基于fork这个系统调用。这个系统调用的本质,是拷贝当前正在运行的进程,繁殖出一个新的进程。在接口调用之后,新旧进程就分道扬镳了,各自去执行自己的代码逻辑。此时,新进程对系统资源的了解,一部分来自操作系统自身的接口,一部分则继承自创建者。新进程的创建,就如同孵化一个新生命,跟创建者既有相似的地方,又有不同的地方。

   操作系统了解到有新进程创建后,就将其加入调度系统,调度该进程执行。该进程执行时,所依赖的shell环境、根文件系统等,也都是通过继承获取的。一个强大的进程可以创建一个更加强大的进程。我们看到,这个过程跟自然界的很多现象是类似的。

   最终,操作系统一步一步,创建了一个一个可以作为基础的,用来复制的进程,这些进程具备了基础的使用操作系统服务的能力。Android中的zygote孵化进程通过融合Framework能力给所有上层应用,也是这种思想的体现。

   下面,对整个操作系统部分内容做个简单的总结。

   最开始,我们强调,操作系统也是一个程序,只不过这个程序有点特殊,一方面是拥有更大的权限,一方面是拥有承上启下的能力。之后,介绍了中断让系统有了分身能力,从而实现了并发,也就是伪并行。这种伪并行,微观上串行,宏观上并行。在之后,我们了解到,进程的概念和实现,隔离了大部分程序,操作系统通过建立档案,管理了所有进程。内存管理使得我们的程序设计几乎可以无所顾忌,只管做自己就好。文件系统可以使我们存储各种数据,网络方便了设备之间的通信,显示拉近了用户的距离…

   上面这些内容,结合消息通讯、任务同步、时间管理、各种资源接口和标准库等,基本上就可以构建应用工作的基础了。

   可见,有了这些功能后,程序运行基础就搭建完成了,我们也就可以在操作系统之上进行各种程序开发了。回到原点上,操作系统是做什么用的?千辛万苦,费尽心机的把它设计出来,并不是为了好玩。而且,最初的、最原始的操作系统诞生出来时,它并不具有现在这个样子。很长一段时间,人们还为操作系统应该包含那些功能,而不包含那些功能,进行过认真的、不短时间的讨论。到目前形成现代操作系统的统一概念,背后有着实际的原因,这其中最主要的莫过于人类庞杂的需求。怎么讲呢,简单来说,同样的硬件,安装同样操作系统的计算机,要可以应用于所有需要的领域,只要是在它的运算性能所及范围内。这样来看,操作系统要做到以不变应万变,在我之上,你可以设计任何程序,我可以最高效、最简化、最完整的把硬件资源封装出来,最大化的满足各种程序设计的需求,如此,使命完成。

   但是,操作系统不是终点,而是起点。安装了操作系统,只是迈出了最重要的一步。仅仅只有操作系统,还远远不足以让计算机无所不能。要做到这一点,需要的是应对各种需求的,运行于操作系统之上的丰富多彩的应用软件。丰富的、好用的软件,反过来,也使得操作系统出名,让计算机普及。如微软Windows,苹果iPhone上的iOS,都是因为其上的大量优质应用,得以流行起来。

   至此,我们介绍了操作系统。作为一个程序员,我想直接接触到操作系统源代码,或者进行操作系统代码编写的毕竟还是少数中的少数,大部分程序员参与的还是操作系统之上的各种应用开发。我们在学校里学习编程语言,学习算法,学习操作系统原理,学习程序设计,很少涉及到背后的逻辑。比如可执行程序格式是怎么样的,都由那几部分构成,静态库与动态库是怎么装载的,我们的代码真的是从main函数开始的吗?等等一系列问题。如果我们能对这块内容有很好的理解和掌握,既可以加深对程序运行的理解,有助于设计更加优秀的程序,也可以对程序的调试起到很大的帮助作用。从下部分开始,我们将开始应用之旅,介绍程序的那些事儿。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龙赤子

你的小小鼓励助我翻山越岭

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

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

打赏作者

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

抵扣说明:

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

余额充值