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

news2024/11/29 22:03:15

在这里插入图片描述

系列文章目录


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


文章目录

  • 系列文章目录
  • 一、物理页面分配
    • 1、分配页面
    • 2、分配辅助函数
    • 3、释放页面
      • (1)__free_pages
        • (a)⇐ mm.h
      • (2)__free_pages_ok
        • (a)⇐ zone_t
        • (b)⇐ free_area_init_core
    • 4、释放辅助函数
      • (1)free_pages
        • (a)⇐ page.h
      • (2)__free_page
      • (3)free_page
  • 二、补充配置
    • 1、空格


一、物理页面分配

1、分配页面

2、分配辅助函数

3、释放页面

(1)__free_pages

    这个函数的调用图如图 6.4 所示。很容易误解的是,alloc_pages() 对应的函数并不是 free_pages(),而是 __free_pages()free_pages() 是一个以地址为参数的辅助函数。

// mm/page_alloc.c
// 参数是我们将释放的 page 和块的幂次。
void __free_pages(struct page *page, unsigned int order)
{
	// 有效性检查。PageReserved()表示页面由引导内存分配器保留。put_page_testzero() 仅
	// 是一个对 atomic_dec_and_test() 的宏封装。它将使用计数减 1,保证它为 0。
	if (!PageReserved(page) && put_page_testzero(page))
	// 调用函数来完成所有的复杂工作。
		__free_pages_ok(page, order);
}

(a)⇐ mm.h

// include/linux/mm.h
#define put_page_testzero(p) 	atomic_dec_and_test(&(p)->count)

#define PageLRU(page)		test_bit(PG_lru, &(page)->flags)
#define ClearPageUptodate(page)	clear_bit(PG_uptodate, &(page)->flags)
#define PageDirty(page)		test_bit(PG_dirty, &(page)->flags)
#define SetPageDirty(page)	set_bit(PG_dirty, &(page)->flags)
#define ClearPageDirty(page)	clear_bit(PG_dirty, &(page)->flags)
#define PageLocked(page)	test_bit(PG_locked, &(page)->flags)
#define LockPage(page)		set_bit(PG_locked, &(page)->flags)
#define TryLockPage(page)	test_and_set_bit(PG_locked, &(page)->flags)
#define PageChecked(page)	test_bit(PG_checked, &(page)->flags)
#define SetPageChecked(page)	set_bit(PG_checked, &(page)->flags)
#define PageLaunder(page)	test_bit(PG_launder, &(page)->flags)
#define SetPageLaunder(page)	set_bit(PG_launder, &(page)->flags)
#define ClearPageLaunder(page)	clear_bit(PG_launder, &(page)->flags)


/*
 * The first mb is necessary to safely close the critical section opened by the
 * TryLockPage(), the second mb is necessary to enforce ordering between
 * the clear_bit and the read of the waitqueue (to avoid SMP races with a
 * parallel wait_on_page).
 */
#define PageError(page)		test_bit(PG_error, &(page)->flags)
#define SetPageError(page)	set_bit(PG_error, &(page)->flags)
#define ClearPageError(page)	clear_bit(PG_error, &(page)->flags)
#define PageReferenced(page)	test_bit(PG_referenced, &(page)->flags)
#define SetPageReferenced(page)	set_bit(PG_referenced, &(page)->flags)
#define ClearPageReferenced(page)	clear_bit(PG_referenced, &(page)->flags)
#define PageTestandClearReferenced(page)	test_and_clear_bit(PG_referenced, &(page)->flags)
#define PageSlab(page)		test_bit(PG_slab, &(page)->flags)
#define PageSetSlab(page)	set_bit(PG_slab, &(page)->flags)
#define PageClearSlab(page)	clear_bit(PG_slab, &(page)->flags)
#define PageReserved(page)	test_bit(PG_reserved, &(page)->flags)

#define PageActive(page)	test_bit(PG_active, &(page)->flags)
#define SetPageActive(page)	set_bit(PG_active, &(page)->flags)
#define ClearPageActive(page)	clear_bit(PG_active, &(page)->flags)

#define PageLRU(page)		test_bit(PG_lru, &(page)->flags)
#define TestSetPageLRU(page)	test_and_set_bit(PG_lru, &(page)->flags)
#define TestClearPageLRU(page)	test_and_clear_bit(PG_lru, &(page)->flags)

#ifdef CONFIG_HIGHMEM
#define PageHighMem(page)		test_bit(PG_highmem, &(page)->flags)
#else
#define PageHighMem(page)		0 /* needed to optimize away at compile time */
#endif

#define SetPageReserved(page)		set_bit(PG_reserved, &(page)->flags)
#define ClearPageReserved(page)		clear_bit(PG_reserved, &(page)->flags)

(2)__free_pages_ok

    这个函数将完成实际的释放页面工作,并在可能的情况下合并伙伴。

/*
 * Freeing function for a buddy system allocator.
 * Contrary to prior comments, this is *NOT* hairy, and there
 * is no reason for anyone not to understand it.
 *
 * The concept of a buddy system is to maintain direct-mapped tables
 * (containing bit values) for memory blocks of various "orders".
 * The bottom level table contains the map for the smallest allocatable
 * units of memory (here, pages), and each level above it describes
 * pairs of units from the levels below, hence, "buddies".
 * At a high level, all that happens here is marking the table entry
 * at the bottom level available, and propagating the changes upward
 * as necessary, plus some accounting needed to play nicely with other
 * parts of the VM system.
 * At each level, we keep one bit for each pair of blocks, which
 * is set to 1 iff only one of the pair is allocated.  So when we
 * are allocating or freeing one, we can derive the state of the
 * other.  That is, if we allocate a small block, and both were   
 * free, the remainder of the region must be split into blocks.   
 * If a block is freed, and its buddy is also free, then this
 * triggers coalescing into a block of larger size.            
 *
 * -- wli
 */

static void FASTCALL(__free_pages_ok (struct page *page, unsigned int order));
// 参数是待释放页面块的开始和待释放页面的幂次数。
static void __free_pages_ok (struct page *page, unsigned int order)
{
	unsigned long index, page_idx, mask, flags;
	free_area_t *area;
	struct page *base;
	zone_t *zone;

	/*
	 * Yes, think what happens when other parts of the kernel take 
	 * a reference to a page in order to pin it for io. -ben
	 */
	// 在标志 I/O 时,LRU 中的脏页面将仍然设置有 LRU 位。一旦 I/O 完成,它就会
	// 被释放,所以现在必须从 LRU 链表中移除。 
	if (PageLRU(page)) {
		if (unlikely(in_interrupt()))
			BUG();
		// mm/swap.c			
		lru_cache_del(page);
	}

	// 有效性检查。
	if (page->buffers)
		BUG();
	if (page->mapping)
		BUG();
	if (!VALID_PAGE(page))
		BUG();
	if (PageLocked(page))
		BUG();
	if (PageActive(page))
		BUG();
	// 由于页面现在空闲,没有在使用中,这个标志位表示页面已经被引用,而且是必须被
	// 清洗的脏页面。		
	page->flags &= ~((1<<PG_referenced) | (1<<PG_dirty));

	// 如果设置了该标志,这些已经释放了的页面将保存在释放它们的进程中。如果
	// 调用者是它自己在释放页面,而不是等待 kswapd 来释放,在分配页面时这里调用 
	// balance_classzone()。
	if (current->flags & PF_FREE_PAGES)
		goto local_freelist;
 back_local_freelist:
	// 页面所属管理区用页面标志位编码。宏 page_zone() 返回该管理区。
	zone = page_zone(page);
	// 有关掩码计算的讨论在随书附带的文档中。它基本上与伙伴系统的地址计算有关。
	mask = (~0UL) << order;
	// base 是这个 zone_mem_map 的起始端。对伙伴计算而言,它与地址 0 有关,这样地址
	// 就是 2 的幕。
	base = zone->zone_mem_map;
	// page_idx 视 zone_mem_map 为一个由页面组成的数组,这是映射图中的页索引。
	page_idx = page - base;
	// 如果索引不是 2 的幕,则肯定是某个地方出现严重错误,伙伴的计算将不会进行。
	if (page_idx & ~mask)
		BUG();
	// index 是 free_area->map 的位索引。		
	index = page_idx >> (1 + order);
	// area 是存储空闲链表和映射图的区域,其中映射图是释放页面的有序块。
	area = zone->free_area + order;

	// 管理区将改变,所以这里上锁。由于中断处理程序可能在这个路径上分配页面,所以
	// 这个锁是一个中断安全的锁。
	spin_lock_irqsave(&zone->lock, flags);
	// mask 计算的另一个副作用是 -mask 是待释放的页面数。
	zone->free_pages -= mask;

// 分配器将不断地试着合并块直到不能再合并,或者到达了可以合并的最高次。
// 对合并的每一次序块,mask 都将调整。当到达了可以合并的最高次的时候,while 循环将为 0
// 并退出。
//
	while (mask + (1 << (MAX_ORDER-1))) {
		struct page *buddy1, *buddy2;
	// 如果发生什么意外,mask 被损坏,这个检查将保证 free_area 不会超过末端读。
		if (area >= zone->free_area + MAX_ORDER)
			BUG();
	// 表示伙伴对的位置位。如果以前该位是 0,则两个伙伴都在使用中。因此,在这个伙
	// 伴释放后,另外一个正在使用中,所以不能合并。			
		if (!__test_and_change_bit(index, area->map))
			/*
			 * the buddy page is still allocated.
			 */
			break;
		/*
		 * Move the buddy up one level.
		 * This code is taking advantage of the identity:
		 * 	-mask = 1+~mask
		 */
	// 这两个地址的计算在第 6 章讨论。		 
		buddy1 = base + (page_idx ^ -mask);
		buddy2 = base + page_idx;
	// 有效性检查保证页面在正确的 zone_mem_map 中,而且实际上属于这个管理区。		
		if (BAD_RANGE(zone,buddy1))
			BUG();
		if (BAD_RANGE(zone,buddy2))
			BUG();

	// 伙伴已经被释放,所以这里将其从包含它的链表中移除。
		list_del(&buddy1->list);
// 准备检查待合并的高次伙伴。	
//
	// 将掩码左移 1 位到次 2^(k+1)	
		mask <<= 1;
	// area 是一个数组内指针,所以 area++ 移到下一个下标。		
		area++;
	// 高次位图的索引。		
		index >>= 1;
	// 待合并 zone_mem_map 中的页面索引。		
		page_idx &= mask;
	}

	// 由于尽可能多地合并已经完成,而且释放了一个新页面块,所以这里将其加入到该次
	// 的 free_list 中。
	list_add(&(base + page_idx)->list, &area->free_list);
	// 对管理区的改变已经完成,所以这里释放锁并返回。
	spin_unlock_irqrestore(&zone->lock, flags);
	return;

// 这是在页面没有释放到主页面池时的代码路径,它将页面保留给释放的进程。
 local_freelist:
 	// 如果进程已经有保留页面,则这里不允许再保留页面,所以返回。这里很不寻
	// 常,因为 balance_classzone() 假设多于一个页面块可能从该链表上返回。这很有可能能过虑了,
	// 但是如果释放的第一个页面是同一次的,而 balance_classzone()请求管理区,则这里仍然可以
	// 工作。
	if (current->nr_local_pages)
		goto back_local_freelist;
	// 一个中断没有进程上下文,所以它必须象平常一样释放。现在还不明白这里的
	// 中断如何结束。这里的检查似乎是假的,而且不可能为真的。		
	if (in_interrupt())
		goto back_local_freelist;		

	// 将页面块加入到链表中处理 local_pages。
	list_add(&page->list, &current->local_pages);
	// 记录分配的次数,从而方便后面的释放操作。
	page->index = order;
	// 将 nr_local_pages 使用计数加 1。
	current->nr_local_pages++;
}

(a)⇐ zone_t

    每个管理区由一个 zone_t 描述,具体可参考 ⇒ 2.2 管理区,2.6 页面映射到管理区

// include/linux/mm.h
extern struct zone_struct *zone_table[];

static inline zone_t *page_zone(struct page *page)
{
	return zone_table[page->flags >> ZONE_SHIFT];
}

(b)⇐ free_area_init_core

    这个函数负责初始化所有的区域,并在节点中分配它们的局部 Imem_map(类型 struct page *)。并初始化 pg_data_t 中字段 node_mem_mapnode_sizenode_start_paddrnode_start_mapnrnr_zonesnode_zones 以及全局 zone_table

传送门 free_area_init_core

4、释放辅助函数

    这些函数与页面分配的辅助函数非常相似,因为它们也不完成 “实际” 的工作,它们依赖于 __free_pages() 函数来完成实际的释放。

(1)free_pages

// mm/page_alloc.c
// 这个函数以一个地址,而不是以一个页面作为参数来进行释放操作。
void free_pages(unsigned long addr, unsigned int order)
{
	if (addr != 0)
	// 宏 virt_to_page() 返回 addr 的 struct page。
		__free_pages(virt_to_page(addr), order);
}

(a)⇐ page.h

// include/asm/page.h
#define virt_to_page(kaddr)	(mem_map + (__pa(kaddr) >> PAGE_SHIFT))

(2)__free_page

// include/linux/mm.h
// 这个宏仅调用函数 __free_pages() ,参数为 0 幂次和一个页面。
#define __free_page(page) __free_pages((page), 0)

(3)free_page

// include/linux/mm.h
// 这个小宏仅调用 free_pages() 。这个宏与 __free_page() 的主要区别在于这个函数以一个
// 虚拟地址为参数,而 __free_page() 以一个 struct page 为参数
#define free_page(addr) free_pages((addr),0)

二、补充配置

1、空格

&emsp; 为“全角空格”
&emsp; 为“全角空格”
&nbsp; 为“不换行空格”

123

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

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

相关文章

面试银行测试岗,面试官问你网上银行转账是怎么测的,设计一下测试用例?你知道吗

目录 前言 1、网上银行转账是怎么测的&#xff0c;设计一下测试用例回答思路: 2.测试工作的流程?缺陷状态有什么?设计测试用例有几种方法? 3在项目中到的经典BUG是什么? 4、定期存款到期自动转存该怎么测? 5、登录功能如何设计测试用例?功能测试(FunctionTest) 6、…

上海亚商投顾:创业板指续创新低 AI概念股逆势大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 大小指数今日走势分化&#xff0c;沪指全天横盘震荡&#xff0c;创业板指低开低走&#xff0c;午后一度跌近2%&…

低代码最真实的反馈是什么样的?

一、前言 业内大V陈果曾吐槽&#xff1a;“低代码正在以比中台更快的速度在臭大街”&#xff0c;但在另一方面&#xff0c;微软/华为/百度/网易/腾讯/阿里等互联网巨头又在纷纷入局低代码&#xff0c;并推出了自己的低代码平台。 所以&#xff0c;有人说好&#xff0c;但却又有…

《计算机组成原理》期末考试手写笔记——模块二:计算机数据表示方法

目录 &#xff08;一&#xff09;知识点总结 知识点一&#xff1a;进制表示 知识点二&#xff1a;进制转换 1.基本进制转换计算 &#xff08;1&#xff09;二进制数转八进制 &#xff08;2&#xff09;二进制数转十六进制 &#xff08;3&#xff09;二进制数转十进制 &…

Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解

&#x1f44f; 简介&#xff1a;大家好&#xff0c;我是冰点&#xff0c;从业11年&#xff0c;目前在物流独角兽企业从事技术管理和架构设计方面工作&#xff0c;之前的把博客作为技术流水账在写。现在准备把多年的积累整理一下&#xff0c;成体系的分享给大家&#xff0c;也算…

阿里巴巴最新开源“SpringSecurity手册”用户+案例+认证+框架,面面俱到太全了

pringSecurity 相信Spring大家一定不陌生&#xff0c;那么SpringSecurity你又了解多少呢&#xff1f;市面上有关Spring的介绍有很多&#xff0c;那么对于SpringSecurity只有一些简单的有关概念的介绍&#xff0c;如果想深入了解并使用SpringSecurity还是需要下很大的功夫的&am…

Python爬取广州、深圳、河源、惠州四个城市天气数据,并作数据可视化

本文爬取的网站是天气,天气预报查询,24小时,今天,明天,未来一周7天,10天,15天,40天查询_2345天气王 1.爬取广惠河深2022-2024年的数据 import requests # 发送请求要用的模块 需要额外安装的 import parsel import csvf open(广-惠-河-深天气.csv, modea, encodingut…

【自学网安必看】自学网络安全遇到的问题及解决方法!

自学网络安全很容易学着学着就迷茫了&#xff0c;找到源头问题&#xff0c;解决它就可以了&#xff0c;所以首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题&#xff0c;看到后面有惊喜哦 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#x…

STM32 HAL库开发——入门篇(1)

目录 一、GPIO 1.1 什么是GPIO 1.2 GPIO简介 1.2.1 GPIO特点 1.2.2 GPIO电气特性 1.2.3 GPIO引脚分布 1.3 IO端口基本结构介绍 1.4 GPIO的八种模式 1.5 GPIO的寄存器介绍 1.6 通用外设驱动模型&#xff08;四步法&#xff09; 1.7 GPIO配置步骤 1.8 编程实战…

用Notes打造一个资讯中心

大家好&#xff0c;才是真的好。 有一段时间&#xff0c;我介绍过&#xff0c;从Domino 10版本开始&#xff0c;LotusScript增强了网络数据访问和JSON解析功能等&#xff0c;从而可以从网上获取很多资讯&#xff0c;例如即时新闻、股市资讯等等。 例如我们采用Notes内置的讨论…

多线程-线程安全的懒汉式_死锁-ReentrantLock的使用

线程安全的懒汉式_死锁-ReentrantLock的使用 解决单例模式中的懒汉式的线程安全问题 解决单例模式中的懒汉式的线程安全问题> 饿汉式&#xff1a;不存在线程安全问题。 > 懒汉式&#xff1a;存在线程安全问题&#xff0c;&#xff08;需要使用同步机制来处理&#xff0…

案例39:基于Java办公自动化管理系统开题报告设计

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Spring架构篇--2.7.3 远程通信基础--Netty原理--bind实现端口的绑定

前言&#xff1a;在对ServerBootstrap 进行属性赋值之后&#xff0c;通过bind 方法完成端口的绑定&#xff0c;并开始在NioEventLoop中进行轮询进行事件的处理&#xff1b;本文主要探究ServersocketChannel 在netty 中是如何完成注册&#xff0c;以及端口的绑定 1 Nio selecto…

两个月涨粉90万,B站内容风向又有新指示?

6月1日&#xff0c;B站公布了2023年第一季度财报。 财报中显示第一季度&#xff0c;B站日均活跃用户达9370万&#xff0c;同比增长18%。用户日均使用时长96分钟&#xff0c;日均视频播放量达41亿&#xff0c;其中&#xff0c;本季度B站Story-Mode竖屏视频日均播放量同比增长82…

网络安全怎么学?才不会成为脚本小子?

一&#xff0c;怎么入门&#xff1f; 1、Web 安全相关概念&#xff08;2 周&#xff09; 了解网络安全相关法律法规 熟悉基本概念&#xff08;SQL 注入、上传、XSS、CSRF、一句话木马等&#xff09;。 通过关键字&#xff08;SQL 注入、上传、XSS、CSRF、一句话木马等&#…

Python使用WMI模块获取Windows系统的硬件信息,并使用pyinstaller库编译打包成exe的可执行文件

引言 今天给大家分享一篇有关Python和Windows系统的文章。你有没有想过如何获取Windows系统的硬件信息&#xff1f;或者你是否曾经尝试过将Python脚本编译打包成可执行文件&#xff1f;如果你对这些问题感兴趣&#xff0c;那么这篇文章一定适合你。 背景 由于公司现阶段大多…

软件兼容性测试怎么进行?兼容性测试有什么作用?

随着软件的不断更新和升级&#xff0c;兼容性测试也逐渐成为了软件测试的一项重要内容。那么&#xff0c;软件兼容性测试到底是什么?怎么进行?又有什么作用呢? 一、什么是软件兼容性测试? 软件兼容性测试是指在不同的操作系统、硬件设备、浏览器等多个环境下测试软件的可…

VulnHub项目:Hogwarts:Bellatrix

靶机地址&#xff1a;Hogwarts: Bellatrix ~ VulnHub 哈利波特系列~有趣~~~ 渗透过程&#xff1a; 首先获取靶机ip地址 对其端口探测 访问web端口&#xff0c;发现了小天狼星的堂姐Bellatrix Lestrange贝拉特里克斯莱斯特兰奇那个疯狂的女人&#xff01;&#xff01;&#x…

天涯直播筹资300万,七天仅赚14.99万,重启计划岌岌可危

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 天涯前天涯社区总编直播卖货欲筹300万&#xff0c;重启天涯。直播七天七夜只赚了14.99万。对于普通人来说&#xff0c;这收入真的是望尘莫及了&#xff0c;但对于天涯来说杯水车薪。 也许天涯部分…

前端工程化初体验

最近在尝试着完整地体验一下前端工程化需要的那些流程&#xff0c;于是自己尝试一套属于自己的前端工程化流程。 前端工程化需要做什么&#xff1a; 1、创建项目需要有项目模板资源&#xff0c;所以这里我创建了一个前端脚手架CLI工具&#xff0c;mfex-project&#xff0c;主…