本节将详细介绍计算机硬件的组成。然而,我们首先需要明确所有人造系统都是有目的的,因此我们需要首先关注计算机系统产生的原因,进而再关注计算机系统的本质。不过我们不会从历史角度来讨论计算机的起源,而是在应用层面给出一个明确的答案:计算机的存在是为了执行程序。
一、程序上下文
程序是为了通过计算机硬件和操作系统实现特定功能或解决特定问题而给计算机系统一系列明确指令的。从操作系统的角度来看,程序由操作系统管理,因此执行的细节来源于操作系统的内存管理机制。不同的操作系统有不同的实现,但基本上使用可执行文件、内存占用以及以下模型来描述程序执行时的环境和状态,以及管理程序中的变量和数据。本节只做简略介绍,详细的程序上下文介绍将在计算机系统实施中的程序设计语言一节进行。
1.可执行文件
可执行文件是计算机保存程序的文件,它基本上包含五个部分代码段,数据段,BSS段,堆,栈等,所有这些部分描述了程序代码、数据、程序运行时需要开辟的栈和堆的大小等信息,这些信息在编译时通常就已经被确定并被写入可执行文件中。
代码段 | 包含程序的机器指令,用于执行程序的功能。 |
只读数据段 | 常量、字符串等,这些数据在程序运行时不能被修改。 |
读写数据段 | 存储全局变量、静态变量等,这些数据在程序运行时可以被修改。 |
BSS段 | 用于存储程序中未初始化的全局变量和静态变量。因为没有初始化,他们在程序执行时初始值都是0. |
栈段 | 栈段中通常只有一些基本的信息,如栈底位置、栈顶位置等,具体的栈内容是在程序运行时动态生成的。 |
堆段 | 堆段中通常只有一些基本的信息,如堆的起始、堆的末尾位置等,具体的堆内容是在程序运行时动态生成的。 |
需要注意的是,不同操作系统对区和段的划分方式可能会有所不同。例如,在Windows操作系统中,可执行文件通常被划分为代码段、数据段、资源段和重定位段等,而在Linux操作系统中,可执行文件通常被划分为代码段、数据段、BSS段和动态链接段等。
2.内存占用
程序需要运行时需要将可执行文件加载到内存中后才能运行,操作系统为了对内存进行管理就将它分区域进行管理。大的方向上内存区域分为只读区域和读写区域,其中只读区域又分为代码区和只读数据区,读写区域又可以分为读写数据区、栈区、堆区。
代码区 | 用于存储程序的机器指令。 |
只读数据区 | 用于存储程序中的常量和只读数据,例如字符串常量、全局常量等。 |
读写数据区 | 用于存储已初始化的全局变量和静态变量和未初始化的全局变量和静态变量等。 |
栈区 | 在程序运行时,每个线程都会有自己的栈,用来保存局部变量、函数调用信息、返回地址等数据。 |
堆区 | 堆(heap)是指程序运行时动态分配内存的一块区域,用来存储程序运行时动态生成的数据结构、对象等。 |
3. 程序生命周期
接下来,我们将介绍操作系统如何使用这些概念来管理程序的运行。总的来说,这个过程可以分为以下几个步骤:加载、解析、初始化、执行和退出。程序的加载、解析和初始化阶段由操作系统的加载器(loader)完成。
当用户启动一个程序时,加载器会从磁盘或其他存储介质中读取程序的二进制代码,并将其加载到内存中。在加载过程中,操作系统会为程序分配内存空间,并将程序的代码、数据和堆栈等信息映射到相应的内存区域中。它们之间存在一一对应的关系。
在程序运行时,操作系统会将可执行文件的代码段加载到代码区,只读数据段加载到只读数据区,读写数据段和BSS段加载到读写数据区。堆区和栈区则分别根据堆段和栈段描述的大小等信息动态地开辟空间。读写数据区的分开可以使操作系统将一些数据放在只读区中,这样可以减少内存的使用,并且防止程序运行时修改这些数据。其中,代码区和只读数据区是只读区,其他区域为读写区。需要注意的是,不同的操作系统可能会对内存区域的名称和细节有所不同,但是一般都包括以上的几个区域。
程序解析时,操作系统会解析程序中的符号表信息,确定程序中各个函数和变量的地址和大小等信息,并将其保存到内存中。在解析过程中,操作系统还会进行链接和重定位等操作,以确保程序能够正确地运行。
初始化时,操作系统会为程序设置执行环境,包括命令行参数、环境变量、文件描述符等信息。在初始化过程中,操作系统还会为程序分配系统资源,如文件、网络、设备等,并建立程序与资源之间的联系。
程序执行时,操作系统会将程序的控制权交给程序的入口函数,让程序开始执行。在执行过程中,操作系统会负责管理程序的进程和线程,调度程序的执行顺序,并处理程序的输入输出等操作。
当程序执行完毕或出现异常时,操作系统会终止程序的执行,并释放程序占用的内存和资源等。在终止过程中,操作系统会进行一些清理工作,如关闭文件、释放锁等。
4.程序的执行过程
程序的执行以指令为单位进行。当程序准备执行时,操作系统会将程序计数器初始化为代码段的起始地址。每当程序执行完一条指令时,操作系统会将当前程序计数器指向的代码加载到CPU中,并将程序计数器指向下一条指令,CPU执行指令。程序不断重复这个过程,直到所有指令都执行完毕。
在执行指令时,程序可能从堆区或栈区读取和写入数据,进行各种计算、比较、排序等操作。而为了定义更灵活、高效、可管理、易于多个线程访问的变量,程序可能需要使用堆进行动态内存分配。
在执行指令时,程序可能会进行函数调用。这时,它可以使用栈来存储函数的参数、返回值和局部变量。程序会将函数的参数和返回地址压入栈中,并跳转到函数的入口地址。函数执行完毕后,程序会将返回值存储到寄存器中,并将控制权返回给调用者。
在执行指令时,程序也可能会进行系统调用。当程序需要访问系统资源(如文件、网络、设备等)时,它会向操作系统发送系统调用请求。操作系统会为程序提供访问系统资源的接口,并确保程序可以安全地访问系统资源。
剩下的有时间补充。