Linux源码剖析匿名共享内存shmem原理

news2024/11/26 18:23:38

如下问题如果都清楚了就不用看本文了:

1. shmem ram文件系统的初始化流程是怎样的

2. shmem思想上想复用基于文件的操作流程,实现上shmem也引入了一个文件,那么类似文件open会生成struct file,shmem的struct file怎么生成的

3. shmem的phsycial page是怎么创建的,page属性是如何的(迁移属性,_refcount,_mapcount等)。

4. shmem page怎样回收的

概述

进程间共享匿名内存有两种重要的方式,其一是mmap设置MAP_SHARED | PRIVATE创建的虚拟地址区域,这片区域fork执行之后,由父子进程共享。其二是主动调用shmget/shmat相关接口。此外android操作系统的ashmem匿名共享内存也是基于linux内核的shmem实现。本文我们将从源码角度剖析内核shmem的设计和实现原理。

shmem设计的基本思想

当vma中的页面对应磁盘文件时,系统在缺页的时候为了读取一页会调用vma_area_struct->a_ops中的fault函数(filemap_fault)。要把一个page写回磁盘设备使用inode->i_mapping->a_ops或者page->mapping->a_ops在address_space_operations找到相应的writepage函数。当进行普通文件操作,如mmap(),read()和write()时,系统会通过struct file *filp的file_operations指针f_op进行相应的函数调用。f_op在文件open时候从inode->i_fop设置:

fs/read_write.c: static int do_dentry_open

上面的流程很清晰,不过却无法处理匿名页的情况,因为匿名页没有对应一个文件,所以为复用上述清晰的逻辑Linux引入了基于RAM文件系统的文件,vma都由这个文件系统中的一个文件作为后援。

同时,shmem中的page也要考虑如何回收:即页面回收时会写入swap分区当中。
 

虚拟文件系统初始化

 linux系统启动的过程中会调用到shmem_init初始化虚拟文件系统,linux给shmem创建虚拟文件,必然就有文件对应的inode,shmem文件系统创建的inode附带shmem_inode_info结构,这个结构含有文件系统的私有信息,SHMEM_I()函数以inode为参数,返回shmem_inode_info,而shmem_init_inode_cache就是初始化shmem_inode_info的kmem_cache:

 shmem_inode_info定义在<linux/shmem_fs.h>:

 各个字段的含义:

lock : 数据结构并发访问保护的自旋锁。

flags: 相关标志,详见linux mm.h中的介绍。

alloced: shmem创建的pages的数量。

swapped: 当前inode有很多pages,其中写入swapcache交换缓存的page数量。

shrinklist: 与huge page相关,暂不分析。

swaplist: shmem_inode_info结构体通过该字段挂到shmem.c中shmem_swaplist双向链表中,这个挂接过程是在shmem_writepage中实现,后面源码会分析到。也就是说inode对应的page要写入swapcache的时候,就要将shmem_inode_info挂到shmem_swaplist。

注册文件系统

shmem函数指针结构体

shmem中定义了address_space_operations结构体shmem_aops和vm_operations_struct shmem_vm_ops,分别对应缺页处理和写页面到swapcache中,定义分别如下:

static const struct vm_operations_struct shmem_vm_ops = {
	.fault		= shmem_fault,
	.map_pages	= filemap_map_pages,
#ifdef CONFIG_NUMA
	.set_policy     = shmem_set_policy,
	.get_policy     = shmem_get_policy,
#endif
};

static const struct address_space_operations shmem_aops = {
	.writepage	= shmem_writepage,
	.set_page_dirty	= __set_page_dirty_no_writeback,
#ifdef CONFIG_TMPFS
	.write_begin	= shmem_write_begin,
	.write_end	= shmem_write_end,
#endif
#ifdef CONFIG_MIGRATION
	.migratepage	= migrate_page,
#endif
	.error_remove_page = generic_error_remove_page,
};

匿名VMA使用shmem_vm_ops作为vm_operations_struct,所以中断缺页时会调用shmem_fault分配物理page。

文件和索引节点操作需要两个数据结构file_operations和inode_operations,分别定义如下:

static const struct file_operations shmem_file_operations = {
	.mmap		= shmem_mmap,
	.get_unmapped_area = shmem_get_unmapped_area,
#ifdef CONFIG_TMPFS
	.llseek		= shmem_file_llseek,
	.read_iter	= shmem_file_read_iter,
	.write_iter	= generic_file_write_iter,
	.fsync		= noop_fsync,
	.splice_read	= generic_file_splice_read,
	.splice_write	= iter_file_splice_write,
	.fallocate	= shmem_fallocate,
#endif
};

static const struct inode_operations shmem_inode_operations = {
	.getattr	= shmem_getattr,
	.setattr	= shmem_setattr,
#ifdef CONFIG_TMPFS_XATTR
	.listxattr	= shmem_listxattr,
	.set_acl	= simple_set_acl,
#endif
};

用户态空间向shmem 内存中写入数据就可以调用shmem_file_operations的write_iter接口。

shmem虚拟文件系统创建文件(类似普通文件的open过程)

shmem设计仿照了普通文件的流程,创建普通文件的时候,内核态会初始化struct file结构体和文件相应的inode,shmem这里使用虚拟文件系统也是类似流程,本小节描述shmem对应的文件的创建流程,具体实现函数为:shmem_file_setup,该函数主要创建shmem的strcut file和inode结构体 。

static struct file *__shmem_file_setup(struct vfsmount *mnt, const char *name, loff_t size,
				       unsigned long flags, unsigned int i_flags)
{
	struct inode *inode;
	struct file *res;
    ...

	inode = shmem_get_inode(mnt->mnt_sb, NULL, S_IFREG | S_IRWXUGO, 0,
				flags);
	if (unlikely(!inode)) {
		shmem_unacct_size(flags, size);
		return ERR_PTR(-ENOSPC);
	}
	inode->i_flags |= i_flags;
	inode->i_size = size;
	clear_nlink(inode);	/* It is unlinked */
	res = ERR_PTR(ramfs_nommu_expand_for_mapping(inode, size));
	if (!IS_ERR(res))
        //新建file的f_op = shmem_file_operations
		res = alloc_file_pseudo(inode, mnt, name, O_RDWR,
				&shmem_file_operations);
	if (IS_ERR(res))
		iput(inode);
	return res;
}

shmem_get_inode创建inode;alloc_file_psedo创建file对象。

shmem_get_inode函数:


static struct inode *shmem_get_inode(struct super_block *sb, const struct inode *dir,
				     umode_t mode, dev_t dev, unsigned long flags)
{
	struct inode *inode;
	struct shmem_inode_info *info;
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
	ino_t ino;

	if (shmem_reserve_inode(sb, &ino))
		return NULL;

	inode = new_inode(sb);
	if (inode) {
        ...
		switch (mode & S_IFMT) {
        ...
		case S_IFREG:
			inode->i_mapping->a_ops = &shmem_aops;
			inode->i_op = &shmem_inode_operations;
			inode->i_fop = &shmem_file_operations;
			mpol_shared_policy_init(&info->policy,
						 shmem_get_sbmpol(sbinfo));
			break;
        ...
		}

		lockdep_annotate_inode_mutex_key(inode);
	} else
		shmem_free_inode(sb);
	return inode;
}

shmem的物理page创建

发生缺页中断的时候,如果vma->vm_ops->fault存在,do_faul文件缺页处理函数中会调用该fault函数,具体可以参考Linux mmap系统调用视角看缺页中断_nginux的博客-CSDN博客​​​​​​

所以shmem的缺页中断会调用shmem_vm_ops 中的fault函数,即shmem_fault。核心函数是shmem_getpage_gfp:负责分配新页或者在swapcache或者swap分区中找到该页。


static vm_fault_t shmem_fault(struct vm_fault *vmf)
{
	struct vm_area_struct *vma = vmf->vma;
	struct inode *inode = file_inode(vma->vm_file);
	gfp_t gfp = mapping_gfp_mask(inode->i_mapping);
	enum sgp_type sgp;
	int err;
	vm_fault_t ret = VM_FAULT_LOCKED;
    ...
    处理与fallocate相关逻辑,暂不分析。
    ...
	sgp = SGP_CACHE;

	if ((vma->vm_flags & VM_NOHUGEPAGE) ||
	    test_bit(MMF_DISABLE_THP, &vma->vm_mm->flags))
		sgp = SGP_NOHUGE;
	else if (vma->vm_flags & VM_HUGEPAGE)
		sgp = SGP_HUGE;

    //缺页分配phsical page的核心函数
	err = shmem_getpage_gfp(inode, vmf->pgoff, &vmf->page, sgp,
				  gfp, vma, vmf, &ret);
	if (err)
		return vmf_error(err);
	return ret;
}

shmem_getpage_gfp


/*
 * shmem_getpage_gfp - find page in cache, or get from swap, or allocate
 *
 * If we allocate a new one we do not mark it dirty. That's up to the
 * vm. If we swap it in we mark it dirty since we also free the swap
 * entry since a page cannot live in both the swap and page cache.
 *
 * vmf and fault_type are only supplied by shmem_fault:
 * otherwise they are NULL.
 */
static int shmem_getpage_gfp(struct inode *inode, pgoff_t index,
	struct page **pagep, enum sgp_type sgp, gfp_t gfp,
	struct vm_area_struct *vma, struct vm_fault *vmf,
			vm_fault_t *fault_type)
{
	struct address_space *mapping = inode->i_mapping;
	struct shmem_inode_info *info = SHMEM_I(inode);
    ...
    //先从mapping指向的缓存中查找,如果没有找到,尝试从swapcache和swap分区查找page
	page = find_lock_entry(mapping, index);
	if (xa_is_value(page)) {
		error = shmem_swapin_page(inode, index, &page,
					  sgp, gfp, vma, fault_type);
		if (error == -EEXIST)
			goto repeat;

		*pagep = page;
		return error;
	}

	if (page && sgp == SGP_WRITE)
		mark_page_accessed(page);

    ...

alloc_huge:
	page = shmem_alloc_and_acct_page(gfp, inode, index, true);
	if (IS_ERR(page)) {
alloc_nohuge:
        //最终调用alloc_page创建物理page,内部会调用__SetPageSwapBacked(page);
		page = shmem_alloc_and_acct_page(gfp, inode,
						 index, false);
	}
    ...
	if (sgp == SGP_WRITE)
		__SetPageReferenced(page);

    //将新建的page加入mapping对应的缓存空间,同时设置了page->mapping和page->index字段
	error = shmem_add_to_page_cache(page, mapping, hindex,
					NULL, gfp & GFP_RECLAIM_MASK,
					charge_mm);
	if (error)
		goto unacct;
    //page加入相应的lru链表,shmem是inactive anon lru链表。因为内核最终判定是加入哪个
    //lru是通过page_is_file_lru,该函数:!PageSwapBacked,如果满足即file lru,否则
    //anon lru。
	lru_cache_add(page);
	alloced = true;

    ...
}

 要点:

shmem_alloc_and_acct_page最终调用alloc_page创建物理page,内部会调用__SetPageSwapBacked(page); 即shmem page是SwapBacked

shmem_add_to_page_cache:将新建的page加入mapping对应的缓存空间,设置了page->mapping和page->index字段,同时增加了NR_FILE_PAGES和NR_SHMEM计数:

 lru_cache_add:page加入相应的lru链表,shmem是inactive anon lru链表因为内核最终判定是加入哪个lru是通过page_is_file_lru,该函数:!PageSwapBacked,如果满足即file lru,否则 anon lru。

 shmem页面回写

shmem的物理page在内存紧张的时候会进行回收,由于不像file-back page可以写回磁盘,shmem的流程某些程度上类似anon page,会通过pageout换出到交换分区,其调用栈如下:

#0  shmem_writepage (page=0xffffea0000008000, wbc=0xffff888004857440) at mm/shmem.c:1371
#1  0xffffffff8135e671 in pageout (page=0xffffea0000008000, mapping=0xffff888000d78858) at mm/vmscan.c:830
#2  0xffffffff8134f168 in shrink_page_list (page_list=0xffff888004857850, pgdat=0xffff888007fda000, sc=0xffff888004857d90, ttu_flags=(unknown: 0), stat=0xffff888004857890, ignore_references=false)
    at mm/vmscan.c:1355
#3  0xffffffff81351477 in shrink_inactive_list (nr_to_scan=1, lruvec=0xffff888005c6a000, sc=0xffff888004857d90, lru=LRU_INACTIVE_ANON) at mm/vmscan.c:1962
#4  0xffffffff81352312 in shrink_list (lru=LRU_INACTIVE_ANON, nr_to_scan=1, lruvec=0xffff888005c6a000, sc=0xffff888004857d90) at mm/vmscan.c:2172
#5  0xffffffff81352c97 in shrink_lruvec (lruvec=0xffff888005c6a000, sc=0xffff888004857d90) at mm/vmscan.c:2467
#6  0xffffffff813533f1 in shrink_node_memcgs (pgdat=0xffff888007fda000, sc=0xffff888004857d90) at mm/vmscan.c:2655
#7  0xffffffff81353b0a in shrink_node (pgdat=0xffff888007fda000, sc=0xffff888004857d90) at mm/vmscan.c:2772
#8  0xffffffff81355cd8 in kswapd_shrink_node (pgdat=0xffff888007fda000, sc=0xffff888004857d90) at mm/vmscan.c:3514
#9  0xffffffff813561ac in balance_pgdat (pgdat=0xffff888007fda000, order=0, highest_zoneidx=0) at mm/vmscan.c:3672
#10 0xffffffff81356ae4 in kswapd (p=0xffff888007fda000) at mm/vmscan.c:3930
#11 0xffffffff811a2249 in kthread (_create=<optimized out>) at kernel/kthread.c:292

shmem_writepage在回收页面时将shmem写到swapcache当中:


/*
 * Move the page from the page cache to the swap cache.
 */
static int shmem_writepage(struct page *page, struct writeback_control *wbc)
{
	struct shmem_inode_info *info;
	struct address_space *mapping;
	struct inode *inode;
	swp_entry_t swap;
	pgoff_t index;

	VM_BUG_ON_PAGE(PageCompound(page), page);
	BUG_ON(!PageLocked(page));
	mapping = page->mapping;
	index = page->index;
	inode = mapping->host;
	info = SHMEM_I(inode);
	if (info->flags & VM_LOCKED)
		goto redirty;
	if (!total_swap_pages)
		goto redirty;

    ...
    //从swap分区中获取一个空闲槽位。
	swap = get_swap_page(page);
	if (!swap.val)
		goto redirty;

	/*
	 * Add inode to shmem_unuse()'s list of swapped-out inodes,
	 * if it's not already there.  Do it now before the page is
	 * moved to swap cache, when its pagelock no longer protects
	 * the inode from eviction.  But don't unlock the mutex until
	 * we've incremented swapped, because shmem_unuse_inode() will
	 * prune a !swapped inode from the swaplist under this mutex.
	 */
	mutex_lock(&shmem_swaplist_mutex);
    //将当前shmem_inode_info挂接到shmem_swaplist当中,shmem_swaplist
	if (list_empty(&info->swaplist))
		list_add(&info->swaplist, &shmem_swaplist);

    //将page添加到swapcache address_space当中
	if (add_to_swap_cache(page, swap,
			__GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN,
			NULL) == 0) {
		spin_lock_irq(&info->lock);
		shmem_recalc_inode(inode);
		info->swapped++;
		spin_unlock_irq(&info->lock);

		swap_shmem_alloc(swap);
        //从page cache space中删除
		shmem_delete_from_page_cache(page, swp_to_radix_entry(swap));

		mutex_unlock(&shmem_swaplist_mutex);
		BUG_ON(page_mapped(page));
        //开始向交换分区写入,或者写入磁盘,或者zram压缩内存。
		swap_writepage(page, wbc);
		return 0;
	}

	...
	return 0;
}

注意:

  • swap_writepage会调用set_page_writeback设置page状态为writeback,也就说page正在回写,这影响/proc/meminfo Writeback统计,也就说不管是匿名页写回交换分区(或者压缩zram),还是write系统调用page cache向磁盘文件回写,都将统计到NR_WRITEBACK中,影响proc/meminfo的Writeback字段统计。

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

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

相关文章

C语言 棱形图案

目录 一、问题分析 上部分&#xff1a; 下部分&#xff1a; 二、代码演示 一、问题分析 如上图所示&#xff0c;我们可以将棱形进行拆解&#xff0c;分为上下两个部分。 上部分&#xff1a; 通过观察&#xff0c;我们得到 单边空格数 上半部分总行数 - 行数 - 1 …

graphab 教程 ——安装

graphab 软件致力于从图论的框架对生态网络进行建模。Graphab是基于图论原理建立生态网络模型的软件,它可以实现景观组分可视化、连通性分析等,且易于与地理信息系统兼容。Graphab 是基于Java平台开发的,可直接在 Windows、Linux,Mac等操作系统中运行,界面友好且易于使用。Grap…

HCIP学习--BGP实验

一、实验拓扑 二、实验需求 除R5的5.5.5.0环回外&#xff0c;其他所有的环回均可互相访问 三、实验步骤 首先配置IP&#xff0c;配置好IBGP 建立直连的EBGP邻居关系 R1和R2建立直连的EBGP邻居关系 [r1]bgp 1 [r1-bgp]router-id 1.1.1.1 [r1-bgp]peer 12.1.1.2 as-number …

MyBatis插件开发

目录 一、项目简单搭建二 、一个接口了、两大注解、四大对象三、脱敏插件开发 一、项目简单搭建 demo结构&#xff0c;已经搭建了无数次了&#xff0c;懒的粘贴了 o(╥﹏╥)o pom文件 <dependency><groupId>org.springframework.boot</groupId><artifa…

AIGC商用实例—大模型技术助力AI测谎仪,实现视频通话实施测谎!

大家好&#xff0c;我是千寻哥&#xff0c;最近一段时间&#xff0c;给大家分享了不少的AI绘画相关的项目教程&#xff0c;很多星友都反映真的不错&#xff0c;我自己也是感觉很有意义&#xff01; 哈哈哈&#xff0c;今天我在看到了一个项目柑感觉是一个不错的idea&#xff0c…

下一代深度学习的思考与若干问题

下一代深度学习的思考和若干问题

OpenCV基本操作——图像的基础操作

目录 图像的IO操作读取图像显示图像保存图像 绘制几何图形绘制直线绘制圆形绘制矩形向图像中添加文字效果展示 获取并修改图像中的像素点获取图像的属性图像通道的拆分与合并色彩空间的改变 图像的IO操作 读取图像 cv2.imread()import numpy as np import cv2 imgcv2.imread(…

Postman: 前端必备工具还是后端独享利器

目录 Postman 的使用场景&#xff1a;适用于前端和后端 Postman 适用于前端的场景 Postman 适用于后端的场景 结论 Postman 的使用场景&#xff1a;适用于前端和后端 Postman 是一个流行的 API 测试与开发工具。它被广泛地应用在前后端开发的过程中&#xff0c;但是很多人…

SCAU操作系统知识点之(八)虚拟内存

1、虚拟地址概念&#xff0c;实地址概念 实存储器&#xff08;实存&#xff09;&#xff1a;内存 虚存储器&#xff08;虚存&#xff09;&#xff1a;磁盘 虚拟地址&#xff1a;在虚拟内存中分配给某一位置的地址&#xff0c;它使得该位置可被访问&#xff0c;就好像是主内的一…

机器学习---对数几率回归

1. 逻辑回归 逻辑回归&#xff08;Logistic Regression&#xff09;的模型是一个非线性模型&#xff0c; sigmoid函数&#xff0c;又称逻辑回归函数。但是它本质上又是一个线性回归模型&#xff0c;因为除去sigmoid映射函 数关系&#xff0c;其他的步骤&#xff0c;算法都是…

网络中的一些基本概念整理总结

1.IP地址 是用来定位主机的网络地址,主要是用于标识主机和其他的一些网络设备. 比如路由器是用点分十进制来表示的 2.端口号 用于标识网络协议中不同的服务或应用程序。 3.协议 这里主要说网络协议,是网络通信时,所有经过的网络设备都必须遵守的一套规定,包含怎么建立连接…

机器学习笔记:李宏毅diffusion model

1 概念原理 首先sample 一个都是噪声的vector然后经过denoise network 过滤一些杂质接着继续不断denoise&#xff0c;直到最后出来一张清晰图片 【类似于做雕塑&#xff0c;一开始只是一块石头&#xff08;噪声很杂的雕塑&#xff09;&#xff0c;慢慢雕刻出想要的花纹】 同一个…

简单易懂的 Postman Runner 参数自增教程

目录 什么是 Postman Runner&#xff1f; Postman Runner 如何实现参数自增&#xff1f; 步骤一&#xff1a;设置全局参数 步骤二&#xff1a;将全局参数带入请求参数 步骤三&#xff1a;实现参数自增 资料获取方法 什么是 Postman Runner&#xff1f; Postman Runner 是…

Redis集群 (三十九)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、Redis主从复制 1.1 概念 1.2 作用 1.3 缺点 1.4 流程 1.5 搭建 1.6 验证 二、Reids哨兵模式 2.1 概念 2.2 作用 2.3 缺点 2.4 结构 2.5 搭建 2.6 验证 三、Red…

一文揭秘饿了么跨端技术的演进、实践与落地

跨端技术背景与演进历程 跨端&#xff0c;究竟跨的是哪些端&#xff1f; 自 90 年的万维网出现&#xff0c;而后的三十多年&#xff0c;我们依次经历了 PC 时代、移动时代&#xff0c;以及现在的万物互联&#xff08;的 IoT &#xff09;时代&#xff0c;繁荣的背后&#xff…

SpringBoot后端服务开启Https协议提供访问(使用阿里云资源)

目录 概述 申请/下载证书 部署证书 本地测试访问 服务器部署访问 最后/扩展 总结 概述 本篇博客说明如何将SpringBoot项目开启Https协议提供访问。 博文以步骤【申请/下载证书】&#xff0c;【部署证书】&#xff0c;【本地测试访问】&#xff0c;【服务器部署访问】 &a…

linux0.95(VFS重点)源码通俗解读(施工中)

文件系统在磁盘中的体现 下面是磁盘的内容&#xff0c;其中i节点就是一个inode数组&#xff0c;逻辑块就是数据块可用于存放数据 操作系统通过将磁盘数据读入到内存中指定的缓冲区块来与磁盘交互&#xff0c;对内存中的缓冲区块修改后写回磁盘。 进程(task_struct * task[N…

Python系统学习1-7-字典

一、字典 1、概念及内存图 列表&#xff1a;由一系列变量组成的可变序列容器字典&#xff1a;由一系列键值对组成的可变散列容器字典优势&#xff1a;利用&#xff08;内存&#xff09;空间&#xff0c;换取&#xff08;CPU查找&#xff09;时间 键key 必须唯一且为不…

2023-arxiv-LLaMA: Open and Efficient Foundation Language Models

开放和高效的基础语言模型 Paper&#xff1a;https://arxiv.org/abs/2302.13971 Code: https://github.com/facebookresearch/llama 摘要 本文介绍了 LLaMA&#xff0c;这是⼀个包含 7B 到 65B 参数的基础语⾔模型的集合。作者在数万亿个令牌上训练模型&#xff0c;并表明可以…

windows10 安装WSL2, Ubuntu,docker

AI- 通过docker开发调试部署ChatLLM 阅读时长&#xff1a;10分钟 本文内容&#xff1a; window上安装ubuntu虚拟机&#xff0c;并在虚拟机中安装docker&#xff0c;通过docker部署数字人模型&#xff0c;通过vscode链接到虚拟机进行开发调试.调试完成后&#xff0c;直接部署在云…