├── README.md ├── 敏捷软件开发:原则,模式与实践 ├── 第一部分:敏捷开发.md ├── 第三部分:薪水支付案例研究.md └── 第二部分:敏捷设计.md ├── 深入理解计算机系统(CSAPP) ├── README.md ├── _attachment │ └── 20150127111638009 ├── _image │ ├── 20150127111638009.png │ ├── 2018-10-13-14-48-26.jpg │ └── 汇编-25.png ├── 第一章:计算机系统漫游.md ├── 第七章:链接(一).md ├── 第七章:链接(二).md ├── 第三章:程序的机器级表示(一).md ├── 第三章:程序的机器级表示(三).md ├── 第三章:程序的机器级表示(二).md ├── 第九章:虚拟内存.md ├── 第二章:信息的表示和处理(一).md ├── 第二章:信息的表示和处理(二).md ├── 第八章:异常控制流.md ├── 第六章:存储器层次结构.md ├── 第十一章:网络编程.md ├── 第十二章:并发编程.md └── 第十章:系统级IO.md ├── 算法图解 ├── _image │ └── 2821559619577_.pic_hd.jpg ├── 广度优先搜索.md ├── 快速排序.md ├── 散列表.md ├── 算法简介.md ├── 选择排序.md └── 递归.md └── 计算机网络自顶向下方法 ├── 局域网.md ├── 应用层.md ├── 网络层.md ├── 计算机网络中的安全.md ├── 计算机网络和因特网.md ├── 运输层.md └── 链路层.md /README.md: -------------------------------------------------------------------------------- 1 | # ReadingNotes 2 | 读书笔记,以计算机方向为主,已经读完《深入理解计算机系统》、《敏捷软件开发:原则,模式与实践》、《计算机网络自顶向下方法》,目前在读《算法图解》 3 | 4 | ## 《计算机网络自顶向下方法》 5 | 1. [计算机网络和因特网](./计算机网络自顶向下方法/计算机网络和因特网.md) 6 | 2. [应用层](./计算机网络自顶向下方法/应用层.md) 7 | 3. [运输层](./计算机网络自顶向下方法/运输层.md) 8 | 4. [网络层](./计算机网络自顶向下方法/网络层.md) 9 | 5. [链路层](./计算机网络自顶向下方法/链路层.md) 10 | 6. [局域网](./计算机网络自顶向下方法/局域网.md) 11 | 7. [计算机网络中的安全](./计算机网络自顶向下方法/计算机网络中的安全.md) 12 | 13 | 14 | 15 | 16 | ## 《算法图解》 17 | 1. [算法简介](./算法图解/算法简介.md) 18 | 2. [选择排序](./算法图解/选择排序.md) 19 | 3. [递归](./算法图解/递归.md) 20 | 4. [快速排序](./算法图解/快速排序.md) 21 | 5. [散列表](./算法图解/散列表.md) 22 | 6. [广度优先搜索](./算法图解/广度优先搜索.md) 23 | 24 | 25 | 26 | ## 《敏捷软件开发:原则,模式与实践》 27 | 1. [第一部分:敏捷开发](./敏捷软件开发:原则,模式与实践/第一部分:敏捷开发.md) 28 | 2. [第二部分:敏捷设计](./敏捷软件开发:原则,模式与实践/第二部分:敏捷设计.md) 29 | 3. [第三部分:薪水支付案例研究](./敏捷软件开发:原则,模式与实践/第三部分:薪水支付案例研究.md) 30 | 31 | ## 《深入理解计算机系统》 32 | 1. [第一章:计算机系统漫游](./深入理解计算机系统(CSAPP)/第一章:计算机系统漫游.md) 33 | 2. [第二章:信息的表示和处理(一)](./深入理解计算机系统(CSAPP)/第二章:信息的表示和处理(一).md) 34 | 3. [第二章:信息的表示和处理(二)](./深入理解计算机系统(CSAPP)/第二章:信息的表示和处理(二).md) 35 | 4. [第三章:程序的机器级表示(一)](./深入理解计算机系统(CSAPP)/第三章:程序的机器级表示(一).md) 36 | 5. [第三章:程序的机器级表示(二)](./深入理解计算机系统(CSAPP)/第三章:程序的机器级表示(二).md) 37 | 6. [第三章:程序的机器级表示(三)](./深入理解计算机系统(CSAPP)/第三章:程序的机器级表示(三).md) 38 | 7. [第六章:存储器层次结构](./深入理解计算机系统(CSAPP)/第六章:存储器层次结构.md) 39 | 8. [第七章:链接(一)](./深入理解计算机系统(CSAPP)/第七章:链接(一).md) 40 | 9. [第七章:链接(二)](./深入理解计算机系统(CSAPP)/第七章:链接(一).md) 41 | 10. [第八章:异常控制流](./深入理解计算机系统(CSAPP)/第八章:异常控制流.md) 42 | 11. [第九章:虚拟内存](./深入理解计算机系统(CSAPP)/第九章:虚拟内存.md) 43 | 12. [第十章:系统级IO](./深入理解计算机系统(CSAPP)/第十章:系统级IO.md) 44 | 13. [第十一章:网络编程](./深入理解计算机系统(CSAPP)/第十一章:网络编程.md) 45 | 14. [第十二章:并发编程](./深入理解计算机系统(CSAPP)/第十二章:并发编程.md) 46 | 47 | 48 | -------------------------------------------------------------------------------- /敏捷软件开发:原则,模式与实践/第一部分:敏捷开发.md: -------------------------------------------------------------------------------- 1 | ## 敏捷实践 2 | 3 | #### 敏捷软件开发宣言 4 | 1. **个体和交互胜过过程和工具**。团队的构建要比环境的构建重要的多。许多团队和管理者就犯了先构建环境,然后期望团队自动凝聚在一起的错误。相反,应该首先致力于构建团队,然后再让团队基于需要来配置环境 5 | 2. **可以工作的软件胜过面面俱到的文档**。没有文档的软件是一种灾难,然而过多的文档比过少的文档更糟。所以文档一定要短小精悍!**直到迫切需要并且意义重大时,才来编制文档** 6 | 3. **客户合作胜过合同谈判**。成功的关键在于和客户之间真诚的协作,所以要多和客户交流,而不是说完全按照合同上来,客户可能也不知道自己需要的是什么 7 | 4. **响应变化胜过遵循计划**。较好的做计划的策略是:为下两周做详细的计划,为下三个月做粗略的计划,再以后就做极为粗糙的计划。计划是要有的,并且要保持灵活多变 8 | 9 | ## 极限编程 10 | 1. **客户作为团队成员**。这样便于沟通 11 | 2. **用户素材**。多听听用户的反馈,记录下来,它是一个计划工具,可以调整进度等计划变更 12 | 3. **短周期交付**。短期交付和长期交付结合,实时把控项目进度 13 | 4. **验收测试**。验收测试使用能够让它们自动且反复运行的某种脚本语言编写,这些测试共同来验证系统按照客户指定的行为运转 14 | 5. **结对编程**。结对编程非但不会降低开发团队的效率,而且会大大减少缺陷率。**事实上,大部分老板不会让你这么干的** 15 | 6. **测试驱动的开发方法**。会激发你去解除各个模块间的耦合,这样能够独立的对它们进行测试。面向对象设计的原则在进行这种解除耦合方面具有巨大的帮助作用 16 | 7. **集体所有权**。每个人都需要参加所有的模块,除了你自己负责的模块,还要多了解其他人的代码 17 | 8. **持续集成**。 18 | 9. **可持续的开发速度**。团队必须有意识的保持稳定,适中的速度 19 | 10. **开放的工作空间**。开放式办公 20 | 11. **计划游戏**。多做小的计划,便于把控项目进度 21 | 12. **简单的设计**。考虑最基础的架构,再慢慢演进 22 | 13. **重构**。重构是持续进行的,而不是项目结束的时候 23 | 14. **隐喻**。它是将整个系统联系在一起的全局视图,它是系统的未来景象,是它使得所有单独模块的位置和外观变得明显直观 24 | 25 | ## 计划 26 | * 初始探索 27 | * 发布计划 28 | * 迭代计划 29 | * 任务计划 30 | * 迭代 31 | * 结论:通过一次次的迭代和发布,项目进入了一种可以预测的、舒适的开发节奏。每个人都知道做什么以及何时去做。开发人员看到的是基于他们自己的估算并且由他们自己度量的开发速速控制的合理的计划。他们选择他们感觉舒适的任务,并保持高的工作质量。管理人员从每次迭代中获取数据,他们使用这些数据来控制和管理项目 32 | 33 | ## 测试 34 | 编写测试有很多好处,但是实际情况中我用的并不多。 35 | 36 | 测试最重要的好处就是它对于架构和设计的影响。为了使一个模块或者应用程序具有可测试性,必须要对它进行解耦。越是具有可测试性,耦合关系就越弱。全面的考虑验收测试和单元测试的行为对于软件的结构具有深远的正面影响 37 | 38 | ## 重构 39 | 40 | ## 保龄球比赛 41 | 42 | 一些思考: 43 | 44 | 1. 确定验证测试用例。 45 | 2. 画出UML类图。 46 | 3. 精简合并类图。 47 | 4. 先用最简单的设计,例如:简单的方法参数。 48 | 5. 对于输入参数是否校验的原则是:自己使用的参数不必校验,供别人使用的参数则加入校验。在不清楚的情况下,用最简单的方法处理不加校验。 49 | 6. 设计好类之间的关系。采用原则、模式、数据结构。 50 | 7. 不断演化的单元测试,贯穿始终。 51 | 8. 以通过单元测试为目的不断的简化类。 52 | 9. 编写过程中进行重构。 53 | 10. 自上而下,测试优先设计。可以避免过度设计。 54 | 55 | -------------------------------------------------------------------------------- /敏捷软件开发:原则,模式与实践/第三部分:薪水支付案例研究.md: -------------------------------------------------------------------------------- 1 | ## Command模式 2 | 命令(Command)模式属于对象的行为模式【GOF95】。命令模式又称为行动(Action)模式或事务(Transaction)模式。命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。 3 | 4 | 适用性:在软件系统中,行为请求者与行为实现者之间通常呈现一种紧耦合的关系。但在某些场合,比如要对行为进行记录撤销重做事务等处理,这种无法抵御变化的紧耦合是不合适的。这种情况下,使用command模式将行为请求者与行为实现者进行解耦。 5 | 6 | ## Active Object模式 7 | 主动对象设计模式使方法执行与方法调用去耦合,以增强并发、并简化对驻留在它自己的线程控制中的对象的同步访问。 8 | 9 | 一种异步编程思想,允许任务的提交(相当于对异步方法的调用)和任务的执行(相当于异步方法的真正执行)分离,实现了异步编程。 10 | 有利于提高并发性,从而提高系统的吞吐率。 11 | 12 | ## 模板方法模式(Template Method) 13 | * 提高代码复用性 ,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中 14 | * 实现了反向控制 ,通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则” 15 | 16 | ## 策略模式 17 | 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。 18 | 19 | ## Facade模式 20 | Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。它是为子系统中的一组接口所提供的一个一致的界面。 21 | 22 | ## Mediator模式 23 | 用于模块间解耦,通过避免对象互相显式的指向对方从而降低耦合。 24 | 25 | ## 单例模式 26 | 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 27 | 28 | ## Mono State模式 29 | Mono State模式是另外一种获取对象单一性的方法。它是红完全不同的工作机制 30 | 31 | 和单例模式和Mono State模式对比: 32 | 33 | * Singleton强制结构上的单一性. 34 | * Mono-State强制行为上的单一性. 35 | * Mono-State的测试case对Singleton是有效的.但是反之不行. 36 | * 如果希望透过派生去约束一个现存类,并且不介意它的所有调用者都必须要调用instance()来获取访问权,使用Singleton. 37 | * 如果希望类的单一性本质对调用者透明,或者使用单一对象的多态派生对象.使用Mono-State. -------------------------------------------------------------------------------- /敏捷软件开发:原则,模式与实践/第二部分:敏捷设计.md: -------------------------------------------------------------------------------- 1 | ## 面向对象的一些原则 2 | - 单一职责原则 3 | - 开放-封闭原则 4 | - Liskov替换原则 5 | - 依赖倒置原则 6 | - 接口隔离原则 7 | 8 | 什么是软件设计? 9 | 源代码就是设计 10 | 11 | ## 设计的臭味 12 | - 僵化性 13 | - 脆弱性 14 | - 牢固性 15 | - 粘滞性 16 | - 不必要的复杂性 17 | - 晦涩性 18 | 19 | 开发者必须站在代码阅读者的位置,共同努力的对他们的代码进行重构,这样代码的阅读者就可以理解代码,他们的代码也需要被其他人评审。需求是项目中最不稳定的因素,所以应该保持系统设计尽可能的干净、简单,并使用单元测试和验收测试支持,团队利用这种灵活性,持续改进设计,以便于每次迭代结束所生成的系统都具有最适合那种迭代中需求的设计。 20 | 21 | ## 糟糕的 Copy 程序 22 | 编写一个从键盘读入输入字符并输出到打印机的程序,这段代码很简单 23 | ```C 24 | void Copy() 25 | { 26 | int c; 27 | while((c=RdKbd() != EOF)) 28 | WrtPrt(c); 29 | } 30 | ``` 31 | 第一次需求变更:Copy程序要求能从纸带读入机中读入信息。所以就使用一个全局boolean变量以及三目运算解决 32 | ```C 33 | bool ptFlag = false; 34 | // remember to reset this flag 35 | void Copy() 36 | { 37 | int c; 38 | while((c=(ptFlag ? Rdpt() : RdKbd()))!= EOF)) 39 | WrtPrt(c); 40 | } 41 | ``` 42 | 第二次需求变更:Copy程序可以输出到纸带穿孔机上。所以就再增加一个全局boolean变量以及三目运算解决 43 | ```C 44 | bool ptFlag = false; 45 | bool punchFlag = false; 46 | // remember to reset this flag 47 | void Copy() 48 | { 49 | int c; 50 | while((c=(ptFlag ? Rdpt() : RdKbd()))!= EOF)) 51 | punchFlag() ? WrtPunch(c) : WrtPrt(c); 52 | } 53 | ``` 54 | 可以发现,两次需求变更后,程序的设计已经变得很糟糕,后面的维护会很困难。所以我们的工作就是要保证我们的软件能够经受住那些变化。如果我们软件的设计由于需求变化了而退化,那么我们就不是敏捷的!! 55 | 56 | ## Copy 程序的敏捷设计 57 | ```Java 58 | class Reader 59 | { 60 | public: 61 | virtual int read() = 0; 62 | }; 63 | class keyboardReader : public Reader 64 | { 65 | public: 66 | virtual int read() { return RdKbd(); } 67 | } 68 | KeyboardReader GdefaultReader; 69 | void Copy(reader& reader = GdefaultReader) 70 | { 71 | int c; 72 | while((c=RdKbd() != EOF)) 73 | WrtPrt(c); 74 | } 75 | ``` 76 | 所以说当有需求来时,要考虑一个最符合当下的设计,并且最好能应付未来的变化! 77 | 78 | **有新需求时,应该抓住这次机会去改进设计,以便设计对于将来的同类变化具有弹性,而不是设法去给设计打补丁!!** 79 | 80 | **什么是敏捷设计?** 81 | 82 | 敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持系统设计在任何时间都尽可能的简单、干净以及富有表现力 83 | 84 | 85 | ## 单一职责原则(SRP) 86 | 在 SRP 中,变化的原因就是职责。如果能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。 87 | 88 | 遵守单一职责原则,将不同的职责封装到不同的类或模块中 89 | 90 | 看完这一章的感觉就是看似懂了,又貌似没懂!所以找了一个 [例子](http://www.cnblogs.com/TomXu/archive/2012/01/06/2305513.html) 参考一下 91 | 92 | ## 开放-封闭原则 93 | 两个主要特征: 94 | - 对于扩展是开放的 95 | - 对于更改是封闭的 96 | 97 | 这一章感觉讲的就是如何更合理的设计类和方法,如何设计出耦合度低的代码 98 | 99 | ## Liskov替换原则 100 | 子类型必须能够替换掉他们的基类型 101 | 102 | 正方形和矩形的例子有点难理解! 103 | 104 | 从网上找了一个不错的答案: 105 | > 里氏替换原则有至少以下两种含义:1. 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。2. 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。 如何符合LSP?总结一句话 —— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。 106 | 107 | ## 依赖倒置原则 108 | 1. 高层模块不应该依赖于底层模块。二者都应该依赖于对象 109 | 2. 抽象不应该依赖于细节。细节应该依赖于抽象 110 | 111 | [https://blog.csdn.net/zhengzhb/article/details/7289269](https://blog.csdn.net/zhengzhb/article/details/7289269) 112 | 113 | ## 接口隔离原则 114 | 115 | 与单一职责原则的区别: 116 | 117 | 1. 单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。 118 | 2. 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。 119 | [https://blog.csdn.net/zhengzhb/article/details/7296921](https://blog.csdn.net/zhengzhb/article/details/7296921) 120 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/深入理解计算机系统(CSAPP)/README.md -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/_attachment/20150127111638009: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/深入理解计算机系统(CSAPP)/_attachment/20150127111638009 -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/_image/20150127111638009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/深入理解计算机系统(CSAPP)/_image/20150127111638009.png -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/_image/2018-10-13-14-48-26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/深入理解计算机系统(CSAPP)/_image/2018-10-13-14-48-26.jpg -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/_image/汇编-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/深入理解计算机系统(CSAPP)/_image/汇编-25.png -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第一章:计算机系统漫游.md: -------------------------------------------------------------------------------- 1 | ## 信息就是位+上下文 2 | 位:也叫比特,英文为Bit,是Binary digit(二进制数字)的缩写,是信息的最小单位。当一件事只有两个选择并且两个选择发生的概率相等,则一个二进制位可以表示其中的一个选择。比如:正负、开关、是否等。另外,1字节=8比特。1个字节可以表示一个文本字符。当然在不同的编译器中不同类型变量占用的字节是不一样的。 3 | 4 | | C类型 | 32位 | 64位 | 5 | |:-------:|:-------|:-------| 6 | |char|1|1| 7 | |short int|2|2| 8 | |int|4|4| 9 | |long int|4|4| 10 | |long long int|8|8| 11 | |char*|4|8| 12 | |float|4|4| 13 | |double|8|8| 14 | 15 | 上下文:英文为context。翻译为上下文、环境、语境。这个概念有点抽象,可以理解为信息是比较复杂的,单纯的由比特是不能准确的描述信息的,或者说有了语境,比特能更好的解释信息,不同的语境下,比特也可能有不同的含义。这种感觉就像语文题目中的请联系上下文... 16 | 17 | ## C语言起源 18 | 贝尔是个发明家、企业家。被称为“电话之父”,虽然有点黑历史,但是仍然牛逼。贝尔实验室也是牛的不行,发明和改进了很多项技术。贝尔实验室的一名研究人员发明了B语言,并且用它开发了一个游戏,为了玩这个游戏还发明了UNIX(兴趣绝对是巨大的驱动力)。后来又有一位同事帮助改进了B语言,也就是现在的C语言!同时用C语言完全重写了UNIX。 19 | 20 | ## 程序从源文件到可执行文件的四个阶段 21 | 这个过程其实就是从人能读懂的高级语言到机器能读懂的机器语言的一个翻译过程。 22 | - 预处理:处理以#开头的命令 23 | - 编译:翻译成汇编语言 24 | - 汇编:翻译成机器指令(二进制) 25 | - 链接:链接需要的各种文件,生成最终的可执行文件 26 | 27 | 感受一个这个过程。 28 | 编写一个hello.c如下: 29 | ```c 30 | #include 31 | 32 | int main() 33 | { 34 | printf("hello, world\n"); 35 | return 0; 36 | } 37 | ``` 38 | #### 1.执行预处理命令,生成hello.i 39 | ``` 40 | gcc -E -o hello.i hello.c 41 | ``` 42 | 可以打开hello.i看一下,变成几百行了 43 | ```c 44 | extern int __vsprintf_chk (char * restrict, int, size_t, 45 | const char * restrict, va_list); 46 | extern int __vsnprintf_chk (char * restrict, size_t, int, size_t, 47 | const char * restrict, va_list); 48 | # 412 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/stdio.h" 2 3 4 49 | # 2 "hello.c" 2 50 | 51 | int main() 52 | { 53 | printf("hello, world\n"); 54 | return 0; 55 | } 56 | ``` 57 | #### 2.执行编译命令,生成hello.s 58 | ``` 59 | gcc -S -o hello.s hello.i 60 | ``` 61 | 打开hello.s看一下可以看到程序被编程成汇编语言了。 62 | ``` 63 | .section __TEXT,__text,regular,pure_instructions 64 | .build_version macos, 10, 14 65 | .globl _main ## -- Begin function main 66 | .p2align 4, 0x90 67 | _main: ## @main 68 | .cfi_startproc 69 | ## %bb.0: 70 | pushq %rbp 71 | .cfi_def_cfa_offset 16 72 | .cfi_offset %rbp, -16 73 | movq %rsp, %rbp 74 | .cfi_def_cfa_register %rbp 75 | subq $16, %rsp 76 | leaq L_.str(%rip), %rdi 77 | movl $0, -4(%rbp) 78 | movb $0, %al 79 | callq _printf 80 | xorl %ecx, %ecx 81 | movl %eax, -8(%rbp) ## 4-byte Spill 82 | movl %ecx, %eax 83 | addq $16, %rsp 84 | popq %rbp 85 | retq 86 | .cfi_endproc 87 | ## -- End function 88 | .section __TEXT,__cstring,cstring_literals 89 | L_.str: ## @.str 90 | .asciz "hello, world\n" 91 | 92 | 93 | .subsections_via_symbols 94 | ``` 95 | #### 3.执行汇编命令,生成hello.o 96 | ``` 97 | gcc -c hello.s -o hello.o 98 | ``` 99 | 打开hello.o可以看一下,全是二进制代码 100 | ``` 101 | cffa edfe 0700 0001 0300 0000 0100 0000 102 | 0400 0000 0802 0000 0020 0000 0000 0000 103 | 1900 0000 8801 0000 0000 0000 0000 0000 104 | 0000 0000 0000 0000 0000 0000 0000 0000 105 | 9800 0000 0000 0000 2802 0000 0000 0000 106 | 9800 0000 0000 0000 0700 0000 0700 0000 107 | 0400 0000 0000 0000 5f5f 7465 7874 0000 108 | 0000 0000 0000 0000 5f5f 5445 5854 0000 109 | 0000 0000 0000 0000 0000 0000 0000 0000 110 | 2a00 0000 0000 0000 2802 0000 0400 0000 111 | c002 0000 0200 0000 0004 0080 0000 0000 112 | 0000 0000 0000 0000 5f5f 6373 7472 696e 113 | 6700 0000 0000 0000 5f5f 5445 5854 0000 114 | 0000 0000 0000 0000 2a00 0000 0000 0000 115 | 0e00 0000 0000 0000 5202 0000 0000 0000 116 | 0000 0000 0000 0000 0200 0000 0000 0000 117 | 0000 0000 0000 0000 5f5f 636f 6d70 6163 118 | 745f 756e 7769 6e64 5f5f 4c44 0000 0000 119 | 0000 0000 0000 0000 3800 0000 0000 0000 120 | 2000 0000 0000 0000 6002 0000 0300 0000 121 | d002 0000 0100 0000 0000 0002 0000 0000 122 | 0000 0000 0000 0000 5f5f 6568 5f66 7261 123 | 6d65 0000 0000 0000 5f5f 5445 5854 0000 124 | 0000 0000 0000 0000 5800 0000 0000 0000 125 | 4000 0000 0000 0000 8002 0000 0300 0000 126 | 0000 0000 0000 0000 0b00 0068 0000 0000 127 | 0000 0000 0000 0000 3200 0000 1800 0000 128 | 0100 0000 000e 0a00 0000 0000 0000 0000 129 | 0200 0000 1800 0000 d802 0000 0200 0000 130 | f802 0000 1000 0000 0b00 0000 5000 0000 131 | 0000 0000 0000 0000 0000 0000 0100 0000 132 | 0100 0000 0100 0000 0000 0000 0000 0000 133 | 0000 0000 0000 0000 0000 0000 0000 0000 134 | 0000 0000 0000 0000 0000 0000 0000 0000 135 | 0000 0000 0000 0000 5548 89e5 4883 ec10 136 | 488d 3d1b 0000 00c7 45fc 0000 0000 b000 137 | e800 0000 0031 c989 45f8 89c8 4883 c410 138 | 5dc3 6865 6c6c 6f2c 2077 6f72 6c64 0a00 139 | 0000 0000 0000 0000 2a00 0000 0000 0001 140 | 0000 0000 0000 0000 0000 0000 0000 0000 141 | 1400 0000 0000 0000 017a 5200 0178 1001 142 | 100c 0708 9001 0000 2400 0000 1c00 0000 143 | 88ff ffff ffff ffff 2a00 0000 0000 0000 144 | 0041 0e10 8602 430d 0600 0000 0000 0000 145 | 1900 0000 0100 002d 0b00 0000 0200 0015 146 | 0000 0000 0100 0006 0100 0000 0f01 0000 147 | 0000 0000 0000 0000 0700 0000 0100 0000 148 | 0000 0000 0000 0000 005f 6d61 696e 005f 149 | 7072 696e 7466 0000 150 | ``` 151 | #### 4.执行链接命令,生成hello文件 152 | ``` 153 | gcc -o hello hello.o 154 | ``` 155 | 这样就生成了可执行文件hello,可以运行一下看看效果 156 | ![](./_image/2018-10-13-14-48-26.jpg) 157 | 158 | 这样一个完整的过程就结束了。想要从C文件直接生成可执行文件可以一步到位: 159 | ``` 160 | gcc -o hello hello.c 161 | ``` 162 | 163 | ## [shell](https://zh.wikipedia.org/wiki/%E6%AE%BC%E5%B1%A4) 164 | 一般指在操作系统中,提供访问内核的程序。或者是命令行界面或者是图形界面。shell就是这样一个解析器。可以理解为和计算机交互的工具。 165 | 166 | ## 系统硬件 167 | - 总线:连接各个硬件、传输信息 168 | - I/O设备:输入输出,键盘鼠标显示器磁盘等 169 | - 主存:就是平时说的内存条, 170 | - 处理器:就是CPU啊,不断执行程序计数器的指令 171 | 172 | #### 高速缓存的设计 173 | 高速缓存(cache):速度比主存更快,存储容量小 174 | 175 | [Cache为什么有那么多级?为什么一级比一级大?是不是Cache越大越好?](https://zhuanlan.zhihu.com/p/32058808) 176 | cache的设计是一个技术问题、设计问题、平衡问题。 177 | 178 | ## 操作系统和硬件 179 | - 进程:每个任务都可以理解为一个进程 180 | - 线程:每个进程可以分为多个线程 181 | - 虚拟内存: 182 | - 内存管理: 程序代码和数据、堆、共享库、栈、内核虚拟内存 183 | - 文件:字节序列 184 | ## 小结(摘抄书中原文) 185 | 计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们依据上下文有不同的解释方式。开始时是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。 186 | 处理器读取并解释存放在主存里的二进制指令。因为计算机花费了大量的时间在内存、I/O设备和CPU寄存器之间复制数据,所以将系统中的存储设备划分成层次结构,CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。在层次模型中,位于更高层次的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次的存储设备可以作为较低层次设备的高速缓存。可以通过这种层次机构优化C程序的性能 187 | 操作系统内核是应用程序和硬件之间的媒介。它提供三个基本抽象 188 | - 文件是对I/O设备的抽象 189 | - 虚拟内存是对主存和磁盘的抽象 190 | - 进程是处理器、主存和I/O设备的抽象 191 | 192 | ## 刘老大的几个问题: 193 | #### 1.举出一个计算机软件中“上下文”的例子 194 | 比如相对于进程而言,上下文就是进程执行的环境。具体来说就是各个变量和数据,包括所有的寄存器变量,进程打开的文件、内存信息等。 195 | #### 2.比较下C语言的链接和Java链接有什么不同? 196 | 197 | #### 3.什么是RISC指令集,什么是CISC指令集? 198 | RISC是精简指令集,CISC是复杂指令集。这是CPU的两种架构,区别是设计理念和方法 199 | 简单点来说,RISC指令格式统一,种类较少,寻址方式比复杂指令集小,ARM是RISC的代表,功耗较小。X86是复杂指令集的代表,高性能高功耗。 200 | RISC以后还有很大的发展空间。 201 | #### 4.什么是基于栈的指令集和基于寄存器的指令集? 202 | 203 | #### 5.在进程的虚拟地址空间中,有用户栈,运行时堆等区域,如果一个进程运行了多个线程,用户栈是怎么使用的? 204 | 205 | #### 6.南桥和北桥在什么位置?有什么作用? 206 | 207 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第七章:链接(一).md: -------------------------------------------------------------------------------- 1 | 链接器把程序的各个部分联合成一个文件,处理器可以将这个文件加载到内存,并且执行它。现代操作系统与硬件合作,为每个程序提供一种幻象,好像这个程序是在独占地使用处理器和主存,而实际上,在任何时刻,系统上都有多个程序在运行。 2 | 链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时,也可以执行于加载时,也就是在程序被加载器加载到内存并执行时,甚至执行与运行时,也就是由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器的程序自动执行的。 3 | ## 静态链接 4 | * 符号解析:将每个符号引用和一个符号定义联系起来 5 | * 重定位:链接器把每个符号定义与一个存储器位置联系起来,接着修改所有对这些符号的引用 6 | ## 目标文件 7 | 有三种形式: 8 | * 可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。 9 | * 可执行目标文件:包含二进制代码和数据,其形式可以被直接复制到内存并执行。 10 | * 共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接 11 | ## 符号和符号表 12 | symtab中的符号表不包含对应于本地非静态程序变量的任何符号,这些符号在程序运行时由栈进行管理。 13 | 带有static属性的本地过程变量是不在栈中管理的,相反,编译器在.data或.bss中为这类变量分配空间,编译器把初始化为0的变量放在.bss而不是.data中,且在符号表中创建一个本地链接器符号。 14 | ## 符号解析 15 | 编译器遇到一个不是在当前模块中定义的符号时,他会假设该符号是在其他模块定义的,生成一个链接器符号表条目。 16 | 链接器在它的任何输入模块中若找不到这个被引用的符号,则报错 17 | 18 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第七章:链接(二).md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/深入理解计算机系统(CSAPP)/第七章:链接(二).md -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第三章:程序的机器级表示(一).md: -------------------------------------------------------------------------------- 1 | 当我们用高级语言编程的时候,机器屏蔽了程序的细节,即机器级的实现。高级语言提供的抽象级别比较高,大多数时候,在这种抽象级别上工作效率会更高,也更可靠。编译器提供的类型检查能帮助我们发现许多程序错误,并能够保证按照一致的方式来引用和处理数据。 2 | 3 | ## 机器级编程的两种抽象 4 | 1. 由`指令集体系结构或指令集架构`来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响 5 | 2. 机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去是一个非常大的字节数组 6 | ## 数据格式 7 | 大多数GCC生成的汇编代码指令都有一个字符的后缀,表明操作数的大小。例如,数据传送指令有四个变种 8 | - movb(传送字节) 9 | - movw(传送字) 10 | - mov1(传送双字) 11 | - movq(传送四字) 12 | ## 访问信息 13 | 一个`x86-64`的中央处理单元包含一组16个存储64位值的`通用目的寄存器`。这些寄存器用来存储整型数据和指针。16个寄存器,他们的名字都是%r开头。 14 | 15 | 大多数指令有一个或多个操作数,指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。 16 | 各种不同的操作数的可能性被分为三种类型。 17 | 1. 立即数,用来表示常数 18 | 2. 寄存器,表示某个寄存器的内容 19 | 3. 内存引用,它会根据计算出来的地址访问某个内存位置 20 | 21 | 数据传送指令示例 22 | ```c 23 | long exchange(long *xp, long y) 24 | { 25 | long x = *xp; 26 | *xp = y; 27 | return x; 28 | } 29 | ``` 30 | C代码转成汇编后,变成: 31 | ``` 32 | long exchange(long *xp, long y) 33 | xp in %rdi, y in %rsi 34 | exchange: 35 | movq (%rdi), %rax 36 | movq %rsi, (%rdi) 37 | ret 38 | ``` 39 | 40 | **C语言中所谓的指针,其实就是地址。间接引用指针就是就是将该指针放在一个寄存器中,然后在内存引用中使用这个寄存器。像X这样的局部变量通常是保存在寄存器中,而不是在内存中。访问寄存器比访问内存要快得多** 41 | ## 算术和逻辑运算 42 | 事实上,给出的每个指令类都有四种不同大小数据的指令。这次操作被分为四组 43 | - 加载有效地址 44 | - 一元操作 45 | - 二元操作 46 | - 移位 -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第三章:程序的机器级表示(三).md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 缓冲区溢出 4 | 缓冲区溢出是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。缓冲区溢出原指当某个数据超过了处理程序限制的范围时,程序出现的异常操作。造成此现象的原因有: 5 | - 存在缺陷的程序设计。 6 | - 尤其是C语言,不像其他一些高级语言会自动进行数组或者指针的边界检查,增加溢出风险。 7 | - C语言中的C标准库还具有一些非常危险的操作函数,使用不当也为溢出创造条件。 8 | 9 | 举个例子: 10 | ``` 11 | void function(char *str) 12 | { 13 | char buffer[16]; 14 |  strcpy(buffer,str); 15 | } 16 | ``` 17 | 上面的strcpy()将直接吧str中的内容copy到buffer中。这样只要str的长度大于16,就会造成buffer的溢出,使程序运行出错。存在象strcpy这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf()等。随便往缓冲区中填东西造成它溢出一般只会出现“分段错误”(Segmentation fault),而不能达到攻击的目的。最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。如果该程序属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,可以对系统进行任意操作了。 18 | 19 | ## 这章的重点是理解机器级函数调用 20 | [https://pan.baidu.com/s/1pLNN3kf]( https://pan.baidu.com/s/1pLNN3kf ) 21 | 22 | 密码:hsg4 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第三章:程序的机器级表示(二).md: -------------------------------------------------------------------------------- 1 | 2 | ## 控制 3 | #### 条件码 4 | 最常用的条件码 5 | - CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作额溢出。 6 | - ZF:零标志。最近的操作得出的结果为0 7 | - SF:符号标志。最近的操作得到的结果为负数 8 | - OF:溢出标志。最近的操作导致一个补码溢出———正溢出或副溢出 9 | #### 访问条件码 10 | 条件码通常不会直接读取,常用的使用方法有三种: 11 | 1. 可以根据条件码的某种组合将一个字节设置为0或者1 12 | 2. 可以条件跳转到程序的某个其他部分 13 | 3. 可以有条件的传送数据 14 | 15 | #### 跳转指令 16 | [汇编跳转指令总结](https://blog.csdn.net/liujiayu2/article/details/50737518) 17 | 18 | #### 循环 19 | C语言提供了多种循环结构,即do-while、while、和for。汇编中没有相应的指令存在,可以用条件测试和跳转组合起来实现循环的效果。 20 | 21 | do-while循环 22 | ``` 23 | loop: 24 | body-statement 25 | t = test-expr; 26 | if (t): 27 | goto loop; 28 | ``` 29 | 30 | while循环 31 | ``` 32 | goto test; 33 | loop: 34 | body-statement 35 | test: 36 | t = test-expr; 37 | if (t) 38 | goto loop; 39 | ``` 40 | 41 | ## 过程 42 | 程序栈由一系列栈帧构成,这些栈帧每一个都对应一个过程,而且每一个帧指针+4的位置都存储着函数的返回地址,每一个帧指针指向的存储器位置当中都备份着调用者的帧指针。每一个栈帧都建立在调用者的下方(也就是地址递减的方向),当被调用者执行完毕时,这一段栈帧会被释放。%ebp和%esp的值指示着栈帧的两端,而栈指针会在运行时移动,所以大部分时候,在访问存储器的时候会基于帧指针访问,因为在一直移动的栈指针无法根据偏移量准确的定位一个存储器位置。 43 | 44 | 由于栈帧是向地址递减的方向延伸,因此如果我们将栈指针减去一定的值,就相当于给栈帧分配了一定空间的内存。因为在栈指针向下移动以后(也就是变小了),帧指针和栈指针中间的区域会变长,这就是给栈帧分配了更多的内存。相反,如果将栈指针加上一定的值,也就是向上移动,那么就相当于压缩了栈帧的长度,也就是说内存被释放了。需要注意的是,上面的一切内容,都基于一个前提,那就是帧指针在过程调用当中是不会移动的。 45 | 46 | ![](./_image/汇编-25.png) 47 | 48 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第九章:虚拟内存.md: -------------------------------------------------------------------------------- 1 | 虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。 2 | 3 | ## 虚拟内存提供了三个重要的能力 4 | 1. 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存 5 | 2. 它为每个进程提供了一致的地址空间,从而简化了内存管理 6 | 3. 它保护了每个进程的地址空间不被其他进程破坏 7 | ## 物理和虚拟地址 8 | 计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一地物理地址(Physical Address, PA)。第一个字节的地址为0,接下来的字节地址为1,再下一个为2,以此类推。给这种简单的结构,CPU访问内存的最自然的方式就是使用物理地址。这种方式称为物理寻址。 9 | 早期的PC使用物理地址,而且诸如数字信号处理器、嵌入式微控制器以及Cray超级计算机这样的系统仍然继续使用这种寻址方式。 10 | 使用虚拟寻址,CPU通过生成一个虚拟地址(Virtual Address, VA)来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。将一个虚拟地址转换为物理地址的任务叫做 地址翻译。就像异常处理一样,地址翻译需要CPU硬件和操作系统之间的紧密合作。CPU芯片上叫做内存管理单元(Memory Management Unit, MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。 11 | 12 | ## 虚拟内存作为缓存的工具 13 | 概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一地虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其它缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。每个虚拟页的大小为 字节。类似地,物理内存被分割为物理页(Physical Page,PP),大小也为P字节(物理页也被称为页帧)。 14 | 一个地址空间的大小由表示最大地址所需要的位数来描述的。例如,一个包含N = 个地址虚拟地址空间就叫做一个n位地址空间。现代系统通常支持32位或者64位虚拟地址空间。 15 | 在任意时刻,虚拟页面的集合都分为三个不想交的子集: 16 | ● 未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。 17 | ● 缓存的:当前已缓存在物理内存中的已分配页。 18 | ● 未缓存的: 未缓存在物理内存中的已分配页。 19 | 20 | ## DRAM缓存的组织结构 21 | 在存储层次结构中,DRAM缓存的位置对它的组织结构有很大的影响。回想一下,DRAM比SRAM要慢大约10倍,而磁盘要比DRAM慢大约100 000多倍。一次DRAM缓存中的不命中比起SRAM缓存中的不命中要昂贵的多,这是因为DRAM缓存不命中要由磁盘来服务,而SRAM缓存不命中通常是由基于DRAM的主存来服务的。而且,从磁盘的第一个扇区读取第一个字节的时间开销比起读这个扇区中连续的字节慢大约100 000倍。 22 | 因为大的不命中处罚和访问第一个字节的开销,虚拟页往往很大,通常是4KB~2MB。 23 | 24 | ## Linux虚拟内存区域 25 | 任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(例如,PID、指向用户栈的指针、可执行目标文件的名字,以及程序计数器)。 26 | 任务结构中的一个条目指向mm_struct,它描述了虚拟内存的当前状态。我们感兴趣的两个字段是pgd和mmap,其中pgd指向第一级页表(页全局目录)的基址,而mmap指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的区域。当内核运行这个进程时,就将pgd存放在CR3控制寄存器中。 27 | 一个具体区域结构包含下面的字段: 28 | * vm_start: 指向这个区域的起始处。 29 | * vm_end: 指向这个区域的结束处。 30 | * vm_prot: 描述这个区域内包含的所有页的读写许可权限。 31 | * vm_flags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的(还描述了其他一些信息)。 32 | * vm_next: 指向链表中的下一个区域结构。 33 | 34 | ## 内存映射 35 | Linux通过将虚拟内存区域与磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射。 36 | 37 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第二章:信息的表示和处理(一).md: -------------------------------------------------------------------------------- 1 | ## has表为什么速度快? 2 | hash表比红黑树之类的都要快。因为hash表内部本质上是使用了数组。数组为什么最快?因为我们知道数组的起始地址以及某个元素的序号,就可以得到该元素在内存中的地址,而对于内存,访问任意一个地址,访问时间是相同的。而类似链表、树等机构,只能遍历。 3 | ## [虚拟内存](https://zh.wikipedia.org/wiki/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98) 4 | 虚拟内存使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分割成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。 5 | 虚拟内存将主存看成一个磁盘的高速缓存,主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。从概念上来说,虚拟内存被组织成为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组,也就是字节数组。每个字节都有一个唯一的虚拟地址作为数组的索引。虚拟内存的地址和磁盘的地址之间建立映射关系。磁盘上活动的数组内容被缓存在主存中。在存储器层次结构中,磁盘的数据被分割成块,这些块作为和主存之间的存储单元。主存作为虚拟内存的缓存。 6 | 7 | ## 寻址空间和寻址能力 8 | 寻址空间一般指CPU对于内存寻址的能力,就是能最多用到多少内存。CPU最大能查找多大范围的地址叫寻址能力,CPU的寻址能力以字节为单位。通常,内存容量越大,处理数据的能力就越强,但内存容量不能无限大,要受到系统结构、硬件设计、制造成本等多方面因素的制约,一个最直接的因素取决于系统地址总线和地址寄存器的宽度(位数),这个地址总线宽度和寄存器位数一般是相同的,这个宽度(位数)就是我们所说的32位、64位。32位CPU寻址能力为2的32次方,64位同理。由此可以推出:地址总线为N位的CPU寻址范围是2的N次方字节。 9 | ## 为什么是2的N次方,而不是其他数的N次方? 10 | 因为计算机采用二进制计算,所有物理元件只有0,1两种状态。假设1根地址总线连接了a、b,那么当地址线上的电压是高电压时读a,低电压读b。那么2根地址线就可以对4个存储单元寻址,对应的电压可以是:低低,低高,高低,高高,而3根地址线就可以对8个存储单元进行寻址,对应的组合为:111,110,100,000,101,100,001,011。以此类推。 11 | 12 | ## 32位和64位 13 | 首先要明确:**既有32位和64位的CPU,又有32位和64位的操作系统,又有32位和64位的软件!** 14 | - 设计初衷不同。64位操作系统的设计初衷是满足机械设计和分析、三维动画、视频编辑和创作,以及科学计算和高性能计算应用程序等领域中需要大量内存和浮点性能的客户需求。也就是说是给专业人员使用的。而32位操作系统是为普通用户设计的。 15 | - 要求配置不同。64位操作系统只能安装在64位电脑上(64位CPU),同时要安装64位软件才能发挥64位的最佳性能。这三者缺一不可。而32位操作系统可以安装在32位电脑(32位CPU)或64位电脑(64位CPU)。但是32位操作系统安装在64位电脑上并不能发挥64位的优势,浪费了64位CPU这个硬件优势。 16 | - 寻址能力不同。32位最大支持4G,64位最大支持大约16E。 17 | - 软件支持不同。64位系统可以运行32位软件和64位软件。32位系统只能运行32位软件,并且只能安装在32位CPU上。目前64位软件比32位少的多。 18 | 19 | ## x86 20 | x86指的是一种cpu架构,因为intel的8086,286,386-586而得名,amd开发的大部分cpu也是基于x86架构。x86架构的特点是cpu的寄存器是32位的,基于32位cpu开发的操作系统就叫32位操作系统,32位操作系统也通常被成为x86系统。 X86架构采用CISC(复杂指令集),而ARM采用RISC(精简指令集)。ARM处理器非常适用于移动通信领域,具有低成本、高性能和地电耗的特性。 21 | 22 | ## 为什么没有128位甚至更多位的处理器呢? 23 | 处理器的位数是用通用寄存器的宽度来定义的。常规的整数运算是用不到这么大的寄存器的,真正的大数运算也不会用这种方式实现,而现代的32位、64位处理器中,更重要的浮点运算所使用的浮点计算器早已经是128位甚至256位了。寻址能力64位就已经用不完了。使用大地址空间还会给精简指令处理器带来性能问题。而且在硬件设计上,需要更高的封装工艺,当然价格也更贵。总结来说就是性价比太低! 24 | 当然在某些专业领域,128位处理器已经有应用。比如说ps2。虽然在近几年不会在民用领域应用,不过随着需求的逐步提升和成本的下降,128位的处理器和系统有望为我们带来多的应用场景和更强的性能。 25 | ## 2.1练习题 26 | 8421 27 | ##### A.将 0x39A7F8转换为二进制 28 | 001110011010011111111000 29 | ##### B.将二进制1100100101111011转换为十六进制 30 | C97B 31 | ##### C.将0xD5E4C转换为二进制 32 | 11010101111001001100 33 | ##### D.将二进制10 0110 1110 0111 1011 0101转换为十六进制 34 | 0x26E7B5 35 | 36 | ## 练习题2.2 37 | 100000000000 38 | 001000000000 39 | 40 | | n | 2^n(十进制) | 2^n(十六进制) | 41 | |:-------:|:-------|:-------| 42 | |9|512|0x200| 43 | |19|524288|0x80000| 44 | |14|16384|0x4000| 45 | |16|65536|0x10000| 46 | |17|131072|0x20000| 47 | |5|32|0x20| 48 | |7|128|0x80| 49 | 50 | 练习题真无聊啊! 51 | 52 | ## 寻址和字节顺序 53 | 0x123456 54 | 小端法:在内存中按照从最低有效字节到最高有效字节的顺序存储对象。即低地址对应低字节(倒序) 55 | 56 | | 低地址 | -------> | 高地址 | 57 | |:-------:|:-------|:-------| 58 | |0x56|...|0x12| 59 | 60 | 大端法:在内存中按照从最高有效字节到最低有效字节的顺序存储对象。即低地址对应高字节(正序) 61 | 62 | | 低地址 | -------> | 高地址 | 63 | |:-------:|:-------|:-------| 64 | |0x12|...|0x56| 65 | 66 | 一个判断大小端的方法,原理就是int是4个字节,强转为char单字节,b指向a的起始字节。 67 | ``` 68 | // 判断是大端还是小端 69 | void endian() 70 | { 71 | int a = 0x1122; 72 | char b = *(char *)&a; 73 | if (b == 0x11) { 74 | printf("大端"); 75 | } else 76 | { 77 | printf("小端"); 78 | } 79 | } 80 | ``` 81 | 82 | **!生成一张ASCII表** 83 | ``` 84 | man ascii 85 | ``` 86 | 87 | 不同的机器类型使用不同的且不兼容的指令和编码方法。即使是完全一样的进程,运行在不同的操作系统上也会有不同的编码规则,因此二进制代码是不兼容的。二进制代码很少能在不同机器和操作系统组合之间移植。 88 | 89 | ## [Unicode](https://zh.wikipedia.org/wiki/Unicode) 90 | 简单点说,Unicode是为了解决传统的字符编码方案的局限而产生的。它对世界上大部分的文字系统进行了整理、编码,使得计算机可以用更为简单的方式来呈现和处理文字。 91 | 92 | [字符编码笔记:ASCII,Unicode 和 UTF-8](http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html) 93 | 94 | ## 逻辑左移、算术左移、逻辑右移、算术右移 95 | 逻辑左移=算术左移=右边补0 96 | 97 | 比如00101011 98 | 99 | 逻辑左移一位:01010110 100 | 101 | 算术左移一位:01010110 102 | 103 | 逻辑右移=左边补0 104 | 105 | 算术右移要看符号位,移动的符号位是啥,就补啥 106 | 107 | 比如00101011 108 | 109 | 逻辑右移一位:00010101 110 | 111 | 算术右移一位:10010101 112 | 113 | 几乎所有的编译器/机器组合都对有符号数使用算术右移。对于无符号数,右移必须是逻辑的。 114 | 115 | **加减法的优先级比移位运算符要高,拿不准的话记得加括号** 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第二章:信息的表示和处理(二).md: -------------------------------------------------------------------------------- 1 | ## 有符号数和无符号数 2 | 有符号数,顾名思义就是有正负,可以表示特定类型规定范围内的整数(包括负数),而无符号数只能表示非负数(0和正数) 3 | 4 | 若将有符号数转换为二进制,则其数值类型允许的最左一位用于表示符号(1为负数,0为正数和0),但在无符号数中,最左一位与其右各位一样用于表示数值。所以有符号数二进制是不对称的 5 | 6 | ## 原码、反码、补码 7 | 原码是指将最高位作为符号位(0表示正,1表示负),其他数字位代表数值本身的绝对值的数字表示方式。 8 | 例如在8位机器上,数字6的原码为0 000 0110,而-6的原码为1 000 0110 9 | 10 | 反码:如果是正数,则表示方法和原码一样,如果是负数,符号位不变,其余各位取反。 11 | 例如在8位机器上,数字6的反码为0 000 0110,而-6的f反码为1 111 1001 12 | 13 | 补码:如果是正数,则表示方法和原码一样,如果是负数,则将数字的反码加上1(相当于将原码数值位取反然后在最低位加1) 14 | 例如在8位机器上,数字6的补码为0 000 0110,而-6的补码为1 111 1010 15 | 16 | 0的反码、补码都是0 17 | 18 | 根据补码求原码,把补码当成原码,再求一次补码即可。 19 | 20 | ## 有符号数和无符号数的转换 21 | 对于大多数C语言的实现,处理同样字长的有符号数和无符号数之间相互转换的一般规则是:**数值可能会改变,但是位模式不变**。 22 | 23 | 当数据类型转换时,同时需要在不同数据大小以及无符号数和有符号数之间转换。C语言标准要求先进行数据大小的转换,之后再进行无符号和有符号之间的转换。 24 | 25 | 比如: 26 | ``` 27 | short int v = -12345; 28 | unsigned short uv = (unsigned short)v; 29 | printf("v = %d, uv = %u\n”, u, uv); 30 | ``` 31 | 输出 32 | ``` 33 | v = -12345, uv = 53191 34 | ``` 35 | 由于-12345的16位补码表示与53191的16位无符号表示是完全一样的,所以会得到以上输出。 36 | 无符号数和有符号数之间的转换是一一对应的关系,w位的有符号数s转换无符号数u的对应关系为: 37 | 如4位有符号数7(0111)转换为无符号数也是7,而4位有符号数-1(1111)转换为无符号数是15。 38 | 类似地,w位的无符号数u转换为有符号数s的对应关系为: 39 | 如4位无符号数5(0101)转换为无符号数也是5,而4位无符号数13(1101)转换为无符号数为-3。 40 | 其实只要知道无符号数和有符号数对二进制位的解释方式,无需记住上述的对应关系,也能算出转换后的值。 41 | 42 | ## 浮点数 43 | [看完这篇文章,你肯定理解什么是浮点数了!](https://mp.weixin.qq.com/s/34Fg9GSqRDoBbGFPzOt_ow) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第八章:异常控制流.md: -------------------------------------------------------------------------------- 1 | ## 异常 2 | 3 | #### 异常表 4 | 系统对每种可能出现的异常都分配了一个唯一的非负整数异常号,前一部分由处理器设计师分配,如零除,缺页,内存访问违例,其他号码由操作系统内核设计师分配,如系统调用和外部I/O信号 5 | 异常表中的表目k就对应着异常k的处理程序代码的地址,异常表的起始地址放在一个叫做异常表基址寄存器的特殊寄存器中 6 | 当处理器检测到事件的发生,并确定事件发生对应的异常号后,处理器通过执行间接过程调用,触发异常,并通过异常表上的对应程序进行处理 7 | 8 | #### 异常和过程调用的区别 9 | 异常处理程序运行在内核模式下,因此对系统资源有完全的访问权限 10 | 处理程序如果从用户程序转移到内核,则处理程序将被压入内核栈中,而不是用户栈中 11 | 处理器会把一些额外的处理器状态压入栈中,当重新执行程序时需要这些状态 12 | 过程调用在跳转到处理程序之前,将返回地址压入栈中,异常处理则根据类型,返回地址要么是当前指令,要么是下一条指令 13 | 14 | #### 分类 15 | 1. 中断:一般是由硬件引起的中断,为异步发生。当产生中断后,标记中断位,然后cpu响应中断,然后根据中断向量表,调用响应的中断处理程序处理中断。 16 | 2. 陷阱:为同步异常,当应用程序需要调用系统级函数的时候,就产生自陷异常。从用户层调入系统层,从而执行系统函数。陷阱的作用是在用户程序和内核之间提供一个象过程一样的接口。陷阱返回到下一条指令。 17 | 3. 故障:为同步异常。其是由错误引起的异常,不过其错误是可以被修复的。如缺页。 18 | 4. 终止:为同步异常。其是由不可恢复的致命错误造成的。典型的为一些硬件错误, 19 | 20 | ## 进程 21 | 进程的定义就是一个执行中的程序实例,通过进程能给我们提供一个假象,好像当前程序是系统中唯一运行的程序一样,好像该程序能独占处理器和内存。但其实每个程序都运行在某个进程的上下文中(上下文包括程序代码和数据,栈、通用目的寄存器的内容,程序计数器,环境变量等) 22 | 23 | 例如当在shell中输入./hello时,shell就会创建一个新的进程,并在该进程的上下文中执行它 24 | 25 | 进程为应用程序提供了关键两点抽象 26 | 27 | 独立的逻辑控制流:程序独占处理器的假象 28 | 29 | 私有的地址空间:程序独占内存系统的假象 30 | 31 | ## 进程控制 32 | 33 | Unix提供了很多从C程序中操作进程的系统调用,比如比较简单的 pid_t getpid(void) 和 pid_t getppid(void)获取当前执行进程的pid和其父进程的pid。pid_t是pid的类型,在Linux系统中在 sys/types.h 定义为int。这两个函数包含在 unistd.h 中。 34 | 35 | 创建和终止进程 36 | 首先要了解进程的状态,从程序员的角度看,我们可以认为进程总是处于下面三种状态之一: 37 | 38 | 运行。进程要么在CPU上执行,要么在等待执行且最终会被内核调度 39 | 停止。进程的执行被挂起(suspend),且不会被调度。直到它收到一个 SIGCONT 信号会再次开始执行 40 | 终止。进程永远地停止了。进程终止有三种原因:收到终止进程的信号;从主程序返沪;调用了exit函数 41 | 从操作系统的角度看会有更多的状态,但是在程序员的角度看,只需要了解这三个状态就可以了。 42 | 43 | 创建一个进程和终止一个进程都是用系统调用函数,即fork和exit,其函数声明如下: 44 | ``` 45 | void exit(int status); 46 | pid_t fork(void); 47 | ``` 48 | 其中exit声明在 stdlib.h, 而fork声明在 unistd.h 中。 49 | 新创建的进程几乎(但是不完全)和父进程相同。子进程得到父进程虚拟地址空间相同的一份拷贝,包括文本,数据和bss段、堆以及用户栈;子进程还会获得与父进程打开的文件描述符相同的拷贝,也就是说子进程可以读写父进程打开的任何文件。理解这部分是很重要的,否则对于多进程的执行会难以理解。由于子进程几乎就是对父进程的一个复制,所以他们的PC(程序计数器)都是指向同样的虚拟地址,这在后面的示例程序中很重要。父子进程之间最大的区别只是它们的PID不同。 50 | 51 | fork 函数非常不同的一点是它只被调用一次,却会返回两次,一次是返回子进程的PID给父进程,一次是在新创建的子进程中返回0。在我们的程序中使用这个不同的PID(肯定是一个0一个非0)判断当前是父进程还是子进程。这和其他高级语言的并行程序设计很不一样(不过像Java等一般是多线程,很少用多进程)。看下面一个我修改过的经典示例: 52 | ``` 53 | #include 54 | #include 55 | 56 | void exit(int); // 函数声明,防止vim保存时报错 57 | 58 | int main(void) { 59 | int a[] = {1,2,3,4}; 60 | pid_t pid = fork(); 61 | // 子进程和父进程都会执行之后的代码 62 | if (pid == 0) { 63 | a[0] = 11; 64 | a[1] = 22; 65 | printf("fork pid is %5d, a is %d, %d, %d, %d\n", pid, a[0], a[1], a[2], a[3]); 66 | //exit(0); // if no exit here, it will go-on to the rest lines of(15+) of main 67 | return 0; 68 | } 69 | a[0] = 111; 70 | a[1] = 222; 71 | a[3] = 444; 72 | printf("main pid is %5d, a is %d, %d, %d, %d\n", pid, a[0], a[1], a[2], a[3]); 73 | exit(0); 74 | } 75 | ``` 76 | 可以在一个Linux系统编译运行这段代码,尝试取消注释和注释子进程中的 exit(0) 调用看看前后的区别。 77 | 78 | 通过上面的示例,我们知道子进程是由一份独立的拷贝,实际上操作系统课程也讲到过,进程之间使用的是独立的地址空间,相互不会影响,所以也不难理解了。不过虽然数据是独立的,但是父子进程共享打开的文件,对同一个文件资源的操作还是反映在同一个文件上的。 79 | 80 | #### 回收子进程 81 | 进程终止时,不会立即被内核从系统中清除,将保持在一种已经终止的状态,这种终止但是没有被回收的进程称为僵死进程,这种进程虽然终止,但是依旧会消耗内存资源 82 | 83 | 当父进程回收僵死进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程 84 | 85 | 系统驱动时就会启动一个由内核创建的init进程,它是无敌进程,不会终止,而且是所有进程的祖先,其PID为1 86 | 87 | 如果父进程终止,内核将安排init进程成为被父进程抛弃的孤儿进程的养父,如果父进程的底下存在僵死的子进程没有被回收,内核就安排init进程去回收他们## 信号 88 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第六章:存储器层次结构.md: -------------------------------------------------------------------------------- 1 | 存储器系统是一个具有不同容量、成本和访问时间的存储设备的层次机构。CPU寄存器保存着最常用的数据。靠近CPU的小的、快速的高速缓存存储器作为一部分存储在相对慢速的主存储器中数据和指令的缓冲区域。主存缓存存储在容量较大的、慢速磁盘上的数据,而这些磁盘常常又作为存储在通过网络连接的其他机器的磁盘或磁带上的数据的缓冲区域。 2 | 存储器层次机构是可行的,这是因为与下一个更低层次的存储设别相比来说,一个编写良好的程序更倾向于更频繁的访问某一个层次上的存储设备。所以,下一层的存储设备可以更慢速一点,也因此可以更大,每个比特更便宜。整体效果是一个大的存储器池,其成本与层次结构底层最便宜的存储设别相当,但是却以接近于层次结构顶部存储设备的高速率向程序提供数据。 3 | 4 | ## 存储技术 5 | #### 静态 RAM 6 | SRAM:存储器单元具有双稳态特性,只要有电就会永远保持它的值,干扰消除时,电路就会恢复到稳定值 7 | #### 动态DRAM 8 | DRAM每一位的存储是对一个电容的充电,当电容的电压被扰乱之后,就永远不会恢复 9 | 10 | DRAM存储不稳定的应对机制: 11 | - 存储器系统必须周期性地通过读出,或者重写来刷新存储器的每一位 12 | - 使用纠错码 13 | 14 | #### SRAM和DRAM的区别 15 | 1. 只要有电,SRAM就会保持不变,而DRAM需要不断刷新 16 | 2. SRAM的存取比DRAM快 17 | 3. SRAM对光和电噪声等干扰不敏感 18 | 4. SRAM比DRAM需要使用更多的晶体管,所以更昂贵 19 | 20 | DRAM组织成二维阵列降低芯片上地址引脚的数量,但增加了访问时间,DRAM芯片包装在存储器模块中插入到主板的扩展槽上 21 | 22 | 存储器模块分类: 23 | 1. 168个引脚的双列直插存储器模块,以64位为块传送数据 24 | 2. 72个引脚的单列直插存储器模块,以32位为块传送数据 25 | #### 访问主存 26 | - 读事务:从主存传送数据到CPU 27 | - 写事务:从CPU传送数据到主存 28 | - 总线:一组并行的导线,能携带地址、数据和控制信号(两个以上的设备也能共享同一根总线) 29 | #### 磁盘存储 30 | 1. 磁盘容量 31 | - 记录密度:磁道一英寸的段中可以放入的位数 32 | - 磁道密度:从盘片中心出发半径上一英寸的段内可以有的磁道数 33 | - 面密度:记录密度与磁道密度的乘积 34 | - 记录区:柱面的集合被分割成不相交的子集合(多区记录技术) 35 | #### 固态硬盘 36 | 一个SSD包由一个或多个闪存芯片和闪存翻译层组成 37 | 随机读和写的性能差别是由底层闪存基本属性决定的 38 | 39 | 读写很慢的原因: 40 | - 擦除块需要相对较长的时间 41 | - 写操作试图修改一个包含已经有数据的页p,那么这个二块中的所有带有用数据的页都必须被拷贝到一个新(擦除过的)块,然后才能进行对页p的写 42 | 43 | 优点:随机访问时间比旋转磁盘块,能耗更低,更结实。 44 | 缺点:闪存翻译层中的平均磨损逻辑试图通过将擦除平均分布在所有的块上来最大化每个块的寿命 45 | 46 | ## 存储器层次结构 47 | 48 | ![](./_image/20150127111638009.png) 49 | 50 | - 高速缓存:是一个小而快速地存储设备,它作为存储在更大、也更慢的设备中的数据对象的缓冲区域cash 51 | - 缓存:使用高速缓存的过程cashing 52 | - 块:第k+1层的存储器被划分成连续的对象片,每个块有一个唯一的地址或名字,使之区别于其他的块 53 | - 传送单元:数据总是以块大小为传送单元 54 | #### 缓存命中 55 | 当程序需要第k+1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d,如果d刚好缓存在第k层中,那么就是我们说的缓存命中。 56 | 57 | #### 缓存不命中 58 | 若第K层中没有缓存数据对象D,那么就是我们所说的缓存不命中 59 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第十一章:网络编程.md: -------------------------------------------------------------------------------- 1 | ## 客户端-服务器编程模型 2 | 每个网络应用都是基于客户端-服务器模型的。 3 | 4 | 客户端-服务器模型中的基本操作是事务。一个客户端-服务器事务由以下四步组成: 5 | 6 | 1. 当一个客户端需要服务时,它向服务器发送一个请求,发起一个事务。 7 | 2. 服务器收到请求后,解释它,并以适当的方式操作它的资源。 8 | 3. 服务器给客户端发送一个响应,并等待下一个请求。 9 | 4. 客户端收到响应并处理它 10 | ## 网络 11 | 客户端和服务器通常运行在不同的主机上,并通过计算机网络的硬件和软件资源来通信。 12 | 对主机而言,网络是一种I/O设备,是数据源和数据接收方 13 | 从物理上而言,网络是一个按照地理远近组成的层次系统。最低层是LAN(局域网),最流行的局域网技术是以太网。 14 | 15 | ## Socket 16 | 网络通信的端点。网络编程也常称为Socket编程。 17 | * 流式套接字:以IO流的形式来进行数据的传递, 双向通信的。也就TCP编程。 18 | * 数据报式套接字:以数据名的形式来进行数据的传递。也就UDP编程。 19 | 20 | ## 总结 21 | 每个网络应用都是基于客户端-服务端模型的。根据这个模型,一个应用是由一个服务器和一个或多个客户端组成的。服务器管理资源,以某种方式操作资源,为它的客户端提供服务。 22 | 23 | 从程序员的观点来看,我们可以把因特网看成是一个全球范围的主机集合,具有以下几个属性: 24 | 1. 每个因特网主机都有一个唯一的32位名字,称为它的IP地址 25 | 2. IP地址的集合被映射位一个因特网域名的集合 26 | 3. 不同因特网主机上的进程能够通过连接互相通信 27 | 28 | Web服务器使用HTTP协议和它们的客户端彼此通信。浏览器向服务器请求静态或者动态的内容。对静态内容的请求是通过从服务器磁盘取得文件并把它 返回给客户端来服务的。对动态内容的请求是通过在服务器上一个子进程的上下文运行一个程序并将它的输出返回给客户端来服务的。 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第十二章:并发编程.md: -------------------------------------------------------------------------------- 1 | 如果逻辑控制流在时间上重叠,那么它们就是`并发的`。 2 | 3 | * 访问慢速 I/O 设备。当一个应用正在等待来自慢速I/O设备(如磁盘)的数据到达时,内核会运行其他进程,使CPU保持繁忙。 4 | * 与人交互。和计算机交互的人要求计算机有同时执行多个任务的能力。如,我们在打印一个文档时,可能想要调整一个窗口的大小。 5 | * 通过推迟工作以降低延迟。有时,应用程序能够通过推迟其他操作和并发地执行它们,利用并发来降低某些操作的延迟。例如,一个动态内存分配器可以通过推迟合并,把它放到一个运行在较低优先级上的并发“合并”流中,在有空闲的CPU周期时充分利用这些空闲周期,从而降低单个free操作的延迟。 6 | * 服务多个网络客户端。创建一个并发服务器,它为每个客户创建一个单独的逻辑流。允许服务器同时为多个客户端服务,也避免了慢速客户端独占服务器。 7 | * 在多核机器上进行并行计算。多核处理器包含多个CPU,可以同时处理多个应用程序 8 | * 进程。进程有独立的虚拟地址空间,想要和其他流通信,控制流必须使用某种显式地进程间通信机制。 9 | * I/O 多路复用。应用程序在一个进程地上下文中显式地调度它们自己地逻辑流。逻辑流被模型化为状态机,数据到达文件描述符后,主程序显式地从一个状态转换到另一个状态。因为程序是单独地进程,所有所有地流都共享同一个地址空间。 10 | * 线程。线程是运行在一个单一进程上下文中地逻辑流,由内核进行调度。 11 | 12 | ## 基于进程的并发编程 13 | 基于进程的并发echo服务器,父进程派生一个子进程来处理每个新的连接请求 14 | ``` 15 | #include"csapp.h" 16 | void echo(int); 17 | void sigchld_handler(int sig) 18 | { 19 | while(waitpid(-1,0,WNOHANG)>0) 20 | ; 21 | return; 22 | } 23 | int main(int argc,char **argv) 24 | { 25 | int listenfd,connfd; 26 | socklen_t clientlen; 27 | struct sockaddr_storage clientaddr; 28 | 29 | if(argc != 2) 30 | { 31 | fprintf(stderr,"usage:%s\n",argv[0]);//将信息错误写入strerr中 32 | exit(0); 33 | } 34 | signal(SIGCHLD,sigchld_handler);//当信号来时唤醒,回收僵死子进程 35 | listenfd = open_listenfd(argv[1]);//监听客户端连接请求 36 | while(1){ 37 | clientlen=sizeof(struct sockaddr_storage); 38 | connfd=accept(listenfd,(SA *)&clientaddr,&clientlen);//接受客户端连接请求 39 | if(fork() == 0)//子进程 40 | { 41 | close(listenfd);//子进程关闭监听描述符 42 | echo(connfd); 43 | close(connfd);//子进程关闭已连接的描述符 44 | exit(0); 45 | } 46 | close(connfd);//父进程关闭已连接的描述符 47 | } 48 | } 49 | void echo(int connfd)//反复读写文本行,直到读到EOF 50 | { 51 | size_t n; 52 | char buf[MAXLINE]; 53 | rio_t rio; 54 | 55 | rio_readinitb(&rio,connfd);//初始化函数 56 | while((n=rio_readlineb(&rio,buf,MAXLINE))!=0){ 57 | printf("server received %d bytes\n",(int)n); 58 | rio_writen(connfd,buf,n); 59 | } 60 | ``` 61 | 说明: 62 | 63 | 1. 通过用signal函数回收僵死子进程的资源 64 | 2. 父子进程关闭各自的connfd副本,避免内存泄漏 65 | 3. 因为套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止 66 | 67 | ## 基于I/O多路复用的并发编程 68 | 69 | I/O多路复用技术的基本思路是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。 70 | ``` 71 | #include 72 | int select(int n,fd_set *fdset,NULL,NULL);//返回已准备好的描述符的非零的个数,出错-1 73 | //处理描述符集合的宏 74 | FD_ZERO(fd_set *fdset);//清空fdset中的所有描述符 75 | FD_CLR(int fd,fd_set *fdset); 76 | FD_SET(int fd,fd_set *fdset);//将fd描述符加入fdset集合中 77 | FD_ISSET(int fd,fd_set *fdset);//查看哪个描述符准备好 78 | ``` 79 | 80 | ## 基于线程的并发编程 81 | 线程就是运行在进程上下文中的逻辑流。每个线程都有自己的线程上下文,包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条形码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。 82 | 83 | 基于线程的逻辑流结合了基于进程和基于I/O多路复用的流的特性。 84 | 85 | * 同进程一样,线程由内核自动调度,并且内核通过一个整数ID来识别线程。 86 | * 同基于I/O多路复用的流一样,多个线程运行在单一进程的上下文中,共享这个进程虚拟地址空间的所有内容,包括它的代码、数据、堆、共享库和打开的文件 87 | 88 | 89 | -------------------------------------------------------------------------------- /深入理解计算机系统(CSAPP)/第十章:系统级IO.md: -------------------------------------------------------------------------------- 1 | ## I/O 2 | 3 | 所有的I/O设备都被模型化为文件。 4 | 5 | 包括:磁盘,网络,终端。 6 | 7 | 磁盘是磁盘控制器连在主板上的,网络是通过网络适配器连在主板上的,终端是什么?这么想,想不通,想不通就不想,当作概念好了。 8 | 9 | * 打开文件——一个应用程序通过要求内核打开相应的文件。——文件操作总是内核做的。应用只是告诉内核做什么。——文件打开后,应用程序记录描述符,内核记录所有和文件相关的信息。 10 | 每个进程开始的时候都有3个打开的文件:0,1,2,入,出,错出。 11 | * 改变当前的文件位置——对于每个打开的文件,内核记录其所有相关的信息,其中一个是文件位置,初始为0。——从文件开头起始的字节偏移量。 12 | * 读文件——一个读操作就是从文件拷贝n个字节到存储器,从当前文件位置k开始,然后将k增加到k+n。 13 | 给定的一个大小为m字节的文件,当k>=m时,执行读操作会触发一个称为EOF的条件,应用程序能检测到这个条件。——EOF是一个条件,不是一个字节,不是一个位,在文件的结尾处什么都没有,也没有EOF符号,EOF是一个条件。 14 | * 写操作——从存储器拷贝n>0个字节到一个文件,从当前文件k开始,然后更新k。 15 | * 关闭文件——应用程序通知内核,让内核去关闭文件。内核会释放其记录的关于文件的所有。 16 | 17 | ## 打开和关闭文件 18 | 19 | 进程通过open函数来打开一个已存在的文件或者创建一个新文件。 20 | 21 | int open(char *filename, int flags, int mode); 22 | 23 | flag有:O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND。 24 | 25 | 每个进程都有一个 umask,这是上下文的一部分。 26 | 27 | open函数创建一个新文件时,文件的访问权限位被设置成mode & ~umask。 28 | 29 | ## 读和写文件 30 | 31 | 应用程序通过分别调用read和write函数来执行输入和输出。 32 | 33 | ssize_t read(int fd, void *buf, size_t n); 34 | ssize_t write(int fd, const void *buf, size_t n); 35 | read函数调用返回有三种情况: 36 | 37 | -1——错误。 38 | 0——调用的时候,触发了k>=m的EOF条件,应该这么说,调用的时候,当前位置k已经在文件结尾了。 39 | 实际传送的字节数——这里又有两种情况,一种是n,也就是实际传送了n个;另一种是小于n的,也就是实际没有传送到n那么多,导致这种情况的原因可能就是从当前位置到文件尾部之间的字节数没有n那么多了(在这种情况下,下一次再read,直接就返回0了)。 40 | 如果打开文件是与终端相关联的,那么每个read函数将一次传送一个文本行,返回的不足值对于文本行的大小。 41 | 42 | ## 用RIO包健壮地读写 43 | 44 | Robust I/O,RIO提供了两类不同的函数: 45 | 46 | 无缓冲的输入输出函数——这些函数直接在存储器和文件之间传送数据,没有应用级缓冲。当文件是网络时,尤其有用。 47 | 带缓冲的输入函数——文件的内容缓存在应用级缓冲区内。 48 | 无缓冲的函数: 49 | 50 | ssize_t rio_readn(int fd, void *usrbuf, size_t n); 51 | ssize_t rio_writen(int fd, void *usrbuf, size_t n); 52 | rio_readn比read好的地方在于:信号中断会重启。 53 | 54 | 带缓冲的输入函数(没有输出函数) 55 | 56 | 结构rio_t。 57 | 58 | void rio_readinitb(rio_t *rp, int fd); 59 | static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n); 60 | ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); 61 | ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); 62 | 63 | ## 读取文件元数据 64 | 65 | 文件的元数据,就是文件的相关信息。 66 | 67 | int stat(const char *filename, struct stat *buf); 68 | int fstat(int fd, struct stat *buf); 69 | 结构stat中,我们关心的,是st_mode和st_size。st_mode编码了文件访问许可位和文件类型。 70 | 71 | Unix识别大量不同的文件类型。 72 | 73 | 普通文件:文本文件和二进制文件。 74 | 目录文件 75 | 套接字 76 | 77 | ## 共享文件 78 | 79 | 内核用三个相关的数据结构来表示打开的文件:描述符表,文件表,v-node表。 80 | 81 | 描述符表是每个进程都有的。文件表和v-node表是所有进程共享的。 82 | 83 | 描述符表的每个条目是由文件描述符索引的,条目值指向文件表中的一项。 84 | 85 | 文件表的条目包含一个文件的当前位置,引用计数,以及一个指向v-node表中对应表项的指针。 86 | 87 | 也就是说,如果两个进程的描述符各自有一个条目指向文件表中的一个,那么这两个进程就共享了文件的当前位置这一属性。一个read了,当前位置变了,另一个读的时候,这种变化会体现出来。 88 | 89 | v-node表,包含了文件的元数据,也就是,stat结构的内容。 90 | 91 | 一次open,将在文件表中建立一个新的条目,将在描述符表中建立一个新的描述符指向这个文件表中的条目。 92 | 93 | 如果已open之后,进程fork了,那么父子进程都拥有相同的描述符内容,指向的也会是文件表中相同的条目,也就是共享了该文件的当前位置。 94 | 95 | 两次open同一个文件,虽然是同一个文件,但是有两个描述符条目,指向的也是文件表中的两个条目,虽然文件表中的两个条目指向v-node表中的一个条目,但是,文件表两个条目是关键。 96 | 97 | ## I/O重定向 98 | 99 | I/O重定向的一个方式是使用dup2函数。 100 | 101 | int dup2(int oldfd, int newfd); 102 | 103 | oldfd将覆盖newfd。也就是说newfd将指向和oldfd相同的文件表的某一项,oldfd保持不变。再简单点——后者变成和前者一样的了。 104 | 105 | ## 标准I/O 106 | 107 | ANSI C定义了一组高级输入输出函数,称为标准I/O库。 108 | 109 | 标准I/O库将一个打开的文件模型化为一个流。 110 | 111 | 对于程序员而言,一个流就是一个指向FILE类型的结构的指针。 112 | 113 | 类型为FILE的流是对文件描述符和流缓冲区的抽象。——这里,和前面的rio_t有点类似了,FILE将文件描述符和缓冲区合在了一起,而rio_t将文件描述符和缓冲区合在了一起,还是很类似的。 -------------------------------------------------------------------------------- /算法图解/_image/2821559619577_.pic_hd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzcdev/ReadingNotes/08c6be5fa81d092384f108f58bcc430c54410496/算法图解/_image/2821559619577_.pic_hd.jpg -------------------------------------------------------------------------------- /算法图解/广度优先搜索.md: -------------------------------------------------------------------------------- 1 | 图算法-广度优先搜索(BFS) 2 | 3 | 找最短距离(shorterst-path problem) 4 | 5 | 步骤: 6 | - 使用图来建立问题模型 7 | - 使用广度优先搜索解决问题 8 | 9 | 图由节点(node)和边(edge)组成 10 | 11 | 说到 BFS 必须提到队列,队列是一种先进先出(FIFO)的数据结构,而栈是一种后进先出(LIFO)的数据结构 12 | 13 | 14 | 伪代码如下: 15 | ``` 16 | from collections import deque 17 | 18 | def search(name): 19 | search_queue = deque() 20 | search_queue += graph[name] 21 | searched = [] 22 | while search_queue: 23 | person = search_queue.popleft() 24 | if person_is_seller(person): 25 | print(person + " is a mango seller!") 26 | return True 27 | else: 28 | search_queue += graph[person] 29 | return False 30 | 31 | search('you') 32 | ``` 33 | 34 | 广度优先搜索运行时间为O(V+E),V为顶点(vertice)数,E为边数 35 | 36 | 37 | ## 小结 38 | 39 | - 广度优先搜索指出是否有从到B的路径 40 | - 如果有,广度优先搜索将找出最短路径 41 | - 面临类似于寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来解决问题 42 | - 有向图的边为箭头,箭头的方向指定了关系的方向,例如,rama->adit表示rama欠adit钱 43 | - 无向图中的边不带箭头,其中的关系是双向的,例如,ross - rachel 表示 “ross 与 rachel约会,而rachel也与ross约会” 44 | - 队列是先进先出(FIFO)的 45 | - 栈是后进先出(LIFO)的 46 | - 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列 47 | - 对于检查过的人,务必不要再去检查,否则可能导致无限循环 48 | - 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /算法图解/快速排序.md: -------------------------------------------------------------------------------- 1 | ## D&C工作原理(divide and conquer)分而治之 2 | 1. 找出简单的基线条件 3 | 2. 确定如何缩小问题的规模,使其符合基线条件 4 | 求数组中数字的和 5 | 6 | 遍历 7 | ```Python 8 | def sum(arr): 9 | total = 0 10 | for x in arr: 11 | total += x 12 | return total 13 | ``` 14 | 15 | 递归 16 | ```Python 17 | def sum(arr): 18 | if arr == []: 19 | return 0 20 | return arr[0] + sum(arr[1:]) 21 | ``` 22 | 23 | ## 数组快速排序 24 | ```Python 25 | def quicksort(array): 26 | if len(array) < 2: 27 | return array 28 | else: 29 | pivot = array[0] 30 | less = [i for i in array[1:] if i <= pivot] 31 | greater = [i for i in array[1:] if i > pivot] 32 | return quicksort(less) + [pivot] + quicksort(greater) 33 | print(quicksort([10, 5, 2, 4])) 34 | ``` 35 | 36 | 37 | -------------------------------------------------------------------------------- /算法图解/散列表.md: -------------------------------------------------------------------------------- 1 | ## 散列函数 2 | - 散列函数总是将同样的输入映射到形同的索引 3 | - 散列函数将不同额输入映射到不同的索引 4 | - 散列函数知道数组有多大 5 | 6 | 散列函数+数组=散列表(hash table) 7 | 数组和链表都被直接映射到内存,但散列表更复杂,它使用散列函数来确定元素的存储位置。 8 | 9 | Python提供的散列表实现为字典,可以使用dict来创建散列表 10 | ```Python 11 | book = dict() 12 | book["apple"] = 0.67 13 | book["milk"] = 1.49 14 | book["avocado"] = 1.49 15 | print(book) 16 | print(book["avocado"]) 17 | ``` 18 | 19 | ## 散列表用于查找 20 | 比如创建电话薄 21 | - 添加联系人及电话号码(创建影射) 22 | - 通过输入联系人来获悉其电话号码(查找) 23 | ```Python 24 | phone_book = {} 25 | phone_book["jenny"] = 8675309 26 | phone_book["emergency"] = 911 27 | print(phone_book["jenny"]) 28 | ``` 29 | 网址映射到IP也是散列表的一种应用 30 | 31 | ## 防止重复 32 | key是唯一的 33 | ## 缓存 34 | 大型网站都用缓存,缓存的数据存储在散列表中 35 | 36 | 37 | 散列表适用于 38 | - 模拟映射关系 39 | - 防止重复 40 | - 缓存、记住数据,以免服务器再通过处理来生成它们 41 | 42 | ## 冲突 43 | 散列函数最好的情况是将不同的键映射到数组不同的位置。然而,如果两个键映射到了同一个位置,会产生冲突,解决办法就是在这个位置存储一个链表,如果链表很长,速度也会很慢。。 44 | - 散列函数很重要。最理想的情况是,散列函数将键均匀的映射到散列表的不同位置。 45 | - 如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很好,这些链表就不会很长 46 | 47 | ## 性能 48 | 散列表最快是o(1),最差是o(n) 49 | 50 | ## 总结 51 | 52 | - 结合散列函数和数组来创建散列表 53 | - 冲突很槽糕,应该使用可以最大限度减少冲突的散列函数 54 | - 散列表的查找、插入、删除速度都很快o(1) 55 | - 散列表适合用于模拟影射关系 56 | - 一旦填装因子超过0.7,就该调整散列表的长度 57 | - 散列表可用于缓存数据 58 | - 散列表非常适合用于防止重复 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /算法图解/算法简介.md: -------------------------------------------------------------------------------- 1 | ## 二分查找 2 | 每次拿中间那个数和目标数比较,每循环一次可以排除一半的数字。前提条件是列表必须有序。 3 | 4 | Example: 5 | ```python 6 | def binary_search(list, item): 7 | low = 0 8 | high = len(list) - 1 9 | 10 | while low <= high: 11 | mid = (low + high) // 2 12 | guess = list[mid] 13 | if guess == item: 14 | return mid 15 | if guess > item: 16 | high = mid - 1 17 | else: 18 | low = mid + 1 19 | return None 20 | 21 | 22 | my_list = [1,3, 5, 7, 9] 23 | print(binary_search(my_list, 5)) 24 | ``` 25 | 26 | ## 运行时间 27 | 大O表示法,指出了算法运行速度有多快,算法的运行时间是从增速的角度衡量的,大O表示法指出了最糟糕情况下的运行时间。 28 | 常见的大O时间: 29 | * O(log n),对数时间,这样的算法包括二分查找 30 | * O(n),也叫线性时间,这样的算法包括简单查找 31 | * O(n * log n),快速排序 32 | * O(n*n),选择排序 33 | * O(n!),旅行商问题解决方案 34 | 35 | ![](./_image/2821559619577_.pic_hd.jpg) 36 | -------------------------------------------------------------------------------- /算法图解/选择排序.md: -------------------------------------------------------------------------------- 1 | ## 数组和链表 2 | 数组的内存地址是连续的,链表是不连续的。数组查找更快,插入删除较慢,而链表插入删除很快,只需要修改前一个元素的地址即可。但是查找较慢 3 | 4 | 常见的数组和链表操作的运行时间: 5 | 6 | | | 数组 | 链表 | 7 | | :---: | :----: | :----: | 8 | | 读取 | O(1) | O(n) | 9 | | 插入 | O(n) | O(1)| 10 | | 删除 | O(n) | O(1)| 11 | 数组和链表哪个用的更多呢?这个当然要根据实际情况,但是实际上是数组用的多。因为链表支持随机访问,而数组是顺序访问,必须一个一个读取元素。 12 | 13 | ## 选择排序 14 | 选择排序的原理是首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。**时间复杂度为O(n^2)** 15 | 16 | 数组从小到大排列代码 17 | ```python 18 | # 找出数组中最小数的索引 19 | def findSmallest(arr): 20 | smallest = arr[0] 21 | smallest_index = 0 22 | for i in range(1, len(arr)): 23 | if arr[i] < smallest: 24 | smallest = arr[i] 25 | smallest_index = i 26 | return smallest_index 27 | 28 | def selectionSort(arr): 29 | newArr = [] 30 | for i in range(len(arr)): 31 | smallest = findSmallest(arr) 32 | newArr.append(arr.pop(smallest)) 33 | return newArr 34 | 35 | print(selectionSort([2, 4, 5, 1, 7])) 36 | ``` 37 | -------------------------------------------------------------------------------- /算法图解/递归.md: -------------------------------------------------------------------------------- 1 | 递归只是让解决方案更清晰,并没有性能上的优势。实际上,使用循环的性能更好。 2 | 3 | 每个递归函数都有两部分:基线条件和递归条件。递归条件是函数调用自己,而基线条件是函数不在调用自己,从而避免行程无限循环。 4 | 5 | 栈的两种操作:押入和删除 6 | 7 | ## 递归调用栈 8 | 计算阶乘的递归函数 9 | ```python 10 | def fact(x): 11 | if x == 1: 12 | return 1 13 | else: 14 | return x * fact(x-1) 15 | ``` 16 | 不断的压栈和出栈的过程。 17 | 18 | 存储详尽的信息可能会占用大量的内存,每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息,在这种情况下,有两种选择: 19 | - 重新编写代码,转而使用循环 20 | - 使用尾递归。 21 | 22 | -------------------------------------------------------------------------------- /计算机网络自顶向下方法/局域网.md: -------------------------------------------------------------------------------- 1 | ## 链路层寻址和RAP(地址解析协议) 2 | 主机和路由器具有链路层地址,同时也具有网络层地址。事实上,是主机和路由器的适配器(即网络接口)具有链路层地址。 3 | 4 | 链路层地址,也可以称为MAC地址。对于大多数局域网,MAC 地址长度为6字节,共有2的48次方个可能的地址。 5 | 6 | MAC地址的分配:IEEE机构负责分配前三个字节,后三个字节由设备厂商分配;MAC地址是平面结构,节点移动到任何网络,都使用相同的MAC地址;IP地址是层次结构,节点在不同网络使用不同的IP地址。 7 | 8 | MAC地址识别:在广播信道中,发送网卡将目的地址封装到帧中并发送,网络中所有其他网卡都会收到这个帧;接收网卡检查收到帧的目的地址与自己MAC地址是否匹配,若匹配则取出数据并上交给网络层,否则丢弃该帧。 9 | 10 | ## 以太网 11 | 12 | 前同步码:前7字节是10101010,最后一字节是10101011;使接收方和发送方的时钟同步,因为网卡速率是自适应的;最后一个字节,告知后续是帧。(前同步码不属于帧) 13 | 14 | 目的地址:接收方的MAC地址。接收方只接受目的地址与自己MAC地址相同的帧或目的地址为广播地址的帧,并将数据内容上交给网络层,其他情况丢弃该帧。 15 | 16 | 源地址:发送方网卡的MAC地址。 17 | 18 | 类型字段:以太网帧可为不同网络层协议(IP,ARP等)传送数据,使用该字段区分。 19 | 20 | 数据字段:存放网络层分组,若分组小于46字节,必须填充至46字节,超过1500必须分片。 21 | 22 | 循环冗余检测字段:检测帧的比特差错。 23 | 24 | 现代交换机是全双工的,这使得一台交换机和一个节点能够同时向对方发送帧而没有干扰。也就是说,在基于交换机的以太局域网中,没有碰撞,因此可以没必要再使用MAC协议 25 | 26 | ## 链路层交换机 27 | 28 | 交换机的任务是接收入链路层帧并将它们转发到出链路,并且交换机对于子网中的主机和路由器是透明的 29 | 交换机转发和过滤 30 | 31 | * 过滤:决定一个帧应该转发到某个接口还是应当将其丢弃 32 | * 转发:决定一个帧应该被导向哪个接口,并把该帧移动到那些接口 33 | 34 | 交换机的优点 35 | * 消除碰撞,没有因碰撞而浪费的带宽 36 | * 异质的链路,局域网中的不同链路能够以不同的速率(如100Mbps和1Gbps)并且能够在不同的媒体(如铜线和光纤)上运行 37 | * 管理,易于进行网络管理 38 | * 自学习的,是即插即用设备 39 | ## 链路虚拟化 40 | 链路就是网络中连接主机的物理线路 41 | 42 | 电话网:一个逻辑独立、全球性的电信网络,如,家庭用户通过电话网的拨号连接到因特网:连接两台主机的链路实际上是电话网;从因特网链路层角度,电话网可以被看成是一根简单的线路,即将电话网虚拟化,看成是为两台主机提供链路层连接的链路层技术。 43 | 44 | 45 | -------------------------------------------------------------------------------- /计算机网络自顶向下方法/应用层.md: -------------------------------------------------------------------------------- 1 | 研发网络应用程序的核心是写出能够运行在不同的端系统和通过网络彼此通信的程序。 2 | 3 | ## 应用层协议原理 4 | #### 两种体系结构: 5 | 竟然有两种,我一直只知道第一种。。。P2P是不需要服务器的嘛 6 | 7 | 1. 客户-服务器体系结构 8 | 2. P2P体系结构 9 | 10 | #### 进程通信 11 | 应用开发者对于运输层的控制权仅限于: 12 | 1. 选择运输层协议 13 | 2. 也许能设定几个运输层参数,如最大缓存和最大报文段长度 14 | 15 | #### 一个运输层协议能够为调用天它的应用程序提供什么样的服务呢? 16 | 1. 可靠数据传输 17 | 2. 吞吐量 18 | 3. 定时 19 | 4. 安全性 20 | 21 | #### TCP服务 22 | 1. 面向连接的服务 23 | 2. 可靠的数据传送服务 24 | TCP协议还具有拥塞控制机制 25 | #### UDP服务 26 | UDP是一种不提供不必要服务的轻量级运输协议。它仅提供最小服务。UDP是无连接的,因此在两个进程通信前没有握手过程。UDP协议提供一种不可靠传输数据传送服务。报文也可能是乱序到达的 27 | 28 | #### 应用层协议 29 | 应用层协议定义了运行在不同端系统上的应用程序进程如何相互传递报文。 30 | * 交换的报文类型,例如请求报文和响应报文 31 | * 各种报文类型的语法,如报文中的各个字段及这些字段是如何描述的 32 | * 字段的语义,即这些字段中的信息的含义 33 | * 确定一个进程何时以及如何发送报文,对报文进行响应的规则 34 | 35 | 应用层协议只是网络应用的一部分 36 | 37 | ## Web和HTTP 38 | HTTP服务器不保存关于客户的任何信息,所以说HTTP是一个无状态协议。 39 | 非持续连接:每个请求/响应对是经一个单独的TCP连接发送。 40 | 持续连接:所有请求及其响应经相同的TCP连接发送。 41 | HTTP可以采用非持续连接和持续连接,默认情况下使用持续连接,性能更好。 42 | HTTP使用非持续连接的缺点: 43 | * 需要为每一个请求的对象建立和维护一个全新的连接。对于每个这样的连接,在客户和服务器中都要分配TCP的缓冲区和保持TCP变量,这给Web服务器带来严重负担。 44 | * 每一个对象要经受两倍的RTT(往返时间,Round-Trip Time)交付延迟。 45 | 46 | Web缓存器可以理解为服务器的代理,它同样可以处理客户发送的HTTP请求报文,如果它缓存着请求报文所需对象的副本的话。如果没有,Web缓存器会先向本体Web服务器获取所需对象的副本,再提供服务给客户。 47 | 通过“条件GET方法”,保证缓存器的对象是最新的。 48 | 采用Web缓存器的好处: 49 | * 大大减少对客户请求的响应时间 50 | * 大大减少一个机构的接入链路到因特网的通信量。通过减少通信量,该机构就不必急于增加带宽,从而降低费用 51 | * 能从整体上大大减低因特网上的Web流量,从而改善所有应用的性能 52 | 53 | ## 电子邮件 54 | 由三部分组成: 55 | * 用户代理 56 | * 邮件服务器 57 | * 简单邮件传输协议(SMTP) 58 | 邮件如果发生故障,会在服务器中保持一个报文队列,并且30分钟后尝试重发,如果几天没有发送成功,则删除这个报文。。这种情况我貌似没有遇到过。 59 | 60 | SMTP也是使用TCP传输数据的,SMTP一般不使用中间服务器发送邮件,而是两个服务器直连的,不管距离多远。 61 | 62 | #### SMTP 和 HTTP 对比 63 | * HTTP是一个拉协议,SMTP是一个推协议 64 | * SMTP 要求每个报文采用7比特 ASCII 码格式, HTTP 不受这个限制 65 | * 既包含文本又包含图形,HTTP 会把每个对象封装到它自己的 HTTP 响应报文中,而 SMTP 则把所有报文对象放在一个报文之中 66 | 67 | ## DNS:因特网的目录服务 68 | 69 | 识别主机: 70 | * 主机名(比如:www.facebook.com、www.google.com),这里的主机名就是域名? 71 | * IP 地址(比如:121.7.106.83),由4个字节组成 72 | 73 | DNS(域名系统),主机名到 IP 地址的转换。 74 | * 一个由分层的 DNS 服务器实现的分布式数据库 75 | * 一个使得主机能够查询分布式数据库的引用层协议 76 | 77 | 除了进行主机名到 IP 地址的转换, DNS 还提供了一些重要服务 78 | * 主机别名(这里是指二级域名或者多级域名?) 79 | * 邮件服务器别名 80 | * 负载分配 81 | 82 | 83 | #### 概述 84 | * 一个由分层的DNS服务器实现的分布式数据库 85 | * DNS协议运行在UDP上,使用53号端口。 86 | * 根DNS服务器:在因特网上有13个根DNS服务器 87 | * 顶级域DNS服务器:负责顶级域名如com、org、net等 88 | * 权威DNS服务器:一个组织机构的权威DNS服务器收藏了公共可访问的DNS记录。 89 | * 本地DNS服务器:本地DNS服务器严格说来并不属于服务器的层次结构,但它对DNS层次结构是重要的。 90 | 91 | #### DNS缓存 92 | * 为了改善时延性能并减少在因特网上到处传输的DNS报文数量 93 | * 共同实现DNS分布式数据库的所有DNS服务器存储了资源记录(RR),RR提供了主机名到IP地址的映射。 94 | * RR包含了4元祖(Name,Value,Type,TTL) 95 | 96 | ## P2P 97 | 文件分发: 98 | * 体系结构扩展 99 | * BitTorrent 100 | 在 P2P 区域中搜索信息: 101 | * 集中式索引 102 | * 查询泛洪 103 | * 层次覆盖: 104 | * 超级对等方维护索引 105 | * 超级对等方可以向相邻的超级对等方查询泛洪,范围受限查询泛洪 106 | Skype 的 P2P因特网电话本质是 P2P的 107 | 108 | ## 视频流和内容分发网 109 | DASH:经 HTTP 的动态适应性流,可以根据客户的带宽来选择流的速率 110 | 111 | 内容分发网也叫 CDN,分布式?通常采用两种不同的服务器安置原则: 112 | * 深入 113 | * 邀请做客 114 | 115 | ## 套接字编程 116 | * UDP 套接字编程 117 | * TCP 套接字编程 -------------------------------------------------------------------------------- /计算机网络自顶向下方法/网络层.md: -------------------------------------------------------------------------------- 1 | 网络层三个主要组件: IP协议, 选路组件, 报告数据包中的差错和对某些网络层信息请求进行相应的设施(ICMP) 2 | 3 | 网络层的两个主要功能: 4 | 5 | 1. 转发: 当一个分组到达某路由器的一条输入链路时, 该路由器必须将该分组移动到合适的输出链路 6 | 2. 路由: 当分组从发送方流入接收方时, 网络层必须决定分组所采用的路由器路径.(路由算法) 7 | 8 | 9 | 虚电路网络 10 | 11 | * 虚电路建立 12 | * 数据传送 13 | * 虚电路拆除 14 | ## 路由器工作原理 15 | 16 | * 输入端口(物理层->数据链路层->查找与转发(缓存管理)) 17 | * 输出端口((缓存管理)查找转发->数据链路层->物理层) 18 | * 交换结构 19 | * 选路处理器(选路协议) 20 | ## 动态主机配置协议(DHCP)/NAT/ICMP 21 | 22 | * DHCP服务器发现 23 | * DHCP服务器提供 24 | * DHCP请求 25 | * DHCP ACK 26 | 27 | NAT路由器上有一张NAT转换表 28 | 29 | 一个具有专有地址的地域, 在向外界发送分组时, 基本自身的IP和端口号通过转换表映射到全球因特网IP和新的端口号上, 然后接收到分组时, 通过转换表找到对应的专有地址地域的IP和端口号进行发送. 30 | 31 | NAT妨碍了P2P应用, UPnP则允许外界主机使用TCP或UDP向NAT的主机发起通信回话. 32 | 33 | ## 选路算法 34 | 35 | * 全局选路算法: 链路状态算法(具有全局状态, 网络拓扑和所有链路费用作为Dijkstra算法的输入) 36 | * 分布式选路算法: 距离向量算法(没有节点拥有关于所有网络链路费用的完整信息) 37 | 解决规模和管理自治的问题通过将路由器组织进自制系统(AS), 在一个自治系统内运行的选路算法叫做自治系统内部选路协议(RIP, OSPF).自治系统间选路协议(BGP4)用来从相邻AS获取可达性信息以及向该AS的所有路由器传播可达性信息. 38 | 39 | ## 广播和多播 40 | 41 | 广播选路: 网络层提供了一个源节点到网络中的所有其他节点交付分组的服务 42 | 43 | 广播选路算法: N次单播, 洪泛(广播风暴), 受限洪泛(序号控制洪泛, 反向路径转发), 生成树广播 44 | 45 | 多播选路: 单个源节点能够向其他网络节点的一个子集发送分组的拷贝 46 | 47 | 多播选路算法: 距离向量多播选路协议 48 | 49 | 50 | -------------------------------------------------------------------------------- /计算机网络自顶向下方法/计算机网络中的安全.md: -------------------------------------------------------------------------------- 1 | ## 什么是网络安全 2 | 安全通信具有下列所需要的特性: 3 | - 机密性。仅有发送方和希望的接收方能够理解传输报文的内容。因为窃听者可以截获报文,这必须要求报文在一定程度上进行加密,是截取的报文无法被截获者所理解。 4 | - 报文完整性。发送方和希望的接收方希望确保其通信的内容在传输过程中未被改变——或者恶意篡改或者意外活动。 5 | - 端点鉴别。发送方和接收方都应该能证实通信过程所涉及的另一方,以确信通信的另一方确实具有其所生成的身份。 6 | - 运行安全性。几乎所有的机构几天都有了于公共因特网连接的网络。这些网络因此潜在地能够被危及安全。攻击者能够试图在网络主机中安放蠕虫,获取公司秘密,勘察内部网络配置并发起DoS攻击。 7 | ## 密码学的原则 8 | 发送者报文的最初形式被称为明文。发送者使用加密算法加密其明文报文,生成的加密报文被称为密文,该密文对任何入侵者看起来是不可懂的。发送方提供了一个密钥KA,它是一串数字或字符,作为加密算法的输入。加密算法以密钥和明文报文m为输入,生成的密文作为输出。用符号KA(m)表示使用密钥KA加密的明文报文m的密文形式。类似地,接收方将为解密算法提供密钥KB,将密文和接收方的密钥作为输入,输出初始明文。也就是说,如果接收方接收到一个加密的报文KA(m),他可通过计算KB(KA(m)) = m进行解密。在对称密钥系统中,发送方和接收方的密钥是相同并且是秘密的。在公开密钥系统中,使用一对密钥:一个密钥为发送方和接收方两人所知,另一个密钥只有发送方或接收方知道。 9 | 10 | 在实际应用中,RSA通常与对称密钥密码结合起来使用。例如,如果发送方要向接收方发送大量的加密数据,他可以用下述方式做。首先,发送方选择一个用于加密数据本身的密钥,这个密钥称为会话密钥,该会话密钥表示为KS。发送方必须把这个会话密钥告知接收方,因为这是他们在对称密钥密码中所使用的共享对称密钥。发送方可以使用接收方的RSA公钥来加密该会话密钥,即计算c = (KS)e mod n。接收方收到了该RSA加密的会话密钥c后,解密得到会话密钥KS。接收方此时已经知道将要用于加密数据传输的会话密钥了。 11 | 12 | ## 报文完整性和数字签名 13 | 报文完整性是指,接收方为了鉴别收到的报文,需要证实: 14 | - 该报文确实源自希望的发送方。 15 | - 该报文在到达的途中没有被篡改。 16 | 17 | 数字签名是一种在数字领域实现的密码技术。 18 | 使用数字签名的发送方的步骤:发送方让他的初始长报文通过一个散列函数。然后他用自己的私钥对得到的散列进行数字签名。明文形式的初始报文连同已经数字签名的报文摘要一道被发送给接收方。 19 | 20 | ## 端点鉴别 21 | 端点鉴别就是一个实体经过计算机网络向另一个实体证明其身份的过程 22 | 23 | 步骤: 24 | - 发送方向接收方发送报文“我是xxx”。 25 | - 接收方选择一个不重数R,然后把这个值发给发送方。 26 | - 发送方使用他与接收方共享的对称秘密密钥K来加密这个不重数,然后把加密的不重数K®发回给接收方。由于发送方知道K并用它加密一个值,就使得接收方知道收到的报文是由希望的发送方产生的。这个不重数用于确定希望的发送方是活跃的。 27 | - 接收方解密收到的报文,如果解密得到的不重数等于他发送给发送方的那个不重数,则可鉴别发送方的身份。 28 | ## 使TCP连接安全:SSL 29 | TCP的强化版本安全套接字层(Secure Socket Layer, SSL)用安全性服务加强TCP,该安全性服务包括机密性、数据完整性和端点鉴别。SSL版本3的一个稍加修改的版本被称为运输层安全性 30 | 31 | SSL具有三个阶段:握手、密钥导出和数据传输 32 | -------------------------------------------------------------------------------- /计算机网络自顶向下方法/计算机网络和因特网.md: -------------------------------------------------------------------------------- 1 | ## 什么是因特网 2 | * 具体构成:是一个互联了遍及全世界数十亿计算设备的网络 3 | * 服务描述:为应用程序提供服务的基础设施 4 | 5 | ## 什么是协议? 6 | 协议定义了在两个或多个通信实体之间交换的报文的格式和顺序,以及报文发送和/或接收一条报文或其他事件所采取的动作 7 | 8 | 我所理解协议就是规则,双方都按某个规则来才能交互不出问题。 9 | 10 | ## 电路交换和分组交换的概念和区别,为什么分组交换更有效? 11 | 12 | 电路交换:由于电路交换在通信之前要在通信双方之间建立一条被双方独占的物理通路(由通信双方之间的交换设备和链路逐段连接而成) 13 | 14 | 报文交换:报文交换是以报文为数据交换的单位,报文携带有目标地址、源地址等信息,在交换结点采用存储转发的传输方式。 15 | 16 | 区别: 17 | 1. 电路交换是以电路为目的的交换方式,即通信双方要通过电路建立联系,建立后没挂断则电路一直保持,实时性高。而分组交换是把信息分为若干分组,每个分组有分组头含有选路和控制信息,可以到达收信方,但是不能即时通信。 18 | 19 | 2. 分组交换通信双方不是固定占有一条通信线路,而是在不同的时间一段一段地部分占有这条物理通路,因而大大提高了通信线路的利用率。电路交换时,数据直达,不同类型、不同规格、不同速率的终端很难相互进行通信,也难以在通信过程中进行差错控制。通信双方之间的物理通路一旦建立,双方可以随时通信,实时性强。 20 | 21 | 3. 分组交换由于数据进入交换结点后要经历存储、转发这一过程,从而引起转发时延(包括接收报文、检验正确性、排队、发送时间等),而且网络的通信量愈大,造成的时延就愈大,因此报文交换的实时性差,不适合传送实时或交互式业务的数据。电路交换连接建立后,物理通路被通信双方独占,即使通信线路空闲,也不能供其他用户使用,因而信道利用低。 22 | 23 | ## 网络分层的本质,数据包是怎么被封装的? 24 | 协议分层结构的思想是用一个模块的集合来完成不同的通信功能,以简化设计的复杂性。大多数的网络都按照层或级的方式来组织,每一层完成特定的功能,每一层都建立在它的下层之上 25 | 26 | 分层的优点: 27 | 28 | 1. 各层之间相互独立,复杂程度下降。 29 | 2. 结构上可分隔开:各层都可以采用最合适的技术来实现。 30 | 3. 易于实现和维护:系统已被分解为若干个相对独立的子系统。 31 | 4. 灵活性好:一层发生变化其他各层不受影响。 32 | 5. 能促进标准化工作:每一层的功能及所提供的服务都有精确的说明。 33 | 34 | 数据包怎么封装? 35 | em... 36 | 37 | 38 | -------------------------------------------------------------------------------- /计算机网络自顶向下方法/运输层.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 运输层为运行在不同主机上的应用进程之间提供逻辑通信功能。应用进程使用运输层提供的逻辑通信功能彼此发送报文,而无需考虑承载这些报文的物理基础。 3 | 4 | 运输层和网络层的关系:网络层提供了主机之间的逻辑通信,运输层为在不同主机上的进程之间提供了逻辑通信。运输层协议只在主机起作用,运输层能够提供的服务受制于网络层协议的服务模型。 5 | 6 | UDP和TCP的责任是将两个端系统间IP的交付服务扩展到进程之间的交付服务。将主机间交付扩展到进程间交付被称为多路复用和多路分解。 7 | 8 | 网络协议IP协议提供的是不可靠服务,并不能保证数据的完整性等。UDP同样是一种不可靠服务,但可以提供进程间数据传输和差错检查两种服务。TCP在此基础上提供附加服务: 9 | 1. 可靠数据传输,通过流量控制、序号、确认和定时器,TCP可以将两个端系统间不可靠的IP服务转为进程间的可靠数据传输服务 10 | 2. 拥塞控制,防止某一条TCP连接用过多流量来淹没主机间的链路。 11 | 12 | ## 多路复用和多路分解 13 | 将运输层报文段中的数据交付到正确的套接字的工作称为多路分解。 14 | 15 | 在源主机从不同的套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用。 16 | 17 | 端口号是一个16比特的数,其大小在0-65535之间。0-1023范围的端口号称为周知端口号,受限制的,指它们保留给如HTTP(80)和FTP(21)之类的周知应用层协议来使用。 18 | 19 | ## UDP(无连接运输) 20 | DNS(域名系统)是运用UDP的一个例子 21 | 22 | UDP的优势: 23 | 1. 实时性较强,TCP由于其拥塞控制实时性较弱 24 | 2. 无需建立连接,没有建立连接的时延,因此DNS运行在UDP上,HTTP运行在TCP上 25 | 3. 无连接状态,TCP需要在端系统中维护连接状态,包括接收和发送的缓存、拥塞控制参数以及序号与确认号的参数 26 | 4. 分组首部开销小。 27 | 28 | UDP报文首部包括4个字段,每个字段2个字节,分别为源端口号,目的端口号,长度,检验和。 29 | 30 | ## 可靠数据传输原理 31 | - 经完全可靠信道的可靠数据传输 rdt1.0 32 | - 有限状态机 33 | - 有完全可靠的信道,接收方就不需要提供任何反馈信息给发送方 34 | - 经具有比特差错信道的可靠数据传输 rdt2.0 35 | - 自动重传请求协议(ARQ,又叫停等协议) 36 | - 处理比特差错还需三种协议功能: 37 | - 差错检测。需要利用额外的比特(组原中海明码、奇偶校验码之类) 38 | - 接收方反馈。肯定确认ACK(收到了),否定确认NAK(请再传一遍),接收方没收到确认就不能发送(停等协议) 39 | - 考虑ACK、NAK分组受损的可能性,在数据分组中添加新字段,分组序号0和1(向前取模) 40 | - 重传。接收方收到有差错分组时,发送方重传该分组。 41 | - 经具有比特差错的丢包信道的可靠数据传输 rdt3.0( 比特交替协议) 42 | - 发送方负责检测和回复丢包工作,对每一个分组维护一个定时器。如果在一个时间段内没收到ACK,则判定为丢包,重传分组,这也引入了冗余数据分组的可能性(时延很大的情况) 43 | 44 | ## TCP 45 | #### 为什么是3次握手? 46 | - 主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误连接。 47 | - 改为两次握手可能产生死锁。假设A向B发送请求,B收到请求后发送确认信号。此时如果按照两次握手协议,连接已建立,B开始传输数据。然后如果B的确认信号A没有接受到,A将不能知道B的序列号,将无法接受B的数据。而B在发送数据超时后会重复发送数据,从而死锁。 48 | #### 为什么要客户端要等待2MSL(报文最大生存时间)? 49 | 50 | - 保证客户端发送的最后一个ACK报文能够到达服务器。 51 | - 如果客户端直接进入closed状态,而服务端还有数据在网络中,当有一个新连接的端口和服务端端口一样时,那么客户端会认为这些数据是新连接的。 52 | 53 | 当主机接收到一个TCP报文段,其端口号或源IP地址与该主机上进行中的套接字都不匹配,此时主机会向源发送一个特殊的重置报文段,其RST标志位置1. 54 | 55 | ## TCP拥塞控制(加性增、乘性减(AIMD)拥塞控制方法) 56 | 57 | TCP采用的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。 58 | 59 | 如何调整发送方的发送速率:在发送方跟踪一个变量,即拥塞窗口(cwnd),其对一个TCP发送方能向网络中发送流量的速率进行了限制(发送方中未被确认的数据量不超过cwnd和rwnd的最小值)。 60 | 61 | 如何感知拥塞:通过出现超时或3次冗余ACK来确认是否发生拥塞。 62 | 63 | TCP发送方能够以更高的速率发送而不会使网络拥塞,有三个原则: 64 | 1. 一个丢失的报文段意味拥塞,此时应降低TCP发送方速率(减小cwnd的大小)。 65 | 2. 一个确认报文段指示网络正在向接收方交付发送方的报文段,此时可以增加发送方的速率。 66 | 3. 带宽探测 67 | 68 | TCP拥塞控制算法: 69 | 1. 慢启动 70 | 2. 拥塞避免 71 | 3. 快速恢复 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /计算机网络自顶向下方法/链路层.md: -------------------------------------------------------------------------------- 1 | ## 链路层概述 2 | 链路层上面是网络层,负责网络中的终端和终端的通信,下面是物理层,负责提供物理链路节点间的比特流的传输 3 | 4 | 沿着通信路径连接相邻节点的通信信道称为链路(link)。为了将一个数据报从源主机传输到目的主机,数据报必须通过沿端到端路径上的每段链路传输。在通过特定的链路时,传输节点将此数据报封装在链路层的帧中,并将该帧发送到链路上;接受节点然后接收该帧并提取出数据报。 5 | 6 | 虽然网络层的任务是将运输层报文段从源主机端到端地传送到目的主机,而链路层协议的任务是将网络层的数据报通过路径中的单端链路节点到节点地传送。链路层的一个重要特点是:数据报在路径的不同链路上可能由不同链路层协议所承载。 7 | 8 | ## 链路层提供的服务 9 | * 成帧。几乎所有的链路层协议都在经过链路传送之前,将每个网络层数据报用链路层帧封装起来。一个帧由一个数据字段和若干个首部字段组成,其中网络层数据报就插在数据字段中。因此,不同链路层协议,有不同的帧格式。 10 | * 链路接入。对于在链路的一端有一个发送方、链路的另一端有一个接收方的点对点链路,MAC协议比较简单(或者说不存在),即只要链路空闲,发送方都可以发送帧。主要的情况是多个节点共享单个广播链路,这就是所谓的多路访问问题,需要用MAC协议来协调多个节点的帧传输。 11 | * 可靠交付。当链路层协议提供可靠交付服务时,它保证无差错地经链路层移动每个网络层数据报。与TCP之类提供的可靠交付服务类似,但是链路层的可靠交付服务通常是通过确认和重传取得的,用于易产生高差错率的链路,比如无线链路,其目的是本地(也就是在差错发生的链路上)纠正一个差错,而不是通过运输层或者应用层协议迫使进行端到端的数据重传。然后,对于低比特差错的链路,比如光纤、同轴电缆等,链路层可靠交付可能会被认为是一种不必要的开销。由于这个原因,许多有线的链路层协议不提供可靠的交付服务。 12 | * 差错检测和修正。当帧中的一个比特作为1传输时,接收方节点可能错误地判断为0,反之亦然。这种比特差错是由信号衰减和电磁噪声导致的。因为没有必要转发一个有差错的数据报,所以许多链路层协议提供一种机制以检测是否存在一个或多个差错。通过让发送节点在帧中设置差错检测比特,让接收节点进行差错检测,以此来完成差错检测。相对于运输层和网络层的差错检测服务,链路层的通常更复杂,并且用硬件实现。差错纠正和差错检测类似,区别在于纠正不仅需要判断出是否有错,并且能够准确地判决出差错出现在哪里,并据此纠正这些差错。某些协议(例如ATM)只为分组首部而不是整个分组提供链路层差错纠正。 13 | ## 差错检测和修正技术 14 | 奇偶校验(用来描述差错检测和纠错隐含的基本思想)、校验和(通常更多地应用于应用层)和CRC(通常更多地应用在适配器中的链路层) 15 | 16 | 17 | ## 多路访问链路和协议 18 | * 信道划分协议 19 | * 随机接入协议 20 | * 轮流协议 21 | * DOCSIS 22 | 23 | --------------------------------------------------------------------------------