zoukankan      html  css  js  c++  java
  • Linux Memory Management:The Function and the Implementation of DAX(Direct Access)Mechanism

    1. DAX 简述

    直接访问(Direct Access,DAX) 机制是一种支持用户态软件直接访问存储于持久内存(Persistent Memory,PM) 的文件的机制,用户态软件无需先将文件数据拷贝到页高速缓存(Page Cache)1

    上述描述对应到下面这张图(Typical NVDIMM Software Architecture2)中,就是说(File)和(Memory)这两条 IO 路径都能绕过页高速缓存。

    • 其中 File 路径(下称普通文件路径)表示,用户态软件通过标准文件接口(Standard File API)访问持久内存文件系统。
    • 其中 Memory 路径(下称映射文件路径)表示,用户态软件通过映射文件(Memory-mapped File)直接访问 PM。

    Typical NVDIMM Software Architecture

    2. DAX 的原理

    以下将结合 Linux v5.8-rc1 中的 XFS 为例进行介绍。

    2.1. 普通文件路径如何旁路页缓存

    以文件写路径为例,其由 xfs_file_write_iter 提供,该函数部分代码如下所示:

    STATIC ssize_t
    xfs_file_write_iter(
    	struct kiocb		*iocb,
    	struct iov_iter		*from)
    {
    	struct file		*file = iocb->ki_filp;
    	struct address_space	*mapping = file->f_mapping;
    	struct inode		*inode = mapping->host;
    	ssize_t			ret;
    
    	if (IS_DAX(inode))
    		return xfs_file_dax_write(iocb, from);
    
    	if (iocb->ki_flags & IOCB_DIRECT) {
    		ret = xfs_file_dio_aio_write(iocb, from);
    		if (ret != -EREMCHG)
    			return ret;
    	}
    
    	return xfs_file_buffered_aio_write(iocb, from);
    }
    
    

    这段代码在执行写操作的时候,将分别处理三种情况:

    • DAX
      • 在将文件系统挂载到 PM 设备时,若设置 DAX 标识(mount -o dax),则持久内存文件系统将为所有写操作采用该路径;
      • 该路径主要调用 dax_iomap_rw。该函数在通过 dax_direct_access 获取目标物理内存的地址后,通过 dax_copy_from_iter 调用 NVDIMM 驱动直接把数据拷贝到目标物理内存中,并冲洗(Flush)相应缓存行(Cache Line)。
    • DIO(Direct IO):
      • 在打开文件时,若设置直接 IO 标识(O_DIRECT) ,则文件操作将采用该路径;
      • 该路径主要调用 iomap_dio_rw。该函数仍通过传统存储栈(Storage Stack)访问设备,即通过构造 bio,将请求传递到块设备层(Block Device Layer),再由块设备层调用驱动从而访问设备。
    • 正常 IO:

    2.2. 映射文件路径如何旁路页缓存

    调用 mmap 时,文件系统仅仅在进程的 mm_struct 中注册了一段使用虚拟内存区域(Virtual Memory Area,VMA)描述的虚拟地址。后续当用户态软件首次访问映射文件时,内存管理单元(Memory Managment Unit)发现页表项(Page Table Entry,PTE)为空,于是触发 14 号故障,即页故障(Page Fault),使得操作系统开始执行请求调页(Demand Paging)。此时,由虚拟内存管理器(Virtual Memory Manager)和文件系统共同管理页表,以建立虚拟内存到物理内存之间的映射关系。注意,以上为同步过程,而非异步过程,因为页故障是一个异常(Exception)而非软/硬件中断(Software/Hardware Interrupt)。

    2.2.1 调用 mmap 时发生了什么

    在执行 mmap 系统调用时,主要执行 do_mmap 中的 mmap_region,其根据用户态软件的请求,返回一个用于描述一段可用进程虚拟地址空间的 VMA,接着通过 call_mmap 执行文件系统注册的 mmap 实现,最后将该段 VMA 之添加在进程的 mm_struct 中。

    XFS 中 mmap 由 xfs_file_mmap 实现,其中主要语句就一个:vma->vm_ops = &xfs_file_vm_ops;。它告诉异常处理例程(Exception Handler)应该调用 xfs_file_vm_ops 中相应的函数处理页错误。

    2.2.2 请求调页时发生了什么

    请求调页主要执行 __xfs_filemap_fault,其代码如下所示:

    static vm_fault_t
    __xfs_filemap_fault(
    	struct vm_fault		*vmf,
    	enum page_entry_size	pe_size,
    	bool			write_fault)
    {
    	struct inode		*inode = file_inode(vmf->vma->vm_file);
    	struct xfs_inode	*ip = XFS_I(inode);
    	vm_fault_t		ret;
    
    	trace_xfs_filemap_fault(ip, pe_size, write_fault);
    
    	if (write_fault) {
    		sb_start_pagefault(inode->i_sb);
    		file_update_time(vmf->vma->vm_file);
    	}
    
    	xfs_ilock(XFS_I(inode), XFS_MMAPLOCK_SHARED);
    	if (IS_DAX(inode)) {
    		pfn_t pfn;
    
    		ret = dax_iomap_fault(vmf, pe_size, &pfn, NULL,
    				(write_fault && !vmf->cow_page) ?
    				 &xfs_direct_write_iomap_ops :
    				 &xfs_read_iomap_ops);
    		if (ret & VM_FAULT_NEEDDSYNC)
    			ret = dax_finish_sync_fault(vmf, pe_size, pfn);
    	} else {
    		if (write_fault)
    			ret = iomap_page_mkwrite(vmf,
    					&xfs_buffered_write_iomap_ops);
    		else
    			ret = filemap_fault(vmf);
    	}
    	xfs_iunlock(XFS_I(inode), XFS_MMAPLOCK_SHARED);
    
    	if (write_fault)
    		sb_end_pagefault(inode->i_sb);
    	return ret;
    }
    

    显然,这段代码有两条分支:

    • DAX:
      该路径主要调用 dax_iomap_fault。该函数首先通过 grab_mapping_entry 获取页缓存中的 DAX Exception Entry(详见3),接着通过 xfs_bmbt_to_iomap 准备一个名为 struct iomap 的数据结构。

      struct iomap {
      	u64			addr; /* disk offset of mapping, bytes */
      	loff_t			offset;	/* file offset of mapping, bytes */
      	u64			length;	/* length of mapping, bytes */
      	u16			type;	/* type of mapping */
      	u16			flags;	/* flags for mapping */
      	struct block_device	*bdev;	/* block device for I/O */
      	struct dax_device	*dax_dev; /* dax_dev for dax operations */
      	void			*inline_data;
      	void			*private; /* filesystem private */
      	const struct iomap_page_ops *page_ops;
      };
      

      准备好 struct iomap 之后,通过 dax_iomap_pfn ,结合 struct iomap 所提供的信息,获取目标 PM 页的物理页号(Physical Page Number,pfn)。之后由 dax_insert_entry 将与该页相关联的 DAX Exception Entry 添加到用于维护页缓存的数据结构 XArray 中。最后调用 vmf_insert_mixed_mkwrite 将从 DAX Exception Entry 中缓存的 pfn 填写到对应虚拟页的 PTE 中。

    • 正常请求调页
      该路径主要调用 filemap_fault。该函数首先通过 do_sync_mmap_readahead 试图同步地预读文件数据(预读行为可受 madvise 系统调用的影响,因此也可能完全不读取),接着通过 pagecache_get_page 分配页缓存,再通过 xfs_vm_readpage 将读取块设备数据的请求发送到块设备层,从而将文件数据读取到页缓存中。在将文件数据拷贝到页缓存之后,取决于映射文件的类型(MAP_SHARED、MAP_PRIVATE)执行不同的分支。最后返回的页保存在 vmf->page 中。

      • 当是 MAP_SHARED,主要调用 do_shared_fault 函数,该函数:

          1. 调用 xfs_file_vm_ops 中注册的 xfs_filemap_page_mkwrite,从而调用 iomap_page_create 在 vmf->page 对应的 struct page 的 private 字段中塞进去一个 struct iomap_page

            /*
            * Structure allocated for each page when block size < PAGE_SIZE to track
            * sub-page uptodate status and I/O completions.
            */
            struct iomap_page {
            	atomic_t		read_count;
            	atomic_t		write_count;
            	spinlock_t		uptodate_lock;
            	DECLARE_BITMAP(uptodate, PAGE_SIZE / 512);
            };
            
          2. 1调用 
        finish_fault
            ,该函数最终通过 
        alloc_set_pte
             将 vmf->page 映射到虚拟页上,为此需要设置 PTE,并设置 Reverse Mapping
        6 信息以支持空闲页回收。
      • 当是 MAP_PRIVATE,调用 do_cow_fault。该函数首先通过 alloc_page_vma 为 VMA 分配一个页,该页保存在 vmf->cow_page 中。接着通过 copy_user_highpage 将 vmf->page 中的数据拷贝到 vmf->cow_page 中。最后通过 finish_fault 将 vmf->cow_page 映射到虚拟页上。

    附录 1:术语表

    • 持久内存(Persistent Memory,PM):指能通过访存指令(区别于系统调用)访问、可按字节寻址的(区别于块)非易失存储器(Non-volatile Memory,NVM)7。其中可按字节寻址(Byte-addressable)表示每个寻址单位对应一个 PM 单元(而非字或块)。
    • 持久内存系统(PM-aware File System):指支持 DAX 机制的文件系统。该词的常见表述也包括「Persistent Memory File System」89、「DAX Enabled File System」2
    • 映射文件(Memory-mapped File):是一段虚内存逐字节对应于一个文件或类文件的资源,使得应用程序处理映射部分如同访问主内存。10进程地址空间由映射文件和匿名内存(Anonymous Memory)组成。

    附录 2:DAX 历史沿革

      • 2015,Carsten Otte 在 Linux v2.6 中引入 XIP(Execute-in-place)机制11
        XIP 原本用于嵌入式系统,它摒弃了存储栈中的通用块层及驱动层,并旁路了页高速缓存,使得进程可以直接访问只读存储器或基于 Flash 的内存。
      • 2014 年,Subramanya R Dulloor 等人在 PMFS 中基于 XIP 机制管理 PM12
      • 同年,Matthew Wilcox 改进了 XIP 并提出了名为 DAX 的子系统。
        他在尝试将 XIP 集成到 Ext4 文件系统时,发现 XIP 无法很好地应对竞争条件(Race Conditions)13。当多个线程需要同时访问共享资源,且结果依赖于它们执行的相对速度时,便出现了竞争条件14。他所做出的最主要的变动,就是使用文件系统的 get_block 路径替代 struct address_space_operations 中的 get_xip_mem操作15
  • 相关阅读:
    ASP.NET学习篇(4)——服务器端的控件【转自www.bitsCN.com】
    sql2005 管道的另一端上无任何进程解决方法
    SQL服务器名称的更改
    如何辨别移动硬盘的好坏
    ADO绑定SQL数据库过程
    SQL变量的使用
    SQL子查询
    什么SQL解发器?
    什么是存储过程呢?
    显式事务和隐式事务之间有什么区别?
  • 原文地址:https://www.cnblogs.com/dream397/p/13900936.html
Copyright © 2011-2022 走看看