Linux 메모리 관리: DAX(Direct Access) 매커니즘의 역할 및 구현 원리
참고: CSDN
Linux 메모리 관리: DAX(Direct Access) 매커니즘의 역할 및 구현 원리
1. DAX에 대한 간략한 소개
DAX(Direct Access) 메커니즘은 사용자 모드 소프트웨어가 영구 메모리(PM) 에 저장된 파일에 직접 액세스할 수 있도록 지원하는 메커니즘으로, 사용자 모드 소프트웨어는 먼저 파일 데이터를 페이지 캐시(Page Cache) 에 복사할 필요가 없습니다 . 1 .
위 설명은 아래 그림(일반 NVDIMM 소프트웨어 아키텍처 2 )에 해당하며, 이는 IO 경로(파일)와 (메모리) 모두 페이지 캐시를 우회할 수 있음을 의미합니다.
- 파일 경로(이하 일반 파일 경로)는 사용자 모드 소프트웨어가 표준 파일 인터페이스(표준 파일 API)를 통해 영구 메모리 파일 시스템에 액세스함을 나타냅니다.
- Memory 경로(이하, 매핑된 파일 경로라 함)는 사용자 모드 소프트웨어가 매핑된 파일(Memory-mapped File)을 통해 PM에 직접 접근하는 것을 의미한다.
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);
}
12삼4567891011121314151617181920이십 일스물 둘
이 코드는 쓰기 작업을 수행할 때 다음 세 가지 상황을 처리합니다.
- DAX
- PM 장치에 파일 시스템을 탑재할 때 DAX 플래그(
mount -o dax
)가 설정된 경우 영구 메모리 파일 시스템은 모든 쓰기 작업에 이 경로를 사용합니다. - 이 경로는 주로 dax_iomap_rw 를 호출합니다 . 이 함수는 dax_direct_access를 통해 대상 물리 메모리의 주소를 얻은 후 dax_copy_from_iter를 통해 NVDIMM 드라이버를 호출하여 데이터를 대상 물리 메모리에 직접 복사하고 해당 캐시 라인(Cache Line)을 플러시합니다.
- PM 장치에 파일 시스템을 탑재할 때 DAX 플래그(
- DIO(직접 IO):
- 파일을 열 때 직접 IO 플래그(
O_DIRECT
)가 설정된 경우 파일 작업은 이 경로를 사용합니다. - 이 경로는 주로 iomap_dio_rw 를 호출합니다 . 이 기능은 여전히 기존 스토리지 스택(Storage Stack)을 통해 장치에 액세스합니다. 즉, bio를 구성하고 요청을 블록 장치 계층(Block Device Layer)에 전달한 다음 블록 장치 계층이 드라이버를 호출하여 장치에 액세스합니다. 장치.
- 파일을 열 때 직접 IO 플래그(
- 일반 IO:
- 페이지 캐시를 이용한 기존 IO 방식.
- 이 경로는 주로 iomap_file_buffered_write 를 호출합니다 . 이 함수는 먼저 pagecache_get_page를 통해 페이지 캐시를 얻은 다음 (페이지 캐시 메커니즘의 최신 설계에서는 3 4 5를 읽을 수 있음 ) 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)가 PTE(Page Table Entry)가 비어 있음을 발견하고 페이지 폴트인 폴트 14번을 발생시킨다. (페이지 오류)로 인해 운영 체제가 요구 페이징(요구 페이징) 실행을 시작하게 됩니다. 이때, 가상 메모리 관리자(Virtual Memory Manager)와 파일 시스템이 페이지 테이블을 공동으로 관리하여 가상 메모리와 물리 메모리 간의 매핑 관계를 구축한다. 페이지 폴트는 소프트웨어/하드웨어 인터럽트(Software/Hardware Interrupt)가 아닌 예외(Exception)이기 때문에 위의 내용은 비동기 프로세스가 아닌 동기 프로세스입니다.
2.2.1 mmap이 호출되면 무슨 일이 일어나는가
mmap 시스템 호출 실행 시 do_mmap 의 mmap_region이 주로 실행되며 사용자 모드 소프트웨어의 요청에 따라 사용 가능한 프로세스 가상 주소 공간의 섹션을 설명하는 데 사용되는 VMA를 반환한 후 파일의 mmap 구현을 실행합니다. call_mmap을
XFS에서 mmap은 xfs_file_mmap 에 의해 구현되며 여기서 기본 명령문은 단 하나입니다: vma->vm_ops = &xfs_file_vm_ops;
. 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;
}
12삼4567891011121314151617181920이십 일스물 둘이십 삼스물넷25262728293031323334353637383940
분명히 이 코드에는 두 가지 분기가 있습니다.
-
DAX : 이 경로는 주로 dax_iomap_fault 를 호출합니다 . 이 함수는 먼저 Grab_mapping_entry를 통해 페이지 캐시에서 DAX 예외 항목을 가져온 다음(자세한 내용은 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; }; 12삼456789101112
struct iomap을 준비한 후 dax_iomap_pfn을 사용 하고 이를 struct iomap에서 제공하는 정보와 결합하여 대상 PM 페이지의 물리적 페이지 번호(pfn)를 얻습니다. 페이지와 연결된 DAX 예외 항목은 dax_insert_entry 에 의해 페이지 캐시를 유지 관리하는 데 사용되는 XArray 데이터 구조에 추가됩니다 . 마지막으로 vmf_insert_mixed_mkwrite가 호출되어 DAX 예외 항목에 캐시된 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 함수가 주로 호출됩니다. 이 함수는 다음과 같습니다.
-
xfs_file_vm_ops에 등록된 xfs_filemap_page_mkwrite를 호출하여 iomap_page_create를 호출하여 vmf->page에 해당하는 구조체 페이지의 private 필드에 구조체 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); }; 12삼45678910
-
마지막으로 vmf->page를 alloc_set_pte를 통해 가상 페이지에 매핑하는 finish_fault를 호출합니다 .이를 위해서는 PTE를 설정하고 무료 페이지 재활용을 지원하도록 Reverse Mapping 6 정보를 설정해야 합니다.
-
-
MAP_PRIVATE시 do_cow_fault 가 호출됩니다 . 이 함수는 먼저 vmf->cow_page에 저장된 alloc_page_vma를 통해 VMA용 페이지를 할당합니다 . 그런 다음 vmf->page의 데이터를 copy_user_highpage를 통해 vmf->cow_page로 복사합니다. 마지막으로 vmf->cow_page는 Finish_fault를 통해 가상 페이지에 매핑됩니다 .
-
부록 1: 용어집
- 영구 메모리(PM) : 메모리 액세스 명령(시스템 호출과 다름)을 통해 액세스할 수 있고 바이트 주소 지정이 가능한(블록과 다름) 비휘발성 메모리(NVM)를 나타 냅니다 . 그중 Byte-addressable은 각 주소 지정 단위가 (단어나 블록이 아닌) PM 단위에 해당함을 의미합니다.
-
영구 메모리 시스템(PM 인식 파일 시스템) : DAX 메커니즘을 지원하는 파일 시스템을 나타냅니다. 이 용어의 일반적인 표현에는 “영구 메모리 파일 시스템” 8 9 및 “DAX 지원 파일 시스템” 2 도 포함됩니다 .
- 메모리 매핑 파일(Memory-mapped File) : 파일이나 클래스 파일을 바이트 단위로 가상 메모리에 대응시켜 애플리케이션이 매핑된 부분을 메인 메모리에 접근하듯이 처리할 수 있도록 하는 리소스이다. 10 프로세스 주소 공간은 매핑 파일과 익명 메모리(Anonymous Memory)로 구성됩니다.
부록 2: DAX 역사
- 2015년에 Carsten Otte는 Linux v2.611에 XIP(Execute-in-place) 메커니즘을 도입했습니다 . 원래 임베디드 시스템에서 사용되었던 XIP는 스토리지 스택의 공통 블록 계층과 드라이버 계층을 버리고 페이지 캐시를 우회하여 프로세스가 읽기 전용 메모리 또는 플래시 기반 메모리에 직접 액세스할 수 있도록 합니다.
- 2014년에 Subramanya R Dulloor 등은 PMFS의 XIP 메커니즘을 기반으로 PM 12를 관리했습니다 .
- 같은 해에 Matthew Wilcox는 XIP를 개선하고 DAX라는 하위 시스템을 제안했습니다.
그는 XIP를 Ext4 파일 시스템에 통합하려고 시도했을 때 XIP가 경쟁 조건(Race Conditions) 13을 잘 처리할 수 없다는 것을 발견했습니다 . 경쟁 조건은 여러 스레드가 공유 리소스에 동시에 액세스해야 할 때 발생하며 결과는 상대적인 실행 속도에 따라 달라집니다14 . 그가 수행한 가장 중요한 변경 사항은 파일 시스템의 경로
get_block
대체 작업을 사용하는 것입니다 15 .struct address_space_operations
get_xip_mem