iOS菜鸟的CS之路(计算机系统漫游)

计算机系统是由硬件+软件构成,共同工作来运行应用程序。这里由一个非常简单的例子入手观察在整个程序生命周期中,计算机系统分别承担了什么样的责任。

#include <stdio,h>

int main(){
    printf("hello, world");
}

系统简介

信息就是位+上下文

这是一个很简单的C语言源程序,在计算机系统中,像这样的源程序一般都是由ASCⅡ字符构成,也称为文本文件,其他文件则称为二进制文件。这也透露出来一个基本的理念:系统中所有的信息(磁盘上,存储器中,网络中)都是由一串位表示的,唯一区别只是上下文。

程序被其他程序翻译成不同的格式

本身hello程序没有任何意义,只是一个简单地C语言源程序,能够被人读懂,然而系统却不认识。但是经过编译系统一系列的处理过后,最终会成为一个可执行的目标程序。具体的翻译过程是这样的:

hello.c - (预处理器) - hello.i - (编译器) - hello.s - (汇编器) - hello.o - (链接器) - hello

  • 预处理阶段,经过预处理器展开预处理程序,填充stdio库中的程序得到另一个C程序,通常用.i做后缀。
  • 编译阶段,经过编译器翻译成汇编程序,这时候语言已经很接近机器指令了,不同的的高级语言,不同的编译器,最终得到的却是通用的汇编程序
  • 汇编阶段,通过汇编器将汇编程序翻译成机器语言指令,然后打包成一种叫relocatable object program格式的目标文件
  • 链接阶段,链接器把不同的目标程序链接合并处理,最终产出一个可执行文件。

了解编译系统如何工作的益处

  • 优化程序性能 (一个switch语句是否总是比一系列if-else高校,一个函数调用的开销有多大,while和for哪个更有效,指针还是数组等等)

  • 理解链接时出现的错误 (很长时间,在链接过程的错误总是最让人头疼的,静态库和动态库的区别,有些问题会知道运行时才暴露等等)

  • 避免安全漏洞 (缓冲区溢出是困扰网络和Internet服务器上安全漏洞的主要原因)

处理器

处理器读并解释存储在存储器中得指令

当前面的程序已经翻译成一个可执行程序后,我们开始在操作系统上执行,会在控制台上打印出hello, world这些内容,那么这之间发生了什么呢?

硬件组成
  • 总线 (通常总线被设计成传送定长的字节块,其中字节长是一个基本系统参数,比如常用的32位,64位分别对应了4字节长,和8字节长。)

  • I/O设备 (这是一个很广的概念,鼠标,键盘,显示器属于I/O设备,硬盘等也是。一种是本身就在主板上,一种是适配器插上去。)

  • 主存 (也称为内存,是由一组DRAM芯片组成,是一个线性的字节数组,每个字节都有一个唯一的地址,从零开始。一般来说不同的机器,指令的长度不相同。)

  • 处理器 (CPU,简称处理器,用来执行存储在主存中指令的引擎,由寄存器,程序计数器(PC),算数/逻辑单元(ALU)组成。)

软件运行

在整个运行期间,先把存放在磁盘上得可执行文件传送到主存(通过直接存储器(DMA)),然后再到寄存器。

高速缓存的重要性

前面整个过程可以看出,有大量的时间是用在资源的传输上,于是聪明的系统设计者就在处理器和主存间加入了高速缓存存储器,访问速度几乎和寄存器一样快。L1和L2运用的是一种静态随机访问存储器(SRAM)的硬件技术实现,现在很多还加入了L3缓存。

存储设备的层级结构

寄存器 -> L1高速缓存(SRAM) -> L2高速缓存(SRAM) -> L3高速缓存(SRAM) -> 主存(DRAM)-> 本地二级存储(磁盘) -> 远程二级存储(分布式,Web)。

操作系统

在hello程序中我们并没有直接调用键盘,显示器等,这些其实都是由操作系统完成的,在应用程序和硬件之间。操作系统有两个基本功能:

  • 防止硬件被滥用。
  • 向应用提供简单一致的控制硬件机制。

总的来说,文件是对I/O设备的抽象,虚拟存储器是对主存和磁盘I/O的抽象,进程则是对处理器,主存和磁盘I/O的抽象。

进程和线程

进程是操作系统对一个正在运行程序的抽象,无论是在单核,还是多核系统中,一个CPU看上去都像在并发的执行多个进程,这是通过处理器在进程间切换实现的,这种交错机制也称为上下文切换。

通常情况下一个进程只有单一的控制流,但是现代系统中,一个线程实际上可以由多个线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样地代码和全局数据。

虚拟存储器

这是一个抽象的概念,为进程提供一个假象,好似每个进程都在独占整个主存。每个进程看到的都是一致的存储器,称为虚拟地址空间。

  • 程序代码和数据 (对于所有的进程来说,代码是从一个固定的地址开始的,紧接着是C全局变量相对应的数据位置。
  • 堆 (运行时使用的堆,代码和数据区在刚开始运行已经被设定好大小,而堆是动态分配的)
  • 共享库 (大致是放一些C标准库,数学库这样的共享库的代码和数据区)
  • 栈 (位于虚拟地址空间顶部,编译器用来实现函数调用,可以动态扩展和收缩)
  • 内核虚拟存储器 (内核总是驻留在内存中,顶部空间是为内核预留的)

文件

文件就是字节序列,每个I/O设备都可以看成文件,向应用程序提供了一个统一的视角。

重要主题

网络通信

前面我们都是孤立的看待系统,分为硬件和软件,实际上在现代系统中已经离不开网络的存在。从一个单独的系统来看,网络可以视为一个I/O设备,通过一个网络适配器,系统可以读取其他机器发来的数据。

并发和并行

- 线程级并发 (可以使应用程序更快,可以并行地高效执行)

- 指令级并行 (处理器可以同时执行多条指令的属性称为指令级并行)

- 单指令,多数据并行 (允许一条指令产生多个可以并行执行的操作)

计算机系统中抽象的重要性

无需了解内部实现可以使用,前面介绍了三种抽象,再往外扩展,虚拟机则是对整个计算机(操作系统,处理器,程序)的抽象。

总结

  1. 计算机系统是由硬件和软件组成,共同协作运行程序,内部信息通过位表示,根据不同的上下文解释不同,经过编译系统翻译成可执行文件。

  2. 处理器读取并解释在主存中的二进制指令,由于大量时间花费在数据传输中,所以引入缓存机制,存储结构分级。

  3. 操作系统内核是应用程序和硬件的媒介,提供了三种抽象:文件,虚拟存储器和进程。

  4. 最后网络提供了计算机之间通信的手段,换个角度看,网络也是一种I/O。