关于pgoff_to_pte宏

#define pgoff_to_pte(off) /
         ((pte_t) { (((off) & 0x1f) << 1) + (((off) >> 5) << 8) + _PAGE_FILE })
这 个宏将页面偏移转化为页表项,实际上这个页表项一点也不规则,但是它却满足一切页表项的性质,除此之外,它根本没有用,只是一个占位的作用,这个页表项的高20位也不是什么页面偏移,因为最低位为0,那么高20位就没有意义了,这个页表项存储的是一些信息,以便将来用到的时候可以取到这些信息,之所以用页 表项来存储信息这是硬件的要求。也不是说用就用的,用之前你必须理解页表项每个位的意义,哪些位固定,哪些位保留,哪些位可以赋予不同的含义,这些如果不理解就直接把页表项作为一个32位变量来存储你的私有数据很容易造成硬件误判从而导致系统崩溃,比如你将最低一位也存储了你的私有数据(这个页表项根本不指向任何页面),而且恰好是1,那么硬件访问到该页表项指向的页面时就会毫不犹豫地取出该项的高20位最为页面偏移然后读写,因为高20也存储的是你的私 有数据,比如是d,而d指向的页面不是该进程的页面,是什么鬼才知道,这样本该缺页的却成功读写了,只不过读写的不是想要读写的页面,这样是很危险的。所以理解各个位的意义是必要的。下面先简要介绍一下页表项各个位的意义:
A-高20位:指向物理页面偏移;
B-保留位和软件可用位:在页表 项中,第7位保留,而且要置0;在指向页表的页目录项中,第6位保留,而且要置0。对于一个4MB页面的页目录项来说,第21~12位保留,而且要将它们 全部置为0。因为对于页表项和页目录项来说,第11、10和9位是由软件来使用的,当控制寄存器CR4中的PSE或PAE标志置位时,如果保留位没有置 0,处理器会产生页面错误;
C-第8位:全局标志位。G标志在P6系列处理器中定义。置位时,表示为全局页面,在页面标址为全局页面且CR4中的页面全局允许(PGE)标志置位时,加载寄存器CR3或产生任务切换,该页面的页表项或页目录项在TLB中不会失效。定义G标志目的是防止频繁使用的页面 (像包含内核或操作系统其他代码的页面)在TLB中被清洗。只有软件才能置位或清除该标志;
D-第7位:页面规模(PS)标志。确定页面规模,该标志只在页目录项中使用,PS标志清零时,页面规模为4KB,而且页目录项指向页表。PS标志置1时,对于常规32位寻址,页面规模为 4MB(如果允许扩展物理寻址,则页面规模为2MB)而且页目录项指向一个页面。如果页目录指向页表,则与该表相关的所有页面都是4KB的页面;
E-第6位:页面重写(D)标志。D标志有时形象地称它为脏位,置1时,表示页面已经被写过。在页面一开始加载到物理存储器时,一般由存储器管理软件将该标志清0。然后在页面第一次被写入时,处理器将该标志置1。该标志是一种“粘贴”标志。
F- 第5位:访问(A)标志。A置位时,表示页面或页表已被访问过。当页面或页表在一开使加载到物理存储器时,一般由存储器管理软件清除A标志。然后在页面或 页表第一次被访问时,处理器将A标志置位,该标志是一种“粘贴”标志,就是说,一旦被置位,处理器就不会将它清除,只有软件才能清除该标志,访问标志和下面的页面重写标志用于存储器管理软件的页面调度;
G-第3位:页面级写直达(PWT)标志。控制页面或页表的写直达或写回的高速缓存写策略。该标志置位时,相应的页面或页表允许写直达到高速缓存。该标志清零时,相应的页面或页表允许回写高速缓存。如果CR0中的CD(缓存禁止)标志置1,PWT标志无意义;
H-第2位:用户/系统(U/S)标志。为页面或页面组(对页目录项而言)指定用户系统特权,U/S标志清0时,该页分配的是系统特权级;将该标志置位时,该页分配的是用户特权级;
I-第1位:读/写(R/W)标志。为页面或页面组(对页目录项而言)指定读写特权。将该标志清0时,页面为只读,将该标志置位时,页面可以读和写。该标志与U/S标志和CR0寄存器中的WP标志一起使用;
J- 第0位:存在(P)标志。表示由“项”所指的页面或页表当前是否加载到了物理存储器中,该标志置位时,页面在物理存储器中存在,表示可以进行地址转换。该标志清0时,说明页面不在物理存储器,如果处理器对该页面进行访问,会产生缺页异常(#PF)。 如果产生缺页异常,操作系统将进行把该页页面从磁盘存储器复制到物理存储器以及维护页目录项或页表项操作,并继续执行被中断的程序。
根据上面的J的约定,我们如果想用页表项保存我们的私有数据,那么为了不让硬件把它作为寻址页表项,我们就必须将第0位清零,又根据B约定,作为页表项第 7位一定要清零,至于别的位完全依赖第0位,事实上也只有页面存在的时候才有“脏”,“可读/可写”等等之说,所以这些相应的标志位我们可以用。于是如果我们要存储一个32位值x,必然的要把它进行拆分,为什么呢?因为我们要避开第0位和第7位,而这两个位不连续,如果用最简单的方式,为了避开0位和7 位,那么直接向左移8位不就得了吗?是的,完全可以,但是想到这个值如果很大的话,比如超过24位的话,左移8位导致高位溢出,也就是说用左移8位的方式 我们只能用页表项存储不超过24位的数,但是考虑一下如果要存储文件以页面大小的偏移就不合适了,一个页面4k,拥有2的24次方个页面的文件就是64g 的大小,而大型应用中超过64g大小的文件多的是,不说别的,看过高清电影吧,看看多大,64g也就是个片头!因此用这种方式很影响伸缩性,如果把32位 的页表项都用上就能处理拥有2的32次方个页面的文件,但是这无法实现,毕竟要保留第0位和第7位,这样我们最多能处理2的30次方个页面的文件,等等, 再想想,第7位保留是一定的,第0位为0只是说明页面没有在内存,也就是说映射无效,那怎样才能说明这次我们存储的是一个文件按页面大小的偏移呢?因此还要有一位保留来说明我们这次在页表项里面存储的是一个文件偏移,到底选择哪一位呢?根据B,第7位置0表明这是一个页表项,那么第六位就没有用处了,第六 位置0的话表明这是一个页目录项,因此我们就选择使用第6位,将它置1,以表示这个页表项实际存储的是一个文件偏移,另外要注意的就是第8位了,它是为了不失效tlb才置位的,不置位的情况下,只要一重新加载cr3寄存器就要失效tlb,这看起来很严重,如果不失效tlb也是导致系统崩溃,但是仔细想一 下,什么情况下页面映射结果才会进入tlb,只有在页面根据页表项成功访问的时候才会,而现在我们存储文件偏移的时候第0位清零根本不可能成功访问,因此第8的值并不影响任何事情,我们可以放心使用。该考虑的都考虑到了,那么怎么实现这一切呢?现在再看看pgoff_to_pte是不是就简单很多了呢?
我们去掉不相关的东西,pte_t就是一个32位无符号长整型,再去掉一些括号,因此((pte_t) { (((off) & 0x1f) << 1) + (((off) >> 5) << 8) + _PAGE_FILE })化为((off & 0x1f) << 1) + ((off >> 5) << 8) + _PAGE_FILE,最后的_PAGE_FILE正是0x40,也就是二进制的1000000,这就是我刚才分析的用第六位表示我们存储了一个文件偏 移,最后加上它就是为了将第六位置1,前面还有两部分(off & 0x1f) << 1和(off >> 5) << 8,然后我们进一步把这两部分分解,off & 0x1f就是保留低5位,将结果左移一位就是将最终结果的第0位清零,这样(off & 0x1f) << 1的结果占据了最终结果的低6位,它清除了off的比5位高的位,下面看看(off >> 5) << 8,因为off的低5位被(off & 0x1f) << 1这部分处理过了,那么off右移5位是没有什么影响的,不会丢失任何数据,然后将结果又左移8位,这是为什么呢?先右移5位再左移8位实际上就是直接左移(8-5)位,然后将低5位清零,毕竟低5位已经被存储到第1到5位了,为什么要左移3位呢?往上看,左移的目的就是空出必须要清零的第7位,还有就是 设置文件标志的第6位,空两个位为何要左移3位呢?不要忘了,处理低5位的时候已经将低5位的结果左移了1位而空出了第0位。这样3部分的和恰好就是最终 的“页表项”,一个位也没有被浪费。这样这个页表项最大能存储的文件偏移就是2的29次方了,根据同样的道理反推回来就从这个页表项还原了我们的文件偏 移,不过这就是缺页中断要考虑的事情了。
看来小小的宏里面的道理这么多,如果我写的话我绝对没有这么“负责”,我会使用直接左移8位的方法,至于能处理的文件大小的限制我才不管呢!我就是不提供那么大的范围,看你能把我怎么样!看来写内核的那帮家伙真是该想到的都想到了

原文链接: https://blog.csdn.net/dog250/article/details/5303232

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    关于pgoff_to_pte宏

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/410479

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 下午12:02
下一篇 2023年4月26日 下午12:02

相关推荐