极简版《计算机组成原理》_程序是怎样跑起来的书评-查字典图书网
查字典图书网
当前位置: 查字典 > 图书网 > 编程 > 程序是怎样跑起来的 > 极简版《计算机组成原理》
六棵树 程序是怎样跑起来的 的书评 发表时间:2016-09-18 21:09:22

极简版《计算机组成原理》

注: 这是markdown原始文稿,无法看到图片,富文本格式文章见我的博文。

这两周读了日本作者矢泽久雄写的[《程序是怎么跑起来的》][1],解开了我这个作为通信专业的软件从业者的很多困惑,为了避免日后遗忘,将一些看了这本书之后的问题的解答记录下来。 Q:电脑的CPU中包含哪些部分?各自的作用有哪些? A:CPU包含寄存器,控制器,时钟和运算器四种主要的结构。 - 控制器负责将内存上的指令、数据等读入到寄存器,并根据运算的结果控制整个计算机; - 寄存器用来暂存数据、指令等处理对象,一般CPU包含20~100个不同的寄存器; - 时钟负责CPU开始计时的时钟信号; - 运算器负责运算从内存读入寄存器的数据 从程序员的角度来说,CPU可以看作寄存器的集合。CPU中包含不同种类的寄存器,各自有不同的功能。 Q:一个典型的C语言源代码在电脑中运行的基本流程是怎样的? A:C语言写成的源代码是高级语言程序,但是CPU运行的代码是本地机器语言,因此C的源代码并不能立即运行。实际上,一个C的源代码需要经过编译、和链接生成.exe的可执行文件之后,电脑会将.exe文件的副本复制到内存中再运行。 Q:内存内部结构如何?内存的数据存取都有哪些数据结构? A:内存是计算机的主存储器,通过芯片与计算机相连,主要负责存储指令和数据,CPU通过基址寄存器和变址寄存器读取和写入内存中的数据。内存由连续的长度为8bit(1个字节)的基本元素构成,程序启动之后CPU的控制寄存器根据时钟信号从内存中读取指令和数据。 存取内存的数据结构包括数组、栈、堆、队列、链表和二叉树。我们可以通过指针直接访问和改变对应内存地址中的变量的数值。 - 数组是多个同样类型的数据在内存中连续的排列的形式,可以通过数组的索引访问数组元素; - 栈可以不通过指定地址和索引对数组元素进行读写。栈由栈底、栈顶描述,一般用来临时保存运算过程中的数据、连接在计算机设备上或者输入输出的数据; - 队列与栈相似,栈的元素是FILO,但是队列是FIFO,队列一般用环形缓冲区实现; - 链表与数组不同,它在内存中不是连续存储的,每个元素都有一个直接后继,像串珠一样将每个元素串联起来,最大优势是增减元素方便快捷; - 二叉树中除了最终的子节点之外,每个元素都有两个后继结点,有序二叉树使得搜索变得更有效 Q:数据和程序是如何保存在计算机中的? A:程序和数据是保存在计算机的硬盘中的,但是程序运行需要将机器语言的程序加载到内存,因为CPU的程序计数器指定内存地址才能读出程序内容。内存和磁盘因为自身特点的差异,它们之间具有紧密的联系。 - 磁盘缓存。由于磁盘的读取速度较慢,为了加快程序的运行,将磁盘中的部分数据加载到内存中缓存起来,之后在访问同一个数据的时候就直接从内存中读取数据,这样的机制叫**磁盘缓存**; - 虚拟内存。**虚拟内存**刚好与之相反,在运行比较大的程序或者内存资源比较紧张可以将部分磁盘当作**假想的内存**来用。实现虚拟内存机制需要在磁盘为内存预留空间,并在程序运行时与内存中的内容进行置换(swap),window中提过**分页式虚拟内存机制**。一般虚拟内存的大小与内存相当或者是内存的两倍。 Q:什么是动态链接和静态链接?二者有何不同? A:DLL(Dynamic link libary)是在程序运行时候动态加载的文件。 所謂動態链接,就是把一些經常會共用的程式碼(靜態链接的OBJ程式庫)製作成DLL檔,當執行檔呼叫到DLL檔內的函數時,Windows作業系統才會把DLL檔載入記憶體內,DLL檔本身的結構就是可執行檔,當程式需求函數才進行链接。透過動態链接方式,記憶體浪費的情形將可大幅降低。 简单来说,已经编译成汇编语言的程序文件,在进一步链接时如果直接将库文件链接进exe可执行文件,则该链接文件就是静态库,如果仅仅在程序运行时才进行链接称为动态链接,链接的目标文件就是动态链接库(windows中为dll文件)。需要说明的是,在链接之后,exe文件中包含了静态链接库的所有内容,所以会比较大,而动态链接库相对轻巧,并且**动态链接库可以在被多个同时运行的程序所共有,并且保证内存中只有一个dll文件中调用函数的副本**,这样就节省了程序运行的空间。实际上window操作系统的大部分API目标文件是动态链接库,动态链接库一般由导入库导入,导入库中并不存在目标函数的实体,仅仅保存目标函数所在的动态链接库的名称及路径。 关于动态链接和静态链接的详细介绍请参考博文[C++静态库与动态库](http://www.cnblogs.com/skynet/p/3372855.html "C++静态库与动态库")。 **Q:一个C语言源程序是如何变成可执行文件(exe)的?又是如何在操作系统中运行的?** **A**:这是个比较大的问题,作者在书中举了个C语言的例子。大体来说,C的源程序需要通过编译器编译成汇编语言(asm文件),进一步链接需要的库文件(dll文件)生成可执行文件(exe文件),最后点击exe将可执行文件导入内存运行程序。以Sample.c文件为例 #include <stdio.h> #include <windows.h> char *title = "messgae box"; double average(double a, double b) { return (a + b)/2.0; } int WINAPI WinMain(HINSTANCE h, HINSTANCE d, LPSTR s, int m) { double ave; char buff[80]; ave = average(123,456); sprintf(buff, "average value is %f", ave); MessageBox(NULL, buff, title, MB_OK); return 0; } 1. **编译**该文件,在源文件目录上运行命令`bcc32 -W -c Sample.c`,生成sample.obj目标文件; 2. **链接**需要的库文件,运行命令`ilink32 -Tpe -c -x -aa c0w32.obj Sample.obj, Sample.exe,, import32.lib cw32.lib` 需要说明的是,c0w32.obj文件是与所有程序起始位置相结合的处理内容,称为程序的**启动**。在源程序中,我们**调用**了系统函数sprintf和messagebox,因此,需要将这两个函数对应的库函数(其中的内容与exe文件相同,都是本地代码)**链接**进来,告诉链接器去哪里找这两个函数对应的本地代码。 sprintf的本地代码在cwlib32.lib中,编译之后会将它的目标函数合成到exe文件中,称为**静态链接**;而messagebox的本地代码在库文件user32.dll里,使用import32.dll是为了告诉连接器“messagebox在库文件user32.dll中,以及user32.dll在哪里”,所以import32.dll称为导入库。程序运行时,执行从DLL文件调出的MessageBox()函数这一信息就会和exe文件结合,称为**动态链接**。 **Q:可执行文件包含哪些内容?它加载到内存中是什么样子?** **A**:可执行文件中包含了源程序的变量和函数的虚拟地址,在加载到内存之后需要必要的信息将虚拟地址转换成实际地址,转换需要的信息就在exe文件开始的部分,称为再配置信息。exe文件被加载到内存之后,就将这些虚拟内存转换成实际内存,程序运行中会生成栈和堆,因此在内存中的样子如下图所示 [![3gXgTTNBST7Ua27e9SaE2B5d.jpg](https://s20.postimg.org/ttpdsl5rx/3g_Xg_TTNBST7_Ua27e9_Sa_E2_B5d.jpg)](https://postimg.org/image/bqwb1d9x5/) **Q:c,o,a,lib,obj,dll这些文件分别是什么?他们之间是什么关系?** **A**:c是C语言的源文件,如博文[Linux的.a、.so和.o文件](http://blog.csdn.net/chlele0105/article/details/23691147 "Linux的.a、.so和.o文件") 中所述 > lib,dll,exe都算是最终的目标文件,是最终产物。而c/c++属于源代码。源代码和最终目标文件中过渡的就是中间代码obj,实际上之所以需要中间代码,是你不可能一次得到目标文件。比如说一个exe需要很多的cpp文件生成。而编译器一次只能编译一个cpp文件。这样编译器编译好一个cpp以后会将其编译成obj,当所有必须要的cpp都编译成obj以后,再统一link成所需要的exe,应该说缺少任意一个obj都会导致exe的链接失败。而 .o,是Linux目标文件,相当于windows中的.obj文件,.so文件为共享库,是shared object,用于动态连接的,相当于windows下的dll,.a为静态库,是好多个.o合在一起,用于静态链接 **Q:什么是_BSS段和_DATA段?全局变量和局部变量在程序运行时有何不同?** **A**:这是汇编语言的概念,编译器将高级语言源程序转换成汇编文件(.asm文件),有如下的源文件sample2.c int AddNum(int a, int b) { return a + b; } void MyFun() { int c; c = AddNum(123,456); } 经过编译之后的汇编文件(软件环境win10,gcc编译器)内容如下: ``` .file "sample.c" .text .globl _AddNum .def _AddNum; .scl 2; .type 32; .endef _AddNum: pushl %ebp movl %esp, %ebp movl 8(%ebp), %edx movl 12(%ebp), %eax addl %edx, %eax popl %ebp ret .globl _MyFun .def _MyFun; .scl 2; .type 32; .endef _MyFun: pushl %ebp movl %esp, %ebp subl $24, %esp movl $456, 4(%esp) movl $123, (%esp) call _AddNum movl %eax, -4(%ebp) leave ret .ident "GCC: (tdm-1) 4.9.2" ``` 汇编程序最接近机器语言,而且其与C语言一一对应,所以通过汇编文件就可以了解程序运行的大体情况。从上面的汇编文件,可以看到如下的结果 1. 寄存器esp指向栈顶元素地址,每个元素占据4个字节的数据; 2. 在每个函数开始的时候,都要将寄存器ebp的数据压入栈中进行保护; 3. 上述程序中隐藏的一个关键步骤是在第21行,call AddNum时,计算机已经将MyFun函数的下一个指令的地址压入栈中,在调用完AddNum时(第12行),返回函数Myfun时候会自动将栈中的返回指令的地址出栈交给CPU的程序计数器,这样就可以实现在调用函数之后仍然返回原来的调用的地方; 4. 函数的入参被保存在栈中,返回值被保存在寄存器里。 ```c int a; int b; float fl; int c = 9; int d = 10; int e = 11; int f = 12; void MyFun(void) { int a1,b1,c1; float fl1; a1 = 1; b1 = -1; fl1 = -99.34; c1 = -87; a1 = a; b1 = b; fl1 = fl; c1 = c; } ``` 以上的C源代码转换成汇编语言是 ``` .file "sample2.c" .comm _a, 4, 2 .comm _b, 4, 2 .comm _fl, 4, 2 .globl _c .data .align 4 _c: .long 9 .globl _d .align 4 _d: .long 10 .globl _e .align 4 _e: .long 11 .globl _f .align 4 _f: .long 12 .text .globl _MyFun .def _MyFun; .scl 2; .type 32; .endef _MyFun: pushl %ebp movl %esp, %ebp subl $16, %esp movl $1, -4(%ebp) movl $-1, -8(%ebp) movl LC0, %eax movl %eax, -12(%ebp) movl $-87, -16(%ebp) movl _a, %eax movl %eax, -4(%ebp) movl _b, %eax movl %eax, -8(%ebp) movl _fl, %eax movl %eax, -12(%ebp) movl _c, %eax movl %eax, -16(%ebp) leave ret .section .rdata,"dr" .align 4 LC0: .long -1027166700 .ident "GCC: (tdm-1) 4.9.2" ``` 从中可以看出全局变量保存在.comm和.globl段,局部变量保存在寄存器中,因此在程序运行的整个过程中,全局变量可以随时访问,但是局部变量却会在用过之后消失。 关于windows的汇编的内容可进一步参考文章[汇编与逆向分析](http://www.mouseos.com/assembly/index.html "汇编与逆向分析") --- 以上是此书最干货的部分,书中该介绍了计算机二进制数,和计算机硬件的部分内容,再次略过不提。

展开全文


推荐文章

猜你喜欢

附近的人在看

推荐阅读

拓展阅读

对“极简版《计算机组成原理》”的回应

六棵树 2017-03-29 16:20:39

这是markdown文本,排版比较乱,干净整洁版请移步https://bugxch.github.io/2016/11/25/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%8E%9F%E7%90%86/