pcid对内核页表切换的优化

前一段时间碰到一个pcid相关的bug,于是就把内核对pcid的支持代码看了一遍。年龄大了看过的东西时间一长就忘了,把要点记下来既方便自己又有利他人。我看的是公司发行版的内核uek4基于upstream的v4.1.12。upstream的pcid支持同旧版本相比用了一套完全不同的更优化的方式,所以另开一篇文章来写。

不同的进程有不同的地址空间用mm_struct表示,进程调度时内核线程借用用户进程的地址空间并使cpu进入LAZY模式,当mm地址空间有更新的时候,cpu会发送tlb shutdown给相关cpu通知他们进行刷新。对于LAZY模式的cpu刷新是没有意义的,因为内核线程永远不会访问用户空间,所以leave_mm被调用,使得当前cpu脱离用户进程mm,并绑定到init_mm,这样cpu不会再收到tlb shutdown。

PTI的引入使得一个进程有两个页表,内核一次分配8K的页,4k用于内核pgd,另外4k用于用户pgd。用户页表映射了用户态地址,gdt,idt,tss,内核entry/exit代码等在页表切换之前就需要的内核地址空间,这部分空间会打上global标记,而其他的普通内核地址空间在内核页表中被去掉了global标记。

这样每次系统调用都会发生页表切换,几乎所有tlb表项会被刷新,代价很大。下面是一个对比数据,每秒的系统调用数从5.2M降到了2.2M!
    no kaiser: 5.2M
    kaiser+  pcid: 3.0M
    kaiser+nopcid: 2.2M

比较新一点的cpu都支持pcid,pcid可以在页表切换的时候避免cpu刷新tlb。4.1.12用asid=0标记内核页表,asid=0x80标记用户页表,于是用户态系统调用进入内核态和退出内核态不用再刷新页表,直到调度器调度另一个进程,内核页表被刷新。之前的用户页表得以保留。内核同时设定一个标记使得新进程返回用户态时刷新一次用户页表,这个标记是一次性的。

这样优化以后性能从2.2M上升到了3.0M。但是这种设计在TLB中只缓存一个进程的表项,在no kaiser时pcid没有发挥任何效果,于是upstream给出了一套新的设计。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章