深入理解Linux虚拟内存管理(四)

在这里插入图片描述

系列文章目录


Linux 内核设计与实现
深入理解 Linux 内核(一)
深入理解 Linux 内核(二)
Linux 设备驱动程序(一)
Linux 设备驱动程序(二)
Linux 设备驱动程序(三)
Linux设备驱动开发详解
深入理解Linux虚拟内存管理(一)
深入理解Linux虚拟内存管理(二)
深入理解Linux虚拟内存管理(三)
深入理解Linux虚拟内存管理(四)


文章目录

  • 系列文章目录
  • 一、启动内存分配
    • 1、初始化引导内存分配器
      • (1)start_kernel
      • (2)setup_arch
      • (3)setup_memory
      • (4)init_bootmem
      • (5)init_bootmem_core
      • (6)总结
    • 2、释放内存
      • (1)register_bootmem_low_pages
      • (2)free_bootmem
      • (3)free_bootmem_core
      • (4)总结
    • 3、保留大块区域的内存
      • (1)reserve_bootmem
      • (2)reserve_bootmem_core
      • (3)总结
  • 二、页表管理
    • 1、初始化页表


一、启动内存分配

1、初始化引导内存分配器

(1)start_kernel

// init/main.c
asmlinkage void __init start_kernel(void)
{// ...setup_arch(&command_line);// ...
}

(2)setup_arch

// arch/i386/kernel/setup.c
void __init setup_arch(char **cmdline_p) {// ...max_low_pfn = setup_memory();paging_init();// ...	
}

(3)setup_memory

    这个函数的调用图如图 2.3 所示。它为引导内存分配器初始化自身进行所需信息的获取。它可以分成几个不同的任务。

  • 找到低端内存的 PFN 的起点和终点(min_low_pfnmax_low_pfn),找到高端内存的 PFN 的起点和终点(highstart_pfnhighend_pfn),以及找到系统中最后一页的 PFN
  • 初始化 bootmem_date 结构以及声明可能被引导内存分配器用到的页面。
  • 标记所有系统可用的页面为空闲,然后为那些表示页面的位图保留页面。
  • SMP 配置或 initrd 镜像存在时,为它们保留页面。
static unsigned long __init setup_memory(void) {unsigned long bootmap_size, start_pfn, max_low_pfn;// 将物理地址向上取整到下一页面,返回页帧号。由于_end 是已载入内核// 镜像的底端地址,所以 start_pfn 现在是可能被用到的第一块物理页面帧的偏移。start_pfn = PFN_UP(__pa(&_end));// 遍历 e820 图,查找最高的可用 PFN。find_max_pfn();// 在 ZONE_NORMAL 中找到可寻址的最高页面帧。max_low_pfn = find_max_low_pfn();#ifdef CONFIG_HIGHMEM// 如果高端内存可用,则从高端内存区域的 0 位置开始。如果内存在 max_low_pfn 后,// 则把高端内存区的起始位置(highstart_pfn)定在那里,而其结束位置定在 max_pfn,// 然后打印可用高端内存的提示消息。highstart_pfn = highend_pfn = max_pfn;if (max_pfn > max_low_pfn) {highstart_pfn = max_low_pfn;}printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",pages_to_mb(highend_pfn - highstart_pfn));
#endif// 为 config_page_data 节点初始化 bootmem_data 结构。// 它设置节点的物理内存起始点(页帧号 start_pfn)和终点(页帧号 max_low_pfn ),// 分配一张位图来表示这些页面,并将所有的页面设置为初始时保留。bootmap_size = init_bootmem(start_pfn, max_low_pfn);// 读入 e820 图,然后为运行时系统中的所有可用页面// 调用 free_bootmem() 这将标记页面在初始化时为空闲(即可分配页面)。register_bootmem_low_pages(max_low_pfn);// 保留页面,即相应的位图的位设置为 1reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));// 保留 0 号页面,因为 0 号页面是 BIOS 用到的一个特殊页面。			 reserve_bootmem(0, PAGE_SIZE);
#ifdef CONFIG_SMP// 保留额外的页面为跳板代码用。跳板代码处理用户空间如何进入内核空间。reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
#endif
#ifdef CONFIG_ACPI_SLEEP// 如果加入了睡眠机制,就需要为它保留内存。这仅为那些有挂起功能的手提// 电脑所用到。它已经超过本书的范围。acpi_reserve_bootmem();
#endif// ...	return max_low_pfn;			 
}

(4)init_bootmem

// mm/bootmem.c
// 这是容易混淆的地方。参数 pages 实际上是该节点可寻址内存的 PFN 末端,而不是
// 按名字的意思:页面数。
unsigned long __init init_bootmem (unsigned long start, unsigned long pages) {// 如果没有依赖于体系结构的代码,则设置该节点的可寻址最大 PFN。max_low_pfn = pages;// 如果没有依赖于体系结构的代码,则设置该节点的可寻址最小 PFN。min_low_pfn = start;// 调用 init_bootmem_core()(见 E.1.3 小节),在那里完成初始化 bootmem_data 的实// 际工作。return(init_bootmem_core(&contig_page_data, start, 0, pages));
}

(5)init_bootmem_core

static unsigned long __init init_bootmem_core (pg_data_t *pgdat,unsigned long mapstart, unsigned long start, unsigned long end)
{bootmem_data_t *bdata = pgdat->bdata;unsigned long mapsize = ((end - start)+7)/8;pgdat->node_next = pgdat_list;pgdat_list = pgdat;mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL);// 内存最低内存分配给 node_bootmem_map , 大小为 mapsize 个页面(struct page)bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT);bdata->node_boot_start = (start << PAGE_SHIFT);bdata->node_low_pfn = end;/** Initially all pages are reserved - setup_arch() has to* register free RAM areas explicitly.*/// 把所有位图初始化为 1, 即所有内存设置为保留(被占用)memset(bdata->node_bootmem_map, 0xff, mapsize);return mapsize;
}

(6)总结

    一旦 setup_memory() 确定了可用物理页面的界限,系统将从两个引导内存的初始化函数中选择一个,并以待初始化的节点的起始和终止 PFN 作为调用参数。在 UMA 结构中,init_bootmem() 用于初始化 contig_page_data,而在 NUMAinit_bootmem_node() 则初始化一个具体的节点。这两个函数主要通过调用 init_bootmem_core() 来完成实际工作。

    内核函数首先要把 pgdat_data_t 插入到 pgdat_list 链表中,因为这个节点在函数末尾很快就会用到。然后它记录下该节点的起始和结束地址(该节点与 bootmem_data_t 有关)并且分配一个位图来表示页面的分配情况。位图所需的大小以字节计算,计算公式如下:

m a p s i z e = ( e n d _ p f n − s t a r t _ p f n ) + 7 8 mapsize =\frac{(end\_pfn-start\_pfn)+7}{8} mapsize=8(end_pfnstart_pfn)+7

    该位图存放于由 bootmem_data_t→node_boot_start 指向的物理地址处,而其虚拟地址的映射由 bootmem_data_t→node_bootmem_map 指定。由于不存在与结构无关的方式来检测内存中的空洞,整个位图就被初始化为 1 来标志所有页已被分配。将可用页面的位设置为 0 的工作则由与结构相关的代码完成。在 x86 结构中,register_bootmem_low_pages() 通过检测 e820 映射图,并在每一个可用页面上调用 free_bootmem() 函数,将其位设为 1,然后再调用 reserve_bootmem() 为保存实际位图所需的页面预留空间。

2、释放内存

    在上文 setup_memory 函数中有:

static unsigned long __init setup_memory(void) {// ...	register_bootmem_low_pages(max_low_pfn);// ...	return max_low_pfn;			 
}

(1)register_bootmem_low_pages

// arch/i386/kernel/setup.c
// 读入 e820 图,然后为运行时系统中的所有可用页面
// 调用 free_bootmem() 这将标记页面在初始化时为空闲(即可分配页面)。
static void __init register_bootmem_low_pages(unsigned long max_low_pfn)
{int i;for (i = 0; i < e820.nr_map; i++) {unsigned long curr_pfn, last_pfn, size;// ...	size = last_pfn - curr_pfn;free_bootmem(PFN_PHYS(curr_pfn), PFN_PHYS(size));}
}

(2)free_bootmem

// mm/bootmem.c
void __init free_bootmem (unsigned long addr, unsigned long size)
{// 调用核心函数,以 contig_page_data 的启动内存数据为参数。return(free_bootmem_core(contig_page_data.bdata, addr, size));
}

(3)free_bootmem_core

static void __init free_bootmem_core(bootmem_data_t *bdata, unsigned long addr, unsigned long size)
{unsigned long i;unsigned long start;/** round down end of usable mem, partially free pages are* considered reserved.*/unsigned long sidx;// 计算受影响的末端索引 eidx。unsigned long eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE;// 如果末端地址不是页对齐,则它为受影响区域的末端向下取整到最近页面。unsigned long end = (addr + size)/PAGE_SIZE;// 如果释放了大小为 0 的页面,则调用 BUG()。if (!size) BUG();// 如果末端 PFN 在该节点可寻址内存之后,则这里调用 BUG()。if (end > bdata->node_low_pfn)BUG();/** Round up the beginning of the address.*/// 如果起始地址不是页对齐的,则将其向上取整到最近页面。	 start = (addr + PAGE_SIZE-1) / PAGE_SIZE;// 计算要释放的起始索引。sidx = start - (bdata->node_boot_start/PAGE_SIZE);// 释放全部满页面,这里清理在启动位图中的位。如果已经为 0,则表示是一次重// 复释放或内存从未使用,这里调用 BUG()。for (i = sidx; i < eidx; i++) {if (!test_and_clear_bit(i, bdata->node_bootmem_map))BUG();}
}

(4)总结

    由上分析可知:函数 free_bootmem_core 主要就是把对应页帧号的位图(bootmem_data_t ->node_bootmem_map)设置为 0, 来表示对应的页是空闲的。

3、保留大块区域的内存

    在上文 setup_memory 函数中有:

static unsigned long __init setup_memory(void) {// ...	// 保留页面,即相应的位图的位设置为 1reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));// 保留 0 号页面,因为 0 号页面是 BIOS 用到的一个特殊页面。			 reserve_bootmem(0, PAGE_SIZE);
#ifdef CONFIG_SMP// 保留额外的页面为跳板代码用。跳板代码处理用户空间如何进入内核空间。reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
#endif// ...	return max_low_pfn;			 
}

(1)reserve_bootmem

// mm/bootmem.c
void __init reserve_bootmem (unsigned long addr, unsigned long size)
{reserve_bootmem_core(contig_page_data.bdata, addr, size);
}

(2)reserve_bootmem_core

// mm/bootmem.c
/** Marks a particular physical memory range as unallocatable. Usable RAM* might be used for boot-time allocations - or it might get added* to the free page pool later on.*/
static void __init reserve_bootmem_core(bootmem_data_t *bdata, unsigned long addr, unsigned long size)
{unsigned long i;/** round up, partially reserved pages are considered* fully reserved.*/// sidx 是服务页的起始索引。它的值是从请求地址中减去起始地址并除以页大小得到的。	 unsigned long sidx = (addr - bdata->node_boot_start)/PAGE_SIZE;// 末尾索引 eidx 的计算与 sidx 类似,但它的分配是向上取整到最近的页面。这意味着// 对保留一页中部分请求将导致整个页都被保留。unsigned long eidx = (addr + size - bdata->node_boot_start + PAGE_SIZE-1)/PAGE_SIZE;// end 是受本次保留影响的最后 PFN。							unsigned long end = (addr + size + PAGE_SIZE-1)/PAGE_SIZE;// 检查是否给定了一个非零值。if (!size) BUG();// 检查起始索引不在节点起点之前。if (sidx < 0)BUG();// 检查末尾索引不在节点末端之后。		if (eidx < 0)BUG();// 检查起始索引不在末尾索引之后。		if (sidx >= eidx)BUG();// 检查起始地址没有超出该启动内存节点所表示的内存范围。		if ((addr >> PAGE_SHIFT) >= bdata->node_low_pfn)BUG();// 检查末尾地址没有超出该启动内存节点所表示的内存范围。		if (end > bdata->node_low_pfn)BUG();// 从 sidx 开始,到 eidx 结束,这里测试和设置启动内存分配图中表示页面已经分// 配的位。如果该位已经设置为 1,则打印一条消息:该位被设置了两次。		for (i = sidx; i < eidx; i++)if (test_and_set_bit(i, bdata->node_bootmem_map))printk("hm, page %08lx reserved twice.\n", i*PAGE_SIZE);
}

(3)总结

    由上分析可知:保留内存主要通过函数 reserve_bootmem_core 来实现的,其主要就是把对应页帧号的位图(bootmem_data_t ->node_bootmem_map)设置为 1, 来表示对应的页被占用了。

二、页表管理

1、初始化页表

    在上文 setup_arch 函数中有:

// arch/i386/kernel/setup.c
void __init setup_arch(char **cmdline_p) {// ...max_low_pfn = setup_memory();paging_init();// ...	
}

123

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/351699.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

锐捷AC的部署实例

进行锐捷AC部署时&#xff0c;遇到了一些问题&#xff0c;遂记录下来&#xff0c;如若大家在项目过程中遇到类似问题可以对照解决。 写在前面&#xff08;锐捷AC的基础配置&#xff09; ac-controller //配置AC的capwap源地址信息&#xff0c;国家码等…

Nginx配置域名证书

Nginx配置域名证书 1、证书存放路径 2、nginx.conf文件中增加以下配置&#xff0c;注意路径不一样&#xff0c;访问地址目录不一样 server {listen 443 ssl http2;server_name jistest.vwatj.ap.vwg;ssl_certificate D:/home/XXX/ssl/2023/XXX.cer; ssl_certificate_key D…

在线配资平台哪家正规?排名在前的有哪些平台?

在线配资平台哪家正规&#xff1f;排名在前的有哪些平台&#xff1f; 比如有永华证券、联华证券、财盛证券、中信证券、东方证券等等这些平台。也是排名比较靠前的&#xff01; 选择平台需要投资者仔细研究不同的在线配资平台的投资风格、费用、客户服务等因素&#xff0c;并根…

《辉煌优配》科技股强势引领A股反弹 沪深两市日成交额再超万亿元

受美联储再度加息扰动&#xff0c;昨日早盘沪深两市指数低开&#xff0c;随后科技股强势拉升&#xff0c;带动商场回暖。到收盘&#xff0c;上证综指报3286.65点&#xff0c;上涨0.64%&#xff1b;深证成指报11605.29点&#xff0c;上涨0.94%&#xff1b;创业板指报2361.41点&a…

A股管家股票自动交易软件系统,功能完善强大

2013年的时候&#xff0c;有个广东的朋友说再用这款A股管家股票自动系统&#xff0c;我当时比较惊讶&#xff0c;以前想过要是有一款股票自动交易软件能偶尔代替我一下就好了&#xff0c;虽然是职业股民&#xff0c;但也经常遇到太忙的时候&#xff0c;实在没时间。然后就在朋友…

亚马逊云科技出海日,让数字经济出海扩展到更多行业和领域

数字化浪潮之下&#xff0c;中国企业的全球化步伐明显提速。从“借帆出海”到“生而全球化”&#xff0c;中国企业实现了从传统制造业“中国产品”出口&#xff0c;向创新“中国技术”和先导“中国品牌”的逐步升级。 作为全球云计算的开创者与引领者&#xff0c;亚马逊云科技…

JDK11+mybatis-plus+shardingsphere分库分表

1、引入jar dynamic-datasource-spring-boot-starter&#xff1a;2.5.6 sharding-jdbc-spring-boot-starter&#xff1a;4.1.1 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId>&…

【单目标优化算法】沙猫群优化算法(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

华为路由器 NAT 配置

拓扑图 静态 NAT 静态地址转换是指外部网络和内部网络之间的地址映射关系由配置确定&#xff0c;该方式适用于内部网络与外部网络之间存在固定访问需求的组网环境。静态地址转换支持双向互访&#xff1a;内网用户可以主动访问外网&#xff0c;外网用户也可以主动访问内网。 一…

Java 冒泡排序法

冒泡排序法是交换排序法的一种 思想&#xff1a; /** * 冒泡法排序 * 比较相邻的元素。如果第一个比第二个小&#xff0c;就交换他们两个。 * 对每一对相邻元素作同样的工作&#xff0c;从开始第一对到结尾的最后一对。在这一点&#xff0c;最后的元素应该会是最小的数。 * 针…

冒泡排序法定向冒泡排序法的Python实现

冒泡排序法 冒泡排序法&#xff1a;每轮对相邻的两者进行比较&#xff0c;若顺序不对&#xff0c;则进行位置互换&#xff0c;每轮都将使每轮的 最后一位是该轮的大数。 比如在数列&#xff1a;[71, 1, 14, 78, 75, 38, 10, 49, 40, 95] 第一轮交换&#xff1a;71>1 > […

Java:冒泡排序法

冒泡排序法是最基本的排序法之一&#xff0c;冒泡排序法的运行机制是通过循环遍历元素并调整相邻元素顺序来实现的一种简单排序方法。冒泡排序的实质是相邻两个元素比较&#xff0c;然后按照升序或降序调换位置。 下为降序冒泡排序的代码: public class Training {public sta…

基于Java的冒泡排序法

基本过程&#xff1a; 原始顺序是48&#xff0c; 52&#xff0c; -51 &#xff0c;0 &#xff0c;67 &#xff0c;23&#xff0c; -24。开始48和52比较&#xff0c;48<52&#xff0c;则顺序不变&#xff1b;52和-51比较&#xff0c;52>-51,则两者交换位置&#xff08;48…

冒泡排序法过程分析

冒泡排序法过程分析&#xff1a; 下面是代码。建议先自己尝试去实现一下看看&#xff0c;再来看我是如何实现的。 #include <stdio.h> main() { int a[100],i,j,t,n; scanf("%d",&n); //输入一个数n&#xff0c;表示接下来有n个数 for(i1;i<n;i)…

冒泡排序法全攻略

1 算法介绍 冒泡排序法又叫起泡法&#xff0c;在许多程序设计中&#xff0c;我们需要将一个数列进行排序&#xff0c;以方便统计&#xff0c;常见的排序方法有冒泡排序&#xff0c;二叉树排序&#xff0c;选择排序等等。而冒泡排序一直由于其简洁的思想方法和比较高的效率而倍…

js冒泡排序法

冒泡排序法 冒泡排序法其实也是一种最简单&#xff0c;最清晰明了的一种排序算法。主要的运行过程就是重复比较一个数组里面的所有元素&#xff0c;两两做比较&#xff0c;如果他们的顺序不对&#xff0c;则把他们交换位置&#xff0c;一直重复到没有再需要交换元素就结束循环…

冒泡排序法(C语言)

冒泡排序&#xff1a;相邻两个数两两比较&#xff0c;小的数向前移&#xff08;上浮&#xff09;&#xff0c;大的数向后移&#xff08;下沉&#xff09;&#xff0c;如同水中的泡泡上浮一般&#xff1b; 冒泡排序图示&#xff1a; 如果有N个数&#xff0c;则要跑N-1次比较&…

Word控件Spire.Doc 【其他】教程(6):从 Word 中提取 OLE 对象

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

力扣刷题笔记——动态规划

动态规划基础 简称DP&#xff0c;如果某⼀问题有很多重叠⼦问题&#xff0c;使⽤动态规划是最有效的。 动态规划中每⼀个状态⼀定是由上⼀个状态推导出来的 1. 确定dp 数组&#xff08; dp table &#xff09;以及下标的含义 2. 确定递推公式 3. dp 数组如何初始化 4. 确定遍…

【Java系列】MyBatis-Plus常见面试题

问题列表 Q1&#xff1a;MyBatis-Plus是什么&#xff1f;它有什么优点&#xff1f; MyBatis-Plus是MyBatis框架的一个扩展库&#xff0c;它提供了一系列方便的API和工具&#xff0c;可以简化常见的数据库操作。MyBatis-Plus的优点包括&#xff1a; 提高开发效率&#xff1a;My…