《程序是怎样跑起来的》(下)

2019-05-05  本文已影响0人  c747190cc2f5

学习笔记

第8章 从源文件到可执行文件

本章问题:

问题

本章重点:

编译器的功能;程序从源代码到可执行文件的流程;程序运行时的堆和栈。


8.1 计算机只能运行本地代码

一个例子1
一个例子2
图中栗子的源代码文件命名为Sample1.c。
源代码需要转换成本地代码才能运行

8.2 本地代码的内容

直接用记事本打开本地代码:

记事本打开本地代码
把本地代码Dump一下,每一个字节用2位16进制数(每个16进制数代表4位二进制数,2位16进制数恰好代表8位即1字节)来表示:
本地代码的真是面目是数值的罗列

8.3 编译器负责转换源代码

通过命令行编译上面的栗子

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

链接上面的栗子

8.5 启动及库文件

8.6 DLL文件及导入库

用下图总结下:

Windows中编译和链接机制

8.7 可执行文件运行时的必要条件

EXE文件中函数和变量的内存地址是如何来表示的呢?
链接后EXE的构造

8.8 程序加载时会生成堆和栈

加载到内存中的程序由4部分组成

8.9 有点难度的Q&A



问题答案:

答案

第9章 操作系统和应用的关系

本章问题:

问题

本章重点


9.1 操作系统功能的历史

监控程序是操作系统的雏形
初期的操作系统 = 监控系统 + 输入输出程序
操作系统是多个程序的集合体

9.2 要意识到操作系统的存在

应用程序通过操作系统间接向硬件发送指令。
比如printf()、time()函数运行的结果,是面向操作系统而非硬件的,操作系统接到指令后,首先解释这些指令,然后会对时钟IC和显示器的IO进行控制。

应用程序通过操作系统间接控制硬件

9.3 系统调用和高级编程语言的移植性

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

9.4 操作系统和高级编程语言使硬件抽象化

操作系统的系统调用,给程序的编写带来的巨大的方便。


9.5 Windows操作系统的特征

问题答案: 问题答案


第10章 通过汇编语言了解程序的实际构成

本章问题:

问题

本章重点:

当然是汇编。


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

  1. 使用汇编语言有助于理解本地代码。直接打开本地代码只能看到数值的罗列,通过添加助记符,如加法运算add,比较运算cmp等,可以更好地理解本地代码。使用助记符的语言被称为汇编语言,通过查看汇编语言的源代码,可以更容易地理解本地代码。
  2. 汇编语言和本地代码是一一对应的,所以可以通过本地代码反汇编得到汇编代码。但是高级语言和本地代码不是一一对应的,所以反编译到高级语言比较困难,而且完全还原是不太可能的。

10.2 通过编译器输出汇编语言的源代码

原书作者通过编译命令把源代码输出为汇编代码,汇编文件的后缀为.asm(assemble)。

源代码
汇编

10.3 不会转换成本地代码的伪指令

汇编语言的源代码,是由转换成本地代码的指令(操作码)和针对汇编器的伪指令组成的。
伪指令负责把程序的构造及汇编的方法指示给汇编器,但是伪指令本身是无法转换成本地代码的。

伪指令
其中由伪指令segmentends围起来的部分,是程序中命令和数据的集合体,称之为段定义。_TEXT_DATA_BSS是段定义的名称。
group表示把_BSS_DATA这两个段定义汇总名为DGROUP的组。
_AddNum proc_AddNum endp围起来的部分,是函数AddNum的范围,同理_MyFunc procMyFunc endp 围起来的部分表示函数MyFunc的范围。这两个函数都置于_TEXT中,表示属于_TEXT段定义。虽然源代码中的指令和数据比较混乱,但是通过段定义,汇编之后会转换成划分整齐的本地代码。
end表示源代码的结束。

10.4 汇编语言的意思是“操作码”+“操作数”

操作码是指令动作,操作数是指令对象。

常用操作码
主要寄存器

10.5 最常用的mov指令

mov指令中有两个操作数,分别指定数据的存储地和来源。
如果操作数没有用[]围起来,就表示对其值进行操作,否则的话会把值解释为内存地址,然后对相应地址中的值进行操作。

两种情况

10.6 对栈进行push和pop

栈是存储临时数据的区域,数据的读取要符合先进后出原则。

栈模型

10.7 函数调用机制

函数调用需要依赖栈的作用。
下图为在MyFunc函数中调用AddNum函数的处理内容:


函数调用

10.8 函数内部的处理

下图为call AddNum后AddNum函数内部的处理过程。


函数内部的处理

10.9 始终确保全局变量用的内存空间

简单地说,在Borland C++中,初始化和非初始化的全局变量分别被划到两个不同的段定义中,未被初始化的变量都会被设定为0进行初始化。


10.10 临时确保局部变量用的内存空间

临时变量储存在寄存器和栈中。
由于寄存器的访问速度较快,寄存器空闲时就使用寄存器来存储局部变量,否则就用栈。
函数调用完毕后,栈中局部变量的值就会被销毁(通过恢复栈指针的方式)。


10.11循环处理的实现方法

循环源代码:


循环源代码
循环汇编代码

10.12 条件分支的实现方法

与循环类似。
条件分支C++源代码:



条件分支汇编代码:



10.13 了解程序运行方式的必要性

了解程序运行方式有助于我们更好的理解程序出错的原因。
下面的代码是两个函数更新同一个全局变量:


实际的汇编代码:

可以看出,counter的值乘2是把counter的值读入累加寄存器后实现的。MyFunc1和MyFunc2都是把counter的值乘2,最后理应是4倍,但是MyFunc1运行时如果尚未来的及把eax中两倍的数值写入到counter中去MyFunc2就读取了counter的值,最后运算的结果是counter的值只变为了原来的两倍。

因此为了避免这种错误,我们可以采用以函数或者C语言代码的行为单位来禁止线程切换的锁定方法。

问题答案: 答案

第11章 硬件控制方法

本章提问:

问题

11.1 应用和硬件无关?

Windows应用通过调用Windows操作系统的API(系统调用)来间接控制硬件。


应用通过操作系统间接控制硬件

下面是一个🌰,调用WindowsAPI中的TextOut函数在窗口中显示字符串。



11.2 支持硬件输入输出的IN指令和OUT指令

IN指令是把指定端口号的端口数据存储在CPU内的寄存器中,OUT指令是把CPU寄存器中的数据输出到指定端口号的端口当中。
每个硬件都会有各自的I/O控制器,一个控制器可以控制多个端口,端口就是内存,储存着要交换的数据,端口用端口号来识别。


11.3 编写测试用的输入输出程序

这个例子是通过编写程序控制计算机内部的蜂鸣器发声。
小知识:C代码可以和汇编代码混写,但是汇编代码必须写在asm{}的大括号里。
在AT兼容机中,蜂鸣器的端口号是61H(末尾的H表示的是16进制数的意思),通过把向蜂鸣器端口发送的数据的后两位设为1或0,来控制蜂鸣器发声、关闭。
实现方法:

  1. 与0进行OR运算,不改变原二进制序列;与1进行AND运算,不改变原二进制序列。
  2. 因此,可以让数据与03H(二进制为00000011)进行OR运算,这样数据前6位不变,后两位变为1,进而控制蜂鸣器发声;同样,与FCH(11111100)进行AND运算,前6位不变,后两位为0,进而控制蜂鸣器关闭。
    代码如下:

    通过IN和OUT指令,在寄存器和61H端口中输入输出数据,控制蜂鸣器发声。
    程序在低版本Windows中可以运行,高版本Windows禁止了应用直接控制硬件,这个程序会被禁止运行。

11.4 外围设备的中断请求

每个设备会有自己的中端编号。
收到中断请求后,CPU会把当前任务暂时挂起来处理中断请求。
计算机通过中断控制器来管理中断请求。


中断请求的顺序
中断控制器的功能

11.5 用中断来实现实时处理

如题。


11.6 DMA可实现短时间内传输大量数据

DMA,Direct Memory Access,指不通过CPU的情况下,外围设备直接和主内存进行数据传送,这样会更快,DMA不是必选项,也有自己动编号。



11.7文字及图片的显示机制

显示器中显示的内容储存在VRAM(Video RSM)中,在程序中,向VRAM写入数据,就会在显示器中显示出来,实现该功能的程序,由BIOS(Basic Input Output System)提供,并借助中断运行。
现代计算机中,显卡等专用硬件一般都配置有与主内存相独立的VRAM和GPU(Graphics Processing Unit 图形处理器),过去的VRAM是主内存的一部分。



问题答案:

答案

第12章 让计算机“思考”




完。
上一篇下一篇

猜你喜欢

热点阅读