第1章 对程序员来说CPU是什么 1
1.1 CPU的内部结构解析 3
-
cpu中有什么组件?
时钟、控制器、运算器、寄存器(几十到上百个)
-
内存中的每个字节都有地址编号
1.2 CPU是寄存器的集合体 6
-
汇编是什么操作?
把汇编语言转换为机器语言的过程,用于这种转换的程序叫编译器。机器语言转为汇编语言的过程叫反汇编
1.3 决定程序流程的程序计数器 9
- 操作系统把程序读入内存后,会把程序计数器的地址设为内存中程序的起始内存地址,cpu在执行完一条指令后,程序计数器的值会自动加上刚执行完的指令所占的字节数
1.4 条件分支和循环机制 10
-
标志寄存器能做什么?
cpu使用累加器来对两个数做减法,从而得到两个数的大小关系;保存累加器的计算结果是正、负、零,或者是奇数、偶数,或者是加法运算后的溢出值;32位cpu的寄存器长度就是4字节,那么刚才说到的正、负、零这种值会固定对应到32个字节的相应固定index处
-
条件分支和循环的实现是通过跳转指令修改程序计数器中的地址
1.5 函数的调用机制 13
- 虽然也是修改程序计数器中的地址,达到跳转到函数在内存中的位置,但是还需要记录跳转前的下一条指令的地址,使得在函数执行完成后,可以再跳回到原来的地方继续执行。而实现跳转的方式是先用call指令将调用后要执行的地址存入到内存中的栈里,程序执行完使用return指令把栈中的地址设定到程序计数器中
1.6 通过地址和索引实现数组 16
- 因为用二进制表示的话占的位数太多,所以一般用十六进制表示,这样32位二进制就可用8位十六进制表示了
1.7 CPU的处理其实很简单 17
- cpu可以执行的指令有哪几类
- 数据传送:寄存器与内存间、内存与内存间、寄存器与I/O设备间
- 运算:使用累加寄存器执行算术运算、逻辑运算、比较运算、移位运算
- 跳转:条件、循环
- 函数调用:call、return
第2章 数据是用二进制数表示的 19
2.1 用二进制数表示计算机信息的原因 21
- 内存和磁盘都是以字节为单位来存储和读写数据,所以字节是信息的基本单位,位是最小单位
- 32位cpu就是有32个引脚,就是能一次处理32位二进制数
2.2 什么是二进制数 23
2.3 移位运算和乘除运算的关系 25
- 十进制数左移1位就是10倍,左移2位就是100倍,左移3位就是1000倍,右移1位就是1/10,右移2位就是1/100,可以想到小数点移动
- 二进制左移1位是2倍,左移2位是4倍,左移3位是8倍,右移1位是1/2,右移2位是1/4
- 左移后,右面空出的位用0补;右移后,左面空出的位要区分正负数情况使用0还是1来补,见2.5节
2.4 便于计算机处理的“补数” 27
-
计算机中如何做减法?如
3 - 5
计算机中不做减法,减法都被转为做加法,步骤是将5转为-5,然后计算3+(-5)
-
计算机中如何表示负数?
使用补数,补数的最高位是符号位,最高位为0表示正数,为1表示负数。
-
-1在计算机中的8位二进制表示是什么?
1的二进制是0000 0001,那么-1就是1000 0001么?答案是否定的,正数转为负数(补数)的方式是取反后加一,1取反后是1111 1110,再加一是1111 1111,这就是计算机中-1的8位二进制数表示
-
验证
1 - 1 = 0
0000 0001 + 1111 1111 = 1 0000 0000,但是最高位1超出了8位,在8位的范围内进行计算时,计算机会忽略溢出位,所以计算结果是0000 0000,就是0。同时,这也就证明了为什么-1的二进制不是上面猜测的1000 0001
-
负数如何变为正数?
同样是取反后加一,如-1的二进制1111 1111取反后0000 0000,再加一是0000 0001,得到1
-
如何计算
3 - 5
?3的二进制是0000 0011,5的二进制是0000 0101,那么-5就是1111 1011,那么
3 - 5 = 3 + (-5)
= 0000 0011 + 1111 1011 = 1111 1110,那么1111 1110就表示-2喽?上面说过负数取反后加一就转为正数,我们试一下,1111 1110取反是0000 0001,再加一是0000 0010,结果正确 -
有了上面的基础,我们看一下C语言
C中的短整型是占2字节的,所以unsigned short的取值范围是
0~65535
,short的取值范围是-32768~32767
,为什么有符号类型的取值范围两边不对称呢?是因为有0的存在,因为0是0000 0000,最高位是0,被当做了正数,0~32767
是32768个数,-1~-32768
也是32768个数
2.5 逻辑右移和算术右移的区别 31
-
逻辑右移是怎么填补高位的?
只用0填充,右面溢出的位忽略,这一般用于二进制数不表示数值时,比如二进制数表示的是图像、文字等,这时二进制是用来区分不同信息映射关系的编号,而不表示2的多少次方的值这种大小关系
-
算术右移是怎么填补高位的?
使用符号位填充,即正数填0,负数(补数)填1,右面溢出的位忽略
-
-4 (=1111 1100)
右移2位的二进制是什么?- 如果是做逻辑右移,那么得到0011 1111 = 63,如果是数值计算的话那么这显然是错误的
- 如果是做算术右移,那么得到1111 1111 = -1,这显然是对的,变为了原来的1/4
-
什么符号扩充?如8位二进制数扩充到16位二进制数
将扩充的内容用符号位填补,如-1的8位二进制1111 1111,扩充到16位是1111 1111 1111 1111,再如1的8位二进制是0000 0001,扩充到16位是0000 0000 0000 0001
2.6 掌握逻辑运算的窍门 34
- 逻辑运算包括与、或、非、异或。0表示false,1表示true,所以逻辑运算被认为是真假运算,没有进位的概念,所有运算结果列表的话就叫真值表
- 算术运算包括加、减、乘、除
COLUMN 如果是你,你会怎样介绍?——向小学生讲解CPU和二进制 38
第3章 计算机进行小数运算时出错的原因 41
3.1 将0.1累加100次也得不到10 43
- C语言中的输出是10.000002
3.2 用二进制数表示小数 44
-
二进制小数1011.0011转换成十进制数是什么?
二进制小数与十进制小数一样,也是每一位乘以位权重,如十进制小数中就是乘10-1、10-2,10-3,二进制也一样,就是乘2-1,2-2,2-3,所以1011.0011 = 23+21+20.2-3+2-4 = 11.1875
3.3 计算机运算出错的原因 46
- 原因是:有一些十进制数的小数无法转换成二进制数,如十进制0.1没有与之对应的二进制数,准确来说十进制0.1可以用二进制0.00011001100…(1100循环)来表示,就像我们无法精确表示1/3一样,只能用0.33333…来近似,因为只要后面3的个数能数出来,那么乘以3后就不可能等于1
3.4 什么是浮点数 47
-
浮点数是指:用符号、尾数、基数、指数表示的小数,符号 * 尾数 * 2指数,理解成科学计数法。
- 符号位:是0时是正数,1时为负数;
- 尾数:需要通过移动小数点来达到小数点前面只有1位且这1位的值等于1的效果,而真正存储时,并不存储小数点前面的1,这样可以省存储空间,这就是3.5节中正则表达式的意思,就像科学计数法也有规范一样
- 基数:固定是2;
- 指数:使用的是EXCESS系统,“剩余”,就是以中间数表示0,大于中间数的表示正,小于中间数的表示负。具体见下
-
双精度占64位,单精度占32位
3.5 正则表达式和EXCESS系统 50
- 这里说的正则表达式和字符串处理中的正则表达式不是一回事儿,这里的意思就是表示要按照规则来表示数据的意思,只是针对的尾数部分。例如:用单精度存二进制1011.0011时,尾数部分存储的内容这样获得:
- 1011.0011右移使小数点前面只有1位且这1位的值等于1,那么就变成1.0110011
- 将小数点后面的值扩充到23位(见图3-4),那么变成1.01100110000000000000000
- 仅把小数点后面的内容作为尾数存储起来,即01100110000000000000000
- EXCESS系统针对的是指数部分,为了不用考虑最高位是符号位而设计的,例如:单精度的指数部分占8位(见图3-4),可以表示-127 ~ 128(2-127 ~ 2128),怎么来的呢?是因为8位的最大值是1111 1111 = 255,取中间数是127 = 0111 1111,规定0111 1111在指数系统中代表0,那么0111 1110 = 126就表示-1 = 126-127,1000 0000 = 128就表示1 = 128-127,0000 0000 = 0表示-127 = 0-127,1111 1111 = 255表示128 = 255-127
3.6 在实际的程序中进行确认 52
-
举例:十进制0.75的单精度二进制是怎么存储的?
-
首先:0.75的二进制表示是0.11,2-1+2-2 = 0.75;
-
因为大于0那么符号位自然等于0;
-
基数:固定是2;
-
尾数:0.11移位后变成1.1,后面补0达到23位得1.10000000000000000000000,那么实际存储的就是10000000000000000000000
-
指数:因为从1.1到0.11要向左移动一位小数(与十进制意思一样),那么指数为-1,通过EXCESS系统转换,实际存储的是0111 1110
-
最终,十进制0.75的单精度二进制的32位表示为 0 - 0111 1110 - 1000 0000 0000 0000 0000 000,连字符用来区分不同部分(见图3-4)
-
3.7 如何避免计算机计算出错 55
- 法一:忽略这种精读误差,因为一般取一两位小数的话10与10.000002是没区别的,见3.1节10.000002的由来
- 法二:计算过程中只用整数,最后输出结果时才转为小数,如对0.1求和100次转化为对1求和100次后再除以10
- 法三:使用BCD(Binary Code Decimal),简单来说就是用4二进制来表示0~9的1位十进制数,我猜这就是python中BigDecimal的做法
3.8 二进制数和十六进制数 56
- C语言中0x开头的,用十六进制就是为了少写点儿0和1
第4章 熟练使用有棱有角的内存 59
4.1 内存的物理机制很简单 61
- 内存IC中地址引脚的数量决定了地址空间的大小,数据引脚的数量决定了一次可以操作的字节数
4.2 内存的逻辑模型是楼房 65
4.3 简单的指针 67
- 指针也是变量,也有类型,类型就标记出指针对应的数据占多少字节。指针就是内存地址
4.4 数组是高效使用内存的基础 69
- 内存可以理解成一个很大的的数组
4.5 栈、队列以及环形缓冲区 71
- 队列一般是用环形缓冲区来实现的
4.6 链表使元素的追加和删除更容易 75
4.7 二叉查找树使数据搜索更有效 79
第5章 内存和磁盘的亲密关系 81
5.1 不读入内存就无法运行 83
5.2 磁盘缓存加快了磁盘访问速度 84
-
磁盘缓存指什么?
指的是把从磁盘中读出的数据存储到内存中的方式,内存当做磁盘的缓存。不过现在好像已经有了新技术
5.3 虚拟内存把磁盘作为部分内存来使用 85
- 上一小节是把内存中的一部分当做磁盘,这里是把一部分磁盘当做内存。但要记得正在运行中的程序一定是在内存中的,所以被存储到虚拟内存(磁盘)中的内存数据属于当前没在运行中的程序
- 虚拟内存的交换方法有两种:分页式、分段式,主要区别就是交换数据的大小不同,Windows采用分页式,页大小是4KB,数据由磁盘到内存的过程称为Page In,由内存到磁盘的方式称为Page Out
- 虚拟内存在Windows中也可以叫作页文件
5.4 节约内存的编程方法 88
- 使用DLL文件(Dynamic Link Library)达到多个程序共享同一函数的效果,这样内存中就不会出现同一个代码实现属于两个程序的情况。用DLL还有一个好处是可以在不修改exe文件的情况下,通过对DLL的更新来更新程序中的功能。更多DLL内容见第8章
- C语言中,在函数声明时加入
_stdcall
,可以达到在函数被调用完后,被调用函数自己清理内存栈中的数据,而不是由调用方来来清除,这样如果函数被调用多次的话,就可以减少调用方的清理内存栈代码(这个清理工作是编译器自动加上去的)
5.5 磁盘的物理结构 93
- 扇区是对磁盘进行物理读写的最小单位,一般是521KB;簇是Windows对磁盘读写的最小单位,簇要求是扇区的整数倍,具体1簇等于多少扇区,要根据处理速度和存储容量来权衡确定;如果文件大小没有达到1簇,那么磁盘中也会占用1簇的空间,剩下的部分也不能存其他的信息了,只能空着,这感觉和Linux中的块是一个概念
第6章 亲自尝试压缩数据 97
6.1 文件以字节为单位保存 99
- 文件是字节数据的集合
6.2 RLE 算法的机制 100
- 数据*重复次数的方式,例如:AAAAABBB(占8字节)被转换成A5B3(占4字节)
6.3 RLE 算法的缺点 101
- 这种压缩方式主要用在黑白图像的压缩,实际使用时,字节内容并不理想
6.4 通过莫尔斯编码来看哈夫曼算法的基础 103
-
哈夫曼算法的思想与摩尔斯码的思想类似,不是所有的内容都存入等长的二进制位中,而是把数据中经常出现的字符用尽可能短的二进制位数表示,较少出现的字符用较长的二进制位数表示。在计算机中存储时当然要以字节的整数倍存储,所以不足的要补全
-
下图用2字节存储了原来占5字节的数据(因为原来1字节的数据就占8位)
6.5 用二叉树实现哈夫曼编码 105
-
因为不同字符所占的二进制位数不一样了,所以原始做法是加入分隔符来区分字符,但是分隔符也是要占据空间的
-
哈夫曼树有一个优化是,他会对不同的文件,按文件内部字符出现的频率,来确定字符占用的二进制位数,这部分编码信息也会占据空间,但这是值得的
-
通过哈夫曼树,可以不使用分隔符,哈夫曼树是从叶子结点向根节点方向构建的,先统计出字符出现的频率,然后每次将词频最低的两个连接
6.6 哈夫曼算法能够大幅提升压缩比率 109
6.7 可逆压缩和非可逆压缩 110
- BMP格式的图像是完全没有经过压缩的,所以叫bitmap
- jpeg、tiff、gif都是经过压缩的,jpeg是非可逆压缩,gif是可逆压缩
COLUMN 如果是你,你会怎样介绍?——向沉迷游戏的中学生讲解内存和磁盘 114
第7章 程序是在何种环境中运行的 117
7.1 运行环境=操作系统+硬件 119
- MIPS是一款cpu
- 本地代码(native code)指的就是机器语言(二进制)
7.2 Windows克服了CPU以外的硬件差异 122
7.3 不同操作系统的API不同 124
7.4 FreeBSD Port帮你轻松使用源代码 125
- Ports机制是porting(移植)的意思,作者介绍这个的目的就是表明这种机制可以实现同一份源代码可以在不同硬件环境下编译并运行起来
7.5 利用虚拟机获得其他操作系统环境 127
7.6 提供相同运行环境的Java虚拟机(JVM) 128
- java源代码(xxx.java)经过java编译器编译后,生成java字节码(xxx.class),java字节码在jvm(java.exe)中运行,jvm一边把java字节码转成本地代码(机器语言),一边运行,不过这种方式现在可能有做优化
- 站在操作系统的角度,jvm是一个应用程序;站在java源码的角度,jvm是一个执行环境
7.7 BIOS和引导 130
- BIOS(Basic Input/Output System)存储在ROM(只读存储器)中,是出厂时内置在计算机中的程序
- BIOS的功能除了键盘、磁盘、显卡等控制程序外,还有启动引导程序的功能
- 引导程序:存储在启动驱动器(硬盘、u盘、光盘)起始区域中的小程序。引导程序的作用是把启动驱动器中的操作系统加载到内存中运行
- 开机流程:BIOS检查硬件是否都运行正常,确认正常后就会启动引导程序,然后引导程序再加载操作系统
- 引导程序(Bootstrap)的原意是靴子上的拔靴带,方便穿靴子用的
第8章 从源文件到可执行文件 133
8.1 计算机只能运行本地代码 135
- 本地代码=机器语言(二进制)
8.2 本地代码的内容 137
- Windows的exe文件中就是本地代码
8.3 编译器负责转换源代码 139
- 编译器也是程序,不同架构cpu中指令集不同,要执行的本地代码不同,编译器负责将同一个源代码转成不同的本地代码来给不同的cpu执行
8.4 仅靠编译是无法得到可执行文件的 141
- 在Windows系统中,sample.c文件编译后生成sample.obj文件(即目标文件),不是sample.exe文件,sample.obj文件中存的是本地代码,接下来用链接器把sample.obj、c0w32.obj、import32.lib、cw32.lib等文件链接起来才能生成可执行文件smaple.exe,这样才能执行程序。上面几个文件的作用见后面几节
8.5 启动及库文件 143
- 启动:就是上一节中提到的c0w32.obj文件,由编译器提供,就是规定要链接这个文件
- 库文件:是把多个目标文件(.obj)集成保存到一个文件中的形式,如import32.lib、cw32.lib
- 标准函数:如sprintf(),来自某个.obj文件,并且.obj存储在cw32.lib中,这种实现代码存储在库中的库称为静态链接库
8.6 DLL文件及导入库 145
-
API:Windows平台提供的一些函数,如MessageBox()
-
存储API的目标文件并不直接存储在库中,而是存储在DLL(动态链接库)中,然后.dll文件被关联到某个.lib中,这样的.lib被称作导入库
-
Windows中编译和链接的机制,即如何从源代码生成exe文件的过程,见下图
8.7 可执行文件运行时的必要条件 146
-
exe文件中给变量和函数分配的是虚拟的内存地址,在程序运行时,虚拟的内存地址会被转换为实际内存地址。链接器运行阶段会在exe文件的开头加入虚拟的内存地址转换为实际内存地址所必需的信息,这种信息称作再配置信息
-
虽然源文件中的变量和函数分布在不同的位置,但在链接后生成的exe文件中,这些变量的地址会放到一起(变量组),函数的地址也会放到一起(函数组),见下图
8.8 程序加载时会生成栈和堆 148
- 当双击exe文件,程序加载到内存时,除了8.7节中图8-9中提到的再配置信息、变量组、函数组以外,还会在内存中开辟栈(局部变量和函数调用时的参数)和堆(程序运行时用到的数据)
- 栈和堆的内存地址并不在exe文件中指定,而是在exe文件加载到内存后开始运行时才分配的
- 负责对栈中数据的存储和清除的代码是由编译器自动生成的,所以程序员不用关注
- 堆中申请和释放空间的代码是由程序员写的,例如:C语言中申请空间用malloc(),释放空间用free();C++中申请用new关键字,释放用delete关键字
- 如果在堆中申请了空间,用完后忘记释放,那么这部分空间会一直残留,这种现象称作内存泄漏
8.9 有点难度的Q&A 150
-
编译器和解释器的区别是什么?
编译器是在运行前对源代码处理,解释器是运行时对源代码逐行解释执行
-
垃圾回收就是指对堆内存中不在使用的空间进行清理,C语言中用free(),C++中用delete关键字
第9章 操作系统和应用的关系 153
9.1 操作系统功能的历史 155
9.2 要意识到操作系统的存在 157
- 操作系统出现之前,是需要程序员来直接操作代码中要用的的硬件的,操作系统出现后,程序员就不用关注硬件的细节了,只面对操作系统就行了,硬件的统一由操作系统来完成
9.3 系统调用和高级编程语言的移植性 160
- 不同的操作系统,系统调用(即操作系统提供的函数)也不同
- C语言中,向printf()这种标准函数的底层会调用操作系统的系统调用,在编译时,printf()就会选择适合当前操作系统的系统调用,这样同一份源码,在不同的操作系统中都能运行
9.4 操作系统和高级编程语言使硬件抽象化 161
- 向C语言中使用fopen()、fputs()、fclose()时,并没有关心是对哪些扇区进行操作,这是因为磁盘硬件的读写被抽象成了对文件的操作。fopen()会返回文件指针,在文件打开后,操作系统会自动申请出用来管理文件读写用的内存,后续我们只是对文件指针操作就行,而操作系统开辟出的内存在哪儿,程序员不用太关心
9.5 Windows操作系统的特征 163
- 32位操作系统,指的是一次可以处理32比特数据
- Windows环境下的系统调用就是API函数集,被封装到各种DLL中,API函数是用C语言实现的
- 中间件:处于操作系统和应用程序中间。网络功能和数据库功能就是中间件,系统软件=操作系统+中间件,不过我感觉中间件这种说法好像被弱化,不用太关注这种叫法
COLUMN 如果是你,你会怎样介绍?——向超喜欢手机的女高中生讲解操作系统的作用 170
第10章 通过汇编语言了解程序的实际构成 173
10.1 汇编语言和本地代码是一一对应的 175
- 汇编语言:使用助记符替换本地代码(机器语言)的语言,汇编语言和本地代码是一一对应的
- 汇编器:把汇编语言转为本地代码,这一转换称为汇编
- 反汇编器:把本地代码转为汇编语言,这一转换称为反汇编
- C语言写的源代码经过编译器后得到相应cpu的本地代码,对本地代码反汇编可以准确得到汇编语言源码,因为汇编语言和本地代码是一一对应的,而对本地代码反编译就不能得到完全一样的C语言源码,因为高级语言与本地代码不一一对应,不过我感觉好像也是大差不差,这里之后得再看看,记得C语言编译后就没发反编译了啊
10.2 通过编译器输出汇编语言的源代码 177
- 编译器支持把C语言的源码编译成汇编语言
- 汇编语言源代码文件的后缀是.asm
10.3 不会转换成本地代码的伪指令 180
- 汇编语言中,分号“;”后面的是注释
- 汇编语言中,有大写字母的行是伪指令,伪指令不会转换为本地代码
- 汇编语言代码中的段:用来划定代码区域的,感觉可以理解为高级语言中的
{}
的作用,段是一块连续的内存空间。_TEXT segment ... ends
是指令段,_DATA segment ... ends
是有初始值的数据段,_BSS segment ... ends
是没有初始值的数据段
10.4 汇编语言语法是“操作码+操作数” 182
- 操作码:如mov、and、push、pop、jp,支持什么操作码是由cpu决定的
- 操作数:寄存器名称、内存地址、常数。内存中的数据是用地址编号来区分的,cpu寄存器使用名字来区分的,如:
- eax:累加寄存器,用于计算
- ebx:基址寄存器,存储内存地址
- ebp:扩展基址指针寄存器
- esp:扩展栈指针寄存器,存储内存栈顶地址
- 程序运行时,cpu把指令和数据从内存中读出,存储到寄存器中,或直接通过寄存器计算
10.5 最常用的mov指令 185
mov ebx,esp
的意思是把寄存器esp中的值存储到寄存器ebx中mov eax,dword ptr [ebp+8]
逐层分解,eax、ebp是寄存器名;[ebp+8]
是把ebp中存的值加8,结果作为内存地址编号访问内存,dword ptr
是doulbe word pointer,指4字节;所以这条mov指令的意思就是:从内存地址为ebp+8
处读4字节内容存到寄存器eax中
10.6 对栈进行push和pop 185
- esp寄存器负责管理栈顶内存地址,32位cpu中,push操作会使esp中的值+4,pop使esp-4。当然如果我们要清理栈中8字节的数据时,可以执行两次pop,但也可以使用一次
add esp,8
来实现。清理栈中的数据并不一定是把栈里面的数据删除,直接移动栈顶指针就可以,后面有数据push时会自动把之前的数据盖掉,感觉这和标记删除差不多
10.7 函数调用机制 187
- 调用函数时,函数的地址会存入栈中,传参的值或地址也会存入栈中
10.8 函数内部的处理 189
- 函数的参数是通过栈来传递的,函数的返回值是通过寄存器返回的
10.9 始终确保全局变量用的内存空间 191
10.10 临时确保局部变量用的内存空间 196
10.11 循环处理的实现方法 199
cmp ebx,10
的意思是把ebx寄存器中的值与10进行比较,比较结果会存储到标志寄存器中jl short @4
的意思要拆解说明:jl
是(jump on less than小于时跳转),判断依据是标志寄存器中的值,所以所有跳转指令需要和cmp搭配使用;short @4
是跳转到标签4。所以cmp ebx,10
+jl short @4
的意思就是如果ebx中的值小于10,那么就跳到标签4处执行
10.12 条件分支的实现方法 202
- 跳转指令还有:
- jle:小于等于时跳转
- jge:大于等于时跳转
- jmp:无条件跳转,即直接跳转,相当于高级语言中的goto
10.13 了解程序运行方式的必要性 204
- 线程:操作系统分配给cpu的最小运行单位。源代码中的一个函数就相当于一个线程,多线程处理就可以理解成在一个程序中同时在运行多个函数
- 假设有一个全局变量count,有两个函数都要进行
count += 1
,在多线程时就会出问题,原因是count += 1
对应的汇编代码有三行move eax,dword ptr [_count]
、add eax,1
、mov dword ptr [_count],eax
,且cpu可能在执行完某行汇编代码后切换到其他线程执行,这样在两个线程(函数)同时执行时,count
就一不定是2。高级语言中的加锁机制就是防止cpu切换
第11章 硬件控制方法 209
11.1 应用和硬件无关? 211
11.2 支撑硬件输入输出的IN指令和OUT指令 212
- I/O控制器:处于cpu与I/O设备之间,不同设备可能有不同的I/O控制器,在数模、模数转换中起协调统一作用
- 端口:I/O控制器中用来临时保存输入、输出数据的内存(寄存器),一个I/O控制器中可能有多个端口,所以被分配了固定的端口号来做区分,端口号也称作I/O地址
- IN和OUT是汇编语言中的指令,IN把某端口号中的数据存入cpu寄存器,OUT把cpu寄存器中的数据输出到某端口号
11.3 编写测试用的输入输出程序 215
- 在C语言中,可以通过
_asm { }
的方式直接写汇编代码,这样可以实现C代码与汇编代码共存
11.4 外围设备的中断请求 218
- IRQ(interrupt request)中断请求
- 中断处理机制:暂停当前正在运行的程序,跳转到中断处理程序的机制。如果没有中断处理机制,那么cpu就只能在处理完当前任务后,再来处理中断请求,这显然不是我们想要的
- I/O控制器是中断请求的发出者,不同I/O设备有不同的中断编号,以便cpu能区分出是什么设备发出的中断请求
- 操作系统或BIOS中会提供响应中断编号的中断处理程序
- cpu和I/O控制器之前还会有中断控制器,这可以理解成有权重的队列,好让cpu可以有序处理中断请求,因为cpu同一时间只能做一件事
11.5 用中断来实现实时处理 221
- 如果没有中断机制,那么就只能轮询了
11.6 DMA可以实现短时间内传送大量数据 222
- DMA(Direct Memory Access)指在不经过cpu的情况下,I/O设备直接与内存进行数据传输。磁盘就有这种功能
- DMA通道:就是编号,用来区分不同I/O设备的DMA的
11.7 文字及图片的显示机制 224
- 显示器中显示的信息一直存储在显存(内存)中,也就是在程序中,只要往显存中写入数据,那么数据就会在显示器中显示出来,实现这种功能的程序,由操作系统或BIOS提供,并借助中断来进行实现
COLUMN 如果是你,你会怎样介绍?——向邻居老奶奶说明显示器和电视机的不同 226
第12章 让计算机“思考” 229
12.1 作为“工具”的程序和为了“思考”的程序 231
12.2 用程序来表示人类的思考方式 232
12.3 用程序来表示人类的思考习惯 235
12.4 程序生成随机数的方法 237
-
蒙特卡洛:因赌博而闻名的城市
-
伪随机数:利用公式产生的随机数,具有周期性,可用公式有:线性同余法、乘同余法、M系法、Knuth减算法
-
随机数种子指什么?
生成随机数时用的参数,如果种子不换,每次跑程序时生成的随机数的值和顺序不变
12.5 活用记忆功能以达到更接近人类的判断 239
12.6 用程序来表示人类的思考方式 242
COLUMN 如果是你,你会怎样介绍?——向常光临的酒馆老板讲解计算机的思考机制 245
附录 让我们开始C语言之旅 247
C语言的特点 247
-
Unix、LInux都是用C语言写的,C语言具备了与汇编语言相媲美的底层处理(内存操作、位操作)功能
-
C语言的编译器具备将C语言代码转成汇编语言代码的功能,以及可以在C语言的源码中嵌入汇编语言
变量和函数 248
数据类型 249
标准函数库 250
函数调用 251
局部变量和全局变量 254
数组和循环 255
其他语法结构 256
- 书中给出了一种思路是,如果掌握了编程语言中的所有关键词,那么就可以说是完全掌握了这门编程语言