Linux-3.14.12内存管理笔记【建立内核页表(1)】
前面已经分析过了Intel的内存映射和linux的基本使用情况,已知head_32.S仅是建立临时页表,内核还是要建立内核页表,做到全面映射的。下面就基于RAM大于896MB,而小于4GB ,切CONFIG_HIGHMEM配置了高端内存的环境情况进行分析。
建立内核页表前奏,了解两个很关键的变量:
- max_pfn:最大物理内存页面帧号;
- max_low_pfn:低端内存区(直接映射空间区的内存)的最大可用页帧号;
max_pfn 的值来自setup_arch()中,setup_arch()函数中有:
max_pfn = e820_end_of_ram_pfn();
那么接下来看一下e820_end_of_ram_pfn()的实现:
804762
【file:/arch/x86/kernel/e820.c】
unsigned long __init e820_end_of_ram_pfn(void)
{
return e820_end_pfn(MAX_ARCH_PFN, E820_RAM);
}
e820_end_of_ram_pfn()直接封装调用e820_end_pfn(),而其入参为MAX_ARCH_PFN和E820_RAM,其中MAX_ARCH_PFN的定义(x86的32bit环境)为:
# define MAX_ARCH_PFN (1ULL<<(32-PAGE_SHIFT))
最终值为0x100000,它表示的是4G物理内存的最大页面帧号;而E820_RAM为:
#define E820_RAM 1
接下来看一下e820_end_pfn()函数实现:
【file:/arch/x86/kernel/e820.c】
/*
* Find the highest page frame number we have available
*/
static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type)
{
int i;
unsigned long last_pfn = 0;
unsigned long max_arch_pfn = MAX_ARCH_PFN;
for (i = 0; i < e820.nr_map; i++) {
struct e820entry *ei = &e820.map[i];
unsigned long start_pfn;
unsigned long end_pfn;
if (ei->type != type)
continue;
start_pfn = ei->addr >> PAGE_SHIFT;
end_pfn = (ei->addr + ei->size) >> PAGE_SHIFT;
if (start_pfn >= limit_pfn)
continue;
if (end_pfn > limit_pfn) {
last_pfn = limit_pfn;
break;
}
if (end_pfn > last_pfn)
last_pfn = end_pfn;
}
if (last_pfn > max_arch_pfn)
last_pfn = max_arch_pfn;
printk(KERN_INFO "e820: last_pfn = %#lx max_arch_pfn = %#lx\n",
last_pfn, max_arch_pfn);
return last_pfn;
}
这个函数用来查找最大物理的页面帧号,通过对e820图的内存块信息得到内存块的起始地址,将起始地址右移PAGE_SHIFT,算出其起始地址对应的页面帧号,如果足够大,超出了limit_pfn则设置最大页面帧号为limit_pfn,否则则设置为遍历中找到的最大的last_pfn。
e820_end_of_ram_pfn()函数的调用位置:
start_kernel() #init/main.c
└─>setup_arch() #arch/x86/kernel/setup.c
├─>e820_end_of_ram_pfn() #arch/x86/kernel/e820.c
└─>find_low_pfn_range() #arch/x86/kernel/e820.c
其中find_low_pfn_range()用于查找低端内存的最大页面数的 ,max_low_pfn则在这里面初始化。
find_low_pfn_range()代码实现:
【file:/arch/x86/mm/init_32.c】
/*
* Determine low and high memory ranges:
*/
void __init find_low_pfn_range(void)
{
/* it could update max_pfn */
if (max_pfn <= MAXMEM_PFN)
lowmem_pfn_init();
else
highmem_pfn_init();
}
函数实现很简单,根据max_pfn是否大于MAXMEM_PFN,从而判断是否初始化高端内存,也可以认为是启用。那么来看一下MAXMEM_PFN的宏定义:
(file:/arch/x86/include/asm/setup.h)
#define MAXMEM_PFN PFN_DOWN(MAXMEM)
其中PFN_DOWN(x)的定义为:
(file:/include/linux/pfn.h)
#define PFN_DOWN(x) ((x) >> PAGE_SHIFT)
PFN_DOWN(x)是用来返回小于x的最后一个页面号,对应的还有个PFN_UP(x)是用来返回大于x的第一个页面号,此外有个PFN_PHYS(x)返回的是x的物理页面号。接着看MAXMEM的定义:
(file:arch/x86/include/asm/pgtable_32_types.h)
#define MAXMEM (VMALLOC_END - PAGE_OFFSET - __VMALLOC_RESERVE)
那么VMALLOC_END的定义则为:
(file:arch/x86/include/asm/pgtable_32_types.h)
#define VMALLOC_END (PKMAP_BASE - 2 * PAGE_SIZE)
//永久内存映射
#define PKMAP_BASE ((FIXADDR_BOOT_START - PAGE_SIZE * (LAST_PKMAP + 1)) & PMD_MASK)
其中PKMAP_BASE是永久映射空间的起始地址,LAST_PKMAP则是永久映射空间的映射页面数,定义为:
#define LAST_PKMAP 1024
另外PAGE_SHIFT和PAGE_SIZE的定义为:
#define PAGE_SHIFT 12
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)
而FIXADDR_BOOT_START是临时固定映射空间起始地址,其的相关宏定义:
临时内存映射:
#define FIXADDR_BOOT_SIZE (__end_of_fixed_addresses << PAGE_SHIFT)
#define FIXADDR_BOOT_START (FIXADDR_TOP - FIXADDR_BOOT_SIZE)
unsigned long __FIXADDR_TOP = 0xfffff000;
extern unsigned long __FIXADDR_TOP;
#define FIXADDR_TOP ((unsigned long)__FIXADDR_TOP)
这里其中的__end_of_fixed_addresses是来自fixed_addresses枚举值,是固定映射的一个标志。此外这里的FIXADDR_TOP是固定映射区末尾,而另外还有一个这里未列出的FIXADDR_START,是固定映射区起始地址。
既然到此,顺便介绍一下内核空间映射情况。

内核空间如上图,分为直接内存映射区(低端内存,线性)和高端内存映射区。其中直接内存映射区是指3G到3G+896M的线性空间,直接对应物理地址就是0到896M(前提是有超过896M的物理内存),其中896M是high_memory值,使用kmalloc()/kfree()接口操作申请释放;
而高端内存映射区则是至超多896M物理内存的空间,它又分为动态映射区、永久映射区和固定映射区。
- 动态内存映射区,又称之为vmalloc映射区或非连续映射区,是指VMALLOC_START到VMALLOC_END的地址空间,申请释放操作接口是vmalloc()/vfree(),通常用于将非连续的物理内存映射为连续的线性地址内存空间;
- 而永久映射区,又称之为KMAP区或持久映射区,是指自PKMAP_BASE开始共LAST_PKMAP个页面大小的空间,操作接口是kmap()/kunmap(),用于将高端内存长久映射到内存虚拟地址空间中;
- 最后的固定映射区,也称之为临时内核映射区,是指FIXADDR_START到FIXADDR_TOP的地址空间,操作接口是kmap_atomic()/kummap_atomic(),用于解决持久映射不能用于中断处理程序而增加的临时内核映射。
下图是根据个人的实验环境绘制的一张关于内核空间映射情况。

PMD_MASK涉及的宏定义:
(file:/include/asm-generic/pgtable-nopmd.h)
#define PMD_SHIFT PUD_SHIFT
#define PMD_SIZE (1UL << PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE-1))
(file:/include/asm-generic/pgtable-nopud.h)
#define PUD_SHIFT PGDIR_SHIFT
(file:arch/x86/include/asm/Pgtable-2level_types.h)
#define PGDIR_SHIFT 22
PMD_MASK计算结果是:0xFFC00000,其实是用于数据对齐而已。
已知PAGE_OFFSET默认的为0xC0000000,而__VMALLOC_RESERVE为:
unsigned int __VMALLOC_RESERVE = 128 << 20;
最后在个人的实验环境上,得出MAXMEM_PFN的值为0x377fe。
Linux是一个支持多硬件平台的操作系统,各种硬件芯片的分页并非固定的2级(页全局目录和页表),仅仅Intel处理器而言,就存在3级的情况(页全局目录、页中间目录和页表),而到了64位系统的时候就成了4级分页。 所以Linux为了保持良好的兼容性和移植性,系统设计成了以下的4级分页模型,根据平台环境和配置的情况,通过将页上级目录和页中间目录的索引位设置为0,从而隐藏了页三级目录和页中间目录的存在。也就是为什么存在PMD_SHIFT、PUD_SHIFT和PGDIR_SHIFT,还有pgtable-nopmd.h、pgtable-nopud.h和Pgtable-2level_types.h的原因了。

由此管中窥豹,看到了Linux内存分页映射模型的存在和相关设计,暂且也就先了解这么多。
分析宏是一件很乏味的事情,不过以小见大却是一件很有意思的事情。
【file:/arch/x86/mm/init_32.c】
/*
* We have more RAM than fits into lowmem - we try to put it into
* highmem, also taking the highmem=x boot parameter into account:
*/
static void __init highmem_pfn_init(void)
{
max_low_pfn = MAXMEM_PFN;
if (highmem_pages == -1)
highmem_pages = max_pfn - MAXMEM_PFN;
if (highmem_pages + MAXMEM_PFN < max_pfn)
max_pfn = MAXMEM_PFN + highmem_pages;
if (highmem_pages + MAXMEM_PFN > max_pfn) {
printk(KERN_WARNING MSG_HIGHMEM_TOO_SMALL,
pages_to_mb(max_pfn - MAXMEM_PFN),
pages_to_mb(highmem_pages));
highmem_pages = 0;
}
#ifndef CONFIG_HIGHMEM
/* Maximum memory usable is what is directly addressable */
printk(KERN_WARNING "Warning only %ldMB will be used.\n", MAXMEM>>20);
if (max_pfn > MAX_NONPAE_PFN)
printk(KERN_WARNING "Use a HIGHMEM64G enabled kernel.\n");
else
printk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");
max_pfn = MAXMEM_PFN;
#else /* !CONFIG_HIGHMEM */
#ifndef CONFIG_HIGHMEM64G
if (max_pfn > MAX_NONPAE_PFN) {
max_pfn = MAX_NONPAE_PFN;
printk(KERN_WARNING MSG_HIGHMEM_TRIMMED);
}
#endif /* !CONFIG_HIGHMEM64G */
#endif /* !CONFIG_HIGHMEM */
}
highmem_pfn_init()看起来很长,貌似很复杂,实际上仅仅是把max_low_pfn设置为MAXMEM_PFN,而highmem_pages设置为max_pfn - MAXMEM_PFN,至于后面的几乎都是为了防止某些数据过大过小引起翻转而做的保障性工作。需要说明的是这里的max_low_pfn作为直接映射空间区的内存最大可用页帧号,并不是896M大小内存的页面数。896M只是定义高端内存的一个界限,至于直接映射内存大小只定义了不超过896M而已。
此外还有一个准备操作,在setup_arch()函数中调用的页表缓冲区申请操作:
early_alloc_pgt_buf():
【file:/arch/x86/mm/init.c】
void __init early_alloc_pgt_buf(void)
{
unsigned long tables = INIT_PGT_BUF_SIZE;
phys_addr_t base;
base = __pa(extend_brk(tables, PAGE_SIZE));
pgt_buf_start = base >> PAGE_SHIFT;
pgt_buf_end = pgt_buf_start;
pgt_buf_top = pgt_buf_start + (tables >> PAGE_SHIFT);
}
Linux-3.14.12内存管理笔记【建立内核页表(1)】的更多相关文章
- Linux-3.14.12内存管理笔记【建立内核页表(2)】-低端内存的建立
前面的前奏已经分析介绍了建立内核页表相关变量的设置准备,接下来转入正题分析内核页表的建立. 建立内核页表的关键函数init_mem_mapping(): [file:/arch/x86/mm/init ...
- Linux-3.14.12内存管理笔记【建立内核页表(3)
前面已经分析了内核页表的准备工作以及内核低端内存页表的建立,接着回到init_mem_mapping()中,低端内存页表建立后紧随着还有一个函数early_ioremap_page_table_ran ...
- Linux-3.14.12内存管理笔记【伙伴管理算法(1)】
前面分析了memblock算法.内核页表的建立.内存管理框架的构建,这些都是x86处理的setup_arch()函数里面初始化的,因地制宜,具有明显处理器的特征.而start_kernel()接下来的 ...
- Linux-3.14.12内存管理笔记【构建内存管理框架(1)】
传统的计算机结构中,整个物理内存都是一条线上的,CPU访问整个内存空间所需要的时间都是相同的.这种内存结构被称之为UMA(Uniform Memory Architecture,一致存储结构).但是随 ...
- Linux-3.14.12内存管理笔记【伙伴管理算法(2)】
前面已经分析了linux内存管理算法(伙伴管理算法)的准备工作. 具体的算法初始化则回到start_kernel()函数接着往下走,下一个函数是mm_init(): [file:/init/main. ...
- Linux-3.14.12内存管理笔记【构建内存管理框架(5)】
前面已经分析了内存管理框架的构建实现过程,有部分内容未完全呈现出来,这里主要做个补充. 如下图,这是前面已经看到过的linux物理内存管理框架的层次关系. 现着重分析一下各个管理结构体的成员功能作用. ...
- 2. Linux-3.14.12内存管理笔记【系统启动阶段的memblock算法(2)】
memory:表示可用可分配的内存: 结束完memblock算法初始化前的准备工作,回到memblock算法初始化及其算法实现上面.memblock是一个很简单的算法. memblock算法的实现是, ...
- 1. Linux-3.14.12内存管理笔记【系统启动阶段的memblock算法(1)】
memblock算法是linux内核初始化阶段的一个内存分配器(它取代了原来的bootmem算法),实现较为简单.负责page allocator初始化之前的内存管理和分配请求. 分析memblock ...
- Linux-3.14.12内存管理笔记【构建内存管理框架(2)】
前面构建内存管理框架,已经将内存管理node节点设置完毕,接下来将是管理区和页面管理的构建.此处代码实现主要在于setup_arch()下的一处钩子:x86_init.paging.pagetable ...
随机推荐
- c++多个文件中共用一个全局变量 变量跨文件使用
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/12047602.html 虽然很多博客都写过这个了 但是 我还是继续补充的详细一点吧 毕竟很多 ...
- C# Monitor and transfer or copy the changed or created file to a new location
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Tex ...
- java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5
A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成 ...
- Vue学习笔记Day2
1.mustache语法 如何将data中的文本数据插入到HTML中? 通过使用mustache语法(也就是双大括号),将data中的变量名插入到HTML元素中,显示在页面上. 如下图:并且数据是响应 ...
- 在Vue中添加css扩展语言sass
npm install vue-loader --save-dev npm install node-sass --save-dev npm install sass-loader --save-de ...
- 集合 set方法
集合 number = {1, 2, 4} # 添加元素到集合 number.add(100) print(number) # 从集合中删除 number.remove(2) print(number ...
- Python监控主机是否存活,并发报警邮件
利用python写了简单测试主机是否存活脚本,此脚本不适于线上使用,因为网络延迟.丢包现象会造成误报邮件,那么后续会更新判断三次ping不通后再发报警邮件,并启用多线程处理. #!/usr/bin/e ...
- linux 磁盘分区和挂载看这一篇就够了
Linux fdisk 和 mount 命令操作指南,linux磁盘管理.新增磁盘.挂载新硬盘(linux运维入门) 首先列出文件系统的整体磁盘空间使用情况.可以用来查看磁盘已被使用多少空间和还剩余多 ...
- (办公)记事本_Linux目录
转载自菜鸟教程:https://www.runoob.com/linux/linux-system-contents.html /bin: bin是Binary的缩写, 这个目录存放着最经常使用的命令 ...
- 30(1).原型聚类---k-means
原型聚类prototype-based clustering假设聚类结构能通过一组原型刻画. 常见的原型聚类有: k均值算法k-means 学习向量量化算法Learning Vector Quanti ...