有两点注意:1.有些体系结构上tlb不命中和tlb实效是两回事,不命中的意思是tlb表项的有效位为1,存在位为0,而tlb无效是有效位为0,存在位无意义,当tlb无效的时候,预取逻辑就要查页表,并且不把结果回存进tlb,只有当用户实际访问该地址(而不是预取)时才将查表结果存入tlb,当tlb不命中的时候,还是查页表,但是此时会将结果存入tlb;2.x86的tlb查表如果不命中的话,就会将查页表的结果直接输出,而别的一些体系结构不命中 tlb的时候,会先将地址映射存入tlb,然后从tlb输出。我很想说清这个问题,再看看下面的解释吧,我相信这下没有什么问题了。
tlb命中是得到了物理地址,但是cpu是不会直接将这个物理地址放到地址总线去内存取数据的,而是先会查一下cache里面有没有这个物理地址对应的数据,如果没有的话再阻塞然后去物理内存取,这个确实有一些复杂,我想了好久都没有想出一个例子讲这些阐述清楚,有时自己也能被绕进去,不管怎样我还是想尽力去说清,本来表达能力就不好更应该练练了,呵呵。
举个终结例子吧,tlb中存的是页表缓存,其实是“虚拟地址<->页表项”对,如果tlb命中,那么就会从tlb对应的“页表项”中取得一个物理地址,然后取这个物理地址的数据,cpu会先从cache中取,如果没有再访问内存,这时就不再需要通过页目录-〉页表的方式访问内存了,而是直接用从tlb中得到的物理地址访问内存,接下来我试图给个终结者场景:cpu预取逻辑预取一个地址的数据,但是这个地址在tlb没有命中,于是cpu预取逻辑开始按照页目录->页表的方式进行扫描,当然它要访问页目录,页表,这时它要访问页表了,加上地址偏移得到页表项,mmu先看看cache里面有没有页表项,有还是没有呢?没有,没有页表项的数据,于是 cache要从内存取回页表项物理地址对应的数据,其实就是一个页表项pte,于是阻塞将页表项读到cache,在cache取页表项的路上,页表项所在的页表被释放并重分配为其他用途并将这个要取得的页表项的G位设置1,此时cache取信号到了,取回的页表项将是无效的页表项,带有G位,并把它存到 TLB,并且将永存于TLB(因为G),然后从这个页表获得的数据也将是无效数据,比如本来需要取一个“add”指令,结果取回的这个带有G位的页表项对应的高20位指示的地址里面却是“fuck”,于是等到执行这个fuck的时候就真的fuck了...
希望这下说清楚了,这个场景是一个完整的访问流程。
加载cr3就刷新了tlb,之后预取逻辑会重新开始页目录-〉页表扫描,注意,flush tlb或者load cr3将时间分为了三个部分,分别为刷新前,刷新时,刷新后,flush之前的部分保证页表还没有被释放,因此不可能出现页表被重分配然后设置G位,然后存到tlb不被刷新影响的情况;刷新时,所有的tlb将失效,至于已经预取的指令,则由cpu执行逻辑和分支预测逻辑判断,反正tlb中的东西都无效了;刷新后,因为tlb无效了,因此预取逻辑必须按照页目录-〉页表方式取得物理地址并且可能的情况下添到tlb,可是此时的页表项或者页中间目录项已经设置为“页不在内存”了,也就是最低位已经是0了,那么预取将不再进行。这里关键是要使得已经给出的 "cache从内存读页表项的请求"失效, 而回滚从头来一次。
关于linux输入子系统的通知
今天早上打开邮件列表,发现有个人提交了一个补丁,是“Input notifier support”大致看了一下,感觉不错,到我惊叹的时候,我只是一味的觉得内核的开发者的思维就是开阔,哪里有需求,哪里就有代码,那么在我提出相反观点之前,我还是先说一下这个补丁吧。
这个补丁主要解决input子系统和别的系统通信问题,现在的问题是,input子系统设计的非常好,想想我们使用计算机的方式,就是给计算机一个输入,然后等待计算机的输出,input子系统涵盖了计算机输入,而输出可以以可视的形式显示在终端,又可以驱动一些设备,因此计算机基本就是三部分,输入,计算,输出,而输入很有必要单独成为一个子系统,可见input子系统的重要,它有必要在有动作的时候进行一些通知,因为输入动作虽然是单纯的输入,但是有时候有必要告诉别的系统有了输入,比如按下某些键盘的按键的时候,led灯必须闪烁,而再按下或者放开的时候,该灯就要熄灭,人们虽然很注意输入过后的输出,另外还对输入事件的确认很在乎,因此这个补丁就提出来了,其实补丁提出来之前,输入的某些确认照样可以被感知,但是方式却很土,比如在按下一个按键的时候,特定的代码会写死到相关的按键处理里面,一改则大改,很不方便。这个哥们提出了一个补丁,用通知链的技术解决了这个问题,通知链技术可以用统一的方式通知一切相关的感兴趣的实体,这个补丁就是注册了一个通知链,然后在特定的输入事件触发时,通知这个链子上的所有感兴趣者就可以了。
void input_event(struct input_dev *dev,
add_input_randomness(type, code, value);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
+ if (type == EV_KEY)
+ input_notifier_call_chain(value, &code);
}
}
+int input_register_client(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&input_notifier_list, nb);
+}
+int input_notifier_call_chain(unsigned long val, void *v)
+{
+ return blocking_notifier_call_chain(&input_notifier_list, val, v);
+}
就是以上那么简单,需要被输入事件通知的只需要注册一个notifier_block就行了,就是调用input_register_client就可以,在输入事件触发的时候就调用input_notifier_call_chain来触发被通知的事件。这样就用通知链将不相干的模块联系了起来,如果按照这个补丁的思想,内核中任何的模块都能通过通知链联系起来,可是这样可以吗?内核代码早就实现了通知链,为何到了2.6.29了还没有用通知链联系很多东西呢?我觉得可能内核压根就不想这么做,因为内核没有必要耦合这些模块,linux十分反感这种高耦合的东西,linux总是将很急迫的两个模块直接耦合,不是很急迫的总是通过一个二传手,最好的二传手就是用户空间,一般都是通过netlink将事件导到用户空间,然后用户空间少处理后在传入内核的另外一个模块,对于输入的通知,到底该怎么做呢?
我个人认为这个补丁虽然解决了问题,但是却开了一个不好的头,以后内核中将大量充斥这种强耦合的代码,其实这里不能叫做强耦合,因为通知链本身就是一个粘合层,可是要知道通知通知链的实体是同步进行的,这势必要增加处理时间,如果换成异步方式通知,那么就要修改内核通知链架构,而且我们看一下这个输入通知,如果仅仅是反馈一个输入事件比如闪烁一个灯,那么根本没有必要用这么耗时的处理机制,于是我提出我的改进方式。
这个哥们提出的输入通知思想非常好,可是我不太赞同他的实现,我的实现有两种,第一就是用netlink通知用户空间,然后用户空间再用netlink通知需要被通知的实体,用户空间启动一个守护进程充当这个二传手,还有一种方式就是实现异步方式的通知链,这样的话通知链解除了两个通信实体之间的耦合,而异步的通知链技术不再延迟通知的执行,我准备明天开始写一个异步通知链补丁,然后提交到内核maillist。
附:/dev目录和/sys目录
这连个目录都和设备有关系,/sys用于设备的是管理,/dev则是抽象出来的真实地可以操纵的设备,sysfs就是内核的设备模型的kobject组合成的提供给用户空间的管理实体,而/dev则是实际的设备