前两天读了《如何阅读一本书》,感觉在看这本书《深入理解计算机系统》的时候,需要先做一个检视阅读,即:15分钟的有系统的略读,3~5天的粗读。
系统略读本书。
明后天粗读。
周六 29号 团建,半宿都在玩,休息了4个小时左右
周日 30号 上午打游戏,下午扛不住了,睡了2小时左右,晚上22点睡。
今早7点半才醒。
这一天天过的。
从10.9日开始,计划学这本书。到现在为止整整20天过去了,第一部分都还没看完,看来自己的毛病还挺多的。各种坚持不下去。
最近借口还挺充足的,早上很冷,没空调和暖气,不愿起来。周六团建,状态不佳,最近事多压力大。等等。
今天晚上开始,21:30躺下,准备睡觉。早6点起,步行或骑车到公司。路上取711看看有没有早餐,没有就买点零食。7~10点三个小时的时间,来看书。
可能前一两天都没精神,毕竟突然早起,但此事,功在当代,利在千秋,所以要坚持。能不能完成这个计划,能不能让自己找回正能量,能否积极向上的良性循环,就看今晚能不能睡着,明早能不能起来了。
起来,前途就有一点点亮光,起不来,以后就得卷铺盖卷回去了。
[end]2016年10月31日09:56:47
早上6点的闹钟,被闹钟叫起来了,脑子里完全没有昨天发过的誓言,躺了二十分钟,还是起来了。冷的要死,冻成狗。
洗刷后来到公司,6点59,哈哈。明天继续。
今天暂时不能做粗读,原因是下午有一个分享,屁屁踢写的不是很ok,在优化一下,以确保每月一次的组内分享有点干货,尽量让大家节省出来的时间有些收获。
[end]2016年11月01日07:10:13
今天稍微晚了点。一般来说,坚持早起的前三天最难,但鉴于自己之前有过这种经历,所以只要有一天早起了,剩下的就简单多了。
昨天的分享比较成功,虽然时间短,但达到想要的效果了。
近期没啥需要花费时间的事情了,以后的早上时间,就可以用来读书了。很开心。再做一个规定,晚上下班回去的时间,不开电脑。除非有线上问题需要处理,否则不开电脑。因为开电脑不是必要的事情,完全可以用其他事情替代。
深入理解计算机系统,粗读第一天。
懒床了,就一小会,然后就完了这么久。。。好吧
继续搞。
得稍微提升下速度,不然得看到啥时候才能搞定呀。
粗读,针对每个章节以及章节下的小标题,提出问题,知道这节在说什么即可。分析阅读时再好好看细节。粗读的目的在于掌握整本书的架构。
实践:每个章节只读一遍,如果没能理解在说什么,则标识【不懂】,然后继续阅读下一章节。如果能理解,则写出概要。
6点的闹钟,6点30多才穿衣服。。。 太冷了。。。 6点就起还真是个挑战。
继续搞,得稍微加快点进度了。看看一早上能不能搞一章。
昨天加班,昨晚很晚才睡,早上起不来。。。
继续
昨晚对自己最近的行为进行反思,为什么指定的计划老是失败,这本书从十月就开始计划学习,三个月内搞定。到现在已经40天了,才看完第三章,还是粗读的(只知道章节在说啥,不知道细节)。
回头又看了下关于自控力的一些东西,其中知乎有一篇帖子写的很好,点这里,最高票回答。里面提到了哈弗公开课的一个视频。找到视频看了看,搞懂一个事情。
每个人的自控力跟时间一样是有限的。每天就这么多。每次强迫自己都会消耗一些。想要真正做一件事情,最好的方法是养成习惯。把它变成例行公事,而不是每次强迫自己去做。
搞清楚这一点之后,剩下的就稍微简单点了。养成习惯的平均周期大概是30天左右。
本月要养成的习惯是:早起+读书。
早起:06:30起
读书:早上2小时,晚上半小时。
每天打卡,月底总结。在朋友圈发出,只要在发出去了,你自己就会不断的鞭策自己,很简单。
继续搞。
打卡。。。
继续搞。
打卡
继续
昨晚11点睡的。。。
早上差点没起来。。。 贵在坚持
继续
早上不到6点就醒了,睡不着。。。 难道是每天早起习惯了?
不过没看书,双十一,买了些东西。 所以来公司就晚了。
继续搞,搞完。感觉自己不会读书,或者说知识体系为零,所以读一本技术书籍难得要死。坚持多读,总有一天会建立起知识体系的。
早上6点20,闹钟吵醒的。昨晚跟同事一起去看海贼王剧场版,回家后又下了个游戏《王者荣耀》类lol 的手游,稍微一玩,就快0点了。。。早上起床后,洗刷完毕,依然有点困,坐在床边,打开《王者荣耀》没敢玩,只是看了下英雄、技能、装备等东西,回过神来,又半个小时过去了。
在脑袋不清醒的时候很容易被诱惑,保持清醒很重要。
再就是,不要把诱惑摆在自己面前,脑子清醒的时候可以抵抗,但总有累的时候吧。所以,手游删掉了。哈哈
再就是读书,缺乏自己的知识体系,从头啃很难,先啃完这本,之后再找路子。依然坚持:“读书百遍,其义自现”。
赶紧搞完,今天还得加班搞需求。
再就是今天是走路上班的,没骑车。
周天也是早起的,但没看书。。。
今天也早起了,走过来的,没骑车,所以有点晚了。
继续搞把,搞完之后换一本。这本书讲述的内容有点不太适合自己,原计划是了解linux 系统的,而这本书讲述的东西感觉有点偏硬件了。
太难读了,基础差太多。硬着头皮啃下来的。收获不多,只知道了一些模糊的概念。
更新了下下面的检视阅读三问。 这本书暂且封存,换一本来读。如果想了解原理的时候再过来翻这本书。
[end] 2016年11月14日10:04:36
分类:计算机科学类
目的:以程序员的视角详细阐述计算机系统的本质概念,并展示这些概念如何影响程序的性能、正确性和实用性。
阐述计算机系统的核心概念,包括:
- 第2章:信息的表示和处理
- 第3章:程序的机器级表示
- 第4章:处理器体系结构
- 第5章:优化程序性能
- 第6章:存储器层次结构
- 第7章:链接
- 第8章:异常控制流
- 第9章:虚拟存储器
- 第10章:系统级I/O
- 第11章:网络编程
- 第12章:并发编程
主要分了三部分:程序结构和执行、在系统上运行程序、程序间的交互和通信
介绍计算机的整体结构,例如,如何表示信息,如何运行程序,CPU 的结构,存储器的结构,异常如何处理,如何实现网络、并发编程等等。以C语言描述的。
大神进阶必读,但目前阶段不太适合自己,现有阶段的目标是理解 linux 系统,能排查线上问题,偏应用。这本书偏向介绍C语言与操作系统之间的一些实现,偏原理。
超高
无符号、补码、浮点数、溢出
虚拟存储器、地址、虚拟地址空间、
决定虚拟地址空间的大小,32位字长,可用内存是4GB。
C里面的 各种类型大小
跨多字节的程序对象,两个规则:对象的地址,在内存中如何排列。
关于排列:介绍了大端法、小端法。
C中,字符串是一个以 null 为字符结尾的字符数组。
程序是字节序列,每个机器的编码不同。展示了windows、linux32、linux64之间的差异
计算机编码、存储和操作都是用的二进制。
布尔代数可以对0和1进行逻辑运算。
衍生出显示器 多色光源来控制显示。
或、与、取反、异或
|| 、&& 和 !
左右移位
整数的两种编码方式:
- 只能表示非负数(无符号的)
- 能表示负数、零和整数
介绍了C中 各种数据类型,根据两种不同的编码,所能表示的最大最小范围。
如:
C数据雷系 | 最小值 | 最大值 |
---|---|---|
char | -128 | 128 |
unsigned char | 0 | 255 |
int | -21 4748 3648 | 21 4748 3648 |
unsigned int | 0 | 42 9496 7295 |
C和C++都支持有符号(默认)和无符号数,java只支持有符号数。
没符号而已,没啥好说的
表示负数的一种方式,最高位 1 时为负,最高位 0 时为证。
如:
+7 0111
+6 0110
+5 0101
+4 0100
+3 0011
+2 0010
+1 0001
+0 0000
-0 1000
-1 1001
-2 1010
-3 1011
-4 1100
-5 1101
-6 1110
-7 1111
-8 超出4个bit所能表达范围 超出4个表达范围 1000
0000 = 0
1111 = -1 * 23 + 22 + 21 + 20 = -1
1111 = 0000 - 0001 = -1
有符号数还有其他两种标准方式表示:
- 反码
- 原码
介绍了下C语言在各种数据类型之间如何做强制类型转换的。原则是:底层的位保持不变
C语言,几乎所有的机器都采用补码。大多数数字都默认为无符号的。
C允许无符号和有符号数之间进行转换,原则是底层的位保持不变。
[end 2016年11月02日10:04:31]
较小的数据类型扩展到较大数据类型时怎么处理。
如:short 类型 改成 int 类型的。
无符号数,直接在前面加零即可,叫零扩展。
有符号数,比较麻烦。
int 强转 short 时如何处理的,直接丢弃高位。
有符号数到无符号数的隐式强制类型转换会导致某些非直观行为,这些行为有时会让程序出错。并且程序员经常忽略。
除了C以外很少语言支持无符号整数。
无符号的用途在于:
- 把字仅仅看成位的集合,并且没有数字意义时。
- 某些其他情况
解释计算机如何做运算的
会溢出
也会溢出,如-8 + -5 = -13
不懂
不懂
不懂
计算机做乘法较慢,编译器做了一个重要的优化:
把乘法计算 –> 改成移位运算 和 加法运算。
如:
x∗14=x∗(23+22+21)=(x<<3)+(x<<2)+(x<<1)
大多数机器上,除法比乘法更慢,但也可以用位移来实现。只不过用的是向右位移。
计算机执行整数运算,实际上是一种模运算形式。
表示数字的字长限制了取值范围,计算可能溢出。
补码表示提供了一种既能表示负数也能表示正数的灵活方法。
C语言中的某些规定会产生意想不到的结果,如:unsigned数据类型,虽然概念简单,却会导致即使是资深程序员都想不到的行为。
对有理数编码,无论是非常大的数字还是接近零的数字。
十进制小数如何用二进制小数表示。
如:十进制 5.75 的二进制表示
5.7510=534=101.112
但,因为位有限,所以精度有限,如不能表示 13
上一节的定点表示法不能有效的表示大数字,
如: 5×2100 需要用 101 后面跟100个零才能表示。
IEEE 采用类科学计数法的形式来表示。公式如下:
V=(−1)s×M×2E
其中 s 表示 是负数还是正数
M 是一个二进制小数,有范围。
E 是对浮点数加权。权重是2的E次幂。
展示了 用上一节 的公式能表示啥样的数字,多大的数字,都一一举例了。
因为浮点数的表示方式限制了精度和范围,所以浮点运算只能近似的表示实数运算,由此产生了 舍入 这一运算的任务。
IEEE 规定了4中舍入方式:
- 向偶数舍入
- 向零舍入
- 向下舍入
- 向上舍入
IEEE 指定了一个简单的规则,来确定加法乘法运算的结果:
按照实数运算,对结果进行舍入。
实际中,浮点单元的设计者使用一些小技巧来避免这种精确运算,只要能保证结果是一个正确的舍入结果即可。
C语言中两种浮点数:float 和 double,分别对应IEEE 的单精度和双精度。
但C语言不要求使用 IEEE 规定的浮点数。
int 和 float double 之间强转时,出现的一些情况
浮点溢出引发的惨案:
Ariane 5 火箭因为浮点溢出问题,发射失败,5亿美元的通信卫星报废。好多钱
[end]2016年11月03日10:09:37
大多数机器对整数使用补码编码。
对浮点数使用IEEE浮点编码,类科学计数法的形式。
相同长度无符号和有符号整数的强制类型转换,遵循的原则是底层位不变。补码机器上转换是由: T2Uw 和 U2Tw 来描述的 (w表示位)
编码长度有限,超出范围时会溢出。
汇编是机器代码的文本表示。
为什么要看懂汇编代码?
- 对高层语言不可见的一些特性,将在汇编中体现出来。
- 可以通过汇编了解到一些编译器的优化性能。
本章将详细学习两种汇编语言:
- 编译器产生的汇编代码
讲解之前会先了解 C、汇编、机器码之间的关系。
之后介绍 IA32,在之后介绍x86-64
介绍历代 intel 芯片的晶体管数量,性能。
摩尔定律的改版,晶体管数目从1978年开始,每18个月翻一倍。
介绍 gcc 的运作流程。C预处理器、编译器、汇编器、链接器。
介绍了下机器级代码的构成。
展示一段代码用 gcc 编译后的结果,以及如何查看。
intel 用”字”表示16位数字
双字 表示32位数字
四字 表示64位数字
IA32 的 CPU 包含8个32位的寄存器。寄存器用来存数据和指针。
介绍这个寄存器的结构
多数指令有一个或多个“操作数”,指出一个操作中要引用的原数值,以及存放结果的位置。
IA32 支持多种操作数:
- 立即数:常数值
- 寄存器:
- 存储器引用:
将数据从一个位置复制到另一个位置的指令是最频繁使用的指令。
“操作数”的通用性用一条指令就可以替代其他系统多条指令才能完成的功能
我们把不同的指令,分成了指令类,一类做一种操作。
介绍move 类的指令
用一段 C 赋值代码,演示传送指令。
基本每个指令都有对:字节、字、双字进行操作的的指令。
操作分为四类:
- 加载有效地址
- 一元操作:如,++,–
- 二元操作:如,x+=y
- 移位操作:如,x >> 2,x << 3
加载有效地址实际是 movl 指令的变形,指令作用是从存储器读取数据到寄存器。
一元操作:一个操作数,既是原,又是目的,如 i++,i–类的运算。
二元操作:元操作数是一个,目的操作数是一个。如 x+=y
x >> 2,x << 3
双操作数乘法指令,没看懂
C语言中的某些结构,要求有条件的执行。if else等,根据数据测试的结果来决定执行的操作顺序。
机器代码提供两种低级机制来实现条件行为:
- 测试数据值。
- 根据结果来改变控制流或数据流。
jump 指令可以用来跳转
除了整数寄存器,CPU还维护者一个 条件码寄存器
没看懂
条件码通常不会直接读取,常用方法有三种:
- 通过条件码组合,将一个字节设置为 0 或1
- 可以条件跳转到程序的某个地方
- 有条件的传送数据
介绍 jump 指令
条件语句从C语言翻译成机器码,最常用方式,结合有条件和无条件跳转。
C中有循环语句,汇编中没有。用条件测试和跳转组合实现循环语句。
讲述 do while 循环,while 循环,for循环等
讲述逆向工程循环。
实现条件的传统方法利用控制的条件转移。条件满足走一条路径,不满足走另一条路径。效率低下。
数据的条件转移是替代策略。
c是如何实现 switch 语句的,跳转表的用途
过程调用,就是方法。需要将数据和控制从代码的一部分转移到另一部分,同事还要对过程的局部变量分配空间,释放空间。
IA32只提供 简单的指令
- 转移控制到过程
- 从过程中转移出控制
数据传递、局部变量分配和释放通过操纵程序栈来实现。
程序用程序栈来实现过程调用。
栈用来传递过程参数、存储返回信息、保存寄存器用于以后恢复,以及本地存储。
支持过程调用和返回的指令:
指令 | 描述 |
---|---|
call Label | 过程调用 |
call *Operand | 过程调用 |
leave | 为返回准备栈 |
ret | 从过程调用中返回 |
寄存器组是唯一能被所有过程共享的资源。
为了保证一个过程调用另一个过程,后者不会覆盖掉前者将要用的寄存器的值。所以 IA32 约定了惯例。
- 调用者保存寄存器:P调用Q,Q可以覆盖。寄存器 %eax、%edx 和 %ecx
- 被调用者保存寄存器:P调用Q,Q在覆盖之前,需要先把值写入栈中,并在返回之前恢复。(寄存器 %ebx、%esi 和 %edi)
演示一个过程
过程是如何递归调用自身的。
C语言实现数组的方式很简单,很容翻译成机器码。
但,C有一个特点是可以产生指向数组中元素的指针,并且对这些指针进行运算。
优化编译器会简化数组索引所使用的地址计算,不过这样产生的机器码会很难立即。
定义一个数组:
int A[N];
会有两个效果:
- 内存中分配一个连续区域,大小为 int 的大小 * N 个。
- 引入标识符 A,A作为指向数组开头的指针。
C语言编译器能优化定长多为数组上的操作代码。
C语言 提供了两种不同类型的对象来创建数据类型的机制:
- 结构(struct):将多个对象集合到一个单位中
- 联合(union):允许用几种不同的类型来引用一个对象
结果的所有组成部分都存放在存储器中一段连续的区域内。
指针指向结构第一个字节的地址。
编译器根据每个结构类型的信息,指示每个字段的字节偏移。
联合:使几个不同类型的变量共占一段内存(相互覆盖)
语法跟 struct 一样,区别在于:
- struct 每个字段都有自己的内存。内存占用长度是所有类型之和。
- union 所有的字段是共享内存的,内存占用长度是最长类型的长度。
对齐:许多计算机系统对基本数据类型合法地址做出了一些限制:要求某种类型对象的地址必须是某个值 的倍数。
对齐可以简化处理器和存储系统之间接口的硬件设计。
指针的作用是帮助程序员避免寻址错误。
重点介绍指针和它们映射到机器代码的关键原则。
- 每个指针都对应一个类型
- 每个指针都有一个值
- 指针用 & 运算符创建
- 运算符 * 用于指针的间接引用
- 数组与指针紧密联系
- 将指针从一种类型强制转换成另一种类型,只需要改变他的类型,而不改变它的值。
- 指针也可以指向函数。
介绍这个玩意的玩法。
c 不对数组引用进行任何边界检查,而且局部变量和状态信息都存放在栈中。这两种情况可能导致很严重的问题。
如:蠕虫和病毒。
介绍了如何对抗缓冲区溢出攻击。
- 栈随机化
- 栈破坏检测
- 限制可执行代码区域
物理机很容易就能配备 4GB 以上的内存,但字长32位却用不了。
加上很多大型应用越来越多。
x86-64 代码与 IA32 机器生成的代码有极大不同。主要特性如下:
- 指针和长整数是64位长。
- 通用目的寄存器组从 8 个扩展到 16个。
- 许多程序状态都保存在寄存器上,而不是栈上。有些过程根本不需要访问栈。
- 如果可能,条件操作用条件传送指令实现。
- 浮点操作用面向寄存器的指令集实现,而不用 IA32 支持的基于栈的方法来实现。
介绍了 64 位机器的C语言数据类型与 32位的区别
跟32位的区别在于:
- 寄存器的数量翻倍至16个。
- 所以寄存器都是 64 位长。
- 可以直接访问每个寄存器的低32位、16位、8位。
实现控制转移的指令和方法与 IA32 一样。新增了一些指令。
我们窥视了C语言提供的抽象层下面的东西,以了解机器级编码。
通过让编译器产生机器及程序的汇编代码,我们了解编译器和它的优化能力,以及机器、数据类ing和指令级。
指令级体系结构(ISA):一个处理器支持的指令和指令的字节级编码。
不同的处理器“家族”,有不同的ISA。编译好的程序,在家族之间不通用。
ISA在 编译器编写者 和 处理器设计人员之间提供了一个抽象层概念。编写者只关心能用哪些指令,以及如何编码的,处理器设计者关心造出执行指令的处理器。
本章简要介绍处理器硬件的设计。
大概介绍了一些指令,其实是 IA32 的子集。
介绍 Y86指令的一些编码
数字电路的基本元素。AND(&&)、OR(||) 和 NOT(!) 门。不用C语言中的 &|~ 表示的原因是,前者只针对位进行运算,后者针对整数进行运算。
组合电路如何构建的。
讲解一个顺序的处理器,最终目标是实现一个高效、流水线化的处理器,而这是第一步
讲解一个实现,分为6个阶段。
取指阶段
译码和回写阶段
执行阶段
访问存储阶段:读写程序数据
更新PC阶段:
SEQ小结。
在做一个流水线的CPU之前,先介绍下原理。
流水线就是排着队干活,举个例子,做黄油面包。
顺序流程是:切片、抹黄油、对折面包。每次只做一个步骤。直到完成
流水线是:切片车间、抹黄油车间、对折车间。面包从头源源不断的送进去,各个车间负责处理自己的部分,从尾部获取搞好的面包。
指令集体系结构,在处理器行为和如何实现处理器之间提供了一层抽象。
流水线化让不同的阶段并行操作,改进了系统的吞吐量。
本章的几个重要经验:
- 管理复杂性是首要问题。创建一个简单一致的框架,来处理所有不同的指令类型。
- 不需要直接实现ISA:ISA的直接实现意味着顺序的设计,我们可以采用流水线。
- 硬件设计人员必须小心谨慎:一旦芯片造出来,就几乎不能改正任何错误了。开始就设计正确非常重要。
优化程序性能有三种方式:
- 选择合适的算法和数据结构
- 写出编译器能有效优化,转成高效可执行的代码。
- 运算量特别大的计算将一个任务分成多个部分,分别在其他机器上做。
本章不是很关心啊,快速过了把。
编译器必须很小心的对程序只是用安全的优化,必须得保证优化前后的程序有一样的行为。
大多数编译器可以定制优化级别,包括GCC。
引入一个量化标准:每元素的周期数。
说明一个抽象的程序如何转换成系统代码的。
到目前为止的优化:
- 降低了过程调用的开销
- 消除一些妨碍优化的“重大因素”
为了进一步优化,必须考虑处理器的微体系结构的优化。
这是一种程序变换,增加每次迭代的数量,减少迭代次数。
不是并发,而是多存出一些变量,一起计算。
编译器可以生成高效的代码,但程序员可以协助编译器做这件事情。
使用正确的数据结构和算法是很重要的,编译器不能做这个事情。
妨碍优化的因素有一些,它会影响编译器的优化。
更高级的优化需要对处理器微体系结构的理解。
存储系统是一个具有不同容量、成本和访问时间的存储设备层次结构。
越靠近CPU 越小越快。
计算机系统基本持久的思想:理解数据在存储层次中上下移动,利用这个可以将数据存放在更高的层次,运行更快。
介绍都有哪些存储技术
分为两类:
- 静态的(SRAM):比动态的快,但贵的多。
- 动态的(DRAM):
1.静态RAM
SRAM 将位存储在一个双稳态的存储单元里。每个单元使用一个六晶体管电路实现。
2.动态RAM
DRAM 将位存储为对一个电容充电,电容很小。
3.传统的DRAM
展示了下传统的DRAM
4.存储器模块
DRAM 包装在存储器模块里面,然后查到主板插槽上。
5. 增强的DRAM
6. 非易失性存储器
永久存储类的存储器,如:闪存,PROM(只能写一次)
7.访问主存
通过总线来访问主存,每次和CPU传送数据都是通过一系列步骤来实现的,称为总线事务。分为读事务和写事务。
介绍磁盘的组成。
介绍固态硬盘的一些基本构成:多个闪存芯片和一个闪存翻译层。
速度快,但会磨损,并且贵。
容量越来越大。访问速度也稍微变快
局部性原理:是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
局部性分为两种形式:
- 时间局部性:命中率高,访问次数多。(在一个具有良好时间局部性的程序中,被引用过一次的存储器位置在将来也会被再次引用)
- 空间局部性:没命中的数据,在下一层cache 也会命中。
L1、L2、L3高速缓存,主内存,本地磁盘,远程磁盘。
缓存命中
缓存不命中
缓存不命中的种类:
缓存管理
通用的告诉缓存存储器结构
组相联高速缓存
全相联高速缓存
有关写的问题
一个真实的高速缓存层次结构的解剖
高速缓存参数的性能影响
一个程序从存储系统读数据的速率称为读吞吐量,有时称为读带宽。
示例:用一个小程序,测试出 Core i7 处理器的存储器山。
基本存储技术包括随机存储器(RAM)、只读存储器(ROM)和磁盘。
RAM有两种:
- SRAM:静态存储器,速度快,但贵
- DRAM:动态存储器,主内存使用。
程序员通过编写有良好空间和时间局部性的程序来显著改进程序 的运行时间。
本书的第一部分,介绍了程序和硬件之间的交互关系。
本书的第二部分,介绍程序和操作系统之间的交互关系。
将学会如何利用操作系统提供的服务来构建系统级程序。如 unix 外壳和动态存储器分配包。
链接:将各种代码和数据部分收集起来并组合成一个单一文件的过程。
链接是由叫做链接器的程序自动执行的。
为什么要了解链接知识?
- 可以构造大型程序:大型程序的程序员经常会遇到链接问题,如,缺少模块,缺少库或不兼容的版本。
- 可以避免一些危险的编程错误:Unix 链接器解析符引用时所做的决定会偷偷的干一些事情。
- 理解语言作用域规则是如何实现的:全局和局部变量的区别。
- 理解其他重要的系统概念:链接器产生的可执行文件,在重要的功能中,扮演重要的角色,如,加载和运行程序、虚拟存储器、分页和存储映射。,
- 能够利用共享库:共享库和动态链接越来越重要,链接也越来越复杂。
这章在说啥:
给了两个函数,讲述如何使用 编译驱动程序 来把这两个函数编译成一个可执行程序的。
这章在说啥:
介绍静态连接器:输入可重定位的目标文件、命令行参数,输出可执行文件。
链接器有两个任务:
- 符号解析:
- 重定位:
目标文件有三种:
- 可重定位目标文件:可以在编译时与其他可重定位文件合并,创造可执行目标文件。
- 可执行目标文件:可被直接拷贝到内存并执行。
- 共享目标文件:特殊类型的可重定位目标文件,可以再加载或运行时被动态的加载到存储器并链接。
展示了一可重定位的目标文件长啥样。
每个可重定位目标模块 m 都有一个符号表,描述自己定义和引用的符号信息。
链接器上下文有三种类型的符号:
- 由m 定义,能被其他模块引用的全局符号:对应非静态的C函数和非静态的C全局变量。
- 由其他模块定义,能被m引用的外部符号,也是全局符号:对应其他模块中的C函数和变量。
- m自己定义,自己使用的本地符号:带status 的C函数和变量。
链接器是如何解析符号的:
将每个引用和可重定位目标文件符号表中的符号联系起来。
本地符号很好解析。
全局符号的引用难解析。
链接器完成符号解析,它就把代码中的符号引用和符号定义联系起来。
前面说的是链接器将多个目标合成一个可执行文件。
现在介绍下可执行文件
如何加载的
静态库有一些缺点:
- 需要定期维护,如果有更新,程序员则要了解处理
- 有一些标准函数,如 printf 和 scanf,如果分散到个个代码里面,很浪费内存。
共享库是一个目标模块,解决了如上问题。可以加载到任意存储地址,并和存在存储器中的程序链接起来。
之前说的是,程序执行之前,动态链接器加载和链接共享库的情景,现在了解程序执行后,如何加载的。
介绍各种工具
完成链接的三种方式:静态编译器、加载时和运行时有动态链接器完成。
目标文件有三种形式:可重定位的、可执行的和共享的。
可重定位的目标文件由静态链接器合并成可执行文件,可以加载到内存中子星。
共享目标文件(共享库)在运行时由动态链接器链接和加载。
链接器主要任务:符号解析和重定位。
程序计数器中指令的地址的过渡称为控制转移,控制转移的序列称为处理器的控制流。最简单的是平滑流。跳转、调用和返回等指令会造成平滑流的突变,来对内部的程序状态中的变化做出反应。系统也需要能够对系统状态的变化做出反应,这些系统状态不能被内部程序变量捕获,系统通过使控制流突变来完成,这些突变称为异常控制流(ECF)。ECF发生在系统的各个层次,包括异常、系统调用、信号和非本地跳转等,本篇对它们一一总结。
异常控制流(ECF)发生在计算机系统的各个层次,是计算机系统提供并发的基本机制。
系统中的进程共享CPU和主存资源,但存储器空间是有限的,而且还容易被破坏。现代系统提供了一种对主存的抽象,称为虚拟存储器,以更有效地管理存储器。虚拟存储器将主存看作磁盘上的地址空间的高速缓存,为每个进程提供了一致的地址空间,并保护进程的地址空间不被其他进程破坏。
http://www.yeolar.com/note/2012/04/20/linux-io/
大概介绍了,网络的基本概念。局域网、广域网的组成,TCP/IP 协议。
使用C语言中的函数如何做一个,类似于 java socket 的 web服务程序。
介绍这么一个模型概念,用于后期的讲解。实际上也是这种模型。
客户端负责请求、交互、展示。服务器负责 执行操作、返回资源。
对于主机来说,网络只是又一种 I/O 设备。通过网络适配器对外发送数和接收数据。
大概介绍了下局域网、广域网是如何通信的。
介绍 TCP/IP 的功能。
因特网的域名,以及如何解析域名:早期,是放在 HOSTS文件中,从1988年之后放在 DNS 服务器中。DNS 就是一个大数据库,存放了 IP 和 域名的映射关系。
介绍C语言中套接字的相关函数。
socket函数、connect函数、open_clientfd函数、bind函数、listen函数、open_listenfd函数、accept函数、echo客户端和服务器的示例。
介绍web 服务器的一些基本知识。以及如何用C语言输出一个html页面。各种printf。
操作系统提供了三种基本的构造并发程序的方法:
- 进程:每个逻辑控制流都是一个进程,由内核来调度和维护。
- I/O多路复用:应用程序在一个进程的上下文中显式地从一个状态转换到另一个状态。
- 线程:运行在一个单一进程上下文中的逻辑流,由内核进行调度。
一个并发程序是由在时间上重叠的一组逻辑流组成的。
无论哪种并发机制,同步对共享数据的并发访问都是一个困难的问题。提出对信号量的 P 和 V 操作就是为了帮助解决这个问题。
并发也引入了其他一些困难的问题,被线程调用的函数必须具有一种称为线程安全的属性。