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

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

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_operationsres = 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);} elseshmem_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分区查找pagepage = 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_swaplistif (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.rhkb.cn/news/88144.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible Playbook快速部署一主多从MySQL集群

部署目标&#xff1a; 1、快速部署一套一主两从的mysql集群 2、部署过程中支持交互式定义安装目录及监听端口号 部署清单目录结构&#xff1a; rootmaster:/opt/mysql# tree . . ├── group_vars │ └── all.yml ├── hosts ├── mysql.yml └── roles└── mys…

【Spring Cloud +Vue+UniApp】智慧建筑工地平台源码

智慧工地源码 、智慧工地云平台源码、 智慧建筑源码支持私有化部署&#xff0c;提供SaaS硬件设备运维全套服务。 前言&#xff1a;互联网建筑工地&#xff0c;是将互联网的理念和技术引入建筑工地&#xff0c;从施工现场源头抓起&#xff0c;最大程度的收集人员、安全、环境、材…

C++的六大“天选之子“拷贝构造与与运算符重载

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f…

MySql(干货)

写这篇博客的目的不是为了将介绍原理&#xff0c;而是为了Sql中的代码操作属实太多了&#xff0c;在这里进行一个汇总&#xff0c;方便查阅&#xff01;&#xff01;&#xff01; Sql分类 分类全称说明 DDL Data Definintion Language数据定义语言&#xff0c;用来定义数据库对…

Docker知识(详细笔记)

概览图 文章目录 概览图docker 知识速查1. 初识 Docker1.1 概念1.2 特点1.3 架构1.4 应用场景1.5 安装 Docker1.6 配置 Docker 镜像 2. Docker 命令2.1 Docker 进程相关命令2.2 Docker 镜像相关命令2.3 Docker 容器相关命令 3. Docker 容器的数据卷3.1 数据卷概念及作用3.1.1 概…

jvm里的内存溢出

目录 堆溢出 虚拟机栈和本地方法栈溢出&#xff08;栈溢出很少出现&#xff09; 方法区和运行时常量池溢出 本机内存直接溢出&#xff08;实际中很少出现、了解即可&#xff09; 堆溢出 堆溢出&#xff1a;最常见的是大list&#xff0c;list里面有很多元素 堆溢出该怎么解决…

修改IDEA的idea.vmoptions参数导致IDEA无法打开(ReservedCodeCacheSize)

事发原因 Maven导依赖的时候OOM&#xff0c;因此怀疑是内存太小&#xff0c;尝试修改idea.vmoptions的参数&#xff0c;然后发现IDEA重启后打不开了&#xff0c;卸载重装后也无法打开。。。 实际上如果导包爆出OOM的话应该调整下图参数&#xff0c;不过这都是后话了 解决思路…

52.Linux学习day02 基础命令详解2

目录 Linux常见的基础命令 1.cp 2.mv 3.rm 4.find 5.grep 6.管道 | 7.wc 8.su 9.关机与重启 10.runleve Linux常见的基础命令 1.cp 用于复制文件或目录 使用 cp 命令的基本格式如下&#xff1a; cp [选项] 源文件 目标文件或目录选项&#xff1a;cp 命令支持一些选…

Spring5新功能

文章目录 前言一、整合日志功能二、Nullable注解三、函数式风格编程四、JUnit5单元测试框架总结 前言 整合日志、Nullable注解、函数式风格编程、整合JUnit5、Webflux 一、整合日志功能 Spring5移除了Log4jConfigListener&#xff0c;官方建议使用Log4j2. 依赖&#xff1a; &…

k8s 滚动更新控制(一)

在传统的应用升级时&#xff0c;通常采用的方式是先停止服务&#xff0c;然后升级部署&#xff0c;最后将新应用启动。这个过程面临一个问题&#xff0c;就是在某段时间内&#xff0c;服务是不可用的&#xff0c;对于用户来说是非常不友好的。而kubernetes滚动更新&#xff0c;…

研发工程师玩转Kubernetes——PVC使用Label和storage选择PV

在《研发工程师玩转Kubernetes——local型PV和PVC绑定过程中的状态变化》和《研发工程师玩转Kubernetes——使用local型PV在不同Pod上共享数据》中&#xff0c;我们介绍了指定VPC的spec.volumeName为PV名称来绑定它们的方法。本文将介绍PVC在创建时&#xff0c;系统自动选择绑定…

什么是DNS欺骗及如何进行DNS欺骗

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、什么是 DNS 欺骗&#xff1f;二、开始1.配置2.Ettercap启动3.操作 总结 前言 我已经离开了一段时间&#xff0c;我现在回来了&#xff0c;我终于在做一个教…

[vscode]vscode运行cmake时候exe不执行而且前面多一些字符

遇到一个奇怪问题,你单独打开cmd去执行vscode编译过程序没问题&#xff0c;但是你在vscode确不会执行&#xff0c;这是因为vscode没有读取到电脑环境变量导致加载DLL失败&#xff0c;但是在vscode终端不会给你提示少DLL&#xff0c;需要你自己把DLL复制到exe目录即可解决问题。…

Vue.js 生命周期详解

Vue.js 是一款流行的 JavaScript 框架&#xff0c;它采用了组件化的开发方式&#xff0c;使得前端开发更加简单和高效。在 Vue.js 的开发过程中&#xff0c;了解和理解 Vue 的生命周期非常重要。本文将详细介绍 Vue 生命周期的四个阶段&#xff1a;创建、挂载、更新和销毁。 …

计算机视觉的应用9-视觉领域中的61个经典数据集【大集合】的应用与实战

大家好,我是微学AI,今天给大家介绍一下计算机视觉的应用9-视觉领域中的61个经典数据集【大集合】的应用与实战,我们都知道计算机视觉是一门研究如何使计算机能够理解和解释数字图像或视频的技术和方法。在计算机视觉领域中,数据集是非常重要的资源,它们可以用于训练和评估…

从源码Debug深入spring事件机制,基于观察者模式仿写spring事件监听骨架

文章目录 1.测试案例2.DEBUG源码分析3. 异步监听4.ApplicationListener子接口5. 注解支持6. 基于观察者模式高仿spring事件监听6.1 先定义自定义一个事件6.2 定义两个监听器6.3 定义一个持有所有监听器的对象&#xff0c;类似spring的SimpleApplicationEventMulticaster6.4 事件…

什么是响应式设计?列举几种实现响应式设计的方法。

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是响应式设计&#xff1f;⭐ 实现响应式设计的方法⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏…

Python实现图片文本支持中文,自定义字体

Python实现图片文本支持中文&#xff0c;自定义字体 # 支持中文 import matplotlib #用下载好的字体文件设置字体&#xff0c;从而正确显示中文 myfont matplotlib.font_manager.FontProperties(fnamer"./simsun.ttc") # 自定义的字体文件 plt.figure(figsize (1…

STM32F429IGT6使用CubeMX配置外部中断按键

1、硬件电路 2、设置RCC&#xff0c;选择高速外部时钟HSE,时钟设置为180MHz 3、配置GPIO引脚 4、NVIC配置 PC13相同 5、生成工程配置 6、部分代码 中断回调函数 /* USER CODE BEGIN 0 */void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {if(GPIO_Pin GPIO_PIN_0){HAL_GPIO…

化工行业案例 | 甄知科技助力万华化学重构IT服务价值,打造信息中心ERP!

随着科技的发展&#xff0c;新材料的应用领域与日俱增&#xff0c;近年来&#xff0c;全球化工新材料产业发展整体步入高技术引领、产品迭代速度快、产业规模和需求不断扩大的阶段。一体化协同与数字化转型策略是实现化工新材料生产原料自给、节能降耗、降低排放和物料成本的重…