linux内核探索之内存管理(三):页表

主要参考《深入Linux内核架构》、《深入理解Linux内核》及内核linux-3.18.3

页表用于建立用户进程的虚拟地址空间和系统物理内存(内存、页帧)之间的映射。IA-32系统默认使用两级分页系统,但是内核中总是使用四级页表,第三和第四级页表由特定于体系结构的代码模拟。

页表管理分为两个部分,第一部分依赖于体系结构,第二部分体系结构无关。但是所有的数据结构和操作数据结构的几乎所有函数都定义在特定于体系结构的文件中。这些数据结构和函数通常在include/asm-arch/page.h和include/asm-arch/pgtable.h中找到,简称为pagh.h和pgtable.h。但IA-32和AMD64平台的文件为:arch/asm-x86/page_32.h、arch/asm-x86/page_64.h。

数据结构

 1. 内存地址的分解

根据四级页表的需要,虚拟内存地址分为5部分(4个表项用于选择页,1个索引表示页内位置)。各体系结构不仅地址子长不同,而且地址字拆分方式也不同,内核定义了宏,用于将地址分解为各分量。

更直观描述:

每一个进程有自己的页全局目录和自己的页表集。当发生进程切换时,linux就把CR3控制寄存器的内容保存在当前一个执行进程的描述符中。然后把下一个要执行的描述符的值装入CR3寄存器中,当心进程重新开始在CPU上执行时,分页单元可以指向正确的页表。

BIT_PER_LONG:表示用于unsigned long 变量的比特位的数目

PAGE_SHIFT:表示每个指针末端的几个比特位,用于指定所选页帧内部的位置

PMD_SHIFT:指定页内偏移量和最后一级页表所需比特位的总数。该值减去PAGE_SHIFT可得最后一级索引所需比特位的数目。该值表明了一个中间层页表项管理的部分地址空间的大小,为2^PMD_SHIFT字节。

PUD_SHIFT:由PMD_SHIFT加上中间层页表索引所需的比特位长度。

PGDIR_SHIFT则由PUD_SHIFT加上上层页表索引所需的比特位长度。对全局目录中的一项所能寻址的部分地址空间长度计算以2为底的对数,即PGDIR_SHIFT。

在各级页没目录/页表中所能存储的指针数目,可以通过宏定义确定。PTRS_PER_PGD指定了全局页目录中项的数目。PTRS_PER_PMD对应于中间页目录,PTRS_PER_PUD对应于上层页目录中项的数目,PTRS_PER_PTE则是页表中项的数目。两级页表的体系结构中会将PTRS_PER_PMD和PTRS_PER_PUD定义为1,这使得内核的剩余部分感觉该体系结构也提供了四级页转换结构,尽管实际上只有两级页表。中间层页目录和上层页目录实际上被消去。

相关头文件:

Include/asm-generic/pgtable-nopud.h用来提供模拟上层页目录所需的所有声明;

Include/asm-generic/pgtable-nopmd.h用于在只有二级地址转换的系统上模拟中间层页表。

n比特位长的地址可寻址的地址区域长度为2^n字节。内核定义了额外的宏变量保存计算得到的值,以避免多次重复计算。相关的宏定义如下:

include/asm-generic/page.h:
#define PAGE_SHIFT      12
#ifdef __ASSEMBLY__
#define PAGE_SIZE       (1 << PAGE_SHIFT)
#else
#define PAGE_SIZE       (1UL << PAGE_SHIFT)
#endif
#define PAGE_MASK       (~(PAGE_SIZE-1))

include/asm-generic/pgtable-nopud.h:
#define PUD_SHIFT       PGDIR_SHIFT
#define PTRS_PER_PUD    1
#define PUD_SIZE        (1UL << PUD_SHIFT)

include/asm-generic/pgtable-nopmd.h:
#define PMD_SHIFT       PUD_SHIFT
#define PTRS_PER_PMD    1
#define PMD_SIZE        (1UL << PMD_SHIFT)
#define PMD_MASK        (~(PMD_SIZE-1))

./include/asm/pgtable_32_types.h:
#define PGDIR_SIZE     (1UL << PGDIR_SHIFT)

./include/asm/pgtable-2level_types.h:
#define PGDIR_SHIFT        22
#define PTRS_PER_PGD       1024

./include/asm/pgtable-3level_types.h:
#define PGDIR_SHIFT        30
#define PTRS_PER_PGD       4

./include/asm/pgtable_64_types.h:
#define PGDIR_SHIFT    39
#define PTRS_PER_PGD   512

PTR_PER_XXX指定了给定目录项能够代表多少指针。由于x64对每个页表索引使用9个比特位,所以每个页表可容纳2^9=512个指针。

内核也需要一种方法从给定地址中提取各个分量。内核使用如下定义的位掩码来完成该工作。

#define PAGE_MASK		(~(PAGE_SIZE-1))
#define PUD_MASK		(~(PUD_SIZE-1))
#define PMD_MASK		(~(PMD_SIZE-1))
#define PUD_MASK		PGDIR_MASK

2. 页表的格式

上述定义已经确立了页表项的数目,但是没有定义其结构。内核提供了4个数据结构(pagh.h)来表示页表项的结构:

Pgd_t 用于全局页目录项

Pud_t 用于上层页目录项

Pmd_t用于中间页目录项

Pte_t 用于直接页表项

include/asm-generic/page.h:
typedef struct {
        unsigned long pte;
} pte_t;
typedef struct {
        unsigned long pmd[16];
} pmd_t;
typedef struct {
        unsigned long pgd;
} pgd_t;
typedef struct {
        unsigned long pgprot;
} pgprot_t;
typedef struct page *pgtable_t;

用于分析页表项的函数:


函数


描述


include/asm-generic/page.h:

#define pte_val(x)      ((x).pte)

#define pmd_val(x)      ((&x)->pmd[0])

#define pgd_val(x)      ((x).pgd)

#define pgprot_val(x)   ((x).pgprot)


将pte_t等类型的变量转换为unsigned long整数


include/asm-generic/page.h:

#define __pte(x)        ((pte_t) { (x) } )

#define __pmd(x)        ((pmd_t) { (x) } )

#define __pgd(x)        ((pgd_t) { (x) } )

#define __pgprot(x)     ((pgprot_t) { (x) } )


Pgd_val等函数的逆,将unsigned long型的整数转换为pgd_t等类型的变量


arch/x86/include/asm/pgtable.h:

#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))

static inline unsigned long pud_index(unsigned long address)

static inline unsigned long pte_index(unsigned long address)

static inline unsigned long pmd_index(unsigned long address)


从内存指针和页表项获得下一级页表的地址


static inline int pgd_present(pgd_t pgd)

static inline int pte_present(pte_t a)

static inline int pmd_present(pmd_t pmd)

static inline int pud_present(pud_t pud)


检查对应项的_PRESENT位是否设置。如果该项对应的页表或页在内存中,则会置位


static inline int pgd_none(pgd_t pgd)

static inline int pte_none(pte_t pte)

static inline int pmd_none(pmd_t pmd)

static inline int pud_none(pud_t pud)


对_present函数的值逻辑去翻。如果返回true,则检查的页不在内存中


#define pgd_clear(pgd)                  native_pgd_clear(pgd)

#define pud_clear(pud)                  native_pud_clear(pud)

#define pte_clear(mm, addr, ptep)       native_pte_clear(mm, addr, ptep)

#define pmd_clear(pmd)                  native_pmd_clear(pmd)


删除传递的页表项,通常是将其设置为零


static inline int pmd_bad(pmd_t pmd)

static inline int pud_bad(pud_t pud)

static inline int pgd_bad(pgd_t pgd)

static inline int pmd_bad(pmd_t pmd)


检查中间层页表、上层页表、全局页表的项是否无效。如果函数从外部接收输入参数,则无法假定参数是有效的,为保证安全性,可以调用这些函数进行检查


#define pud_page(pud)           pfn_to_page(pud_val(pud) >> PAGE_SHIFT)

#define pgd_page(pgd)           pfn_to_page(pgd_val(pgd) >> PAGE_SHIFT)

#define pte_page(pte)   pfn_to_page(pte_pfn(pte))


返回保存页数据的page结构或中间页目录的项


static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address)

static inline pud_t *pud_offset(pgd_t *pgd, unsigned long address)

#define pgd_offset(mm, address) ((mm)->pgd + pgd_index((address)))

#define pgd_offset_k(address) pgd_offset(&init_mm, (address))


接收内存描述地址,和线性地址address,该宏产生地址addr在相应表项的线性地址或目录项的地址

未完待续:

特定于PTE(page table的信息)

时间: 2024-08-04 11:38:18

linux内核探索之内存管理(三):页表的相关文章

linux内核探索之内存管理(四):对页表和页表项的操作

接上一节,主要参考<深入Linux内核架构>(3.3节),即linux-3.18.3 1. 对PTE的操作 最后一级页表中的项不仅包含了指向页的内存位置的指针,还在上述的多于比特位包含了与页有关的附加信息.尽管这些数据是特定于CPU的,它们至少提供了有关页访问控制的一些信息.下列位在linux内核支持的大多数CPU中都可以找到. arch/x86/include/asm/pgtable_types.h #define _PAGE_BIT_PRESENT 0 /* is present */ #

linux内核探索之内存管理(二):linux系统中的内存组织--结点、内存域和页帧

本文主要参考<深入linux内核架构>(3.2节)及Linux3.18.3内核源码 概述:本文主要描述了内存管理相关的数据结构:结点pg_data_t.内存域struct zone以及页帧(物理页):struct page ,以及该结构相关的一些基本概念. 1. 概述 内存划分为接点,每个结点关联到系统中的一个处理器,在内核中表示为pg_data_t. 各个结点又划分为内存域,比如DMA内存域,高端内存域,普通内存域. 内核内存域的宏: enum zone_type { #ifdef CONF

linux内核分析之内存管理

1.struct page 1 /* Each physical page in the system has a struct page associated with 2 * it to keep track of whatever it is we are using the page for at the 3 * moment. Note that we have no way to track which tasks are using 4 * a page, though if it

Linux内核工程导论——内存管理(一)

Linux内存管理 概要 物理地址管理 很多小型操作系统,例如eCos,vxworks等嵌入式系统,程序中所采用的地址就是实际的物理地址.这里所说的物理地址是CPU所能见到的地址,至于这个地址如何映射到CPU的物理空间的,映射到哪里的,这取决于CPU的种类(例如mips或arm),一般是由硬件完成的.对于软件来说,启动时CPU就能看到一片物理地址.但是一般比嵌入式大一点的系统,刚启动时看到的已经映射到CPU空间的地址并不是全部的可用地址,需要用软件去想办法映射可用的物理存储资源到CPU地址空间.

[转]linux内核分析笔记----内存管理

转自:http://blog.csdn.net/Baiduluckyboy/article/details/9667933 内存管理,不用多说,言简意赅.在内核里分配内存还真不是件容易的事情,根本上是因为内核不能想用户空间那样奢侈的使用内存. 先来说说内存管理.内核把物理页作为内存管理的基本单位.尽管处理器的最小可寻址单位通常是字,但是,内存管理单元MMU通常以页为单位进行处理.因此,从虚拟内存的交代来看,页就是最小单位.内核用struct  page(linux/mm.h)结构表示系统中的每个

Linux内核剖析 之 内存管理

1. 内存管理区 为什么分成不同的内存管理区? ISA总线的DMA处理器有严格的限制:仅仅能对物理内存前16M寻址. 内核线性地址空间仅仅有1G,CPU不能直接訪问全部的物理内存. ZONE_DMA                  小于16M内存页框 ZONE_NORMAL          16M~896M内存页框 ZONE_HIGHMEM        大于896M内存页框 ZONE_DMA和ZONE_NORMAL区域包括的页框,通过线性的映射到内核线性地址空间.内核能够直接訪问(对应的内

&lt;Linux内核源码&gt;内存管理模型

题外语:本人对linux内核的了解尚浅,如果有差池欢迎指正,也欢迎提问交流! 首先要理解一下每一个进程是如何维护自己独立的寻址空间的,我的电脑里呢是8G内存空间.了解过的朋友应该都知道这是虚拟内存技术解决的这个问题,然而再linux中具体是怎样的模型解决的操作系统的这个设计需求的呢,让我们从linux源码的片段开始看吧!(以下内核源码均来自fedora21 64位系统的fc-3.19.3版本内核) <include/linux/mm_type.h>中对于物理页面的定义struct page,也

PHP内核探索:内存管理开篇

内存是计算机非常关键的部件之一,是暂时存储程序以及数据的空间,CPU只有有限的寄存器可以用于存储计算数据,而大部分的数据都是存储在内存中的,程序运行都是在内存中进行的.和CPU计算能力一样, 内存也是决定计算效率的一个关键部分. 计算中的资源中主要包含:CPU计算能力,内存资源以及I/O.现代计算机为了充分利用资源, 而出现了多任务操作系统,通过进程调度来共享CPU计算资源,通过虚拟存储来分享内存存储能力. 本章的内存管理中不会介绍操作系统级别的虚拟存储技术,而是关注在应用层面: 如何高效的利用

linux内核源码——内存管理:段页式内存及swap

os的内存管理大概可以分成两块:1.段页式管理(虚存)2.swap in 和 swap out 段页式管理   多级页表的管理图像 用户(程序员)希望用段,物理内存希望用页来进行管理 原文地址:https://www.cnblogs.com/zsben991126/p/12069937.html