LINUX 2.4.22 内存管理 之四 页表管理【上】


上一篇:物理内存初始化(二)

下一篇:页表管理【中】

前言

linux 采用独立于底层架构的三级页表,从概念上容易理解,但也意味着不同类型架构的页面之间的差异变得模糊,这种结构不同于其他OS的页表结构,其他os需要管理最下层的物理页面,例如bsd 的 pmap
非3层架构的mmu将模拟这种三级页表结构,例如,不打开PAE功能 的x86 ,只有两级页表,那么二级页表(PMD)将只有一个页,并直接折返指向一级页表(PGD)项,这在编译的时候会自动优化,不幸的是,对于不自动管理cache和TLB的mmu架构,必须使用钩子函数来修改或者清除cache和TLB的内容,即使是空操作,例如x86,3.8 节将讨论这些钩子函数
本章首先介绍
1.页表如何组织,以及三级页表的各自数据类型;
2.介绍虚拟地址是怎么通过各级页表翻译成物理地址的;
3.讨论分析最底层的页表PTE,以及介绍地址中一些硬件有关的特殊位。相关页表操作和属性检测及设置的宏。
4.页表怎样增加项,页面如何分配和释放
5.内核启动阶段的页表如何初始化
6.怎样利用TLB and CPU caches

3.1 页表

linux 的每个进程都有一个指针(mm struct→pgd)指向他的PGD(一级页表的所在物理页地址),该页包含 pgd_t结构的数组, pgd_t数据结构依赖架构,定义在<asm/page.h>,pgd页表的装载依赖于架构,例如x86将 mm struct→pgd复制到 cr3 寄存器,这种方式还有另外一个作用,就是清除TLB,事实上清除TLB的函数__flush_tlb()就是通过这种方式来实现的,__flush_tlb的实现也是依赖架构的。
PGD 页表中的每一个有效项指向 PMD 页表所在的物理页。PMD 页表包含pmd_t结构的数组。PMD 页表的每一个有效项又指向PTE页表,PTE页表是pte_t结构的数组,其中的每一项指向包含实际用户数据的物理页。如果页面已经换出到swap区,pte将保存swap 中页面的入口地址,用来在发生缺页错误时,通过do_swap_page() 来寻找swap区中的页面地址
图3.1
在这里插入图片描述
任意一个线性(虚拟)地址将被拆分成三个页表的偏移量和页内偏移,内核提供多组宏来帮助得到各个偏移量。分别为SHIFT, SIZE,MASK ,这个看图就明白了。ARM 中:

 #define PAGE_SHIFT 12
 #define PAGE_SIZE (1UL << PAGE_SHIFT)
 #define PAGE_MASK (~(PAGE_SIZE-1))

#define PMD_SHIFT		20
#define PMD_SIZE		(1UL << PMD_SHIFT)
#define PMD_MASK		(~(PMD_SIZE-1))

#define PGDIR_SHIFT		20
#define PGDIR_SIZE		(1UL << PGDIR_SHIFT)
#define PGDIR_MASK		(~(PGDIR_SIZE-1))

在这里插入图片描述
在这里插入图片描述
PTRS_PER_x宏定义了各级页表包含的条目个数即数组大小。ARM中定义如下

/*
 * entries per page directory level: they are two-level, so
 * we don't really have any PMD directory.
 */
#define PTRS_PER_PTE		256
#define PTRS_PER_PMD		1
#define PTRS_PER_PGD		4096


ARM 页大小4096,PGD 的页表数目有4096 ,所以pgd 页表大小为16k
而PTE 只有256 ,总共覆盖4G空间,但256页只需1024字节,这不空间浪费吗?

3.2 页表条目

如上所述,PTE PMD 和PGD 页表的每一项对应的分别是pte_t,pmd_t,pgd_t结构。虽然他们通常是一个无符号整数,但定义成结构体的目的有两点,一是类型保护,防止程序中错误使用,二是为了一些特殊情况,例如x86 带PAE的地址中的有额外的4位用来4GiB 以上的寻址.pgprot_t用来表示虚拟地址中的保护位,通常在低位。上述这些结构ARM定义如下

typedef struct { unsigned long pte; } pte_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pgd; } pgd_t;
typedef struct { unsigned long pgprot; } pgprot_t;

下面两组宏用来结构到数值的相互转换

#define pte_val(x)      ((x).pte)
#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) } )

地址中保护位的位置是依赖硬件的
我们将以非PAE来举例说明地址翻译过程,但其实原理(支持PAE的)是一样的。在无PAE 的x86系统中,pte页表项是32bit 整数,每个pte条目 指向的是一个页的地址。并且所有的页地址都是页对齐的。因此,PAGE_SHIFT 所对应的低12位 (页大小为4096字节),pte 条目中的低12位可以用来保存页表项的状态和标志,下图是一些状态和标志位列表,不同硬件架构的所包含的位和具体含义是不同的。ARM中的标志位如下

PTE_DIRTY:CPU在写操作时会设置该标志位,表示对应页面被写过,为脏页。
PTE_YOUNG:CPU访问该页时会设置该标志位。在页面换出时,如果该标志位置位了,说明该页刚被访问过,页面是young的,不适合把该页换出,同时清除该标志位。
PTE_PRESENT:表示页在内存中。

#define L_PTE_PRESENT		(1 << 0)//Page is resident in memory and not swapped out.
#define L_PTE_YOUNG		(1 << 1)
#define L_PTE_BUFFERABLE	(1 << 2)
#define L_PTE_CACHEABLE		(1 << 3)
#define L_PTE_USER		(1 << 4) //the page is accessible from userspace
#define L_PTE_WRITE		(1 << 5)
#define L_PTE_EXEC		(1 << 6)
#define L_PTE_DIRTY		(1 << 7)

在这里插入图片描述
_PAGE_PROTNONE后面会讨论,其他的自行理解
x86 的Pentium III以及更新的系统中,pte中有一个PAT位( Pentium II 中是保留位),用来表示pte所指向的页面大小,在PGD 条目,这个位叫做 Page Size Extension(PSE),显然,这两个位要联合使用

因为linux 在用户空间页面不会使用 PSE bit ,因此pte项的PAT bit 没有用,用作其他目的。
有可能出现这样的情况:页面需要驻留在内存中,但不能被用户空间的进程访问。例如被 mprotect() 保护的一段的带PROT_NONE标志的区域。当这个区域被保护, PAGE_PRESENT bit被清除,PAGE_PROTNONE bit 将设置。内核通过pte_present() 宏检测这两个位是否有一个存在,从而得知到这个pte是否存在。它只是不能被用户空间访问,这是个隐秘但又重要的点,因为当PAGE_PRESENT bit被清除,访问该页会产生缺页错误,此时linux内核将对其保护并知道该页存在(用户无法访问),如果有进程退出或需要将该页换出

3.3 页表项的使用

下面这些重要的宏定义在<asm/pgtable.h>,用来地址翻译和检验页表条目
pgd_offset 获得对应的PGD页表项虚拟地址
pmd_offset获得对应的PMD页表项虚拟地址,(由于硬件只有两级页表,因此得到的还是PGD页表项虚拟地址)
pte_offset获得对应的PTE页表项虚拟地址
而线性地址的剩余位用来表示页内偏移,如图3.1点击跳转

/* to find an entry in a page-table-directory */
#define pgd_index(addr)		((addr) >> PGDIR_SHIFT)
#define pgd_offset(mm, addr)		((mm)->pgd+pgd_index(addr))
#define pmd_offset(dir, addr)	((pmd_t *)(dir))	
/* Find an entry in the third-level page table.. */
#define __pte_offset(addr)	(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
#define pte_offset(dir, addr)	((pte_t *)pmd_page(*(dir)) + __pte_offset(addr))
static inline unsigned long pmd_page(pmd_t pmd)
{
	unsigned long ptr;

	ptr = pmd_val(pmd) & ~(PTRS_PER_PTE * sizeof(void *) - 1);

	ptr += PTRS_PER_PTE * sizeof(void *);
	//页表项存放的是物理地址,而程序使用的是虚拟地址,因此需要转换	
    return __phys_to_virt(ptr);
}

下面一组宏用来判断页表条目是否存在或使用。
pte_none(), pmd_none() and pgd_none() 返回 1如果对应页表条目不存在
pte_present(), pmd_present() and pgd_present() 返回 1 如果对应页表条目存在
pte_clear(), pmd_clear() and pgd_clear() 清除对应的页表条目
pmd_bad() and pgd_bad() 用来检测是否错误(页表项被错误篡改),其检测依赖于硬件架构。实际使用中,最终要的还是判断是否存在以及是否可以访问。

#define pgd_none(pgd)		(0)
#define pgd_bad(pgd)		(0)
#define pgd_present(pgd)	(1)
#define pgd_clear(pgdp)		do { } while (0)

#define pmd_none(pmd)		(!pmd_val(pmd))
#define pmd_bad(pmd)		(pmd_val(pmd) & 2)//?
#define pmd_present(pmd)	(pmd_val(pmd))
#define pmd_clear(pmdp)		set_pmd(pmdp, __pmd(0))

#define pte_none(pte)		(!pte_val(pte))
#define pte_clear(ptep)		set_pte((ptep), __pte(0))
#define pte_present(pte)		(pte_val(pte) & L_PTE_PRESENT)

虚拟内存管理中很多地址翻译的代码,下面是 mm/memory.c 中follow_page函数的部分代码,展示了地址翻译的过程

pgd_t *pgd;	
pmd_t *pmd;
pte_t *ptep, pte;
pgd = pgd_offset(mm, address);
if (pgd_none(*pgd) || pgd_bad(*pgd))
	goto out;

pmd = pmd_offset(pgd, address);
if (pmd_none(*pmd) || pmd_bad(*pmd))
	goto out;
	
ptep = pte_offset(pmd, address);
if (!ptep)
	goto out;
 	
pte = *ptep;	

通过三个XXX_offset宏来一步步找到实际的物理页,使用XXX_none 和 XXX_bad宏来保证页表的有效性

下面这组宏检测以及设置页面的权限,权限决定了用户进程对该页的访问权限。例如内核空间是不允许用户进程访问的。
pte_modify 基本没有使用,除了在 mm/mprotect.c 的 change_pte_range 函数

name功能
pte_readtested the read permissions for an entry
pte_writetested the write permissions for an entry
pte_exectested the execute permissions for an entry
pte_mkreadset the read permissions for an entry
pte_mkwriteset the write permissions for an entry
pte_mkexecset the execute permissions for an entry
pte_rdprotectclear the read permissions for an entry
pte_wrprotectclear the write permissions for an entry
pte_exprotectclear the execute permissions for an entry
pte_modifymodified the permissions for an entry

最后一组宏测试和设置页表项的状态位,在linux只有两个重要的状态位,分别是dirty bit 和 accessed bit.

name功能
pte_dirtycheck the dirty bit
pte_youngcheck the accessed bit
pte mkdirtyset the dirty bits,
pte_mkyoungset the accessed bits,
pte_mkcleanclear the dirty bits,
pte_mkoldclear the accessed bits,
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值