良心公众号
关注不迷路
菜鸡最近在读深入理解计算机系统(CS:APP)一书,将自己学习过程中的收获整理成笔记分享给大家。PS:本书是基于C语言展开论述的,但C语言的特性并不是本文的重点,书中的知识及设计思想对其他程序员同样适用。
今天的主要内容是第一章——计算机系统漫游的相关知识总结。和市面上很多书都不同的一点是,哪怕是第一章,也是满满的干货,而不仅仅是一些流于表面的科普与介绍。有时间的小伙伴建议读原著,没有时间的小伙伴看菜鸡整理的文章就够了!话不多说,开始!
计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。深入了解计算机系统的硬件和软件组件,有助于写出更健壮、更高效的程序,同样有助于更快速地定位和解决问题。
书中以最简单的hello world程序为例,通过跟踪其生命周期(被程序员创建 → 在系统上运行 → 输出简单的消息 → 终止)来展开对系统的学习。
hello程序的代码如下:
#include
int main()
{
printf("hello, world\n");
return 0;
}
hello程序的生命周期是从一个源程序(或者说源文件)开始的,即程序员通过编辑器创建并保存的文本文件,文件名是hello.c。源程序实际上就是一个由值0和1组成的位(又称为比特)序列,8个位被组织成一组,称为字节。每个字节表示程序中的某些文本字符。
为什么8个位是一个字节呢?或者说为什么要引入字节的概念呢?因为大部分现代计算机系统都使用ASCII标准来表示文本字符,用一个唯一的单字节大小的整数值来表示每个字符。只由ASCII字符构成的文件称为文本文件,所有其他文件都称为二进制文件。
hello程序的生命周期是从一个高级C语言开始的,为了在系统上运行,需要转化为低级机器语言指令。然后这些指令按照可执行目标程序(可执行目标文件)的格式打包,并以二进制磁盘文件的形式存放起来。
在Unix系统上,从源文件到目标文件的转化是由编译器驱动程序完成的:
linux> gcc -o hello hello.c
GCC编译器驱动程序读取源程序文件hello.c,并将其翻译成可执行目标文件hello。该翻译过程可分为四个阶段完成,如下图所示。执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)一起构成编译系统。
预处理阶段:预处理器根据以字符#开头的命令,修改原始的C程序,得到.i文件。
编译阶段:编译器将.i文件翻译成包含汇编语言程序的.s文件。
汇编阶段:汇编器将.s翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在二进制目标文件.o文件中
链接阶段:链接器将程序需要的其他.o文件以某种方式合并到得到的.o文件中,得到可执行目标程序。
了解编译系统如何工作的益处:
优化程序性能
理解链接时出现的错误
避免安全漏洞
编译系统将hello.c源程序翻译成可执行目标文件hello,并存放在磁盘上,通过以下shell命令可以运行该可执行文件:
linux> ./hello
那么,运行hello程序时,究竟发生了什么?这时候就涉及到计算机系统的硬件组成的相关知识了。
一个典型系统的硬件组成主要有以下部分:
总线:贯穿整个系统的一组电子管道,它携带信息字节并负责在各个部件间传递。
I/O设备:计算机系统与外部世界的联系通道,通过控制器或者适配器与I/O总线相连。
主存:临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。主存在物理上是一组动态随机存取存储器(DRAM)芯片;在逻辑上是一个线性的字节数组,每个字节都有其唯一的从零开始的地址(数组索引)。
处理器:中央处理单元(CPU),简称处理器,是解释(或执行)存储在主存中指令的引擎,其核心是一个大小为一个字的存储设备(或寄存器),称为程序计数器(PC)。PC在任何时刻,都指向主存中的某条机器语言指令。
基于对计算机典型系统的硬件组成的认识,hello程序运行的过程可以大致描述为以下过程:
通过I/O输入设备(键盘)输入shell指令
shell程序将字符逐一读入寄存器,再放到内存中
指令以回车键表示结束,然后shell执行一系列指令来加载可执行的hello文件,将hello目标文件中的代码和数据从磁盘复制到主存
然后处理器开始执行hello程序的main程序中的机器语言指令,将数据从主存复制到寄存器文件,然后再从寄存器文件中复制到显示设备进行显示
上述示例中,hello程序的机器指令最初存放在磁盘上,当程序加载时,它们被复制到主存;当处理器运行程序时,指令又从主存复制到处理器。打印的内容也经历了类似的过程。这些复制就是开销,因此,加快复制操作的执行是很有必要的,这也就是高速缓存存在的意义所在,同样的,在编写程序时善于利用高速缓存,可以将程序提高一个数量级。
高速缓存的思想催生了存储器层次结构的形成。一个存储器层次结构由低速大容量到高速小容量依次为:
远程二级存储(分布式文件系统,Web服务器:L6) → 本地二级存储(本地磁盘:L5) → 主存(DRAM:L4) → 高速缓存(SRAM:L3 → L2 → L1) → 寄存器(L0)
大致了解过系统的硬件组成之后,我们把目光移到操作系统层面。所有应用程序对硬件的操作尝试都必须通过操作系统。
操作系统有两个基本功能:
防止硬件被失控的应用程序滥用
向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
操作系统通过几个基本的抽象概念(进程、虚拟内存和文件)来实现上述两个功能。如下图所示,文件是对I/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程是对处理器、主存和I/O设备的抽象表示。
是不是又一次体会到了,程序员最重要的能力就是抽象能力。
我们逐一来看一下操作系统的抽象概念:
进程:进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件。其实不同进程之间是并发运行的,即一个进程的指令和另一个进程的指令是交错执行的,这是通过处理器在进程间切换来实现的。操作系统实现这种交错执行的机制称为上下文切换。进程间的转换是由操作系统内核管理的。内核是操作系统代码常驻主存的部分。当应用程序需要操作系统的某些操作时,就会执行系统调用指令,将控制权传递给内核,然后内核执行被请求的操作并返回应用程序。内核不是一个独立的进程。它是系统管理全部进程所用代码和数据结构的集合。
线程:一个进程可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
虚拟内存:虚拟内存为每个进程提供了一个假象,即每个进程都在独占地使用主存。每个进程看到的主存都是一致的,称为虚拟地址空间。虚拟地址空间由低到高依次是程序代码和数据、堆、共享库、栈、内核虚拟内存。
文件:文件就是文件序列,它向应用程序提供了一个统一的视图。
本篇关于CS:APP——计算机系统漫游的知识点总结就到这里了。
欢迎大家一起讨论技术,共同成长!
学习 | 工作 | 分享
????长按关注“有理想的菜鸡”
只有你想不到,没有你学不到