Art of Pr0gr4m

[Linux Kernel 5] Virtual Memory & Paging 본문

IT/Linux Kernel

[Linux Kernel 5] Virtual Memory & Paging

pr0gr4m 2020. 5. 11. 12:08

이번 포스트부터는 리눅스의 메모리 모델과 관리 정책들에 대해서 알아본다.

가장 먼저 가상 메모리와 페이징에 대해서 알아볼 것이다.

 

참고로 세그먼테이션은 추후에 부팅과정에 대해 다룰 때 운영 모드와 함께 다룰 예정이며,

메모리 관리 정책에서는 넘어갈 것이다.

 

또한, 해당 포스트는 가상 메모리와 페이징의 일반적인 개념을 공부하는 포스트는 아니므로

컨셉에 대한 공부가 필요하다면 아래 링크를 참고하도록 한다.

 

https://en.wikipedia.org/wiki/Virtual_memory

 

Virtual memory - Wikipedia

Virtual memory combines active RAM and inactive memory on DASD[NB 1] to form a large range of contiguous addresses. In computing, virtual memory (also virtual storage) is a memory management technique that provides an "idealized abstraction of the storage

en.wikipedia.org

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/4/html/introduction_to_system_administration/s1-memory-concepts

 

4.3. Basic Virtual Memory Concepts Red Hat Enterprise Linux 4 | Red Hat Customer Portal

The Red Hat Customer Portal delivers the knowledge, expertise, and guidance available through your Red Hat subscription.

access.redhat.com

https://en.wikipedia.org/wiki/Paging

 

Paging - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search This article needs attention from an expert in computing. See the talk page for details. WikiProject Computing may be able to help recruit an expert. (June 2019) This article is about

en.wikipedia.org

https://www.geeksforgeeks.org/paging-in-operating-system/

 

Paging in Operating System - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/chap-memory_allocation

 

Chapter 2. Memory Allocation Red Hat Enterprise Linux for Real Time 7 | Red Hat Customer Portal

The Red Hat Customer Portal delivers the knowledge, expertise, and guidance available through your Red Hat subscription.

access.redhat.com

 

 

 

1. 5단계 페이징

 

과거 리눅스 커널은 3/4 level paging 정책을 사용했다.

4.12 버전부터는 5 level paging을 준비하기 시작했으며, 4.14 버전에서 이를 지원하기 시작했고

5.5 버전에 들어서는 결국 디폴트로 자리잡았다.

물론, 모든 시스템이 언제나 5 level paging을 사용하게 되는 것은 아니다.

32 bit 시스템에서 5 level paging이라니 얼마나 비효율적인가.

이런 경우엔 내부적으로 몇 개의 레이어를 사용하지 않도록 하여 3, 4 level로 단축시킨다.

 

아무튼 기존의 4단계 페이징에서는 48bit를 사용하여 256TB(실구현 상 64TB) 범위의 메모리 주소 공간을 커버하였다.

5단계 페이징에서는 물리 메모리 주소로 52bit를, 가상 메모리 주소로 57 bit를 사용한다.

즉, 4PB 범위의 물리 메모리 주소와 128PB의 가상 메모리 주소를 커버할 수 있다.

또한, 5단계 페이징에서는 또한 512GB의 huge page를 만들 수 있다.

(솔직히 필자는 이 광활한 주소를 어따 쓸지 아직 잘 모르겠다..)

 

5단계 페이징에서는 64비트 선형 주소 공간을 12bit의 프레임 오프셋과 5개의 9bit 페이지 디렉토리 오프셋으로 나눈다.

5단계 페이징을 그림으로 나타내면 다음과 같다.

64bit 5-level paging

CR3 레지스터에는 페이지 글로벌 디렉토리의 주소가 저장되어있다.

이 주소에서 선형 주소의 [56:48] 값을 오프셋으로 찾아간 엔트리에는 페이지 넘버4 디렉토리의 주소가 저장되어있다.

이 주소에서 선형 주소의 [47:40] 값을 오프셋으로 찾아간 엔트리에는 페이지 어퍼 디렉토리의 주소가 저장되어있다.

이 과정을 반복하면 페이지 테이블에 페이지 테이블 엔트리가 저장되어있다.

이 PTE의 물리 페이지 포인터를 따라가서 offset 값만큼 이동하면 실제 물리 주소를 얻을 수 있다.

 

이 괴랄한 주소 변환은 시간을 굉장히 소모하는 작업이다.

따라서 이를 캐싱하는 TLB가 굉장히 중요하다.

 

 

 

2. 페이징 데이터

 

5 level paging을 위한 자료형들과 매크로들을 알아본다.

우선, 페이지와 페이지 디렉토리들의 크기에 관한 상수들은 다음과 같다.

#define PAGE_SHIFT	12
#ifdef __ASSEMBLY__
#define PAGE_SIZE	(1 << PAGE_SHIFT)
#else
#define PAGE_SIZE	(1UL << PAGE_SHIFT)
#endif
#define PAGE_MASK	(~(PAGE_SIZE-1))
 
/*
 * PGDIR_SHIFT determines what a top-level page table entry can map
 */
#define PGDIR_SHIFT	pgdir_shift
#define PTRS_PER_PGD	512
 
/*
 * 4th level page in 5-level paging case
 */
#define P4D_SHIFT		39
#define MAX_PTRS_PER_P4D	512
#define PTRS_PER_P4D		ptrs_per_p4d
#define P4D_SIZE		(_AC(1, UL) << P4D_SHIFT)
#define P4D_MASK		(~(P4D_SIZE - 1))
 
#define MAX_POSSIBLE_PHYSMEM_BITS	52
 
#else /* CONFIG_X86_5LEVEL */
 
/*
 * PGDIR_SHIFT determines what a top-level page table entry can map
 */
#define PGDIR_SHIFT		39
#define PTRS_PER_PGD		512
#define MAX_PTRS_PER_P4D	1
 
#endif /* CONFIG_X86_5LEVEL */
 
/*
 * 3rd level page
 */
#define PUD_SHIFT	30
#define PTRS_PER_PUD	512
 
/*
 * PMD_SHIFT determines the size of the area a middle-level
 * page table can map
 */
#define PMD_SHIFT	21
#define PTRS_PER_PMD	512
 
/*
 * entries per page directory level
 */
#define PTRS_PER_PTE	512
 
#define PMD_SIZE	(_AC(1, UL) << PMD_SHIFT)
#define PMD_MASK	(~(PMD_SIZE - 1))
#define PUD_SIZE	(_AC(1, UL) << PUD_SHIFT)
#define PUD_MASK	(~(PUD_SIZE - 1))
#define PGDIR_SIZE	(_AC(1, UL) << PGDIR_SHIFT)
#define PGDIR_MASK	(~(PGDIR_SIZE - 1))
 
/*
 * See Documentation/x86/x86_64/mm.rst for a description of the memory map.
 *
 * Be very careful vs. KASLR when changing anything here. The KASLR address
 * range must not overlap with anything except the KASAN shadow area, which
 * is correct as KASAN disables KASLR.
 */
#define MAXMEM			(1UL << MAX_PHYSMEM_BITS)

위 상수들의 SHIFT 상수들은 몇 비트를 쉬프트하면 해당 데이터를 구할 수 있는지를 나타낸다.

예를들어 (선형주소 & (PMD_SIZE - 1)) >> PMD_SHIFT를 하면 PMD 값만 나온다.

반대로 PMD 값 << PMD_SHIFT를 하면 선형 주소 상 PMD 값이 된다.

PTRS_PER_XXX는 해당 페이지 디렉토리 하나에 할당된 엔트리의 개수를 뜻한다.

현재 값은 디렉토리들이 9비트씩을 사용하니 2^9인 512이다.

그런데 여기서 주목해봐야 할 점이, PGDIR_SHIFT값과 P4D_SHIFT의 값이 39로 같다는 것이다.

이론 상 5 level paging을 full로 사용하려면 PGDIR_SHIFT = P4D_SHIFT + 9가 되어야 한다.

즉, 이건 위에서 말한 5 level paging 시스템에서 교묘하게 4 level paing을 하는 기법이다.

커널에서 사용할 수 있는 최대 메모리인 MAXMEM값도 MAX_PHYSMEM_BITS를 쉬프트하게 되어있는데, 이 값 또한 다음과 같이 시스템에서 설정하는 페이징 방식에 의존적으로 정해져있다.

#ifdef CONFIG_X86_32
# ifdef CONFIG_X86_PAE
#  define SECTION_SIZE_BITS	29
#  define MAX_PHYSADDR_BITS	36
#  define MAX_PHYSMEM_BITS	36
# else
#  define SECTION_SIZE_BITS	26
#  define MAX_PHYSADDR_BITS	32
#  define MAX_PHYSMEM_BITS	32
# endif
#else /* CONFIG_X86_32 */
# define SECTION_SIZE_BITS	27 /* matt - 128 is convenient right now */
# define MAX_PHYSADDR_BITS	(pgtable_l5_enabled() ? 52 : 44)
# define MAX_PHYSMEM_BITS	(pgtable_l5_enabled() ? 52 : 46)
#endif

 

다음으로 각 페이지 영역의 데이터 타입은 다음과 같다.

typedef struct pgprot { pgprotval_t pgprot; } pgprot_t;
typedef struct { pgdval_t pgd; } pgd_t;
#if CONFIG_PGTABLE_LEVELS > 4
typedef struct { p4dval_t p4d; } p4d_t;
#if CONFIG_PGTABLE_LEVELS > 3
typedef struct { pudval_t pud; } pud_t;
#if CONFIG_PGTABLE_LEVELS > 2
typedef struct { pmdval_t pmd; } pmd_t;
typedef struct { unsigned long pte; } pte_t;

위와 같이 페이지 테이블 레벨에 따라 정의를 하도록 되어있다.

구조체로 타입을 정의한 이유는 암시적 캐스팅을 막고 데이터 타입 체크를 확실히 하기 위해서다.

pgprot_t는 Page Protection의 약자로 페이지의 읽기/쓰기/실행 등의 권한을 표현한다.

 

해당 자료형에서 값을 가져오거나, ulong 형식의 일반 값들을 해당 데이터 타입으로 바꾸기 위하여 다음 매크로를 사용한다.

#define pte_val(x)	((x).pte)
#define pmd_val(x)	((&x)->pmd[0])
#define pud_val(x)	(p4d_val((x).p4d))
#define p4d_val(x)	(pgd_val((x).pgd))
#define pgd_val(x)	((x).pgd)
#define pgprot_val(x)	((x).pgprot)
 
#define __pte(x)	((pte_t) { (x) } )
#define __pmd(x)	((pmd_t) { (x) } )
#define __pud(x)	((pud_t) { __p4d(x) })
#define __p4d(x)	((p4d_t) { __pgd(x) })
#define __pgd(x)	((pgd_t) { (x) } )
#define __pgprot(x)	((pgprot_t) { (x) } )

 

 

 

3. struct page

 

리눅스 커널이 물리 메모리 프레임을 관리하기 위해 struct page 구조체를 사용한다.

page 구조체의 정의는 다음과 같다.

struct page {
	unsigned long flags;		/* Atomic flags, some possibly
					 * updated asynchronously */
	/*
	 * Five words (20/40 bytes) are available in this union.
	 * WARNING: bit 0 of the first word is used for PageTail(). That
	 * means the other users of this union MUST NOT use the bit to
	 * avoid collision and false-positive PageTail().
	 */
	union {
		struct {	/* Page cache and anonymous pages */
			/**
			 * @lru: Pageout list, eg. active_list protected by
			 * pgdat->lru_lock.  Sometimes used as a generic list
			 * by the page owner.
			 */
			struct list_head lru;
			/* See page-flags.h for PAGE_MAPPING_FLAGS */
			struct address_space *mapping;
			pgoff_t index;		/* Our offset within mapping. */
			/**
			 * @private: Mapping-private opaque data.
			 * Usually used for buffer_heads if PagePrivate.
			 * Used for swp_entry_t if PageSwapCache.
			 * Indicates order in the buddy system if PageBuddy.
			 */
			unsigned long private;
		};
		struct {	/* page_pool used by netstack */
			/**
			 * @dma_addr: might require a 64-bit value even on
			 * 32-bit architectures.
			 */
			dma_addr_t dma_addr;
		};
		struct {	/* slab, slob and slub */
			union {
				struct list_head slab_list;
				struct {	/* Partial pages */
					struct page *next;
#ifdef CONFIG_64BIT
					int pages;	/* Nr of pages left */
					int pobjects;	/* Approximate count */
#else
					short int pages;
					short int pobjects;
#endif
				};
			};
			struct kmem_cache *slab_cache; /* not slob */
			/* Double-word boundary */
			void *freelist;		/* first free object */
			union {
				void *s_mem;	/* slab: first object */
				unsigned long counters;		/* SLUB */
				struct {			/* SLUB */
					unsigned inuse:16;
					unsigned objects:15;
					unsigned frozen:1;
				};
			};
		};
		struct {	/* Tail pages of compound page */
			unsigned long compound_head;	/* Bit zero is set */
 
			/* First tail page only */
			unsigned char compound_dtor;
			unsigned char compound_order;
			atomic_t compound_mapcount;
		};
		struct {	/* Second tail page of compound page */
			unsigned long _compound_pad_1;	/* compound_head */
			unsigned long _compound_pad_2;
			/* For both global and memcg */
			struct list_head deferred_list;
		};
		struct {	/* Page table pages */
			unsigned long _pt_pad_1;	/* compound_head */
			pgtable_t pmd_huge_pte; /* protected by page->ptl */
			unsigned long _pt_pad_2;	/* mapping */
			union {
				struct mm_struct *pt_mm; /* x86 pgds only */
				atomic_t pt_frag_refcount; /* powerpc */
			};
#if ALLOC_SPLIT_PTLOCKS
			spinlock_t *ptl;
#else
			spinlock_t ptl;
#endif
		};
		struct {	/* ZONE_DEVICE pages */
			/** @pgmap: Points to the hosting device page map. */
			struct dev_pagemap *pgmap;
			void *zone_device_data;
			/*
			 * ZONE_DEVICE private pages are counted as being
			 * mapped so the next 3 words hold the mapping, index,
			 * and private fields from the source anonymous or
			 * page cache page while the page is migrated to device
			 * private memory.
			 * ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also
			 * use the mapping, index, and private fields when
			 * pmem backed DAX files are mapped.
			 */
		};
 
		/** @rcu_head: You can use this to free a page by RCU. */
		struct rcu_head rcu_head;
	};
 
	union {		/* This union is 4 bytes in size. */
		/*
		 * If the page can be mapped to userspace, encodes the number
		 * of times this page is referenced by a page table.
		 */
		atomic_t _mapcount;
 
		/*
		 * If the page is neither PageSlab nor mappable to userspace,
		 * the value stored here may help determine what this page
		 * is used for.  See page-flags.h for a list of page types
		 * which are currently stored here.
		 */
		unsigned int page_type;
 
		unsigned int active;		/* SLAB */
		int units;			/* SLOB */
	};
 
	/* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
	atomic_t _refcount;
 
#ifdef CONFIG_MEMCG
	struct mem_cgroup *mem_cgroup;
#endif
 
	/*
	 * On machines where all RAM is mapped into kernel address space,
	 * we can simply calculate the virtual address. On machines with
	 * highmem some memory is mapped into kernel virtual memory
	 * dynamically, so we need a place to store that address.
	 * Note that this field could be 16 bits on x86 ... ;)
	 *
	 * Architectures with slow multiplication can define
	 * WANT_PAGE_VIRTUAL in asm/page.h
	 */
#if defined(WANT_PAGE_VIRTUAL)
	void *virtual;			/* Kernel virtual address (NULL if
					   not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
 
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
	int _last_cpupid;
#endif
} _struct_page_alignment;

내용이 방대한데, anonymous page, slab 할당자, compound page, zone device page, cgroup, NUMA 등에 대한 지식이 없으면 당장 이해하기는 어렵다.

일단 큰 틀만 살펴보자면 flags, union으로 묶여있는 페이지 타입별 멤버, union으로 묶여있는 페이지 정보, _refcount, mem_cgroup, virtual, _last_cpuid로 구성되어있다.

flags는 페이지의 상태를 나타내는 플래그다.

페이지 타입별 멤버들은 페이지 타입별로 필요한 정보들이나 리스트 구성을 위한 멤버들이 포함된다.

페이지 정보에는 페이지 매핑 카운터나 커널 페이지 타입을 지정한다.

_refcount는 해당 페이지에 대한 usage count인데, 직접 접근하면 안된다.

mem_cgroup은 memory cgroup을 사용할 때 연결되는 mem_cgroup 구조체를 나타낸다.

virtual은 커널 주소 공간의 가상 주소이다.

_last_cpuid는 페이지 플래그로 cpu 정보를 식별하지 못하는 경우 마지막 사용한 cpu 정보를 저장하여 NUMA 밸런싱에 사용한다.

 

페이지 플래그에서 사용하는 플래그들은 다음과 같다.

/*
 * Various page->flags bits:
 *
 * PG_reserved is set for special pages. The "struct page" of such a page
 * should in general not be touched (e.g. set dirty) except by its owner.
 * Pages marked as PG_reserved include:
 * - Pages part of the kernel image (including vDSO) and similar (e.g. BIOS,
 *   initrd, HW tables)
 * - Pages reserved or allocated early during boot (before the page allocator
 *   was initialized). This includes (depending on the architecture) the
 *   initial vmemmap, initial page tables, crashkernel, elfcorehdr, and much
 *   much more. Once (if ever) freed, PG_reserved is cleared and they will
 *   be given to the page allocator.
 * - Pages falling into physical memory gaps - not IORESOURCE_SYSRAM. Trying
 *   to read/write these pages might end badly. Don't touch!
 * - The zero page(s)
 * - Pages not added to the page allocator when onlining a section because
 *   they were excluded via the online_page_callback() or because they are
 *   PG_hwpoison.
 * - Pages allocated in the context of kexec/kdump (loaded kernel image,
 *   control pages, vmcoreinfo)
 * - MMIO/DMA pages. Some architectures don't allow to ioremap pages that are
 *   not marked PG_reserved (as they might be in use by somebody else who does
 *   not respect the caching strategy).
 * - Pages part of an offline section (struct pages of offline sections should
 *   not be trusted as they will be initialized when first onlined).
 * - MCA pages on ia64
 * - Pages holding CPU notes for POWER Firmware Assisted Dump
 * - Device memory (e.g. PMEM, DAX, HMM)
 * Some PG_reserved pages will be excluded from the hibernation image.
 * PG_reserved does in general not hinder anybody from dumping or swapping
 * and is no longer required for remap_pfn_range(). ioremap might require it.
 * Consequently, PG_reserved for a page mapped into user space can indicate
 * the zero page, the vDSO, MMIO pages or device memory.
 *
 * The PG_private bitflag is set on pagecache pages if they contain filesystem
 * specific data (which is normally at page->private). It can be used by
 * private allocations for its own usage.
 *
 * During initiation of disk I/O, PG_locked is set. This bit is set before I/O
 * and cleared when writeback _starts_ or when read _completes_. PG_writeback
 * is set before writeback starts and cleared when it finishes.
 *
 * PG_locked also pins a page in pagecache, and blocks truncation of the file
 * while it is held.
 *
 * page_waitqueue(page) is a wait queue of all tasks waiting for the page
 * to become unlocked.
 *
 * PG_uptodate tells whether the page's contents is valid.  When a read
 * completes, the page becomes uptodate, unless a disk I/O error happened.
 *
 * PG_referenced, PG_reclaim are used for page reclaim for anonymous and
 * file-backed pagecache (see mm/vmscan.c).
 *
 * PG_error is set to indicate that an I/O error occurred on this page.
 *
 * PG_arch_1 is an architecture specific page state bit.  The generic code
 * guarantees that this bit is cleared for a page when it first is entered into
 * the page cache.
 *
 * PG_hwpoison indicates that a page got corrupted in hardware and contains
 * data with incorrect ECC bits that triggered a machine check. Accessing is
 * not safe since it may cause another machine check. Don't touch!
 */
 
/*
 * Don't use the *_dontuse flags.  Use the macros.  Otherwise you'll break
 * locked- and dirty-page accounting.
 *
 * The page flags field is split into two parts, the main flags area
 * which extends from the low bits upwards, and the fields area which
 * extends from the high bits downwards.
 *
 *  | FIELD | ... | FLAGS |
 *  N-1           ^       0
 *               (NR_PAGEFLAGS)
 *
 * The fields area is reserved for fields mapping zone, node (for NUMA) and
 * SPARSEMEM section (for variants of SPARSEMEM that require section ids like
 * SPARSEMEM_EXTREME with !SPARSEMEM_VMEMMAP).
 */
enum pageflags {
	PG_locked,		/* Page is locked. Don't touch. */
	PG_referenced,
	PG_uptodate,
	PG_dirty,
	PG_lru,
	PG_active,
	PG_workingset,
	PG_waiters,		/* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */
	PG_error,
	PG_slab,
	PG_owner_priv_1,	/* Owner use. If pagecache, fs may use*/
	PG_arch_1,
	PG_reserved,
	PG_private,		/* If pagecache, has fs-private data */
	PG_private_2,		/* If pagecache, has fs aux data */
	PG_writeback,		/* Page is under writeback */
	PG_head,		/* A head page */
	PG_mappedtodisk,	/* Has blocks allocated on-disk */
	PG_reclaim,		/* To be reclaimed asap */
	PG_swapbacked,		/* Page is backed by RAM/swap */
	PG_unevictable,		/* Page is "unevictable"  */
#ifdef CONFIG_MMU
	PG_mlocked,		/* Page is vma mlocked */
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
	PG_uncached,		/* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
	PG_hwpoison,		/* hardware poisoned page. Don't touch */
#endif
#if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT)
	PG_young,
	PG_idle,
#endif
	__NR_PAGEFLAGS,
 
	/* Filesystems */
	PG_checked = PG_owner_priv_1,
 
	/* SwapBacked */
	PG_swapcache = PG_owner_priv_1,	/* Swap page: swp_entry_t in private */
 
	/* Two page bits are conscripted by FS-Cache to maintain local caching
	 * state.  These bits are set on pages belonging to the netfs's inodes
	 * when those inodes are being locally cached.
	 */
	PG_fscache = PG_private_2,	/* page backed by cache */
 
	/* XEN */
	/* Pinned in Xen as a read-only pagetable page. */
	PG_pinned = PG_owner_priv_1,
	/* Pinned as part of domain save (see xen_mm_pin_all()). */
	PG_savepinned = PG_dirty,
	/* Has a grant mapping of another (foreign) domain's page. */
	PG_foreign = PG_owner_priv_1,
	/* Remapped by swiotlb-xen. */
	PG_xen_remapped = PG_owner_priv_1,
 
	/* SLOB */
	PG_slob_free = PG_private,
 
	/* Compound pages. Stored in first tail page's flags */
	PG_double_map = PG_private_2,
 
	/* non-lru isolated movable page */
	PG_isolated = PG_reclaim,
};
 

해당 플래그들이 페이지의 상태를 나타낸다.

이 중 핵심적인 몇가지를 설명하면 다음과 같다.

flag desc
PG_locked 페이지 잠금 상태
PG_referenced 페이지 참조 상태
PG_uptodate 페이지 최신 상태
PG_dirty 페이지 변경 상태
PG_lru LRU 페이지
PG_active 사용중인 페이지
PG_slab 슬랩 할당자에서 사용하는 페이지
PG_checked 페이지 검사 상태
PG_private private 멤버를 사용하는 페이지
PG_writeback write-back을 수행 중인 페이지
PG_mappedtodisk 디스크에 할당된 블록과 연결된 페이지

 

 

 

4. struct mm_struct

 

각 프로세스들은 자기들만의 가상 주소 공간을 갖는다. (크기는 시스템마다 다르다.)

struct mm_struct 구조체는 이 가상 메모리에 대한 정보를 관리한다.

struct mm_struct {
	struct {
		struct vm_area_struct *mmap;		/* list of VMAs */
		struct rb_root mm_rb;
		u64 vmacache_seqnum;                   /* per-thread vmacache */
#ifdef CONFIG_MMU
		unsigned long (*get_unmapped_area) (struct file *filp,
				unsigned long addr, unsigned long len,
				unsigned long pgoff, unsigned long flags);
#endif
		unsigned long mmap_base;	/* base of mmap area */
		unsigned long mmap_legacy_base;	/* base of mmap area in bottom-up allocations */
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
		/* Base adresses for compatible mmap() */
		unsigned long mmap_compat_base;
		unsigned long mmap_compat_legacy_base;
#endif
		unsigned long task_size;	/* size of task vm space */
		unsigned long highest_vm_end;	/* highest vma end address */
		pgd_t * pgd;
 
#ifdef CONFIG_MEMBARRIER
		/**
		 * @membarrier_state: Flags controlling membarrier behavior.
		 *
		 * This field is close to @pgd to hopefully fit in the same
		 * cache-line, which needs to be touched by switch_mm().
		 */
		atomic_t membarrier_state;
#endif
 
		/**
		 * @mm_users: The number of users including userspace.
		 *
		 * Use mmget()/mmget_not_zero()/mmput() to modify. When this
		 * drops to 0 (i.e. when the task exits and there are no other
		 * temporary reference holders), we also release a reference on
		 * @mm_count (which may then free the &struct mm_struct if
		 * @mm_count also drops to 0).
		 */
		atomic_t mm_users;
 
		/**
		 * @mm_count: The number of references to &struct mm_struct
		 * (@mm_users count as 1).
		 *
		 * Use mmgrab()/mmdrop() to modify. When this drops to 0, the
		 * &struct mm_struct is freed.
		 */
		atomic_t mm_count;
 
#ifdef CONFIG_MMU
		atomic_long_t pgtables_bytes;	/* PTE page table pages */
#endif
		int map_count;			/* number of VMAs */
 
		spinlock_t page_table_lock; /* Protects page tables and some
					     * counters
					     */
		struct rw_semaphore mmap_sem;
 
		struct list_head mmlist; /* List of maybe swapped mm's.	These
					  * are globally strung together off
					  * init_mm.mmlist, and are protected
					  * by mmlist_lock
					  */
 
 
		unsigned long hiwater_rss; /* High-watermark of RSS usage */
		unsigned long hiwater_vm;  /* High-water virtual memory usage */
 
		unsigned long total_vm;	   /* Total pages mapped */
		unsigned long locked_vm;   /* Pages that have PG_mlocked set */
		atomic64_t    pinned_vm;   /* Refcount permanently increased */
		unsigned long data_vm;	   /* VM_WRITE & ~VM_SHARED & ~VM_STACK */
		unsigned long exec_vm;	   /* VM_EXEC & ~VM_WRITE & ~VM_STACK */
		unsigned long stack_vm;	   /* VM_STACK */
		unsigned long def_flags;
 
		spinlock_t arg_lock; /* protect the below fields */
		unsigned long start_code, end_code, start_data, end_data;
		unsigned long start_brk, brk, start_stack;
		unsigned long arg_start, arg_end, env_start, env_end;
 
		unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
 
		/*
		 * Special counters, in some configurations protected by the
		 * page_table_lock, in other configurations by being atomic.
		 */
		struct mm_rss_stat rss_stat;
 
		struct linux_binfmt *binfmt;
 
		/* Architecture-specific MM context */
		mm_context_t context;
 
		unsigned long flags; /* Must use atomic bitops to access */
 
		struct core_state *core_state; /* coredumping support */
 
#ifdef CONFIG_AIO
		spinlock_t			ioctx_lock;
		struct kioctx_table __rcu	*ioctx_table;
#endif
#ifdef CONFIG_MEMCG
		/*
		 * "owner" points to a task that is regarded as the canonical
		 * user/owner of this mm. All of the following must be true in
		 * order for it to be changed:
		 *
		 * current == mm->owner
		 * current->mm != mm
		 * new_owner->mm == mm
		 * new_owner->alloc_lock is held
		 */
		struct task_struct __rcu *owner;
#endif
		struct user_namespace *user_ns;
 
		/* store ref to file /proc/<pid>/exe symlink points to */
		struct file __rcu *exe_file;
#ifdef CONFIG_MMU_NOTIFIER
		struct mmu_notifier_subscriptions *notifier_subscriptions;
#endif
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
		pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
#ifdef CONFIG_NUMA_BALANCING
		/*
		 * numa_next_scan is the next time that the PTEs will be marked
		 * pte_numa. NUMA hinting faults will gather statistics and
		 * migrate pages to new nodes if necessary.
		 */
		unsigned long numa_next_scan;
 
		/* Restart point for scanning and setting pte_numa */
		unsigned long numa_scan_offset;
 
		/* numa_scan_seq prevents two threads setting pte_numa */
		int numa_scan_seq;
#endif
		/*
		 * An operation with batched TLB flushing is going on. Anything
		 * that can move process memory needs to flush the TLB when
		 * moving a PROT_NONE or PROT_NUMA mapped page.
		 */
		atomic_t tlb_flush_pending;
#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
		/* See flush_tlb_batched_pending() */
		bool tlb_flush_batched;
#endif
		struct uprobes_state uprobes_state;
#ifdef CONFIG_HUGETLB_PAGE
		atomic_long_t hugetlb_usage;
#endif
		struct work_struct async_put_work;
	} __randomize_layout;
 
	/*
	 * The mm_cpumask needs to be at the end of mm_struct, because it
	 * is dynamically sized based on nr_cpu_ids.
	 */
	unsigned long cpu_bitmap[];
};

프로세스들에 할당된 가상 메모리 공간은 다음에 알아볼 struct vm_area_struct 구조체로 관리한다.

mmap 멤버는 할당된 가상 메모리 리스트의 첫 번째 엔트리를 가리킨다.

mm_rb 멤버는 가상 메모리 공간을 빠르게 검색하기 위한 RB 트리의 루트를 가리킨다.

get_unmapped_area 오퍼레이션은 프로세스 주소 공간에서 이용할 수 있는 (unmapped) 공간을 찾는다.

mmap_base 멤버는 anonymous 주소 공간에서 할당된 주소 또는 파일 메모리 매핑 시 주소로 사용한다.

pgd 멤버는 페이지 글로벌 디렉토리의 시작 위치를 나타낸다.

mm_users 멤버는 사용자 공간에서 참조하고 있는 사용자 수를 나타낸다.

mm_count 멤버는 mm_struct 구조체를 참조하고 있는 횟수를 나타낸다.

map_count는 struct vm_area_struct 구조체 리스트의 엔트리 수이다.

page_table_lock은 페이지 테이블 접근에 대한 동기화를, mmap_sem은 mm_struct 구조체 접근에 대한 동기화를 위해 사용한다.

mmlist는 swap될 수 있는 mm_struct 구조체의 연결 리스트이다.

XXX_vm은 할당된 페이지 수들을 뜻한다. total_vm은 할당된 페이지 총 수이다.

start_xxx / end_xxx 등은 각 메모리 영역을 나타낸다.

프로세스에는 텍스트 영역, data 영역, bss 영역 등이 있는데, 이를 나타낸다.

 

추후에 프로세스 포스팅에서 알아볼 task_struct 구조체는 프로세스를 나타낸다.

task_struct 구조체에서 메모리 정보를 알아내기 위하여 사용하는 멤버 mm이 mm_struct 구조체의 포인터이다.

 

 

 

5. struct vm_area_struct

 

mm_struct 구조체의 멤버인 mmap이 프로세스에 할당된 가상 메모리 리스트를 나타낸다고 하였다.

struct vm_area_struct 구조체는 이 가상 메모리를 표현한다.

/*
 * This struct defines a memory VMM memory area. There is one of these
 * per VM-area/task.  A VM area is any part of the process virtual memory
 * space that has a special rule for the page-fault handlers (ie a shared
 * library, the executable area etc).
 */
struct vm_area_struct {
	/* The first cache line has the info for VMA tree walking. */
 
	unsigned long vm_start;		/* Our start address within vm_mm. */
	unsigned long vm_end;		/* The first byte after our end address
					   within vm_mm. */
 
	/* linked list of VM areas per task, sorted by address */
	struct vm_area_struct *vm_next, *vm_prev;
 
	struct rb_node vm_rb;
 
	/*
	 * Largest free memory gap in bytes to the left of this VMA.
	 * Either between this VMA and vma->vm_prev, or between one of the
	 * VMAs below us in the VMA rbtree and its ->vm_prev. This helps
	 * get_unmapped_area find a free area of the right size.
	 */
	unsigned long rb_subtree_gap;
 
	/* Second cache line starts here. */
 
	struct mm_struct *vm_mm;	/* The address space we belong to. */
 
	/*
	 * Access permissions of this VMA.
	 * See vmf_insert_mixed_prot() for discussion.
	 */
	pgprot_t vm_page_prot;
	unsigned long vm_flags;		/* Flags, see mm.h. */
 
	/*
	 * For areas with an address space and backing store,
	 * linkage into the address_space->i_mmap interval tree.
	 */
	struct {
		struct rb_node rb;
		unsigned long rb_subtree_last;
	} shared;
 
	/*
	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
	 * list, after a COW of one of the file pages.	A MAP_SHARED vma
	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
	 * or brk vma (with NULL file) can only be in an anon_vma list.
	 */
	struct list_head anon_vma_chain; /* Serialized by mmap_sem &
					  * page_table_lock */
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */
 
	/* Function pointers to deal with this struct. */
	const struct vm_operations_struct *vm_ops;
 
	/* Information about our backing store: */
	unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units */
	struct file * vm_file;		/* File we map to (can be NULL). */
	void * vm_private_data;		/* was vm_pte (shared mem) */
 
#ifdef CONFIG_SWAP
	atomic_long_t swap_readahead_info;
#endif
#ifndef CONFIG_MMU
	struct vm_region *vm_region;	/* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
	struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;

vm_start와 vm_end 멤버는 가상 메모리의 시작 및 끝 + 1 주소를 나타낸다.

vm_next와 vm_prev 멤버는 태스크에 할당된 VMA 리스트를 나타낸다.

vm_rb는 가상 메모리 공간의 검색을 빠르게 하기 위한 RB 트리에 사용된다.

vm_mm은 이 가상 메모리 영역을 사용하고 있는 mm_struct 구조체를 가리킨다.

vm_page_prot는 해당 영역의 읽기/쓰기 권한을 나타낸다.

vm_flags는 해당 영역의 속성을 나타내는 플래그다.

anon_vma_chain은 anonymous vma 리스트를 나타낸다.

anon_vma는 anonymous vma를 관리하는 구조체이다.

vm_ops멤버는 가상 메모리에 관련되는 오퍼레이션들을 정의한다.

vm_pgoff는 가상 메모리에서 현재 사용중인 위치(offset)을 나타낸다.

vm_file은 가상 메모리가 파일과 매핑된 경우에 해당 파일 구조체를 가리킨다.

vm_private_data는 해당 메모리 영역의 private 데이터를 나타낸다.

vm_policy는 가상 메모리 영역의 NUMA 정책을 나타낸다.

 

flags에서 사용하는 플래그 값들은 아래와 같다.

/*
 * vm_flags in vm_area_struct, see mm_types.h.
 * When changing, update also include/trace/events/mmflags.h
 */
#define VM_NONE		0x00000000
 
#define VM_READ		0x00000001	/* currently active flags */
#define VM_WRITE	0x00000002
#define VM_EXEC		0x00000004
#define VM_SHARED	0x00000008
 
/* mprotect() hardcodes VM_MAYREAD >> 4 == VM_READ, and so for r/w/x bits. */
#define VM_MAYREAD	0x00000010	/* limits for mprotect() etc */
#define VM_MAYWRITE	0x00000020
#define VM_MAYEXEC	0x00000040
#define VM_MAYSHARE	0x00000080
 
#define VM_GROWSDOWN	0x00000100	/* general info on the segment */
#define VM_UFFD_MISSING	0x00000200	/* missing pages tracking */
#define VM_PFNMAP	0x00000400	/* Page-ranges managed without "struct page", just pure PFN */
#define VM_DENYWRITE	0x00000800	/* ETXTBSY on write attempts.. */
#define VM_UFFD_WP	0x00001000	/* wrprotect pages tracking */
 
#define VM_LOCKED	0x00002000
#define VM_IO           0x00004000	/* Memory mapped I/O or similar */
 
					/* Used by sys_madvise() */
#define VM_SEQ_READ	0x00008000	/* App will access data sequentially */
#define VM_RAND_READ	0x00010000	/* App will not benefit from clustered reads */
 
#define VM_DONTCOPY	0x00020000      /* Do not copy this vma on fork */
#define VM_DONTEXPAND	0x00040000	/* Cannot expand with mremap() */
#define VM_LOCKONFAULT	0x00080000	/* Lock the pages covered when they are faulted in */
#define VM_ACCOUNT	0x00100000	/* Is a VM accounted object */
#define VM_NORESERVE	0x00200000	/* should the VM suppress accounting */
#define VM_HUGETLB	0x00400000	/* Huge TLB Page VM */
#define VM_SYNC		0x00800000	/* Synchronous page faults */
#define VM_ARCH_1	0x01000000	/* Architecture-specific flag */
#define VM_WIPEONFORK	0x02000000	/* Wipe VMA contents in child. */
#define VM_DONTDUMP	0x04000000	/* Do not include in the core dump */
 
#ifdef CONFIG_MEM_SOFT_DIRTY
# define VM_SOFTDIRTY	0x08000000	/* Not soft dirty clean area */
#else
# define VM_SOFTDIRTY	0
#endif
 
#define VM_MIXEDMAP	0x10000000	/* Can contain "struct page" and pure PFN pages */
#define VM_HUGEPAGE	0x20000000	/* MADV_HUGEPAGE marked this vma */
#define VM_NOHUGEPAGE	0x40000000	/* MADV_NOHUGEPAGE marked this vma */
#define VM_MERGEABLE	0x80000000	/* KSM may merge identical pages */

 

 

 

이번 포스트에서는 Linux Kernel 5에서 구현하고 있는 가상 메모리와 페이징 기법에 대해서 알아보았다.

해당 구조체 등을 이용한 예제는 추후에 메모리 할당 정책들을 포스팅한 후 보일 예정이다.