浅谈Linux内核页面换入换出

news2024/11/18 2:36:26

【推荐阅读】

浅谈linux 内核网络 sk_buff 之克隆与复制

深入linux内核架构--进程&线程

了解Docker 依赖的linux内核技术

怎么在Windows下使用Makefile文件

浅析linux内核网络协议栈--linux bridge

0x00内存页面分类与换入换出规则

内存页面分为用户页面和内核页面。

用户页面有以下几种:

1、普通的用户空间页面,包括进程的代码段、数据段、堆栈段、以及动态分配的存储堆。

2、通过系统调用mmap()映射到用户空间的已打开文件的内容。

这些页面既涉及分配、使用和回收,也涉及页面的换出/换入。

内核页面有以下几种:

1、kmalloc分配用作某些临时性的数据结构,如vma_area_struct。

2、内核通过alloc_page分配,如每个进程的系统堆栈所在的两个页面。

这些页面不涉及页面的换出/换入,一旦使用完毕,就可以释放、回收。

3、文件系统相关的结构体如dentry、node

这些页面不涉及页面的换出/换入,但即使使用完毕,其内容仍有保存的价值,只要条件允许,就将这些页面养起来,可以提高以后的操作效率

4、内核代码和内核中全局量所占的内存页面

这些页面既不需要分配,也不会被释放

0x01用户页面的换入

对于内核来说,只有两种用户页面,一种是文件映射,一种是匿名映射。前一种和swap没有关系,直接换出到硬盘上文件。后者会交换到swap。

1、文件映射--->换出到硬盘

2、匿名映射--->换出到swap

既然涉及到换出,我们还是先说下换入,换入也由换出定义的这两种页面为导向。

1、可执行文件(文件映射还包含直接映射硬盘上某个文件,不限于可执行文件)的换入

	error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
				elf_prot, elf_flags, 0);

elf_map会进一步调用mmap将可执行文件的映射到内存,这种映射属于文件映射。

我们先看下映射到内存虚拟地址的情况:

0x00601000-0x00602000这个虚拟地址空间映射为可写,我们再来看看这个区域到底放着什么信息?

里面是.data段、.bss段一些可写的段。

那么就涉及到一个问题,既然是文件映射,当发生页面短缺时,是要换出到硬盘的。我们可以想象下总不可能每执行一次文件,我硬盘的可执行文件的数据就变化一下吧?那么linux内核是怎么处理的呢?我们放在后面再说。

首先理清一个思路,mmap这个系统调用,只是申请一片虚拟内存地址,并没有实际到把硬盘中的数据读到内存,并建立映射(建立页目录表、页表)。

当访问到对应的虚拟地址空间时,触发缺页中断do_no_page,从硬盘中把数据读到内存,并建立映射。

if (!pte_present(entry)) {//页面不在内存中  
        /* 
         * If it truly wasn't present, we know that kswapd 
         * and the PTE updates will not touch it later. So 
         * drop the lock. 
         */  
        spin_unlock(&mm->page_table_lock);  
        if (pte_none(entry))//页表项为空  
            return do_no_page(mm, vma, address, write_access, pte);  
        return do_swap_page(mm, vma, address, pte, pte_to_swp_entry(entry), write_access);//执行到这里  
    } 

static int do_no_page(struct mm_struct * mm, struct vm_area_struct * vma,  
    unsigned long address, int write_access, pte_t *page_table)  
{  
    struct page * new_page;  
    pte_t entry;  
  
    if (!vma->vm_ops || !vma->vm_ops->nopage)  
        return do_anonymous_page(mm, vma, page_table, write_access, address);  
  
    /* 
     * The third argument is "no_share", which tells the low-level code 
     * to copy, not share the page even if sharing is possible.  It's 
     * essentially an early COW detection. 
     */  
    new_page = vma->vm_ops->nopage(vma, address & PAGE_MASK, (vma->vm_flags & VM_SHARED)?0:write_access);//指向了filemap_nopage  
    if (new_page == NULL)   /* no page was available -- SIGBUS */  
        return 0;  
    if (new_page == NOPAGE_OOM)  
        return -1;  
    ++mm->rss;  
    /* 
     * This silly early PAGE_DIRTY setting removes a race 
     * due to the bad i386 page protection. But it's valid 
     * for other architectures too. 
     * 
     * Note that if write_access is true, we either now have 
     * an exclusive copy of the page, or this is a shared mapping, 
     * so we can make it writable and dirty to avoid having to 
     * handle that later. 
     */  
    flush_page_to_ram(new_page);  
    flush_icache_page(vma, new_page);  
    entry = mk_pte(new_page, vma->vm_page_prot);  
    if (write_access) {  
        entry = pte_mkwrite(pte_mkdirty(entry));  
    } else if (page_count(new_page) > 1 &&  
           !(vma->vm_flags & VM_SHARED))  
        entry = pte_wrprotect(entry);  
    set_pte(page_table, entry);//建立映射  
    /* no need to invalidate: a not-present page shouldn't be cached */  
    update_mmu_cache(vma, address, entry);  
    return 2;   /* Major fault */  
}  

此时分配的页面被放入了活跃队列active_list。

2、匿名内存换入

匿名内存通常是堆栈和存储堆,存储堆通过malloc分配,malloc底层调用mmap,不过传入的参数fd为-1,表示匿名映射

void *mmap(void *addr, size_t length, int prot, int flags,

int fd, off_t offset);

匿名映射当发生缺页中断时,也会调用do_no_page,然后调用do_anonymous_page,分配内页,建立映射,加入了活跃队列active_list。

0x02用户页面的换出

1、思考几个问题:

页面是直接换出到swap或者硬盘么?

这样的话,如果刚被换出,又立刻访问了呢?就需要再一次换入到内存,从而造成内存抖动。linux内核的处理如下:

linux内核有两个内核线程,专门用于换出页面,他们是kswapd和kreclaimd。详情请参考Linux内核源代码情景分析-内存管理之用户页面的定期换出。

1)kswapd线程:

当检测是页面短缺时,根据某些规则挑选一些页面,调用refill_inactive_scan和swap_out,把活跃的页面变成不活跃脏的页面。从active_list移除,并加入inactive_dirty队列;

之后还会调用page_launder,把不活跃脏的页面变成不活跃干净的页面,从inactive_dirty队列移除,并加入到inactive_clean队列。

2)kreclaimd内核线程:

把不活跃干净的页面,所有的链表关系都清除,但使用计数仍然为1。

__free_page,此时使用计数减为0,回收这个页面到free_area[MAX_ORDER],下次alloc_page就能分配到了。

2、把活跃的页面变成不活跃脏的页面。从active_list移除,并加入inactive_dirty队列;swap_out会调用try_to_swap_out:

针对被移到inactive_dirty队列中的page,此时页面中内容和磁盘或者swap分区中的内容是一致的,现在的页表项分为两种情况:

1)针对匿名映射的页面,此时页表项指向一个新分配的swap分区地址,用于将来换出到 swap分区

2)针对文件映射的页面,此时页表项已经清空,换出到硬盘有专门的函数处理。

static int try_to_swap_out(struct mm_struct * mm, struct vm_area_struct* vma, unsigned long address, pte_t * page_table, int gfp_mask)
{
	pte_t pte;
	swp_entry_t entry;
	struct page * page;
	int onlist;
 
	pte = *page_table;
	if (!pte_present(pte))
		goto out_failed;
	page = pte_page(pte);
	if ((!VALID_PAGE(page)) || PageReserved(page))
		goto out_failed;
 
	if (!mm->swap_cnt)
		return 1;
 
	mm->swap_cnt--;
 
	onlist = PageActive(page);
	/* Don't look at this pte if it's been accessed recently. */
	if (ptep_test_and_clear_young(page_table)) {
		age_page_up(page);
		goto out_failed;
	}
	if (!onlist)
		/* The page is still mapped, so it can't be freeable... */
		age_page_down_ageonly(page);
 
	/*
	 * If the page is in active use by us, or if the page
	 * is in active use by others, don't unmap it or
	 * (worse) start unneeded IO.
	 */
	if (page->age > 0)
		goto out_failed;
 
	if (TryLockPage(page))
		goto out_failed;
 
	/* From this point on, the odds are that we're going to
	 * nuke this pte, so read and clear the pte.  This hook
	 * is needed on CPUs which update the accessed and dirty
	 * bits in hardware.
	 */
	pte = ptep_get_and_clear(page_table);
	flush_tlb_page(vma, address);
 
	/*
	 * Is the page already in the swap cache? If so, then
	 * we can just drop our reference to it without doing
	 * any IO - it's already up-to-date on disk.
	 *
	 * Return 0, as we didn't actually free any real
	 * memory, and we should just continue our scan.
	 */
	if (PageSwapCache(page)) {
		entry.val = page->index;
		if (pte_dirty(pte))
			set_page_dirty(page);
set_swap_pte:
		swap_duplicate(entry);
		set_pte(page_table, swp_entry_to_pte(entry));
drop_pte:
		UnlockPage(page);
		mm->rss--;
		deactivate_page(page);
		page_cache_release(page);
out_failed:
		return 0;
	}
 
	/*
	 * Is it a clean page? Then it must be recoverable
	 * by just paging it in again, and we can just drop
	 * it..
	 *
	 * However, this won't actually free any real
	 * memory, as the page will just be in the page cache
	 * somewhere, and as such we should just continue
	 * our scan.
	 *
	 * Basically, this just makes it possible for us to do
	 * some real work in the future in "refill_inactive()".
	 */
	flush_cache_page(vma, address);
	if (!pte_dirty(pte))
		goto drop_pte;
 
	/*
	 * Ok, it's really dirty. That means that
	 * we should either create a new swap cache
	 * entry for it, or we should write it back
	 * to its own backing store.
	 */
	if (page->mapping) {
		set_page_dirty(page);
		goto drop_pte;
	}
 
	/*
	 * This is a dirty, swappable page.  First of all,
	 * get a suitable swap entry for it, and make sure
	 * we have the swap cache set up to associate the
	 * page with that swap entry.
	 */
	entry = get_swap_page();
	if (!entry.val)
		goto out_unlock_restore; /* No swap space left */
 
	/* Add it to the swap cache and mark it dirty */
	add_to_swap_cache(page, entry);
	set_page_dirty(page);
	goto set_swap_pte;
 
out_unlock_restore:
	set_pte(page_table, pte);
	UnlockPage(page);
	return 0;
}

0x03用户页面的再次换入

当下一次访问到对应的页面时,由于页表项已经清空或者指向swap分区(但是最后一位为0,依然会触发缺页中断)。

对于匿名映射的页面,缺页中断会调用do_swap_page;

对于文件映射的页面,缺页中断会调用do_no_page;

此时页面并没有从hash列表移除,所以两者可以从hash表中读到对应的页面,直接建立映射即可,不需要重新换入,减少内存抖动。

static int do_swap_page(struct mm_struct * mm,  
    struct vm_area_struct * vma, unsigned long address,  
    pte_t * page_table, swp_entry_t entry, int write_access)  
{  
    struct page *page = lookup_swap_cache(entry);//从hash表中寻找  
    pte_t pte;  
  
    if (!page) {  
        lock_kernel();  
        swapin_readahead(entry);//预读页面  
        page = read_swap_cache(entry);//真正得到一个页面,这个页面可能从hash表中寻找到,因为上面预读了。或者自己申请页面,并且从盘上将其内容读进来。 

PS: 虽然已经建立了映射,但是page所在的队列,应该被移到active_list中,什么时候移到的呢?请参考http://blog.csdn.net/jltxgcy/article/details/44055485

2、针对 kreclaimd内核线程处理后的页面,由于已经从hash列表中移除,所以此时如果发生缺页中断,就要真刀真枪的从swap分区或者硬盘中读入数据。

对于匿名页面映射,此时页表项已经指向了swap分区,页面从swap分区换入,并建立映射,重新放入active_list。

对于文件页面映射,此时页表项为空,页面从硬盘分区换入,并建立,重新放入active_list。

0x04回答最开始的问题

里面是.data段、.bss段一些可写的段。

那么就涉及到一个问题,既然是文件映射,当发生页面短缺时,是要换出到硬盘的。我们可以想象下总不可能每执行一次文件,我硬盘的可执行文件的数据就变化一下吧?那么linux内核是怎么处理的呢?

在加载可执行文件时,有这段代码:

elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;
 
		vaddr = elf_ppnt->p_vaddr;
		if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
			elf_flags |= MAP_FIXED;
		} else if (loc->elf_ex.e_type == ET_DYN) {
			/* Try and get dynamic programs out of the way of the
			 * default mmap base, as well as whatever program they
			 * might try to exec.  This is because the brk will
			 * follow the loader, and is not movable.  */
#if defined(CONFIG_X86) || defined(CONFIG_ARM)
			load_bias = 0;
#else
			load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);
#endif
		}
 
		error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
				elf_prot, elf_flags, 0);

传递给mmap的flag中有MAP_PRIVATE,下面是对MAP_PRIVATE的解释。

MAP_PRIVATE

Create a private copy-on-write mapping. Updates to the

mapping are not visible to other processes mapping the same

file, and are not carried through to the underlying file. It

is unspecified whether changes made to the file after the

mmap() call are visible in the mapped region.

linxu内核也有这段描述, documentation/filesystems/proc.txt:

"Anonymous" shows the amount of memory that does not belong to any file. Even a mapping associated with a file may contain anonymous pages: when MAP_PRIVATE and a page is modified, the file page is replaced by a private anonymous copy.

也就是说上面那段区域当写入时,会执行copy_on_write创建一份匿名映射,然后可以被换出到swap分区。我们在图中看到三个segment是不会被换出的硬盘的,我们已经声明了MAP_DENYWRITE。

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

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

相关文章

GDT践行(第一期):起床---运动--阅读

这里记录每周GDT践行记录.【2022】年第【51】周的第【6】天 封面图 第三部分:起床---运动--阅读 ❝ 小贴士: 在学校,出租房,宾馆,工位很多因素导致 学习区,运动区 卧室 都是同一个地方 环境导致你彻底彻底糊…

[附源码]Python计算机毕业设计Django课程在线测评系统

项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等等。 环境需要 1.运行环境:最好是python3.7.7,…

JVM部分知识点

目录 JVM主要组成部分及其作用? JAVA程序运行机制详情 JVM运行时的数据区 堆和栈的区别? Java垃圾回收机制 Java中有哪些引用类型? 如何判断对象是否可以被回收? JVM中的永久代会发生垃圾回收吗? JVM有哪些垃圾…

我国航空煤油行业发展趋势:燃油附加费复收 或将缓解企业经营压力

根据观研报告网发布的《中国航空煤油市场现状深度研究与投资前景分析报告(2022-2029年)》显示,航空煤油是石油产品之一,别名无臭煤油,主要由不同馏分的烃类化合物组成。航空煤油密度适宜,热值高&#xff0c…

【C语言】浮点型的存储方式

目录 一、浮点型和整型存储方式一样吗? 二、浮点型的存储规则 2.1 S,M,E求法 2.2 如何存放S,M,E 2.2.1 IEEE 754规定 2.2.2 特别的规定 2.2.3 验证 2.3 取出规则 2.3…

改造冰蝎马,实现免杀之default_aes php

本专栏是笔者的网络安全学习笔记,一面分享,同时作为笔记 文章目录 文章目录文章目录前文链接前言效果目标密码验证功能免杀后话前文链接 WAMP/DVWA/sqli-labs 搭建burpsuite工具抓包及Intruder暴力破解的使用目录扫描,请求重发,漏…

[附源码]Python计算机毕业设计黑格伯爵国际英语贵族学校官网Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

5G无线技术基础自学系列 | 单站点验证概述

素材来源:《5G无线网络规划与优化》 一边学习一边整理内容,并与大家分享,侵权即删,谢谢支持! 附上汇总贴:5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 单站点验证是指在基站硬件安装调试完成…

RNA-seq 详细教程:可视化(12)

学习内容 了解如何为可视化准备数据了解如果利用可视化来探索分析结果火山图可视化热图可视化可视化结果 当我们处理大量数据时,以图形方式显示该信息以获得更多信息,可能很有用。在本课中,我们将让您开始使用探索差异基因表达数据时常用的一…

【数电实验】移位寄存器与计数器

实验四 移位寄存器与计数器 一 实验目的 1 掌握任意进制计数器的构成方法; 2 熟悉双向移位寄存器的使用方法。 二 实验内容 1 任意进制计数器的构成方法: 用中规模集成计数器74HC161和与非门74LS00,构成十进制计数器。要求分别使用同步预…

精华推荐 | 【深入浅出RocketMQ原理及实战】「性能原理挖掘系列」透彻剖析贯穿RocketMQ的事务性消息的底层原理并在分析其实际开发场景

什么是事务消息 事务消息(Transactional Message)是指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。RocketMQ的事务消息提供类似 X/Open XA 的分布事务功能,通过事务消息能达到分布式事务的最终一致。 事务消息所对应的场景 在一些对…

docker学习笔记(五)单个服务镜像部署

引言 当前微服务项目已经大面积普及,对于新需求迭代上线有许多疑惑的部分,比如线上的某些功能不能重启,在这种情况下我们需要部署和启动项目就不能搞大范围重启或干脆重新制作镜像,这种方式都是不可取的,这时候就需要…

重学webpack系列(二) -- webpack解决的问题与实现模块化的具体实践

只是根据几个想法,我们便创造出了webpack打包工具,它能够根据我们在前端项目中遇到的疑难杂症对症下药,那么这一章我们就一起来探讨一下我们项目落地所遇到的种种问题。 前端实践中的问题 Jsx / Tsx编译问题Less / Scss编译问题TypeScript编…

【Pintos】实现自定义 UserProg 系统调用 | 添加 syscall-nr 系统调用号 | 编写新的参数调用宏

💭 写在前面:本文讲解的内容不属于 Pintos 的 Project 项目,而是关于 userprog 如何添加系统调用的,学习如何额外实现一些功能到系统调用中以供用户使用。因为涉及到 src/example 下的Makefile 的修改、lib 目录下 syscall-nr 系统…

门诊排队叫号系统,有序叫号就诊,适用医院医院、门诊部、诊所等

排队叫号系统,是将互联网信息技术与门诊预约、签到、提醒、叫号、接诊等环节相结合,实现门诊流程式便捷叫号服务。 为助力门诊营造一个良好有序的就诊环境,打造科学合理的就诊流程,今天给大家推荐一款一款便捷排队叫号系统&#x…

Linux基本权限(2)

Linux基本权限(2) 📟作者主页:慢热的陕西人 🌴专栏链接:Linux 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客主要讲解了目录权限,和目录&#…

2022年底,我手里一共负责了30套系统

2022年真是不平凡的一年,往常熙熙攘攘的办公室人越来越少,真是像曹操说的兄弟相继凋零,好似风中落叶啊。 结果人少了,手里的系统一个没少,慢慢年底了,我汇总了一下,手里的系统达到了30来个。 搞…

Linux--基础IO

目录 C文件IO 系统文件IO 接口介绍 系统调用和库函数 文件描述符 open返回值 文件描述符的分配规则 重定向 代码演示 使用dup2系统调用 缓冲区 FILE 理解文件系统 文件系统 inode 软硬链接 静态库和动态库 概念 生成静态库 生成动态库 C文件IO 写文件 #in…

SpringBoot+Prometheus+Grafana 实现自定义监控

1.Spring Boot 工程集成 Micrometer 1.1引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency><groupId>io.micrometer<…

E1. Erase and Extend (Easy Version)(纯暴力+string)

Problem - 1537E1 - Codeforces 这是该问题的简单版本。唯一的区别是对n和k的约束。只有当所有版本的问题都解决了&#xff0c;你才能进行黑客攻击。 你有一个字符串s&#xff0c;你可以对它进行两种类型的操作。 删除字符串的最后一个字符。 复制字符串&#xff1a;s:ss&…