【CSAPP全书详细笔记系列】第一章-计算机系统概述

1.1 前言

本系列为《Computer Systems A Programmer's Perspective(Third Edition)》的读书详细笔记,本书的相关lab将上传至我个人的github,lab的记录将在后续单独发实验记录的文章!本人github链接为:https://github.com/waibibab123,希望和大家一起交流学习,除了homework之外的其它的部分题目(书中每章的后面有答案)也会在笔记中提及。本系列持续更新,并在此立个flag,2025年年底左右全部更新完毕(全部笔记加每个实验的记录),希望可以完成。

1.2 信息的本质

以编程中的源程序(源文件)为例,它们实际上是由0和1组成的位序列,其中8个位被组织成一组,称为字节 ,每个字节表示程序中的某些文本字符。大部分的计算机系统使用ASCII标准 来表示文本字符,像hello.c这样只由ASCII字符组成的文件称为文本文件 ,所有其它文件称为二进制文件

实际上,所有的信息------磁盘文件、内存中程序、用户数据、网络传输数据,都是一串比特位表示的,那么如何区分不同的数据对象呢?唯一方法是去看这些数据对象的上下文,比如,在不同的上下文中,同样的字节可能表示一个整数、浮点数、字符串或者机器指令。

关于C语言的一些内容

1.C语言与Unix操作系统关系密切

2.C语言小而简单(最好的C语言资料书:K&R)

3.C语言是系统级编程的首选,也适用于部分应用级编写

1.3 一个C文件是如何变成可执行文件的?

hello程序的生命周期是从一个高级C语言程序开始的,因为这种形式能够被人读懂,然而计算机是看不懂的,因此所有C语句都必须被其它程序转化为一系列的低级机器语言 指令,这些指令后续会按照一种成为可执行目标程序 的格式打包,以二进制磁盘文件的形式存储起来。在Unix系统上,上述的这种转化是靠编译器驱动程序 完成的,例如Linux使用GCC编译器驱动程序读取源程序文件hello.c,并将其翻译成一个可执行的目标文件hello:
linux > gcc hello.c -o hello

翻译过程包含了如下四个阶段,执行每个阶段的程序共同组成了编译系统

  • 预处理器(cpp):将.c语言源程序(文本文件)转换为.i的文本文件,在这个过程中,进行去掉c语言程序中注释,进行宏定义转换,处理头文件等工作
  • 编译器(ccl):将.i文件翻译为.s的汇编语言程序(文本文件),汇编语言是很有用的,它为不同高级语言的不同编译器提供了通用的输出语言
  • 汇编器(as):将.s的文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的.o文件(二进制文件)
  • 链接器(ld):负责处理程序与c语言相关库函数的合并,最终得到可执行目标程序,没有后缀,是一种二进制文件,它可以被加载到内存中,由系统执行

1.4 为什么要了解编译系统如何工作?

  1. 优化程序性能:比如,一个switch是否总是比一系列的if-else语句高效?while循环比for循环更有效吗?指针引用比数组索引更有效吗?为什么循环求和的结果放到一个本地变量中会比将其放到一个通过引用传来的参数中运行起来快很多呢?...
  2. 理解链接时出现的错误:比如,链接器报告说它无法解析一个引用?静态变量和全局变量区别?静态库和动态库的区别是什么?为什么有些连接错误直到运行时才会出现?
  3. 避免安全漏洞:学会堆栈原理和缓冲区溢出错误、学习降低攻击威胁的方法

1.5 可执行目标文件如何执行?

经过1.3的过程,一个c文件已经变成了一个可执行目标文件,并存放到了磁盘上,如果我们在Unix系统想执行它,可以在shell 中执行以下命令:linux > ./hello

1.5.1 系统的硬件组成

本小节简单地介绍系统硬件是如何组织的,一个典型的系统硬件组成如下图所示:

  • 总线:贯穿整个系统的一组电子管道。它携带信息字节并负责在各个部件间传递,通常被设计成传送定长的字节块,也就是字(word),具体一个字包含几个字节,各个系统是不同的,一般为4个字节(32位系统)或者8个字节(64位系统)
  • I/O设备:系统与外部世界联系的通道。每个I/O设备都通过一个控制器或适配器与I/O总线相连,上述包含了四个I/O设备:
    • 作为用户输入的键盘&鼠标
    • 作为用户输出的显示器
    • 用于长期存储数据和程序的磁盘驱动器(硬盘)
      刚开始可执行程序hello放在硬盘上
  • 主存:临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,是一组动态随机存取存储器(DRAM) 芯片组成的,从逻辑上来说,是一个线性的字节数组,每个字节都有唯一的地址。
  • 处理器:中央处理单元(CPU) ,简称处理器,是解释(执行)存储在主存中指令的引擎,其核心是一个大小为一个字的寄存器,成为程序计数器(PC),在任何时候,PC指向主存中的某条机器语言指令。

处理器看上去是它的指令集架构的简单实现,但是实际上现代处理器使用了非常复杂的机制来加速程序的执行,我们将处理器的指令集架构 和处理器的微体系结构区分,前者描述每条机器代码指令效果,后者描述处理器实际上是如何实现的。

1.5.2 hello程序的运行

当我们在shell上输入./hello后,shell程序将字符逐一读入CPU的寄存器文件中,再把它放到主存中,当我们敲回车键时,shell程序就知道我们已经结束了命令的输入,然后shell执行一系列指令加载可执行的hello文件,具体地,这些指令将hello目标文件中的代码和数据从磁盘复制到主存,利用直接存储器存取(DMA) 技术,数据可以不通过处理器而直接从磁盘到达主存。当hello中的代码和数据被加载到主存后,处理器开始执行hello中的机器语言指令,将"hello,world\n"字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示到屏幕上。

1.6 高速缓存器是干什么的?

从1.5.2看出,系统花费了大量的时间把信息从一个地方挪到另一个地方,根据机械原理,较大的存储设备要比较小的存储设备运行得慢,一个典型的寄存器文件只存储几百字节的信息,而主存可存放几十亿字节,因此处理器从寄存器文件中读取数据比从主存中读取几乎要快100倍,且这种差距在持续增大。

针对这种差异,系统设计者采用了更小更快的存储设备,即高速缓存存储器(简称cache或高速缓存) 存放处理器近期可能会需要的信息,这是使用静态随机访问存储器(SRAM) 的硬件技术实现的。比较新的、处理能力更强大的系统甚至有三级高速缓存:L1、L2、L3,从而获得一个很大且访问速度很快的存储器,原因是利用了局部性原理

1.7 存储层次结构

每个计算机系统中的存储设备都被组织成了一个存储器层次结构 ,如下图所示,其主要思想是上一层的存储器作为低一层存储器的高速缓存。

1.8 操作系统

在1.5.2的例子中,shell和hello程序的运行是依靠操作系统 提供的服务,操作系统可以看作是应用程序和硬件之间的一层软件,它通过几个抽象的概念(进程、虚拟内存、文件)实现两个功能:

  • 防止硬件被失控的应用程序滥用
  • 向应用程序提供简单一致的机制来控制复杂而又大不相同的低级硬件设备

接下来列举一些操作系统的概念:

  • 进程和线程:进程是操作系统对一个正在运行的程序的一种抽象,无论在单核还是多核系统中,一个CPU看上去都像是在并发地执行多个进程,这是通过处理器在进程间切换来实现的,这种机制成为上下文切换 ,一个例子如下图所示,这种切换是由操作系统内核(kernel) 管理的,内核不是一个独立的进程,它是系统管理全部进程所用代码和数据结构的集合。在现代系统中,一个进程实际上可以由多个线程 的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据,多线程也要比多进程之间更容易共享数据。
  • 虚拟内存:这是一个抽象的概念,它为每个进程提供了假象,即每个进程都在独占地使用内存,每个进程看到的内存都在一致的,称为虚拟地址空间 ,在Linux中,地址空间最上面的区域是保留给操作系统中的代码和数据的,这对所有进程是一样的,地址空间的底部区域存放用户进程定义的代码和数据。在虚拟地址空间中,从下往上,依次是内核虚拟内存共享库程序代码和数据
  • 文件:就是字节序列,每个I/O设备(磁盘、键盘、显示器、网络)都可以看成文件,它向应用程序提供了一个统一的视图,来看待系统中可能含有的所有I/O系统中所有的输入输出都是通过使用一小组称为Unix I/O的系统函数调用读写文件来实现的。

1.9 系统的网络通信

从一个单独的系统来看,网络可视为一个I/O设备,当系统从主存复制一串字节到网络适配器(比如在1.5.1的图中,位于扩展槽右边)时,数据流经网络到达另一台机器,此外,系统可以读取从其他机器发送来的数据,并把数据复制到自己的主存。

假如我们要使用telnet应用在一个远程主机上运行hello程序,分为以下五个步骤:

  1. 用户在键盘上输入"hello"
  2. 客户端向telnet服务器发送字符串"hello"
  3. 服务器向shell发送字符串"hello",shell运行hello程序并将输出发送给telnet服务器
  4. telnet服务器向客户端发送字符串"hello world\n"
  5. 客户端在显示器上打印"hello world\n"

1.10 阿姆达尔(Amdahl)定律

Amdahl定律的主要思想是当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度,若执行某应用程序需要时间为ToldT_{old}Told,系统某部分所需执行时间占总时间的比例为α,该部分性能提升比例为k,则新的执行时间为:

计算加速比S=Told/TnewS=T_{old}/T_{new}S=Told/Tnew为:

1.11并发和并行

并发指一个同时具有多个活动的系统,是逻辑上的同时发生,指多个任务在同一时间段内交替进行,并行指的是用并发来使一个系统运行更快,是物理上的同时发生,指多个任务在同一时刻在多个处理器上同时执行。下面按照系统层次结构中由高到低强调三个层次:

  • 线程级并发:使用线程,我们能够在一个进程中执行多个控制流,传统意义上,这种并发执行只是模拟出来的,是通过使一台计算机在它正在执行的进程间快速切换来实现的,在以前,即使处理器必须在多个任务间切换,大多数实际计算也都是由一个处理器完成的,这种配置称为单处理器系统 。当将多个CPU(称为"核心")集成到一个集成电路芯片上,就得到了一个多核处理器 ,它从两个方面提高系统性能,一是减少多个任务执行时的模拟并发需要,二是使应用程序运行更快,不过这必须要求程序是以多线程方式来写的。此外,超线程(同时多线程) 是一项允许一个CPU执行多个控制流的技术,比如Intel Core i7处理器每个核可执行两个线程,因此一个4核的系统可以并行执行8个线程。
  • 指令级并行:是指处理器可以同时执行多条指令的属性,比如流水线技术 ,如果处理器可以到达比一个周期一条指令更快的执行速率,就称之为超标量处理器
  • 单指令、多数据并行:允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据即SIMD并行,比如新几代的Intel和AMD处理器都具有并行地对8对单精度浮点数做加法的指令。
相关推荐
_Kayo_28 分钟前
VUE2 学习笔记17 路由
网络·笔记·学习
wuxuanok1 小时前
八股——Kafka相关
java·笔记·后端·学习·kafka
不可描述的两脚兽1 小时前
学习笔记《区块链技术与应用》第六天 问答 匿名技术 零知识证明
笔记·学习·区块链
焊锡与代码齐飞2 小时前
嵌入式第十八课!!数据结构篇入门及单向链表
c语言·数据结构·学习·算法·链表·排序算法
yuyousheng2 小时前
C语言使用GmSSL库实现sm3、sm4算法
c语言·算法·哈希算法
伏 念3 小时前
音视频学习笔记
笔记·音视频
C_Liu_3 小时前
从C语言到C++:拥抱面向对象编程的全新世界
c语言·开发语言·c++
瓦特what?3 小时前
C + +
c语言·开发语言·c++·经验分享·笔记·算法·程序员创富
njsgcs3 小时前
师傅mod制作用文件处理bat 删除 → 复制 → 打包 → 替换 → 启动游戏
笔记