Declare data members private
动机
- 首先是避免程序员考虑应该使用括号还是不需要使用,保证一致性
- 其次是可以保证对成员变量的精确控制,细微划分访问控制
根据书中观点:封装性与“当其内容改变时可能造成的代码破坏量”成反比
建议
- 切记将成员声明为private,这可赋予客户访问数据的一致性,可细微划分访问控制,允诺约束条件得到保证,并提供class作者以充分的实现弹性
- protected并不比public更具封装性
根据书中观点:封装性与“当其内容改变时可能造成的代码破坏量”成反比
抢占式,无论是内核态还是用户态都可以抢占
分时技术:根据进程优先级去分配时间片
时间片轮转:对于优先级相同的进程,使用时间片轮转的方法
linux有两种进程:普通进程(100149)和实时进程(099)
IA32体系结构具有两种内存管理模式:
实模式
保护模式
linux把进程的用户空间划分成一个个区间。
创建进程用户空间
linux总是假定处理器支持三级页表结构(对于IA32,把PGD和PMD合二为一):
内核2.6.11之后采用四级页表模型
页帧:物理内存是以页帧为基本单位的,IA32为4kb
结点:访问速度相同的一个内存区域,与处理器相关联
zone:根据用途不同,将不同结点的物理内存分成不同的zone
linux对于zone曹勇buddy system管理
页帧是由page结构体表示的。
物理内存采用的是buddy分配算法,最小的分配单位是页帧,目的是分配一组连续的物理页帧。
buddy:两组连续的页帧,满足——大小相同;物理地址连续;第2个页块后面的页帧必须是2n的倍数。
buddy算法把内存中的所有页帧按照2^n划分,分成1,2,4,8.。。。。1024一共11种页块,每个页块组用一个双向循环链表进行管理,也就是一共有11个链表
比如free_area[0]指向1页帧块的链表
比如free_area[2]指向4页帧块的链表
linux文件系统是一种机制或者说是一种协议,它用来组织存储设备上的数据和元数据。在linux文件系统中采用了多级目录的树型层次结构去管理文件,用/表示。linux系统的文件系统都会安装到一个目录下,并隐藏了该目录的原有内容,这个目录叫做安装目录或者安装点。
linux缺省的文件系统是ext2/ext3/ext4,继承自unix,这种文件系统把文件名和文件控制信息分开管理,文件控制信息组成一个inode结构。
为了使得各种物理文件系统能够转换成具有统一共性的文件系统,对各种文件系统进行抽象,linux使用了一种虚拟文件系统。
另外VFS其实不是一种实际的文件系统,因为它不是存在于外存的,VFS是仅仅存在于内存。
假如用户输入 cp /floopy/TEST /tmp/test,其中前者是MS-DOS磁盘的挂载点,后者是ext2文件系统的一个目录,但因为VFS提供了系统调用接口,使得cp不用理会两者的文件系统而进行文件操作
文件模型:
linux支持的文件系统必须先注册后才能使用,注册有两种方式:
注册后的文件系统会登记在file_system_type结构中,从而组成一个注册链表
把ext2/3/4文件系统的磁盘分区作为系统的根文件系统,其它文件系统则安装在根文件系统下的某个目录下,成为系统树形结构的分支
安装命令: mount -t vfat /dev/hda5 /mnt/win;
mount -t ext2 -i loop ./myfs /mnt
对于打开的文件,有两种管理方式:
执行一个只有部分大小在物理内存中的程序,好处是:
虚拟内存允许文件共享:
一个程序在被加载进内存时有两种方法:一个是整个装载进去,一个是当你有需要的时候再从disk读到memory,而后者就是demand paging。
为了实现这个方法,我们需要硬件来支持该页是否在内存中,因此可以在page table中设置一个有效位。
访问到一个标记为invalid的页,则叫page fault。此时系统中断,操作系统去从disk中加载该页进内存中。
有效访问时间 = (1-p) * ma + p*pft
这个方法的提出是基于系统调用fork()导致的性能下降。在子进程fork一个父进程之后,如果直接将父进程的内存复制一份,考虑到子进程与父进程之间有不少数据内容是一样的,因此可以共享这部分数据,这就是copy-on-write。
步骤如下:
如下图,当process1要修改pageC的内容时,会先复制一份pageC
由于copy-on-write需要找到一个可用的page,所以操作系统的做法是维护一个pool,随时取出来;
linux系统下提供了vfork(),这个系统调用并不会copy-on-write,而只是共享内存,因此要保证子系统不会轻易修改数据;
当一个进程在执行时发生了page fault,当此时没有可用的内存,那么操作系统需要决定去替换一个页,至于替换方法有两种:
一种是直接terminate那个进程,但这不是一个好的方法,因为分页操作应该对用户是透明的(不懂?)
另一种则是交换进程,先把某个进程所有的frame释放了,降低cpu利用率
现在我们来考虑第三种方法page replacement
发送basic page replacement时,操作系统会做以下操作:
在disk发现自己想要的page;
如果内存中没有可用frame,则用replacement 算法去置换一个frame;
将内存中选中的victim frame写进disk
修改page和frame的表内容
将disk中想要的page写进空出来的frame
重启进程
但这里会有一个问题:每次发生置换都需要两个pages的传送,需要大量的IO操作,因此为了提高效率,可以在page中增加一个modify bit(dirty page)
这个bit用来指示,该page在上一次从disk都出来之后有没有被修改过
因此就可以在replacement时检测这个bit,只有dirty的才会需要被写回disk,否则直接从disk读取想要的pages覆盖那个frame即可;
reference string
这是最基本的置换算法,很简单,就是维护一个队列,队列按时间连接,即最新的frame在队列尾。每次置换队列头的frame;
但这个算法有一个bug,具体可以参考Belady’s anomaly。即它可能会随着分配的内存即frame增多,它的page fault rate反而增大。
这是效率最高的算法,也是发送page fault可能性最小的一个算法。简单概括就是:
置换内存中某个frame,该frame在这时候很久才会被再次引用,这里的很久指的是相对其它frame要久
但这个算法不好实现,因为很难去判断未来在什么时候才会再次引用该frame
这是综合以上两种算法的算法,往前追溯reference string,找到距离现在最久的frame,置换该frame
这里介绍两种实现方法:
counter
stack
多数硬件都会提供一个支持——reference bit,用来指示该page是否被使用过(读写)
假设reference有8位,每个时钟周期该8位reference bit就右移一次,如果该周期内内存有被引用,则设最高位为1,否则设为0。这样,将8位reference bit转换成无符号整数,最小的page则为LRU page
实现这个算法的基础是维护一个循环队列和一个指针,队列中的page都有一位reference bit,指针指向要替换的page
在最坏的情况下,指针会遍历一遍,将所有page的reference bit清零
相对second-chance algorithm,这个算法添加了1bit,作为检查该bit是否被修改,即modified bit。
这样就将pages分为了四类:
这种算法有一个好处,就是前面提过的modified bit的好处,减少了I/O的消耗。
做一个计数,选择使用过最多次的/最少次的page。一个too expensive的算法~~
系统维护一个free frames pool,在要被写出的帧写回disk之前,首先将想要的frame写进free frame,然后再把victim frame写回disk,据说?这样进程就能快速重启
分配给每个进程的frame数量:
主要是两种
将对象组合起来,以达到能同时表达整体与局部的效果,使得用户能一致地操作单个对象和组合对象
在一些系统中,用户希望通过简单的组件组合成复杂的组件,但同时由于用户认为这些组件的操作是一致的,因此不适合将组件进行区别的对待。有一种实现方法是为所有简单组件定义一些类,另外定义一些容器类来管理简单组件。但这样势必需要区别对待地操作所有组件,如果组件的组合方式很多样化,那么操作起来就很不方便。因此我们可以使用递归组合这些组件,避免用户对组件使用不同的操作。
1 | // Composite.cpp : 定义控制台应用程序的入口点。 |
在C++中,有各种各样可以让你与代码交互的接口,但有时候一些接口的滥用会导致不必要的麻烦,因此我们要开发一个健壮的接口。
比如一个类:
1 | class Date{ |
这种构造函数有一个问题是它可能传入一些不合法的天数或者月份,或者不小心将day和month交换
因此我们可以用类来进行封装。
1 | class Date{ |
另一个守则是,任何让客户必须记得做某些事情,就可能出现“不正确使用”的倾向,例如要求new出来的原始资源必须客户记得手动删除就不是一个很好的使用方式,应该用智能指针去接管资源。
这个条例是为了避免在“以对象管理资源”中可能出现bug
比如
1 | processWidget(std::shared_ptr<Widget>(new Widget), priority()); |
这里可能出现的问题是,C++对于函数参数的调用次序不确定,假如顺序是:
那么如果在priority中发生异常,new出来widget资源就会泄漏
分离new语句:
1 | std::shared_ptr<Widget> pw(new Widget); |
将抽象部分与它的实现部分分离,使得它们都可以独立地完成变化
当一个抽象对象有多个实现时,通常的实现方式是通过继承来构造。但在某些情况下,子类的实现可能会不够灵活,出现大量重复的代码,也很难对其进行独立的修改。例如有一个公路1,公路2类,和一个角色A、B类,我们需要实现的实例是A在公路1,A在公路2等四个组合方式。但其出现了大量冗余,因为它们的动作是重复的。这时我们可以使用桥接的方式来构造它们。
1 | // Bridge.cpp : ¶¨Òå¿ØÖÆ̨ӦÓóÌÐòµÄÈë¿Úµã¡£ |