文档章节

内存管理之4:页面映射中的结构体

我叫半桶水
 我叫半桶水
发布于 07/14 10:43
字数 2581
阅读 9
收藏 0
PMD

date: 2014-09-10 19:09

备注:本文中引用的内核代码的版本是2.4.0。

在前面的文章中,我们介绍了linux页式内存管理,讲到了页面目录PGD、中间目录PMD以及页表PT,本文来看下内核中对应的结构体定义。

1 页表项pte_t以及相关操作

PGD、PMD以及PT分别是由pgd_t(页面目录项)、pmd_t(中检目录项)以及pte_t(页表项)构成的数组,这些表项(虽然只有32位)被定义成结构体,定义在<asm/page.h>中:

/*
 * These are used to make use of C type-checking..
 */
#if CONFIG_X86_PAE
typedef struct { unsigned long pte_low, pte_high; } pte_t;
typedef struct { unsigned long long pmd; } pmd_t;
typedef struct { unsigned long long pgd; } pgd_t;
#define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32)) 
#else
typedef struct { unsigned long pte_low; } pte_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pgd; } pgd_t;
#define pte_val(x) ((x).pte_low)
#endif
#define PTE_MASK PAGE_MASK

typedef struct { unsigned long pgprot; } pgprot_t;

#define pmd_val(x) ((x).pmd)
#define pgd_val(x) ((x).pgd)
#define pgprot_val(x) ((x).pgprot)

#define __pte(x) ((pte_t) { (x) } )
#define __pmd(x) ((pmd_t) { (x) } )
#define __pgd(x) ((pgd_t) { (x) } )
#define __pgprot(x) ((pgprot_t) { (x) } )

可见,当采用32位地址时,pgd_t、pmd_t和pte_t就是无符号整形数。为什么要定义成结构体呢?一方面是为了方便后续的扩展;另一方面就像面向对象中的封装一样,这里也是一种封装,并定义了相关的“访问器”函数。以pte_t为例,通过pte_val宏来访问结构体中成员,另外通过set_pte来设置结构体。宏set_pte定义在<asm/pgtable-2level.h>中:

/*
 * Certain architectures need to do special things when PTEs 
 * within a page table are directly modified. Thus, the following 
 * hook is made available.
 */ 
#define set_pte(pteptr, pteval) (_(pteptr) = pteval)
/* 
 * (pmds are folded into pgds so this doesnt get actually called,
 * but the define is needed for a generic inline function.)
 */
#define set_pmd(pmdptr, pmdval) (_(pmdptr) = pmdval)
#define set_pgd(pgdptr, pgdval) (_(pgdptr) = pgdval)

前面我们讲过,物理页面是以4K为边界对齐的,意味着每个物理页面的起始地址(当然是物理地址)的低12都为0,只有高20位是有效的。内核中有一个物理页面Page的数组mem_map,每个物理页面对应mem_map数组中的一个元素,而数组的下标就是物理页面的序号。物理页面在数组mem_map中按起始地址顺序存放,因此我们可以根据页面序号得到页面的起始地址,很简单,将页面序号乘以4K(即左移12位)就可以得到页面的起始地址。从这个意义上来讲,物理页面起始地址的高20位可以看做是页面序号。

物理页面起始地址只有高20位是有效的,那么作为指向物理页面起始地址的页表项pte_t,作为指针只需要它的高20位,所以pte_t中的低12位就挪作他用,用来表示页面的状态信息和访问权限。但在页表项pte_t结构的定义中,并没有以位域的方式体现出来,内核为此单独定义了一个用来表示页面保护的结构pgprot_t,它的定义也在上面的代码中,并且内核也位置定义了“访问器”函数。

虽然pgprot_t结构被独立出来了,但一个页面对应的页面保护信息仍然保存在页表项pte_t的低12位中,这里只是为了程序设计的方便单独为页面保护信息抽象出一个结构体。我们可以根据物理页面的起始地址以及页面保护结构pgprot_t拼凑出一个页表项pte_t,内核中__mk_pte宏就是用来干这件事的,宏定义在<asm/pgtable-2level.h>中:

#define __mk_pte(page\_nr,pgprot) \__pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot)) 

将物理页面的序号左移12位得到页面起始地址的高20位,然后位或上低12位的页面保护结构就可得到物理页面对应的页表项pte_t了;那么反过来,从pte_t得到对应的物理页面的Page结构也是顺理成章的了,在同一文件中定义了pte_page宏:

#define pte_page(x) (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT)))) 

数组mem_map的起始地址加上对应的下标,即得到对应元素的地址了。

pgprot_t结构被定义成了一个无符号整形数,但有效的只有其低12位,与pte_t中的低12位对应,其中9位是标志位,表示页面的当前状态和访问权限(具体含义参考《x86页式内存管理》),这些标志位在<asm/pgtable.h>中定义如下:

#define _PAGE_BIT\_PRESENT 0
#define \_PAGE_BIT_RW 1
#define _PAGE_BIT\_USER 2
#define \_PAGE_BIT_PWT 3
#define _PAGE_BIT\_PCD 4
#define \_PAGE_BIT_ACCESSED 5
#define _PAGE_BIT\_DIRTY 6
#define \_PAGE_BIT_PSE 7 /* 4 MB (or 2MB) page, Pentium+, if present.. */
#define \_PAGE_BIT_GLOBAL 8 /* Global TLB entry PPro+ */

#define _PAGE_PRESENT 0x001
#define _PAGE_RW 0x002
#define _PAGE_USER 0x004
#define _PAGE_PWT 0x008
#define _PAGE_PCD 0x010
#define _PAGE_ACCESSED 0x020
#define _PAGE_DIRTY 0x040
#define _PAGE_PSE 0x080 /* 4 MB (or 2MB) page, Pentium+, if present.. */
#define _PAGE_GLOBAL 0x100 /* Global TLB entry PPro+ */

#define _PAGE_PROTNONE 0x080 /* If not present */ 

利用这些标志位,我们就可以判断处对应页面的状态,相关的宏定义如下:

    <asm/pgtable-2level.h>
    #define pte_none(x)		(!(x).pte_low)
    
    <asm/pgtable.h>
    
    #define pte_present(x)	((x).pte_low & (_PAGE_PRESENT | _PAGE_PROTNONE))
    /*
     * The following only work if pte_present() is true.
     * Undefined behaviour if not..
     */
    static inline int pte_read(pte_t pte)		{ return (pte).pte_low & _PAGE_USER; }
    static inline int pte_exec(pte_t pte)		{ return (pte).pte_low & _PAGE_USER; }
    static inline int pte_dirty(pte_t pte)		{ return (pte).pte_low & _PAGE_DIRTY; }
    static inline int pte_young(pte_t pte)		{ return (pte).pte_low & _PAGE_ACCESSED; }
    static inline int pte_write(pte_t pte)		{ return (pte).pte_low & _PAGE_RW; }

对内核来说,当页面表项的内容为空(即值为0)表示尚未为对应的虚存页面建立映射。回想下逻辑地址映射的过程:利用逻辑地址的高10位在目录表中查找到对应的目录项,此目录项指向一个页表,再利用逻辑地址的中间10位在页表中查找对应的页面表项。按道理说,该页表项应该指向物理页面的起始地址(物理地址),但现在页面表项的值为0,即说明对应的虚存页面尚未映射到某个物理页面上。内核用pte_none宏来检测这种情况。

如果页面表项pte_t非空,但P(Present)位为0,则表示映射已经建立,但对应的物理页面不在内存中(已经换出到交换设备上了)。内核用pte_present宏来判断pte_t对应的物理页面是否在内存中。

pte_read等宏检查pte_t中的相关位是否置1,从而得到页面的相关状态和权限。当然这些只有当P位为1时才有效。

2 MASK && SIZE

前一篇文章提到,在将linux三层页式映射模型落实到intel的两层页式映射之上时,内核(2.4.0版本)采用让中间目录PMD“名存实亡”的方案,我们将相关细节集中展示在这里:

    <asm/page.h>
    
    /* PAGE_SHIFT determines the page size */
    #define PAGE_SHIFT 12
    #define PAGE_SIZE	(1UL << PAGE_SHIFT)
    #define PAGE_MASK	(~(PAGE_SIZE-1))
    
    
    <asm/pgtable-2level.h>
    
    /*
     * traditional i386 two-level paging structure:
     */
    #define PGDIR_SHIFT	22
    #define PTRS_PER_PGD	1024
    
    /*
     * the i386 is two-level, so we don't really have any
     * PMD directory physically.
     */
    #define PMD_SHIFT	22
    #define PTRS_PER_PMD	1
    #define PTRS_PER_PTE	1024
    
    
    <asm/pgtable.h>
    
    #define PMD_SIZE	(1UL << PMD_SHIFT)
    #define PMD_MASK	(~(PMD_SIZE-1))
    #define PGDIR_SIZE 	(1UL << PGDIR_SHIFT)
    #define PGDIR_MASK 	(~(PGDIR_SIZE-1))

PGDIR_SHIFT、PMD_SHIFT和PAGE_SHIFT分别表示虚拟地址(即经过段式映射后的线性地址,后文对线性地址和虚拟地址不作区分,认为它们是同一个东西)中页面目录位段、中间目录位段以及页表位段的划分情况,示意如下(偷图自《深入理解linux虚拟内存管理》):

shift

PGDIR_SIZE、PMD_SIZE和PAGE_SIZE分别根据PGDIR_SHIFT、PMD_SHIFT和PAGE_SHIFT来定义,分别表示一个目录项(pgd_t)、一个中间目录项(pmd_t)和一个页面表项(pte_t)所能“领衔”的地址空间的大小。比如一个页面表项指向一个物理页面,它所能“领衔”的地址空间的大小就是4K(1 << 12)。而一个目录项指向1个页表(两层映射的场景下),一个页表共有1024个页面表项,代表1024个页面,每个页面4K,故一个目录项代表4M(1 << 22)的空间

PGD_MASK、PMD_MASK和PAGE_MASK分别由PGDIR_SIZE、PMD_SIZE和PAGE_SIZE来定义。分别表示虚拟地址中页面目录位段、中间目录位段以及页表位段的掩码。将虚拟地址与这些掩码相位与,即可得到对应的位段。比如目录位段为虚拟地址的高10位,那么目录位段的掩码PGD_MASK应该是高10位为1,低22位全为0。MASK与SIZE的对应关系描述如下:

mask

回到正题,2.4.0内核是如何让中间目录项PMD名存实亡的呢?

在上面的代码中,内核将PGD_SHIFT定义为22,PTRS_PER_PGD定义为1024。毫无疑义,虚拟地址的高10位用作页面目录位段,故PGD_SHIFT定义为22。页面目录表PGD中有1024个目录项,这与页面目录位段共有10bit相对应,这就是PTRS_PER_PGD的含义。而PMD_SHIFT也被定义成22,对应的PTRS_PER_PMD被定义成1。显然这是内核玩的花招,让PMD位段在虚拟地址中占0个bit,还“装模作样”的定义了中间目标表中中间目录的个数为1个。

需要指出,linux的三层映射只是软件设计上的概念,表示一种抽象。而在intel上,两层页面映射是由MMU硬件完成的,只要我们设置好了CR3寄存器,MMU硬件自动帮我们完成页面映射(查页面目录表找到对应的目录项,该目录项指向一个页表,再从页表中找到对应的页面表项等等,全程不需要CPU的参入),它压根儿就不认PMD(只认PGD和PT),更不在乎内核耍了什么花招让PMD“名存实亡”,让PMD“名存实亡”只是软件上的诉求,只是为了“套上”linux的“三层映射”模型。之前都是内核欺骗CPU,感觉这次像是内核自己欺骗自己了。

© 著作权归作者所有

共有 人打赏支持
我叫半桶水
粉丝 0
博文 26
码字总数 71642
作品 0
西安
私信 提问
andriod binder-ServiceManager守护进程

ServiceManager是一个守护进程。它的main()函数源码如下: frameworks/native/cmds/servicemanager/service_manager.c int main(int argc, char **argv){ } /frameworks/native/cmds/service......

android开发
2017/12/08
0
0
Binder进程间通信(五)----内核虚拟地址空间管理

上一篇笔记分析到了Binder驱动在调用mmap创建内存映射的过程。但是在分配物理内存的时候,我们只分配了一个页面。当我们需要更多物理内存的时候,Binder驱动才回去继续分配物理内存。 另外,...

街角的小丑
2017/10/26
0
0
Linux内存管理之mmap详解

一. mmap系统调用 1. mmap系统调用 mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反...

zhangyujsj
2016/03/12
52
0
Linux内存管理原理

本文以32位机器为准,串讲一些内存管理的知识点。 1. 虚拟地址、物理地址、逻辑地址、线性地址  虚拟地址又叫线性地址。linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户...

天天顺利
2015/12/18
66
0
02@ V4L2框架分析学习二

v4l2_device v4l2device在v4l2 框架中充当所有 v4l2subdev的 父设备,管理着注册在其下的子设备。 以下是v4l2_device结构体原型( 去掉了无关的成员 ): 可以看出 v4l2_device 的主要作用是 ...

singledevil0
04/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

mac 下 mysql 8.0.13 安装并记录遇到的问题 以便以后查看

安装 官网mysql 下载地址 安装过程 省去 安装好之后 下载navicat 错误1 链接 遇到 mysql 2003 - Can't connect to MySQL server 错误, 解决方案 重启mysql 服务 #错误2 ERROR 1045: Acces...

杭州-IT攻城狮
30分钟前
3
0

中国龙-扬科
33分钟前
1
0
[Spring4.x]基于spring4.x纯注解的Web工程搭建

在前文中已经说明了如何基于 Spring4.x+ 版本开发纯注解的非web项目,链接如下: https://my.oschina.net/morpheusWB/blog/2985600 本文则主要说明,如何在Web项目中,"基于spring纯注解方式...

morpheusWB
今天
13
0
基础编程题目集-7-13 日K蜡烛图

股票价格涨跌趋势,常用蜡烛图技术中的K线图来表示,分为按日的日K线、按周的周K线、按月的月K线等。以日K线为例,每天股票价格从开盘到收盘走完一天,对应一根蜡烛小图,要表示四个价格:开...

niithub
今天
5
0
Jenkins window 下的安装使用

1.下载:https://jenkins.io/download/ 双击安装完毕,将自动打开浏览器: http://localhost:8080 打开对应位置的文件,将初始密钥粘贴至输入框。 第一个是 安装默认的软件;第二个是 自定义...

狼王黄师傅
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部