内核杂七杂八的基础知识

更多内容可关注微信公众号在这里插入图片描述

#内核通知链

  • 内核子系统之间事件的通知,一般用内核通知链来实现。通知链只能在内核子系统之间使用,不能在内核与用户控件之间进行时间的通知。
  • 通知链是一个函数链表,链表上的每一个节点都注册了一个函数,当某个事件发生时,链表上所有节点对应的函数都会被执行,属于订阅者-发布者模型。
  • 通知链有四种类型:
  	//1. 原始通知链(Atomic notifier chains):回调函数只能在中断上下文中运行,不允许阻塞(为什么还不清楚)? 对应链表结构体: 
  	   struct atomic_notifier_head
  	   {
    		spinlock_tlock;
    		structnotifier_block *head;
	   };
  	//2. 可阻塞通知链(Blocking notifier chains):回调函数在进程上下文中运行,允许阻塞。对应链表结构体: 
  	   struct blocking_notifier_head
  	   {
    		structrw_semaphore rwsem;
    		structnotifier_block *head;
	   };
  	//3. 原始通知链(Raw notifier chains): 回调函数没有限制,所有所和保护机制都由调用者维护。对应链表结构体: 
  	   struct raw_notifier_head
  	   {
    		structnotifier_block *head;
       };
  	//4. SRCU通知链(SRCU notifier chains):可阻塞通知链的一种变体,对应链表结构体:
  	   struct srcu_notifier_head
  	   {
    		struct mutexmutex;
    		struct srcu_struct srcu;
    		struct notifier_block *head;
	   };
	//这四者的核心结构都是notifier_block,其定义如下:
	struct notifier_block
	{
		//要执行的函数指针
    	int(*notifier_call)(struct notifier_block *, unsigned long, void*);
    	//下一个函数
    	structnotifier_block *next;
    	//优先级,同一条链上,notifier_block是按照优先级排列的。
    	intpriority;
	};
通知链的一般使用流程:
  1. 通知者定义通知链。
  2. 被通知者在某个通知链上通过notifier_chain_register注册回调函数。
  3. 通知者检测到/自身产生某事件后,调用notifier_call_chain, 这个函数内部调用对应通知链中的所有函数。

#每cpu变量

##多处理器

###多处理器的分类

  • 非对称多处理器(Asymmetric multiprocessing, AMP)
    每个cpu内核运行一个独立的操作系统,或同一操作系统的独立实例。在AMP模式下只有一个主处理器控制系统,其他处理器要么从主处理器出获取要执行的代码,要么就是有预定义好的代码,处理器之间是主从关系。
  • 对称多处理器(Symmetric multiprocessing, SMP)
    每一个处理器可以在执行某个系统中的所有任务,所有处理器是对等的。也就是说,一个操作系统的实例可以同时管理所有cpu内核,且应用并不绑定某一个内核。
  • 混合多处理器(Bound multiprocessing, BMP)
    一个操作系统实例可以同时管理所有cpu内核,但每个应用被锁定在某个指定的核心上。
    ###多核技术
  • 多核技术实际上就是SMP技术,只不过SMP没说多个处理器如何封装,多核技术是把多个处理器封装到同一个芯片中(Chip MultiProcessor, CMP)。
  • 处理器主频的提高会引起更大的能耗和更多的散热,而多核来说总体性能要不提高主频来的好。
  • CMP给操作系统带来一些问题,如多线程如何并行修改共享数据(单线程在同一时刻始终是在某个cpu上执行的,所以单线程在任何时候东不存在同步问题),还有多处理器的进程调度(每个进程应该在哪个cpu上调度,进程在cpu之间何时应该迁移,迁移导致的高速缓存命中下降问题等。2.4内核中所有进程都是在一个共享队列上的,导致竞争激烈,后来每个cpu都有一个进程队列了)等。

###SMP的分类

  • SMP系统根据系统内部件连接方式不同,分为:基于总线的SMP,基于交叉开关的SMP和基于多级交换网络的SMP,这里主要说一下基于总线的SMP。
  • 基于总线的SMP,是系统的所有部件都挂在同一组系统总线上,如果需要内存访问的时候,处理器先要检查系统总线是否空闲,如果总线繁忙就要一直等待到总线空闲,会浪费不少cpu时间,随着处理器个数的增加,系统整机性能必定会急剧下降,总线SMP系统最大的瓶颈在于其总线带宽。
  • 总线SMP的改进方法,就是在每个处理器上(一般是内部)都加上高速缓存,这样处理器的很多读写请求在cache中就可以满足,以减少总线的请求次数。
  • 每当处理器访问一个字长的数据时,与该数据相邻的整个数据块都会被读取到高速缓存中。高速缓存每一个数据块都带有一个读写标志位,当该标志位为可读写时,该数据块的副本可以同时存在于几个处理器的高速缓存中,当该标志位可读写时,则该数据块可能不允许同时存在于多个高速缓存中。当一个处理器修改一个存在于多个处理器的多个高速缓存的数据时,系统总线监视器会检测到这个写操作,然后向总线上其他处理器高速缓存发出通知,如果此时其他高速缓存中的数据是干净的(与当前内存一致),则高速缓存直接丢弃当前cache副本,下次读取的时候,从内存重新获取数据;如果高速缓存中数据是脏的,则必须在当前写操作执行前,将数据会写会内存。高速缓存是通过硬件实现的,有具体的高速缓存传输协议,我们只需要知道,系统访问内存时,会先去缓存找,内存一次读取32/64字节到缓存。
  • 给处理器加缓存会很大程度上减缓内存访问瓶颈,但内存访问始终还是瓶颈,有的系统,为每个处理器设置了私有内存来减少内存访问瓶颈,但这一版需要系统/程序的适配,使用并不广泛。

###商用服务器主要三大体系:SMP、NUMA、MPP

  • 目前的商用服务器从系统结构来看可大体分为三类:对称多处理器结构(SMP, Symmeric Multi-Processor),非一致存储访问结构(NUMA, Non-Uniform Memory Access),海量并行处理结构(MMP, Massive Parallel Processing)。
  • SMP服务器:服务器中多个cpu对称工作,无从属关系,各cpu共享相同的物理内存,每个cpu的访问权限和代价相同,因此SMP也被成为一直存储器访问呢结构(UMA, Uniform Memory Access)。SMP服务器的主要特征是共享,这种特征导致其扩展能力是否有限,每一个共享缓解都可能造成SMP服务器扩展时的瓶颈,而最受限制的的则是内存,由于每个cpu必须通过相同的内存总线访问资源,因此随着cpu数量的增加,内存访问冲突将迅速增加,cpu数量是有上限的。实验证明,SMP服务器cpu利用率最好的情况是2-4个cpu。
  • NUMA服务器:具有多个cpu模块,每个cpu模块由多个cpu组成,并有独立的本地内存,IO槽等。其节点之间可以通过互联模块进行连接和信息交互,因此每个cpu也是可以访问整个系统的内存的(MPP不能,这是二者区别)。但由于本地内存的访问速度远远高于远端内存的访问速度(非一致存储访问的名字就是这么由来的),开发应用时要尽量少的在cpu之间交互信息。利用NUMA技术可以在一个物理服务器内扩展上百个CPU。其缺陷在于,由于访问速度的不一致,导致系统性能无法线性增加。
  • MMP服务器:MMP服务器是由多个SMP服务器通过一定的结点互联网络进行连接并协同工作的,从用户角度看是一个服务器系统,实际上是多个SMP服务器通过结点互联网络工程的,每个节点只能访问自己的本地资源,是一种完全无共享结构,因此扩展能力最好,理论上无限制,这种服务器类似于集群。在NUMA上,访问远程内存的时候cpu必须忙等,而MPP上是通过远程通信是通过IO实现的,所以cpu不需要忙等,可以执行其他任。

##存储体系
现在计算机系统的存储系统是分层的,主要有六个层次:

  1. CPU + 寄存器
  2. 1级高速缓存(On-chip L1 Cache,cpu的片上缓存,一般由static RAM组成,size较小,如16KB)
  3. 二级高速缓存(Off-chip L2 Cache,一般由static RAM组成,size较大,如2MB)
  4. 主存(一般由Dynamic RAM组成,几百MB到几GB不等)
  5. 本地磁盘(目前已有TB级)
  6. Remote disk(网络存储,分布式文件系统)
    决定分层的因素主要是:容量,价格和访问速度,越往上层,访问速度越快,容量越小,单位价格越贵。
    ###SRAM/DRAM
    SRAM(static RAM, 静态随机存储器),特点是工作速度快,只要电源不撤除,写入SRAM的信息就不会消失,不需要刷新电路,同时在读出时不破坏原来存放的信息,已经写入可多次读出,但集成度较低,功耗较大,一般用作计算机中的高速缓存。
    DRAM(dynamic RAM,动态随机存储器),它是利用场效应管的栅极对其衬底间的分布电荣充电来保存信息,以电容两端电压来表示1/0,DRAM的每个存储单元所需的场效应管较少,故集成度较高,功耗也较低,缺点是会漏电,一般1~2ms左右就要刷新一次来防止信息丢失,故一般用作计算机中的主存(集成度高,功耗低)。
    ###存储体系中的cache
  • 缓存可以引用在存储体系的任意两层之间,只要觉得两层速度差异较大,就可以考虑用缓存来解决。
  • 一般而言,cache是按照cache line来组织的,当访问主存储器的时候,如果数据/指令位于cache line中,称之为cache hit;如果数据不在cache中,称为cache miss,这种情况下,需要从外部的主存中加载数据/指令到cache中来。如果cache miss的时候cache已满,还要考虑替换算法。
  • cache miss的三种情况:
  1. 在系统初始化时,cache中没有数据,此时称这个cache是cold cache,这时的miss称为cold miss。
  2. 加载cache line的时候有两种策略,一是主存中的数据可以放在任何一个cache line中,这种方法的问题是判断cache hit的开销太大,需要扫描整个cache。因此实际中,制定的主存数据只能加载到cache中的一个subset中。因此产生了另外一种cache miss叫conflict miss,即当前cache中虽然有空闲的cache line,但由于主存单元对应的哪个subset已经满了,此时叫conflict miss。
  3. 程序有时会在若干个指令中循环,这个循环中有可能不断地访问一个/多个数据块,这些数据块就叫做这个循环过程的working set(工作集),当working set的大小大于cache的大小,则就会产生capacity miss。加大cache或减小working set的的大小是解决capacity miss的唯一方法。
  • 对于L1/L2缓存来说,cache的管理策略是由硬件逻辑来管控的。
  • TLB和cpu缓存用的都是cache,只是一般subset不同。
    ###写操作带来的问题
    对于写操作,也存在cache hit 和cache miss的问题:
    在cache hit时的三种策略:
  1. write through(通写):CPU向cache写入数据时,同时也想主存写入一份,等到主存写完毕才可进行下一步(通常这种用法和不用写缓存差不多了)
  2. 带write buffer的write through:cpu向cache写入的时候,也是需要同时写入主存的,只不过写入主存这个任务并不由cpu直接实现,cpu把数据直接写入write buffer,然后就可以执行下一步了,后续会有其他硬件处理write buffer->主存的步骤。这样的目的是提速很多。
  3. write back(回写):cpu只是将数据写入cache,并不同步到主存,优点是cpu执行效率提高,缺点是实现技术比较复杂。

在cache miss时的两种策略:

  1. no-write-allocate cache:在write cache miss时,直接将数据写入主存而没有cache操作,一般而言,write through(通写)会采用no-write-allocate cache策略。
  2. write allocate cache:当write cache miss时,分配cache line,并将数据从主存读入,之后再进行数据更新的动作,一般而言,write back的cache会采用write allocate的策略。

###cache的命中

  • 假定一个cpu中年的数据cache为16K, cache line = 32字节。那么16K的cache是由512条cache line组成的。由于每条cache line = 32 = 2^5,所以cache块内偏移用5个字节表示即可。
  • 这样每一条cache line 可表示为 |Tag|VA|Offset|Data|,其中Tag + VA + Offset = 4字节,Data = 32字节,其中Data是这个cache line中存放的数据,Offset就是我们前面说的cache块内偏移,大小为=5bit,用这个Offset可以定位一个cache line中的任何一个字节。Tag + VA = 27字节,根据不同的缓存算法,二者长度的分配不同。
  • 在所有的cache算法中,主存的分块大小和cache的分块大小都是相同的,故这里将主存也是分为32字节一块的,故主存中共有2^27个块。前面说到的Tag是用来区分当前主存中的地址块应该映射到缓存的哪个组中的,VA用来区分是组中的哪个块的。
    ####全相连算法
  • 在这种情况下cache被分为1组,每组512个块。此时Tag = 27, VA = 0, Offset = 5。
  • 主存的任意地址块可以映射到cache的任意地址块,此时主存中的2^27个块可以映射到cache中的512个组中的任意一组。所以整个Tag就用27bit来表示,当前这个cache中存的是主存中的哪一个块。
  • 此种映射的优点在于,cache miss最小,但每次cache查找都要遍历512个块,效率低。

####直接映射算法

  • 在这种情况下cache被分为512组,每组1个块。此时Tag = 18,VA = 9, Offset =5。
  • 主存的地址x所在的地址块Y(=x/32),只能被映射到Y%512这个组中,故每组中有2^18个可能的地址块,所以Tag用18位即可区分某个cache为哪个主存地址块的缓存。VA用9位,表示当前主存的缓存在哪个组中,正好代表512个组之一。Offset同样是每个cache块中的偏移。
  • 此种映射的优点在于,速度快,因为根据VA计算出当前主存地址块存放的唯一组,而此组中只有唯一cache块,故可以直接定位到具体cache块,缺点就在于cache miss最高。
    ####组相连算法
  • 这里以cache 被分为8组,每组64个块为例。此时Tag = 24, VA = 3, Offset = 5。
  • 组相连算法是前二者的这种,512条cache line分成8组,每组64条。主存地址x所在的地址块Y(=x/32),只能被映射到Y%8这个组中,故每组中有2^24个可能的地址块,所以Tag用24位来区分某个cache为哪个主存地址块的缓存。VA用3位,表示当前主存的缓存在哪个组中,正好代表8个组之一。Offset同样是每个cache块中的偏移。
  • 此种映射中和了前两种,对每次cache的查找,需要在64个块中匹配,但同时也提高了cache的命中率。

###物理地址/虚拟地址
当cpu发出地址访问的时候,从cpu出去的地址是虚拟地址,经过MMU的映射,最终变成物理地址,此时的问题是:

  1. 我们用虚拟地址还是物理地址来寻找cache set?
  2. 找到cache set后,用虚拟地址还是物理地址来匹配cache line?
    因此引出三种解决方案:
  3. VIVT(Virtual index Virtual tag),寻找cache set的index(index就是前面的VA字段,是用来找到当前块对应的cache组的,cache set相当于cache组的意思)和匹配cache line的tag都是使用物理地址。其好处是,匹配具有唯一性,但由于**开启MMU后,cpu输出的已经是虚拟地址了,**此时MMU译码需要消耗一定的时间。
  4. PIPT(Physical index Phsical tag),寻找cache set的index和匹配cache line的tag都是使用虚拟地址。其好处是,cpu直接输出虚拟地址,省略了译码的时间,但匹配可能不具有唯一性,可能存在cache ambiguity和cache alias的问题。
  5. VIPT(Virtaul index Physical tag),寻找cacheset的index使用虚拟地址,而匹配cache line的tag使用的是物理地址,这种方法是二者的这种,不会存在cache ambiguity问题,可能也会存在cache alise的问题,但是可以避过的。
    ##每cpu变量
  • 多核的情况下,cpu是同时并发运行的,但多个cpu共同使用其他的硬件资源,因此需要解决多个cpu之间的同步问题。每cpu变量(per-cpu-variable)是内核中一种重要的同步机制,为每个cpu构造一个变量的副本,这样多个cpu互相操作各自的副本,互不干涉。current_task(当前进程)就是一个每cpu变量。
  • 每cpu变量的特点:
  1. 用于多个cpu之间的同步,如果是单核结构,没有必要使用每cpu变量。
  2. 每cpu变量不能解决由中断或延迟函数导致的同步问题。
  3. 访问每cpu变量的时候,一定要确保关闭进程抢占,否则一个进程被强占后可能会更换cpu运行,会导致每cpu变量的引用错误。
  • per cpu变量不是通过普通的数组实现的,因为有cpu cache,如果用数组来实现,很可能每个cpu都将这个数组装入了cache,每个cpu修改了其内容,都会导致cache失效,频繁的失效会导致性能急剧下降(如果以cache line大小分开,应该就ok了)。

#参考资料
[1]. http://server.51cto.com/sCollege-198840.htm
[2]. http://publish.it168.com/2007/0124/20070124003601.shtml
[3]. http://blog.163.com/huang_bp/blog/static/123119837200911305045437/
[4]. http://www.wowotech.net/basic_subject/memory-hierarchy.html
[5]. http://blog.sina.com.cn/s/blog_62e4d8800100mosm.html
[6]. http://www.wowotech.net/linux_kenrel/per-cpu.html
[7]. http://blog.chinaunix.net/uid-26817832-id-3244916.html
[8]. http://blog.chinaunix.net/uid-10701701-id-91727.html
[9]. http://blog.csdn.net/hsly_support/article/details/7664689
[10]. 多处理器系统中的线程调度研究,范光雄,电子科技大学

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值