正文:

  1. VFS作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。
  2. 从本质上讲文件系统是特殊的数据分层存储结构,它包含文件,目录和相关的控制信息。
  3. 文件索引节点(文件元数据)存放访问控制权限,大小,拥有者,创建时间等信息。
  4. 文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。
  5. VFS采用的是面向对象的设计思路,使用一组数据结构来代表通用文件对象。主要有四个对象类型:超级块对象,索引节点对象,目录项对象,文件对象。
  6. 超级块对象用于存放特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或控制块。
  7. 内核将目录项对象缓存在目录项缓存dcache中.
  8. 文件对象是已打开的文件在内存中的表示。

正文:

  1. 块设备最小可寻址单元是扇区,多数是512字节。
  2. 内核执行的所有磁盘操作都是按照块进行的。块大小一般为512,1k,4k。
  3. 当一个块被调入内存时,它要存储在一个缓冲区中,每个缓冲区都有一个对应的描述符buffer_head,它包含了内核操作缓冲区所需要的全部信息。
  4. 目前内核中块IO操作的基本容器由bio结构体表示,此结构体代表了正在现场以片段链表形式组织的块IO操作。
  5. bio结构体代替buffer_head结构体的好处:bio可以很容易的处理高端内存,因为它处理的是物理页而不是直接指针;bio既可以代表普通页,同时也可以代表直接IO(那些不通过页高速缓存的IO操作);bio便于执行分散—集中块IO操作,操作的数据可取自多个物理页面;bio更加轻量级。
  6. 块设备将它们挂起的块IO请求保持在请求队列中。
  7. IO调度程序(电梯调度) 将磁盘IO资源分配给系统中所有挂起的块IO请求。通过将请求队列中挂起的请求合并和排序来完成的。
  8. 最后期限IO调度程序 ,每个请求都有一个超时时间。
  9. 预测IO调度程序以最后期限IO调度程序为基础,它也实现了三个队列,并为每个请求设置了超时时间,但它增加了预测启发的能力。
  10. 完全公正的排队IO调度程序 是为专有工作负荷设计的,它把进入的IO请求放入特定的队列中,这种队列根据引起IO请求的进程组织的。
  11. 空操作的IO调度程序,不进行排序,也不进行其他形式的预寻址操作,但会执行请求合并。

前言:

这几天把现代CPU的缓存相关技术做了一个大概的了解,下面总结下一些比较基本以及可以在编写代码时可以优化自己的程序的要点.


  1. 每个核心都有独立的数据,指令缓存。
  2. 默认情况下CPU核心所有的数据读写都存储在缓存中。
  3. 现在AMD和VIA处理器采用的独占型缓存,Intel采用的包容型缓存(有时也采用独占型缓存)。
  4. 在对称多处理器中,内存视图为 高速缓存一致性!
  5. 一个脏缓存线不存在于任何其他处理器的缓存之中。
  6. 同一缓存线中的干净拷贝可以驻留在任意多个其他缓存之中。
  7. 现在的处理器都使用不同长度的内部管道,在管道内指令解码,并准备运行。
  8. 对于写操作,CPU并不需要等待数据被安全的放入内存。
  9. 全关联: 每个高速缓存段都可以容纳任何内存位置的一个副本。
  10. 全关联高速缓存对小缓存实用。对于L1i,L1d和更好级别的缓存,需要采用限制搜索。
  11. 组关联 结合了高速缓存的全关联和直接映射高速缓存的特点。
  12. 元素大小对预取的约束是根源于硬件预取的限制,它无法跨越页边界。
  13. TLB是存储虚实地址映射的缓存。为保持快速,TLB只有很小的容量,如果有大量页被反复访问,超出了TLB缓存容量,就会导致反复进行地址翻译!
  14. 最末级缓存越大,停留在L2访问耗时区的时间越长。处理器能够利用L1d到L2之间的预取消除访问主存,甚至是访问L2的时延。
  15. 在处理器随机访问时,处理器自动预取会导致反效果。
  16. 当工作集大小超过L2时,未命中率开始上升。
  17. 写通: 当缓存线被修改时,处理器立即将它写入主存。当缓存线被替代时,只需要简单的将它丢弃。
  18. FSB(前端总线) 是将CPU连接到北桥芯片的总线.
  19. 写回: 当修改缓存线时,处理器不再马上将它写回主存,而是打上dirty的标记,当以后某个时间点缓存线被丢弃时,这个已弄脏的标记会通知处理器把数据写回到主存中。
  20. 写入合并是一种有限的缓存优化策略,更多的用于显卡等设备之上的内存。
  21. MESI(modified,exclusive,shared,invalid)缓存一致性协议: 变更的(本地处理器修改了缓存线,同时暗示它是所有缓存中唯一的拷贝),独占的(缓存线没有被修改,而且没有被装入其他处理器缓存),共享的(缓存线没有被修改,但可能已被装入其他处理器缓存),无效的(缓存线无效,即未被使用)。
  22. 在MESI协议中,通过处理器监听其他处理器的活动,处理器将操作发布在外部引脚上,使外部可以了解到处理过程,目标的缓存线地址则可以在地址总线上看到。
  23. 请求所有权(RFO) 在两种情况下是必须的:线程从一个处理器迁移到另一个处理器,需要将所有缓存线移到新处理器;某条缓存线确实需要被两个处理器使用。
  24. 多线程或多进程程序总是需要同步,而这种同步要依赖内存来实现。
  25. 对于大缓存(L2,L3)需要以物理地址作为标签,因为这些缓存的时延比较大,虚拟到物理地址的映射可以在允许的时间里完成,而且由于主存时延的存在,重新填充这些缓存会消耗比较长的时间,刷新的代价比较大。
  26. 追踪缓存,统一缓存。
  27. 关键字优先级较早重启技术 在预取操作时无法运用,因为并不知道哪个是关键字。在关键字优先技术生效的情况下,关键字的位置会影响性能。
  28. 虽然缓存放置的位置与超线程,内核,处理器之间的关系不在程序员的控制范围之内,但程序员可以决定线程执行的位置,这样高速缓存与使用的CPU的关系将变得非常重要。

简介

C++是一个多重范型编程的语言,一个同时支持过程形式,面向对象形式,函数形式,泛型形式,元编程形式的语言,
可以把C++看成由四大子部分构成:C,Object-Oriented C++,Template C++,STL;
当我看完Effective c++之后,总是感觉有些书中写的极好的建议我就是想不起来,为了让之后想不起来时可以
随时查看,于是就想此时写一个读书笔记,方便之后的查阅!


正文

  • 尽量以const,enum,inline替换#define ,因为#define只是由预处理器的字符串替换过程,我们所使用的名称并没有进入符号表当中,当出现问题时我们无法得知是哪个名称出问题;对于带参宏,我们也尽量以inline函数来替换,因为有些时候我们不一定会记得把所有的括号打上.
  • 对于那些不应该被改变值的变量,我们尽量使用const来约束 ,如果在const成员函数内,有些成员变量的值也可能改变的话,可以把这些成员变量修饰成mutable.
  • 确定对象被使用前已先被初始化 .我们应该尽可能的避免去读取未初始化的对象的值而产生不明确的行为.C++规定,对象的成员变量的初始化动作发生在构造函数本体之前,因此使用成员初值列表来对对象成员变量进行初始化是一个好做法.
  • 了解C++默认为你编写并调用的那些函数 ,比如default构造函数,copy构造函数,析构函数(编译器产出的析构函数是个non-virtual,除非这个class的base class自身声明有virtual析构函数),copy assignment操作符等.
  • 如果不想让编译器为你自动生成一些函数时,可以将这些成员函数声明为private并且不予以实现.
  • 由于C++明确指出,当derived class对象经由一个base class指针被删除,而此base class带有一个non-virtual析构函数,其结果未定义.为消除此问题,我们可以给base class一个virtual析构函数,这样就可以通过delete基类指针来把derived class的对象成分全部销毁.
  • 别让异常跳出析构函数,析构函数绝对不要吐出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序.如果客户要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而不是在析构函数中进行)执行此操作.
  • 绝对不要在构造和构构过程中调用virtual函数,对象在derived class构造函数开始执行前不会成为一个derived对象.
  • 令operator= 返回一个reference to *this.
  • 在operator=中处理”自我赋值”.
  • 成对使用new和delete时要采用相同形式.
  • 以独立语句将newed对象置入智能指针.如果没这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏.
  • 让接口容易被使用,不易被误用.
  • 宁以pass-by-reference-to-const替换pass-by-value,这样可以大大降低开销.
  • 必须返回对象时,别妄想返回其refrence.
  • 将成员变量声明为private.
  • 若所有参数皆要类型转换,请为此采用non-member函数.
  • 尽可能延后变量定义式的出现时间.
  • 尽量少做转型动作.
  • 将文件间的编译依存关系降至最低.
  • 避免遮掩继承而来的名称.如果想让被遮掩的父类函数名称重现的话,可以用using声明式或转交函数.
  • 声明一个pure virtual函数的目的是为了让derived class只继承函数接口.声明一个非纯virtual函数的目的是让derived class 继承该函数的接口与缺省实现.
  • 绝不重新定义继承而来的non-virtual函数.
  • 绝不重新定义继承而来的缺省参数值.因为缺省参数值都是静态绑定的,而virtual函数却是动态绑定的.
  • 明智而审慎地使用多重继承.
  • 了解隐式接口和编译期多态.
  • 了解typename的双重意义.
  • 将与参数无关的代码抽离template.
  • 不要轻易忽略编译器的warning.

  1. 运算符重载实质就是函数重载,是另一种形式的函数调用而已!只是运算符重载的函数参数就是该运算符涉及的操作数。
  2. 友元不是类的成员,但能直接访问类的所有成员,又不改变受访问类成员的访问权限。c++不是完全面向对象的语言!
  3. 通过友元机制,一个类或函数可以直接访问另一个类的非公有成员。可以将全局函数,类,类的成员函数声明为友元。友元关系不能传递!友元关系是单向的,并且不能继承!
  4. 运算符重载为成员函数最多有一个形参。而重载为友元函数时最多有两个形参!
  5. 不能重载的运算符 (::)(.) (.* )(?:)(sizeof) (typeid)
  6. 参数化的函数称为函数模板,代表的是一个函数家族。函数模板不是一个实实在在的函数,它不产生任何执行代码。当编译系统在程序中发现有与函数模板相匹配的函数调用时,便产生一个重载函数,该重载函数的函数体与函数模板的体力相同,该重载函数称为模板函数。
  7. 适配器 是用来修改其他组件接口的STD组件,是带有一个参数的类模板。有容器适配器,迭代适配器,函数适配器。
  8. 范型算法(algorithm),这些算法可以应用于多种容器类型上,而容器内的元素类型也可以多样化。STD的范型算法中有4类基本算法:变序型队列算法,非变序型队列算法,排序值算法
  9. 迭代器(iterator),迭代器实际上是一种范化指针,如果迭代器指向了容器中的某一成员,那么迭代器将可以通过自增自减来遍历容器中所有成员。迭代器是联系算法和容器的媒介,是算法操作容器的接口。
  10. 函数对象(function)又称仿函数。函数对象将函数封装在一个对象中,使得它可作为参数传递给合适的STD算法。
  11. 指数据从一个位置流向另一个位置,流是字节的序列。
  12. C++编译系统提供的i/O流类库含有2个平行基类:streambuf 和ios,所有的流类都是由他们派生出来的。
  13. iostream包括操作所有I/O所需的基本信息,提供无格式支持的低级I/O和具有格式支持的高级I/O操作功能! fstream头文件包含对文件I/O操作的有关信息。 strstream头文件包含对内存格式化I/O操作的有关信息。 stdiostream头文件包含混合使用C和C++风格的I/O操作有关信息。
  14. 处理大容量文件最好使用无格式I/O.
  15. 用ios类成员函数格式化,主要是通过对状态标志,输出宽度,填充字符以及输出精度的操作来完成输入/输出格式化。
  16. 用操作符函数格式化。标准库的操作符函数专门操控这些状态,它们不属于任何类成员,定义在iomanip头文件中。操作符又称流操纵算子,或控制器函数。
  17. 自定义类型对象的输入输出可以在类中对插入运算符«与提取运算符进行重载!
  18. 异常处理是C++语言中重要的错误处理机制,是提高程序容错性的手段。
  19. 系统层次异常处理的标准设施: 能够在异常发生时,在执行点抛出异常,并将有关异常信息以类型的形式传递到异常处理模块。建立模块间的异常通信机制。保证异常发生时释放所占系统资源。将异常处理代码从普通代码中分离。
  20. try catch throw的实际联系是在程序运行时体现出来的。当抛出的异常找不到与之匹配的catch子句时,由系统函数terminate()通知用户异常未处理,系统终止程序运行。
  21. C++的异常处理机制保证:在栈展开时,临时变量会自动释放,局部类对象的析构函数会自动调用。
  22. 声明为explicit的构造函数禁止在隐式转换中使用
  23. 对象的布局方式和他们的地址计算方式随编译器的不同而不同。
  24. 如果系统有一个函数不具备异常安全性,整个系统就不具备异常安全性!异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。
  25. 一个表面上看起来是inline的函数是否真是inline取决于你的建制环境,主要是编译器。
  26. 将大多数inlining限制在小型被频繁调用的函数身上。
  27. 句炳 是一个整数值,系统用来标识对象数据的,是操作系统所提供的。
  28. 将数据类型参数化来设计函数的机制称为函数模板。还有一种类模板,既类的数据成员也进行参数化。
  29. 复制构造函数中的浅复制与深复制.
  30. 静态成员 是c++解决同一个类的不同对象之间数据和函数共享问题的机制。分为静态数据成员和静态成员函数。
  31. 静态数据成员最好在类的实现部分进行初始化。
  32. this指针 存放当前对象的起始地址。
  33. 静态成员函数 只能直接引用静态成员。如果静态成员函数中要使用非静态成员时,必须通过参数传递方式得到对象名,然后通过对象名来访问。
  34. 析构函数不能重载。
  35. 对于未给出构造函数,析构函数的类,如果不想让编译器自动生成,应该明令禁止!
  36. 名字空间主要是解决程序中标识符名字冲突问题。
  37. 不能通过常对象调用普通成员函数,只能调用常成员函数,它只能访问数据成员,不能修改数据成员的值,如果想改变某个数据成员,可将数据成员声明为mutable.
  38. 一般来说,在类中出现了对象成员时,创建本类对象既要对对象成员进行初始化又要对本类对象的初始化!先调用对象成员的构造函数。析构函数的调用顺序刚好相反。
  39. 派生类不会继承基类的构造函数和析构函数。
  40. 定义派生类的构造函数时除了对派生类成员初始化外还必须调用基类的构造函数初始化基类的数据成员。如果派生类中有对象成员时,还应调用对象成员类的构造函数初始化对象成员。
  41. 对于构造函数,要么在类内声明处用初始化列表全部给出数据成员的初始方式,要么只在类内声明构造函数,在类外可以部分数据成员用初始化列表进行初始化,部分数据成员在函数体内进行初始化!
  42. 派生类析构函数—>对象成员析构函数—>基类析构函数。
  43. class默认继承方式为私有继承。
  44. 利用将直接基类的共同基类设置成虚基类,可避免继承二义性!此时该基类在内存中只有一个副本存在!
  45. 多继承下,在所有基类中先调用虚基类的构造函数。
  46. 派生类的对象可以赋值给基类对象;派生类的对象可以初始化基类对象的引用;派生类对象的地址也可以赋给基类的指针。
  47. 静态联编:对象指针类型在编译阶段将指针名和成员函数彼此关联的过程。
  48. 多态指具有相似功能的不同函数使用同一个名称来实现!包括重载多态,强制多态,包含多态,参数多态!c++采用联编技术来支持多态。
  49. 联编 是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程。分为静态,动态联编。
  50. 动态联编 通过继承和虚函数来实现。运行时多态需要满足3个条件,一是类之间应满足类型兼容规则,二是同名声明虚函数,三是由成员函数来调用或者通过指针,引用来访问虚函数。
  51. virtual 关键字只用在虚函数的声明中,在定义的时候不能用!虚函数派生下去还是虚函数。
  52. 在类对象的内存中,首先是该类的虚函数表指针,然后才是对象数据。
  53. 静态成员函数不能声明为虚函数;构造函数不能是虚函数;内联函数不能声明为虚函数;但析构函数往往被定义为虚函数。
  54. 如果在派生类中没有重新定义虚函数,则派生类的对象将使用基类的虚函数代码。
  55. 在派生类中重新定义虚函数时,必须保证函数值类型和参数与基类中的声明完全一致。
  56. 抽象类 专门作为基类派生新类,主要作用是将有关的派生类组织在一个继承层次结构中,由抽象类为它们提供一个公共的根。抽象类提供了处理各种不同派生类的统一接口,此接口就是纯虚函数。含有纯虚函数的类就是抽象类!
  57. 纯虚函数 是为了解决在基类中无法实现的函数,而在派生类中再给出函数的具体实现。
  58. 声明指向抽象类的指针或引用,通过它们来指向并访问派生类对象。
  59. 如果派生类没有给出全部纯虚函数的实现,继承了部分纯虚函数,这时的派生类仍然是一个抽象类!