天天看點

Linux 記憶體管理:DAX(Direct Access)機制的作用及實作原理Linux Memory Management:The Function and the Implementation of DAX(Direct Access)Mechanism

Linux Memory Management:The Function and the Implementation of DAX(Direct Access)Mechanism

文章目錄

  • Linux Memory Management:The Function and the Implementation of DAX(Direct Access)Mechanism
    • 1. DAX 簡述
    • 2. DAX 的原理
      • 2.1. 普通檔案路徑如何旁路頁緩存
      • 2.2. 映射檔案路徑如何旁路頁緩存
        • 2.2.1 調用 mmap 時發生了什麼
        • 2.2.2 請求調頁時發生了什麼
    • 附錄 1:術語表
    • 附錄 2:DAX 曆史沿革

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。
Linux 記憶體管理:DAX(Direct Access)機制的作用及實作原理Linux Memory Management:The Function and the Implementation of DAX(Direct Access)Mechanism

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:
    • 正常的、使用頁緩存的 IO 方式。
    • 該路徑主要調用 iomap_file_buffered_write。該函數首先通過 pagecache_get_page 擷取頁緩存(有關頁緩存機制的最新設計,可閱讀345),接着通過 iomap_read_page_sync 封裝 bio,以請求塊裝置層調用驅動,将裝置上的資料讀取到頁緩存中。在準備好頁緩存之後,調用 iov_iter_copy_from_user_atomic 将使用者态軟體請求寫入的資料拷貝到頁緩存中。一切完成之後,通過 iomap_set_page_dirty 将頁緩存設定為髒頁。如此疊代,直至使用者所有資料都寫入頁緩存,最後通過 balance_dirty_pages_ratelimited 酌情使用背景程序将髒頁回寫到塊裝置中。

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. 調用 finish_fault,該函數最終通過 alloc_set_pte 将 vmf->page 映射到虛拟頁上,為此需要設定 PTE,并設定 Reverse Mapping6 資訊以支援空閑頁回收。
    • 當是 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。
  1. Corbet J. The future of DAX. https://lwn.net/Articles/717953/, 2017 ↩︎
  2. intel. NVDIMM  Namespace  Specification. http://pmem.io/documents/NVDIMM_Namespace_Spec.pdf, 2015 ↩︎ ↩︎
  3. Zwisler R. A multi-order radix tree. https://lwn.net/Articles/688130/, 2016 ↩︎ ↩︎
  4. Corbet J. The XArray data structure. https://lwn.net/Articles/745073/, 2018 ↩︎
  5. Corbet J. The future of the page cache. http://tinylab.org/lwn-712467/, 2017 ↩︎
  6. McCracken D. Object-based reverse mapping. in: Proceedings of the Ottawa Linux Symposium (OLS’04). Ottawa, Ontario, Canada: July 21–24, 2004. 357~360 ↩︎
  7. Nalli S, Haria S, Hill M D, et al. An analysis of persistent memory use with WHISPER. in: Proceedings of the Twenty-Second International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS’17). Xi’an, Shanxi, China: ACM, April 8-12, 2017. 135~148 ↩︎
  8. SNIA. NVM Programming Model (NPM) v1.2. https://www.snia.org/sites/default/files/technical_work/final/NVMProgrammingModel_v1.2.pdf, 2017 ↩︎
  9. https://software.intel.com/content/www/us/en/develop/articles/persistent-memory-faq.html ↩︎
  10. https://zh.wikipedia.org/zh-hans/%E5%86%85%E5%AD%98%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6 ↩︎
  11. Corbet J. Execute-in-place. https://lwn.net/Articles/135472/, 2005 ↩︎
  12. Dulloor S R, Kumar S, Keshavamurthy A, et al. System software for persistent memory. in: Proceedings of the Ninth European Conference on Computer Systems (EuroSys’14). ‎‎Amsterdam, North Holland, Netherlands: ACM, April 13-16, 2014. 1~15 ↩︎
  13. Corbet J. Supporting filesystems in persistent memory. https://lwn.net/Articles/610174/, 2014 ↩︎
  14. 孫鐘秀, 費翔林, 駱斌. 作業系統教程. 第4版. 北京市: 高等教育出版社, 2008. 1~509 ↩︎
  15. Wilcox M. Add support for NV-DIMMs to ext4. https://lwn.net/Articles/613384/, 2014 ↩︎

繼續閱讀