[0]
리눅스 커널에서 모든 유저 프로세스는 가상 메모리 공간을 할당 받는다. 가상 메모리 덕분에, 각 프로세스들은 자신들이 모든 메모리 공간을 '독점'하는 것처럼 사용할 수 있다. 하지만 실제로 물리 메모리를 각 프로세스가 독점할 수 는 없기 때문에, 커널에서는 이런 유저 프로세스의 메모리를 관리할 필요가 있다. 이를 위헤 커널에서는 mm_struct라는 memory descriptor를 각 테스크마다 할당하여 관리한다. 또한 가상 메모리 공간의 다양한 영역(코드 영역, 데이터 영역 등)을 관리하기 위해 vm_area_struct(VMA) 구조체가 영역마다 할당되어 mm_struct에 저장된다.
[1]
우선 각 테스크의 정보를 저장하는 task_struct에서 mm_struct 멤버를 찾아보자.
struct task_struct {
...
struct mm_struct *mm;
struct mm_struct *active_mm;
...
}
task당 하나의 mm_struct가 mm이라는 멤버로 할당되는 것을 확인할 수 있다. 이때, mm은 모든 테스크마다 unique하지는 않다. 하나의 유저 프로세스에 여러 개의 스레드가 생성된다면, 해당 스레드는 메모리 공간을 공유하므로, mm이 같은 값을 가지게 된다. Task가 스케쥴링되게 되면, mm이 가르키는 mm_struct가 로드 되어 active_mm에 저장된다. 단, 이때 커널 스레드는 mm이 NULL이므로(유저 레벨 메모리 공간이 없으므로) 커널의 메모리 공간이 acive_mm에 로딩된다.
[2]
이제 실제 코드로 mm_struct를 분석해보자. 커널버전은 5.7이고, 일부의 멤버만 남겨놨다. 전체 mm_struct는 include/linux/mm_types.h에서 찾을 수 있다.
struct mm_struct {
struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
/**
* @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;
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;
} __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[];
};
- mm_users : mm_struct의 주소 공간을 사용중인 task를 나타낸다. 만약 하나의 프로세스에 대해 메모리를 공유하는 스레드가 3개라면, mm_users는 3이 된다.
- mm_count : mm_struct의 참조 카운트. 0이 되면 mm_struct가 free된다.
- mmap : 가상 메모리 영역의 리스트. vm_area_struct에 대해서는 아래에서 다룬다.
- mm_rb : 마찬가지로 가상 메모리 영역을 저장한다. mmap의 원소와 완전히 동일하지만 red-black tree 구조이다. mmap은 VMA를 순회할때 사용되고, mm_rb는 VMA를 검색할 때 사용된다.
- pgd : 가상메모리 -> 물리메로리 매핑에 사용되는 페이지 테이블의 주소.
- start_code / end_code : code영역의 시작 주소와 끝 주소. 유사하게, data영역이나(start_data) heap영역(start_brk)도 다른 멤버로 선언되어있다.
[3]
vma_area_struct 역시 mm_struct와 같은 파일에 존재한다.
/*
* This struct describes a virtual 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;
대부분의 멤버들이 주석과 이름을 충분히 활용도를 유추할 수 있다. VMA 하나가, 프로세스의 메모리 공간(code 영역, 데이터 영역 등)을 나타낸다는 점을 상기하면서, 중요한 멤버 몇개만 살펴보자.
- vm_start, vm_end : VMA 영역의 시작 주소와 끝 주소. vm_end - vm_start가 해당 VMA의 사이즈를 나타낸다.
- vm_prev, vm_next : 다음과 이전 VMA 구조체를 가르키는 포인터. 더블 링크드 리스트 형태로 되어있고, vm_start로 소팅되어 있다.
- vm_mm : 해당 VMA가 속해있는 mm_struct
- vm_flags : 해당 메모리 영역의 속성을 나타내는 플래그
- vm_ops : VMA 영역에 대한 opeartions들을 나타내는 function pointer들
- vm_file : mmap을 사용한 경우 해당하는 파일, null일 경우 이 VMA는 mmap으로 사용되고 있지 않음.
[참고]
esos.hanyang.ac.kr/tc/2015gradproject2/i/entry/2
Robert Love, Linux Kernel Development 3rd Edition.
'Computer Science > Kernel' 카테고리의 다른 글
리눅스 커널의 timer interrupt handler (0) | 2020.11.05 |
---|---|
printf 함수 실행 분석하기 (0) | 2020.08.22 |
Linux kernel의 Superblock 구조체 (0) | 2020.06.02 |
Linux Kernel : struct page 구조체 (0) | 2020.04.07 |
Ch.11 Timers and Time Management (0) | 2017.08.27 |