일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- multiqueue
- fastpath
- Apache
- BLOCK
- allocator
- Linux
- Kernel
- vm_area_struct
- page
- commit
- blk-mq
- buddy_system
- spinlock
- vmalloc
- NDK
- pmap
- kmalloc
- mm_struct
- strex
- slowpath
- memory
- Android
- kafka
- 카프카
- devicedriver
- proc
- lruvec
- Network
- slub
- slab
- Today
- Total
Art of Pr0gr4m
[Linux Kernel 5] Virtual Memory & Paging 본문
이번 포스트부터는 리눅스의 메모리 모델과 관리 정책들에 대해서 알아본다.
가장 먼저 가상 메모리와 페이징에 대해서 알아볼 것이다.
참고로 세그먼테이션은 추후에 부팅과정에 대해 다룰 때 운영 모드와 함께 다룰 예정이며,
메모리 관리 정책에서는 넘어갈 것이다.
또한, 해당 포스트는 가상 메모리와 페이징의 일반적인 개념을 공부하는 포스트는 아니므로
컨셉에 대한 공부가 필요하다면 아래 링크를 참고하도록 한다.
https://en.wikipedia.org/wiki/Virtual_memory
https://en.wikipedia.org/wiki/Paging
https://www.geeksforgeeks.org/paging-in-operating-system/
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단계 페이징을 그림으로 나타내면 다음과 같다.
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에서 구현하고 있는 가상 메모리와 페이징 기법에 대해서 알아보았다.
해당 구조체 등을 이용한 예제는 추후에 메모리 할당 정책들을 포스팅한 후 보일 예정이다.
'IT > Linux Kernel' 카테고리의 다른 글
[Linux Kernel 5] kmalloc & vmalloc (memory allocator) (0) | 2020.05.13 |
---|---|
[Linux Kernel 5] Memory Zone (0) | 2020.05.12 |
[Linux Kernel 5] Linked List (0) | 2020.05.10 |
[Linux Kernel 5] Block Device Driver Example (0) | 2020.05.04 |
[Linux Kernel 5] Block Device Driver Basic Concept (2) | 2020.05.04 |