《程序是怎么跑起来的》读书笔记

CPU 是什么

  • 什么是程序?

程序是指指示计算机每一步动作的一组指令。一般意义上的程序,比如运动会、音乐会的程序,表示"事情进行的先后顺序"。在这一点上,计算机程序也是一样的。

  • 程序是由什么组成的?

程序是指令和数据的集合。例如,在C语言程序"printf("你好");"中,printf就是指令," 你好 "就是数据。

  • 什么是机器语言?

CPU可以直接解释执行的语言。CPU可以直接解释执行的只有机器语言,而用C语言、Java等编写的程序,最终都要转换成机器语言才能执行。

  • 运行中的程序存放在什么地方?

内存(主存)。保存在硬盘等媒体中的程序需要复制到内存中后才能运行。

  • 什么是内存地址?

用来表示指令和数据在内存中存放位置的数值。指令和数据在内存中的存放位置用地址来表示,地址由整数表示。

  • 在计算机的组成部件中,负责对程序进行解释和运行的是哪个?

CPU。在计算机的组成部件中,根据程序指令进行数据运算并控制整个计算机的设备称为CPU。

CPU的内部构造

如图所示,CPU内部是由寄存器、控制器(control unit)、运算器(arithmetic unit)和时钟(clock)四个部分组成的,它们之间通过电流信号相互连通。寄存器是用来存放指令、数据这些操作对象的空间。一个CPU内部通常有几个到几十个不等的寄存器。控制器负责将内存中的指令和数据读入寄存器,并根据指令的执行结果对计算机进行控制。运算器负责运算从内存中读入寄存器的数据。时钟负责产生控制CPU工作节律的时钟信号,也有一些计算机将时钟放在CPU的外部。

在计算机中我们通常所说的内存指的是主存储器(main memory),简称主存。它通过一些控制电路与CPU相连,用于存储指令和数据。

当程序启动时,CPU中的控制器会根据时钟信号从内存中读取指令和数据。通过对指令进行解释和执行,运算器会对数据进行运算,控制器根据运算结果控制计算机进行指定的操作。"控制"这个词可能有点让人难以理解,其实它指的就是除运算之外的操作(主要是数据输入输出的时机控制)。内存和磁盘的输入输出、键盘和鼠标的输入,以及显示器和打印机的输出等操作,都属于控制

CPU 是寄存器的集合体

需要程序员特别关注的只有寄存器,其余三个部件不需要关注。为什么我们必须关注寄存器呢?这是因为寄存器是程序的描述对象。

将汇编语言程序转换成机器语言的过程称为汇编(assemble),反过来,将机器语言程序转换成汇编语言的过程称为反汇编(disassemble)

寄存器中存放的值可以是指令,也可以是数据,其中数据又分为"用于运算的数值"和"表示内存地址的数值。不同类型的值会存放在不同类型的寄存器中。

寄存器的主要类型及其功能,如下表所示:

从程序员的视角看CPU(CPU是寄存器的集合体)

条件分支和循环的原理

假设要将累加器中的值XXX和通用寄存器中的YYY进行比较,当执行比较指令时,CPU的运算器会在内部(暗中)执行"XXX-YYY"的减法运算,无论结果是正数、零,还是负数,都会保存到标志寄存器中。如果结果为正,则表示XXX大于YYY ;如果为零,则表示XXX等于YYY ;如果为负,则表示XXX小于YYY。也就是说,程序中的比较指令在CPU内部实际上是通过减法运算来实现的

函数调用的原理

函数调用指令(地址0132)和被调用的函数(地址0260)之间的数据传递是通过内存和寄存器来完成的

程序计数器和栈在函数调用中的作用如下图所示:

编译高级编程语言的程序后,函数调用会转换成调用指令,函数结束的处理则会转换成返回指令。

CPU的处理其实很简单

机器语言指令的主要类型和功能

用二进制来理解数据

  • 32比特是多少字节?

由于8比特=1字节,所以32比特就是32 ÷ 8 = 4字节。

  • 将二进制数00001111左移2位,得到的结果是原数的几倍?

二进制数左移1位,结果变成原来的2倍,因此左移两位就会变成原来的4倍。

  • 以2的补码形式表示的8位二进制数11111111用十进制表示是多少?

在2的补码形式中,所有位都是1的二进制数表示十进制的 -1。

  • 要反转图案中的一部分点,应该使用哪种逻辑运算?

逻辑异或运算

计算机使用二进制处理信息的原因

计算机内部是由称为集成电路的电子元器件构成的。而集成电路的每根引脚都只能表示两种状态。由于集成电路具有这样的特性,所以计算机必然要使用二进制来处理信息。

便于计算机处理的"2的补码"

计算机在进行减法运算时,其内部实际上进行的是加法运算。也就是说,计算机是用加法运算来实现减法运算的。为了实现这一点,在表示负数的时候,我们需要使用"2的补码"这一特殊的方法。2的补码是在二进制中用正数来表示负数的一种神奇的方法。

将一个二进制数反转后再加1,然后和原数相加的结果一定为0

逻辑右移与算术右移的区别

掌握逻辑运算的窍门

在运算中,"逻辑"是与"算术"相对的概念。我们可以这样认为:将二进制数所表示的信息当作四则运算中的数值来处理就是算术 ,而像图案这样,将其当作单纯由0和1组成的序列来处理就是逻辑

对图案进行4种逻辑运算所得到的结果(其中白色部分用1表示,黑色部分用0表示

计算机在计算小数时会出错的原因

  • 将小数分成符号、尾数、基数、指数这4个部分来表示的格式叫什么名字?

浮点数是用"符号 尾数×基数的指数次幂"的格式来表示小数的。

  • 将表示范围的中间值设为0,从而不使用符号位也能表示负数的方法叫什么名字?

移码表示法。如果把01111111看作0,那么比这个数小1的01111110就是-1。

计算机计算出错的原因

原因是有一些十进制小数无法准确转换成二进制数。例如,十进制数0.1就无法用二进制数来准确表示,即使用几百位小数也表示不了。

4位二进制小数能表示的数二进制数是连续的,但十进制数是不连续的

什么是浮点数

很多编程语言提供了两种能表示小数的数据类型:双精度(double precision)浮点型和单精度(single precision)浮点型。双精度浮点型的长度为64位,单精度浮点型的长度为32位。在C语言中,双精度浮点型称为double,单精度浮点型称为float。

浮点数将小数分为符号、尾数、基数和指数4个部分来表示

浮点数的内部构造(IEEE标准)

用程序来实际确认一下吧

单精度浮点数表示的数据

如何避免计算机计算出错

两种解决方法

  1. 第一种是回避策略,也就是忽略错误。
  2. 第二种方法是用整数替代小数进行计算

BCD就是一种用整数替代小数的格式,它用4比特来表示0~9的1位十进制数,具体方式在这里不做赘述。BCD格式常用于不允许有计算误差的金融领域。

让内存化方为圆

  • 有10根地址信号引脚的内存芯片的地址范围是多大?

二进制的0000000000~ 1111111111(十进制的0~1023)。10根地址信号引脚能够表示2^10 = 24个地址

  • 高级编程语言的数据类型表示什么?

表示其占用内存空间的大小,以及存储数据的格式。例如,C语言有一种short数据类型,它表示占用2字节的内存空间,其中储存的数据是整数。

  • 在内存地址为32位的环境中,指针变量的长度是多少?

32位,指针变量存储的内容是内存地址。

  • 长度为几字节的数据类型的数组和内存的物理结构是相同的?

1字节。内存在物理上是以1字节为单位存储数据的。

  • 以LIFO方式读写数据的数据结构叫什么?

栈,栈是一种后进先出(LIFO=Last In First Out)的数据结构。

  • 根据数据的大小向两侧分支的数据结构叫什么?

二叉查找树,二叉查找树是一种从节点分出两个分支的数据结构。

内存的物理结构十分简单

这样的一块内存芯片能存储多少数据呢?数据信号引脚有D0~D7,共8根,因此我们知道它一次可以输入输出的数据长度为8比特( = 1字节)。地址信号引脚有A0~A9,共10根,因此可以指定0000000000~1111111111这1024个地址。地址表示的是数据存储的位置,因此这块内存芯片能够存储1024个1字节的数据。由于1024 = 1 K,所以这块内存芯片的容量是1 KB。

内存芯片数据读写的方法

内存的逻辑结构像一幢大楼

很多讲编程的书会用类似于一幢大楼的图来表示内存。在这幢"大楼"中,每一层都可以存储1字节的数据,楼层编号就是地址。

不同数据类型的变量占用的内存大小不同

指针其实很简单

指针是一种变量,它不存储数据本身的值,而是存储数据所在的内存地址。

这些数据类型所代表的是从指针中存储的地址一次读写多少字节的数据。

用好内存先从数组开始

数组之所以是使用内存的基础,是因为它反映的就是内存的物理结构本身。特别是1字节型的数组,和内存的物理结构完全一致。

各种类型的数组如下图所示:

栈与队列,以及环形缓冲区

队列通常会以环形缓冲区(ring buffer)的形式使用。这也就是本章标题"让内存化方为圆(环形)​"的含义了。假设我们用一个包含6个元素的数组来实现一个队列。数据会按顺序从数组开头存放进来,并按照存放的顺序取出。当数据存放到数组的末尾时,下一个数据就会回到数组开头进行存放(此时数组开头原本存放的数据已被取出,因此这个位置是空的)​。通过这样的方式,数组的末尾和开头就连接在了一起,从而实现了一种可以循环存放和取出数据的结构(图4-9)​。

用二叉查找树高效地查找数据

二叉查找树的方便之处在于它可以高效地查找数据。如果使用一般的数组,我们必须从头开始按照下标顺序逐个访问元素才能找到目标数据。而在使用二叉查找树的情况下,如果目标数据比当前访问的数据小就往左侧找,比当前访问的数据大就往右侧找,由此可以快速找到目标数据。

内存与磁盘的密切联系

  • 存储程序(stored program)方式是什么?

将程序存放在存储器中并依次执行的方式。现在的计算机基本上采用的是存储程序方式。

  • 使用内存来提高磁盘访问速度的机制叫什么?

磁盘缓存。磁盘缓存指将从磁盘中读取的数据暂时保存在内存中,当需要再次读取相同的数据时,就可以不访问磁盘,而是直接从内存中快速读取。

  • 将磁盘的一部分模拟成内存来使用的机制叫什么?

虚拟内存(virtual memory)。虚拟内存可以让内存容量小的计算机运行大型程序。

  • 在Windows中,包含函数和数据,在程序运行时进行动态链接的文件叫什么?

DLL(DLL文件),DLL是Dynamic Link Library(动态链接库)的缩写。

  • 将函数静态链接到EXE文件中的过程叫什么?

静态链接(static link),函数的链接方式分为静态链接和动态链接两种。

  • 在PC中,硬盘的1个扇区大小一般为多少字节?

512字节。扇区(sector)是磁盘的物理存储单位。

程序加载到内存后才能运行

想必大家知道,程序要先存储在存储器中,然后才被依次读取执行。这种方式称为存储程序方式。(现在看起来这好像是理所当然的事情,但其实在提出的时候是一种划时代的方案,因为在此之前,计算机只有通过重新连接线路才能修改程序。)

程序加载到内存后才能运行

提高磁盘访问速度的磁盘缓存

磁盘缓存是一块内存空间,用于临时存放从磁盘读取出来的数据。下次需要读取相同的数据时,就不需要实际访问磁盘,而是从磁盘缓存中读取数据就可以了。有了磁盘缓存,就能够提高磁盘数据的访问速度了。

将低速设备中的数据保存在高速设备中,当需要相同数据时直接从高速设备中读取,这样的设计就叫作缓存。

将磁盘当成内存使用的虚拟内存

虚拟内存是将磁盘的一部分模拟成内存来使用的机制。磁盘缓存是将内存看成虚拟的磁盘,与之相对,虚拟内存是将磁盘看成虚拟的内存。

CPU只能运行已经加载到内存中的程序,因此,即使通过虚拟内存用磁盘来代替内存使用,实际运行的程序部分在运行时也必须存放在内存中。于是,为了实现虚拟内存,就需要在运行程序的过程中,对实际内存(物理内存)和磁盘上的虚拟内存中的部分内容进行置换。

虚拟内存的实现方式分为分页式和分段式,Windows采用的是分页式。在这种方式中,要运行的程序无论结构如何,都会被划分成一定大小的"页面"​,并以页面为单位在内存和磁盘之间进行置换。在分页式中,将磁盘中的内容读入内存称为页面换入(page in),将内存中的内容写入磁盘称为页面换出(page out)。一般来说,PC中页面的大小为4KB,大的程序会被分割成多个大小为4 KB的页面,并以页面为单位存放在磁盘(虚拟内存)或内存中。

在Windows中,为了实现虚拟内存,需要在磁盘上生成一个虚拟内存文件(页面文件)​。这个文件是由Windows自动生成和管理的。文件的大小,即虚拟内存的大小,一般是物理内存大小的1~2倍

打开"控制面板"→"系统和安全"→"系统"→"高级系统设置"​,在弹出的"系统属性"窗口中点击"高级"选项卡,再点击其中"性能"项目中的设置按钮,就会打开"性能选项"窗口。点击其中的"高级"选项卡,此时页面就会显示当前虚拟内存所使用的页面文件大小。

节约内存的编程技巧

所谓DLL文件,顾名思义,就是在程序运行时进行动态链接的库(函数和数据的集合)

静态链接导致内存利用效率下降,如下图所示:

如果我们不将MyFunc()函数嵌入到程序的可执行文件(EXE文件中,而是生成一个独立的DLL文件,那么同一个DLL文件中的内容会被多个运行中的应用程序共享,由此内存中就只有一个MyFunc()函数的程序。这样就提高了内存的利用效率。

通过 _stdcall调用缩小程序大小是C语言程序开发中的一种高级技巧

了解一下磁盘的物理结构

磁盘的表面在物理上被划分成若干区域,划分方法分为按固定长度划分的扇区方式,以及按可变长度划分的可变长方式。一般PC所使用的硬盘是采用扇区方式来进行划分的。在扇区方式中,磁盘表面被划分成若干同心圆状的磁道,每条磁道再被划分成若干固定长度(存储的数据长度相等)的扇区。

Windows中的磁盘,一个扇区的长度一般为512字节。但是,Windows在逻辑(软件)上读写磁盘的单位是簇(cluster),它的长度是扇区的整数倍,其实际长度根据硬盘容量确定,有512字节(1个簇 = 1个扇区)、1 KB(1个簇 = 2个扇区)、2 KB、4 KB、8 KB、16KB、32 KB(1个簇 = 64个扇区)等多种情况。磁盘容量越大,簇的长度也越大。

4097字节的文件会一下子占用2个簇(=8192字节)的空间。

在以簇为单位读写磁盘的情况下,一个簇中没有占满的空间就只能被闲置。尽管看起来很浪费,但按照目前的设计来说,也没有什么解决的办法。如果将簇的长度变小,就会增加磁盘的访问次数,造成文件读写速度下降。由于磁盘需要额外的空间记录扇区的划分方式,所以如果簇的长度太小,磁盘整体的存储容量就会减少。扇区和簇的大小需要在处理速度和存储容量之间寻找平衡。

自己动手压缩数据

  • 在文件中存储数据的基本单位是什么?

1字节(8比特),文件是字节数据的集合体。

  • 将文件内容表示成"数据的值×重复次数"的压缩方法,是叫游程编码还是哈夫曼算法?

游程编码,例如,​"AAABB"压缩后会变成"A3B2"​。

  • 在Windows计算机经常使用的Shift-JIS编码中,一个半角英文或数字字符需要用几字节的数据来表示?

1字节(8比特),半角英文、数字和符号都是用1字节表示的,汉字等全角字符用2字节表示。

  • BMP格式的图片文件是经过压缩的吗?

没有经过压缩,BMP格式的图片文件是没有经过压缩的,因此比PNG等压缩格式的图片文件要大。

  • 无损压缩和有损压缩有什么区别?

压缩后的数据可以恢复成原始数据的是无损压缩,不能恢复成原始数据的是有损压缩。像照片这样只要恢复出来的数据人眼几乎看不出差别,就可以使用有损压缩。

文件是以字节为单位记录的

程序是以字节为单位向文件中存储数据的,文件是字节数据的集合体。

游程编码的原理

像这样将文件内容用"数据 × 重复次数"来表示的压缩方法称为游程编码(run length encoding)(图6-2)​。游程编码是一种很好用的压缩方法,常用在传真的图像压缩等领域。

游程编码的缺点

对于相同数据连续重复的情况较多的图片文件,游程编码的效果比较好,但它并不适合用来压缩文本文件。

使用树来构建哈夫曼编码

通过哈夫曼树编码的步骤

无损压缩与有损压缩

程序在怎样的环境下运行

  • 应用程序的运行环境用什么来表示?

操作系统和硬件。一般来说,应用程序的运行环境是指操作系统的类型以及硬件(CPU、内存等)的类型和性能指标。

  • Windows应用程序能直接在macOS上运行吗?

操作系统和硬件。应用程序是为了在特定操作系统上运行而开发的。

  • PC能安装除Windows以外的操作系统吗?

可以。PC上也可以安装Ubuntu、RHEL(Red Hat Enterprise Linux)等Linux发行版操作系统。

  • Java虚拟机的功能是什么?

运行编译为字节码的Java程序。只要针对不同的环境准备专用的Java虚拟机,就可以让相同的字节码在各种环境中运行。

  • SaaS、PaaS、IaaS这几种类型的云计算中,提供虚拟硬件的是哪一个?

IaaS。SaaS提供应用程序,PaaS提供操作系统,IaaS提供硬件。

  • 引导装入程序(bootstrap loader)的功能是什么?

启动操作系统。计算机内部ROM中存储的BIOS程序负责启动引导装入程序,引导装入程序负责启动存储在硬盘等媒体中的操作系统。

运行环境=操作系统+硬件

Windows消除了CPU之外的硬件差异

在Windows应用程序中,键盘输入、显示器输出等操作不是通过直接访问硬件来实现的,而是通过向Windows发出请求来间接地实现的。这样一来,程序员就不需要关注内存和I/O地址的差异了,因为Windows代替了应用程序对各种不同机型的硬件进行操作。

即便是Windows也无法彻底抹平CPU类型的差异,因为Windows应用程序都是以特定CPU的本机代码的形式来分发的

每种操作系统的API都是不同的

使用源代码进行安装

既然不同的CPU下本机代码无法通用,那干脆直接用源代码来分发程序不是更好吗?​"这的确是一种好方法,Linux就可以使用这种方法。在Linux中,源代码可以编译后使用

在任何地方都能提供相同运行环境的Java虚拟机

不将源代码编译为本机代码,而是一种中间代码,就可以提供不依赖特定操作系统和硬件的运行环境了,Java使用的就是这种方法。

Java应用程序在Java虚拟机上运行

看上去好处多多的Java虚拟机也有它自己的问题。首先,不同的Java虚拟机之间并不能做到完全兼容。Java虚拟机很难做到运行任何字节码程序这一点。其次是运行速度,需要在运行时将字节码转换成本机代码的Java程序,在运行速度上比直接编译成本机代码的C语言程序要慢。

云计算平台提供的虚拟运行环境

通过互联网来使用硬件、操作系统、应用程序等计算机资源的技术称为云计算(cloud computing)。根据其所提供的具体服务,云计算可分为SaaS(Software as a Service,软件即服务)​、PaaS(Platform as a Service,平台即服务)和IaaS(Infrastructure as a Service,基础设施即服务)几种类型。简单来说,SaaS提供的是应用程序,PaaS提供的是操作系统,IaaS提供的是硬件。

BIOS与引导装入程序

程序的运行环境还包括BIOS(Basic Input Output System,基本输入输出系统)。

引导装入程序的英文是bootstrap loader。

短小的引导装入程序将巨大的操作系统提起来

从源文件到可执行文件

  • CPU能解释和执行的程序叫什么?

本机代码(机器语言代码)。对源代码进行编译后可得到本机代码

  • 将多个目标文件拼接成一个EXE文件的工具叫什么?

链接器。通过编译和链接可得到EXE文件。

  • 扩展名 .obj的目标文件的内容是源代码还是本机代码?

本机代码。对源文件进行编译可得到目标文件。例如,对源文件sample.c进行编译可得到目标文件sample.obj。目标文件的内容就是本机代码。

  • 由多个目标文件打包而成的文件叫什么?

库文件(library file)。链接器会从库文件中提取必要的目标文件并将它们拼接成一个EXE文件。在程序运行时进行动态链接的DLL文件也属于库文件。

  • 包含DLL文件中的函数调用信息的文件叫什么?

导入库(import library)。将导入库中的信息链接到EXE文件,由此程序就可以在运行时调用DLL中的函数了。

  • 程序运行时动态分配的内存空间叫什么?

堆(heap)。堆是一种可以根据程序自身的请求来分配和释放的内存空间。

计算机只能执行本机代码

翻译成本机代码之后就会变成同一种语言

编译器负责翻译源代码

负责将用C语言等高级语言编写的源代码翻译成本机代码的程序称为编译器。

CPU的类型不同,其对应的本机代码也不同。

同时,也有一些编译器本身运行在一种CPU上,但它能够生成适配另一种CPU的本机代码,这样的编译器称为交叉编译器(cross compiler)。

同一段源代码可以翻译成适配不同CPU的本机代码。

仅靠编译无法得到可执行文件

编译后生成的并不是EXE文件,而是扩展名为".obj"的目标文件(object file)。将多个目标文件拼接在一起生成一个EXE文件的过程称为链接,用于完成这一操作的程序称为链接器(又称链接编辑器或链接程序)

启动代码与库文件

一些通用代码需要链接在所有程序的开头,这些代码被称为启动代码(startup code)。因此,即便一个程序没有调用位于其他目标文件中的函数,也必须链接启动代码。

库文件是由多个目标文件打包而成的。在链接时指定库文件,链接器就可以从中提取所需的目标文件,并将其与其他目标文件一起链接生成EXE文件。

外部符号(external symbol)是指位于其他目标文件中的变量和函数。

sprintf()等函数并不包含在源代码中,而是以库文件的形式随编译器一起被分发的。这样的函数称为标准函数。使用库文件可以避免在链接器的参数中指定一大堆目标文件。将目标文件打包成库文件的形式分发还有另外一个好处,那就是可以隐藏标准函数的源代码。

DLL文件与导入库

Windows操作系统中包含可供应用程序使用的各种功能,这些功能都是以函数的形式来提供的,这样的函数称为Windows API(Application Programming Interface,应用程序接口。

Windows API的目标文件通常不是以库文件的形式存在的,而是以一种称为DLL(动态链接库)的特殊库文件的形式存在的。与之相对,包含目标文件本身,可以直接链接到EXE文件的库文件称为静态链接库(static link library)

Windows中编译和链接的过程如下图所示:

运行可执行文件需要什么

在EXE文件中,变量和函数被分配的内存地址都是虚拟的,在程序运行时,这些虚拟的内存地址会转换成实际的内存地址。链接器会在EXE文件的开头记录需要进行内存地址转换的各个位置,这些信息被称为重定位信息。

EXE文件中,重定位信息中记录的是变量和函数的相对地址。所谓相对地址,就是某个地址与基地址之间的相对距离,也就是偏移量。

在源代码中,变量和函数都是分散在各个位置的,但在链接后的EXE文件中,变量和函数会被集中起来分成两组连续排列。于是,每个变量的内存地址就可以表示为该变量相对于变量区起始位置的偏移量,每个函数的内存地址也可以表示为该函数相对于函数区起始位置的偏移量。每个区的基地址是在程序运行时确定的。链接后的EXE文件结构如下图所示:

加载时生成的栈和堆

EXE文件的内容分为重定位信息、变量区和函数区。但是,在加载程序的内存空间中,还会生成另外两个区域,它们就是栈和堆。栈是用来存放函数内部临时使用的变量(局部变量)以及调用函数时传递的参数等数据的内存空间。堆是在程序运行时用来存放任意数据的内存空间。

EXE文件中并不包含栈和堆的区域,EXE文件加载到内存并运行的那一刻,栈和堆所需的内存空间才得到分配。

加载到内存中的程序由4个区域组成

进阶问答

  • 问:编译器和解释器的区别是什么? 答:编译器是在程序运行之前对整个源代码进行翻译,而解释器则是在程序运行时对源代码逐行进行翻译。一般来说,C语言和C++ 都属于编译型语言,第12章中提到的Python则属于解释型语言。

  • 问:什么是多文件编译? 答:多文件编译是指将一个程序分为多个源文件,并对其分别进行编译,最后合并生成一个EXE文件。这样做的好处是可以缩短单个源文件的长度,方便对程序代码进行管理。

  • 问:什么是构建(build)? 答:在某些开发工具中,点击菜单中的"构建"命令就可以生成EXE文件。在这里,构建就是指连续执行编译和链接这两个操作。

  • 问:使用DLL文件有什么好处? 答:DLL文件中的函数可以被多个程序共享,从而节约内存和磁盘空间。此外还有一个好处,即如果修改了函数的内容,则不需要重新链接(静态链接)调用该函数的程序。

  • 问:不链接导入库,就无法调用DLL文件中的函数吗? 答:即使不链接导入库,程序也可以使用LoadLibrary()和GetProcAddress()等API在运行时调用DLL文件中的函数,但使用导入库比较简单。

  • 问:我听说过重叠链接(overlay link)这个词,它是什么意思呢? 答:它是指将不会同时运行的函数交替加载到同一内存地址来运行。重叠链接可以使用重叠链接器(overlay linker)这一特殊的链接器来实现,在还没有虚拟内存机制的MS-DOS时代比较常见。

  • 问:什么是"垃圾收集"​?听说和内存管理有关。 答:垃圾收集(garbage collection)是指将堆空间中已经不再需要的数据进行清理,从而释放被占用的内存空间。这里将不再需要的数据比作垃圾。在Java、C# 等编程语言中,程序会在运行时自动执行垃圾收集,这一机制是为了防止程序员粗心(忘记释放内存)导致的内存泄漏。

操作系统与应用程序的关系

  • 监控程序(monitor program)的主要功能是什么?

加载并运行程序。监控程序可以说是操作系统的原型。

  • 在操作系统上运行的程序叫什么?

应用程序。文字处理软件、表格处理软件等都属于应用程序。

  • 调用操作系统提供的功能叫什么?

系统调用。应用程序通过系统调用来间接地控制硬件。

  • GUI的全称是什么?

Graphical User Interface(图形用户界面)。可以通过用鼠标点击屏幕上的窗口、图标等可视化方式进行操作的用户界面。

  • WYSIWYG的全称是什么?

What You See Is What You Get(所见即所得)。WYSIWYG的意思是,显示器上显示的东西可以直接通过打印机打印出来,即"所见即所得"​,这是Windows的特点之一。

从历史发展看操作系统的功能

早期的操作系统=监控程序+基本输入输出程序

随着时代的进一步发展,为了给程序员提供便利,人们又在操作系统中增加了硬件控制程序、语言处理器(汇编器、编译器、解释器)以及各种工具,使其最终形成了接近现代操作系统的形态。操作系统不是一个单独的程序,而是多个程序的集合体

关注操作系统的存在

应用程序通过操作系统间接地访问硬件

系统调用与高级编程语言的可移植性

操作系统的硬件访问功能通常会以大量小型函数的集合体的形式来提供。这些函数及调用这些函数的行为统称为系统调用(sytem call),也就是应用程序调用(call)操作系统(system)的功能。

高级编程语言的函数调用在编译后会变成系统调用

也有一些高级编程语言支持直接进行系统调用,但是以这种风格编写的程序,其可移植性很差。例如,直接使用Windows系统调用的程序肯定无法在Linux中运行。

操作系统和高级编程语言对硬件进行了抽象化

变量fp中存放的是fopen()函数的返回值,这个值称为文件指针(file pointer)。当应用程序打开文件时,操作系统会自动分配用于管理文件读写的内存空间,这块内存空间的地址可以通过fopen()函数的返回值获取。用fopen()打开文件后,就可以通过指定文件指针来操作文件了。因此,fputs()和fclose()的参数中都需要指定文件指针(变量fp)​。

Windows操作系统的特点

API是以若干DLL文件的形式来提供的,每个API的本体都是C语言编写的函数,因此C语言程序很容易使用这些API。

数据库并不是操作系统不可或缺的功能,但它与操作系统很接近,所以一般不将其称为应用程序,而是称为中间件(middleware),也就是介于操作系统和应用程序中间(middle)的软件。操作系统和中间件也统称为系统软件(system software)

即插即用(plug-and-play)是一种让新设备插入(plug)之后就可以立即使用(play)的机制。当新设备连接到计算机后,操作系统可以自动安装并配置用于控制该设备的设备驱动程序(device driver

通过汇编语言认识程序的真面目

  • 在汇编语言中,用来表示各个本机代码功能的英文缩写叫什么?

助记符(mnemonic)。汇编语言是使用助记符来编写程序的。

  • 将汇编语言源代码转换成本机代码的过程叫什么?

汇编。汇编需要使用汇编器来完成。

  • 将本机代码反过来转换成汇编语言源代码的过程叫什么?

反汇编(disassemble)。通过反汇编可以得到人类能够理解的源代码。

  • 汇编语言源文件的扩展名是什么?

.asm和 .s等。汇编语言源文件的扩展名在Windows中主要是.asm,在Linux中主要是 .s

  • 汇编语言程序中的段是什么意思?

将构成程序的指令和数据分别汇总形成的组。在高级编程语言的源代码中,指令和数据都是分散在各个位置的,但在编译后它们会被分别汇总到不同的段中。

  • 汇编语言的跳转指令是干什么用的?

让程序流程跳转到任意地址。汇编语言中可以使用跳转指令实现循环和条件分支。

汇编语言和本机代码是一一对应的

使用助记符的编程语言称为汇编语言。汇编语言源代码和本机代码是一一对应的。

用C编译器输出汇编语言源代码

伪指令与注释

汇编语言源代码中的指令分为两种,一种是会被转换成本机代码的一般指令,另一种是专门针对汇编器的伪指令。伪指令负责告诉汇编器程序的结构和汇编的方法,因此也被称为汇编程序指令(assembler directive)。

开头有一个句点(.)的 .file和 .def等就是伪指令。.section的功能是标记接下来的程序属于哪个段。段就是一组指令和数据的集合。

段的定义语法为 .section段名, " 属性 "。在属性的部分中,"x" 表示可执行,"r" 代表可读,"w" 代表可写。在代码清单10-2中,.section_TEXT, "xr" 这条伪指令的意思是,接下来的程序是一个名为_TEXT、属性为可执行且只读的段

汇编语言的语法是"操作码 操作数"

CPU与内存的关系:

寄存器是CPU内部的存储空间,但是寄存器的功能并不仅限于存储指令和数据,寄存器还可以参与运算。

汇编语言源代码中,充当操作数的寄存器名前面会加上%,如 %eax、%ebx等。内存中的空间是用地址来区分的,而CPU内部的寄存器则是用eax、ebx这样的名称来区分的

最常用的movl指令

操作数可以是数值、标签(命名的地址)​、寄存器名,我们也可以在它们的左右两边加上圆括号 ( )来使用。

当需要直接指定一个数值时,需要在数值的前面加上 <math xmlns="http://www.w3.org/1998/Math/MathML"> 符号,例如这里的 符号,例如这里的 </math>符号,例如这里的456

函数调用的工作原理

movl $456, 4(%esp)表示将456这个值存入已分配内存空间的esp寄存器的值 +4所代表的地址中

当我们在程序任意位置设置标签时,需要在标签名的末尾加上一个冒号(:),如_AddNum:,但在call指令的操作数中,标签是不加冒号的,如calll _Addnum。

全局变量和局部变量的工作原理

C语言中的变量分为两种,在函数外部声明的变量称为全局变量,在函数内部声明的变量称为局部变量。全局变量(global variable)可以在程序的所有函数中访问,而局部变量(local variable)只能在声明它的函数中访问。

我们可以发现全局变量就是事先附加在程序数据段的数据。当程序运行时,指令段和数据段会被一起加载到内存中,并在程序运行过程中一直驻留内存,因此程序中所有的函数都可以访问全局变量。相对地,局部变量则是在调用函数时,由函数的代码临时存入栈中的

访问硬件的方法

  • 汇编语言中用于外部设备输入输出的指令是什么?

in指令和out指令。在用于x86架构CPU的汇编语言中,用in指令进行I/O输入,用out指令进行I/O输出。

  • I/O的全称是什么?

Input/Output(输入 / 输出)。负责在计算机主机与外部设备之间进行输入输出的芯片称为I/O控制器,简称I/O。

  • 用于区分外部设备的编号叫什么?

I/O地址或I/O端口号。为了区分连接到计算机上的不同外部设备,每个设备会被分配一个I/O地址

  • IRQ的全称是什么?

Interrupt Request(中断请求)。IRQ是指用于区分发出中断请求的外部设备的编号。

  • DMA的全称是什么?

Direct Memory Access(直接内存访问)。DMA是指外部设备不经过CPU中转,直接与计算机内存传输数据。

  • 用于区分使用DMA的外部设备的编号叫什么?

DMA通道(DMA channel)。网络、磁盘等数据量大的外部设备会使用DMA,不同设备会通过DMA通道来进行区分

应用程序是否与硬件有关

应用程序通过操作系统间接访问硬件

负责硬件输入输出的in指令和out指令

in指令和out指令的语法,如下图所示:

CPU内部的寄存器可以参与运算,但I/O控制器内部的寄存器基本上只能用来临时存放数据。

一个I/O控制器可以控制一个外部设备,也可以控制多个外部设备,因此我们就需要用端口号来区分不同的端口。端口号也被称为I/O地址。

CPU通过端口与外部设备交换数据。

右键点击Windows"开始"按钮,从弹出的菜单中选择"设备管理器"​。打开设备管理器之后,在设备图标的列表中点击并展开"显示适配器"​,右键点击其中的图标,在弹出的菜单中选择"属性"​。在属性对话框中点击"资源"选项卡,在"资源设置"的"I/O范围"右侧显示的数值就是端口号

外部设备的中断请求

发出中断请求的是连接外部设备的I/O控制器,运行中断处理程序的是CPU。要识别具体是哪个设备发出的中断请求,我们需要使用名为中断号的编号,而不是端口号。

中断控制器的功能

中断处理的流程

通过中断实现实时处理

外部设备的数量很多,因此需要依次查询。依次查询多个外部设备状态的操作称为轮询(polling)。轮询适用于那些不频繁产生中断的系统,但不适用于个人计算机。

如果在查询鼠标有没有输入数据的时候按下了键盘会怎样呢?输入的字符就无法实时显示在屏幕上了。实际上,使用中断来处理键盘输入,就可以将输入的字符实时显示在屏幕上了。

像打印机等专门用来输出的外部设备,也会通过中断来通知计算机自己是否处于可以接收数据的状态。外部设备的数据处理速度要远远慢于计算机主机的处理速度。如果仅当CPU收到中断请求时才发送数据,主程序就不必一直去查询设备的状态,CPU就可以有更多的时间来运行其他程序了。中断处理可真方便。

能够快速传输大量数据的DMA

除了I/O和中断处理,还有一个机制希望大家了解,那就是DMA。DMA是指外部设备不经过CPU中转,直接和内存进行数据传输,常用于网络、磁盘等设备。

DMA是通过名为DMA控制器(DMA Controller,DMAC)的芯片实现的。

与DMA相对,通过CPU在外部设备和内存之间传输数据的方式称为PIO(Programmed I/O)。PIO与DMA的差别如下图所示:

显示字符和图像的原理

简单来说,计算机中有一个用于保存要显示的信息的存储器,这一存储器称为显存(Video RAM,VRAM)。程序只要将数据写入显存,数据就可以在显示器上显示出来。

如何让计算机"学习"

  • 什么是机器学习(Machine Learning,ML)?

让计算机自己进行学习。在机器学习中,我们使用学习程序让计算机读取大量数据并根据数据特征自己进行学习。

  • 分类问题是机器学习的主题之一,那么什么是分类问题呢?

对数据进行正确的识别和分类。

  • SVM是一种机器学习算法,它的全称是什么?

支持向量机(Support Vector Machine)。

  • 为什么在机器学习领域经常使用Python?

因为Python提供了很多机器学习相关的库,我们可以通过解释器方便地使用这些功能

  • 在分类问题的机器学习中,学习器和分类器分别是什么?

学习算法和学习好的模型。在分类问题的机器学习中,我们将学习算法称为学习器,将作为学习结果得到的模型称为分类器。模型就是用于识别的机制。学习器和分类器的本质都是程序。

  • 机器学习中的cross validation中文叫什么?

交叉验证。交叉验证是一种不断轮换编写学习器所使用的训练数据和分类器所使用的测试数据来进行机器学习的方法。由此,我们可以检验学习模型的识别率是否存在因学习数据的类型而出现偏差的情况。

参考

相关推荐
圣火喵喵教14 小时前
程序员的自我修养 - 第三章 目标文件里面有什么
linux·操作系统
博睿谷IT99_5 天前
华为HCIE-openEuler认证:能否成为国产操作系统领域的技术稀缺人才?
华为·开源·操作系统·华为认证·hcie·openeuler
GoGeekBaird5 天前
69天探索操作系统-第59天:容器化内部机制 - 深入探讨命名空间实现
后端·操作系统·github
脑子慢且灵6 天前
计算机操作系统——存储器管理
linux·服务器·windows·操作系统·存储器·存储器多级结构
易保山6 天前
MIT6.S081 - Lab7 Locks(锁优化 | 并发安全)
linux·操作系统·c
塞尔维亚大汉7 天前
【鸿蒙南向开发】标准系统方案之瑞芯微RK3566移植案例(下)
操作系统·嵌入式·harmonyos
知识浅谈7 天前
【Windows】如何在任意文件夹中右键打开cmd终端
操作系统
罗念笙8 天前
操作系统中的进程有哪几种状态?
操作系统
OpenAnolis小助手10 天前
龙蜥社区荣获 OS2ATC 2025 “最具影响力开源创新贡献奖”
开源·操作系统·龙蜥社区·龙蜥·openanolis·行业认可