15. 프로세스 주소 공간
- 커널은 사용자 공간 프로세스의 메모리도 관리해야 하며 이를 ‘프로세스 주소 공간’이라고 부른다.
- 프로세스는 유효한 메모리 영역에만 접근해야 하며, 이를 어길시 segment fault를 만날 것이다.
15.1 메모리 서술자 구조체 mm_struct
struct mm_struct { struct { atomic_t mm_users; /* * Fields which are often written to are placed in a separate * cache line. */ struct { /** * @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; } ____cacheline_aligned_in_smp;
struct maple_tree mm_mt; ... }};
-
<linux/mm_types.h>
에는mm_struct
라는 메모리 서술자 구조체가 정의돼있다.mm_users
: 이 주소 공간을 사용하는 프로세스의 개수를 의미한다.mm_count
: 이 구조체의 주 참조 횟수다.- 9개 스레드가 주소 공간을 공유한다면?
mm_users == 9, mm_count == 1
mm_users == 0
-> mm_count를 하나 감소시킨다.mm_count == 0
-> 이 주소 공간을 참조하는 놈이 하나도 없으니 메모리를 해제한다.
- 9개 스레드가 주소 공간을 공유한다면?
- mmap, mm_rb: 동일한 메모리 영역을 전자는 연결리스트로, 후자는 레드-블랙 트리로 나타낸 것이다.
- 왜 같은 대상을 중복 표현해서 메모리를 낭비하는 걸까?
- 메모리 낭비는 있겠지만, 얻을 수 있는 이점이 있기 때문이다.
- 전후 관계를 파악하거나 모든 항목을 탐색할 때는 연결리스트가 효율적이다.
- 특정 항목을 탐색할 때는 레드-블랙 트리가 효율적이다.
- 이런 방식으로 같은 데이터를 두 가지 다른 접근 방식으로 사용하는 것을 ‘스레드 트리’라고 부른다.
-
이미 3장에서
task_struct
를 배울 때 mm 멤버변수로 이 구조체를 봤었다.- 복습하자면,
current->mm
은 현재 프로세스의 메모리 서술자를 뜻하며, fork()
→copy_mm()
함수가 부모 프로세스의 메모리 서술자를 자식 프로세스로 복사하며,- 복사할 때 12.4절에서 배운 ‘슬랩 캐시’를 이용해
mm_cachep
에서mm_struct
구조체를 할당한다. - 만일 만드는게 스레드라면, 생성된 스레드의 메모리 서술자는 부모의 mm을 가리킬 것이다.
- 그리고 커널 스레드라면, 당연히 프로세스 주소 공간이 없으므로 mm == NULL이다.
- (+ 추가내용: 커널 스레드가 종종 프로세스 주소 공간의 페이지 테이블 일부 데이터가 필요한 경우가 있다. 메모리 서술자의 mm == NULL일 때, active_mm 항목은 이전 프로세스의 메모리 서술자가 가리키던 곳으로 갱신된다. 따라서 커널 스레드는 이전 프로세스의 페이지 테이블을 필요할 때 사용할 수 있다.)
- 복습하자면,
15.2 가상 메모리 영역 구조체 vm_area_struct
- 리눅스 커널에서 ‘가상 메모리 영역’은 VMA라고 줄여 부르며
<linux/mm_type.h>
의vm_area_struct
구조체로 메모리 영역을 표현한다.
// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/mm_types.h#L616struct vm_area_struct { /* The first cache line has the info for VMA tree walking. */
union { struct { /* VMA covers [vm_start; vm_end) addresses within mm */ unsigned long vm_start; unsigned long vm_end; };#ifdef CONFIG_PER_VMA_LOCK struct rcu_head vm_rcu; /* Used for deferred freeing. */#endif };
struct mm_struct *vm_mm; /* The address space we belong to. */ pgprot_t vm_page_prot; /* Access permissions of this VMA. */
/* * Flags, see mm.h. * To modify use vm_flags_{init|reset|set|clear|mod} functions. */ union { const vm_flags_t vm_flags; vm_flags_t __private __vm_flags; };
const struct vm_operations_struct *vm_ops; ...} __randomize_layout;
- 주요 멤버 변수를 살펴보면 아래와 같다.
vm_start
,vm_end
: 가상 메모리 영역의 시작주소와 마지막 주소를 의미하므로 이 둘의 차이가 메모리 영역의 바이트 길이가 된다. 다른 메모리 영역끼리는 중첩될 수 없다.vm_mm
: VMA 별로 고유한 mm_struct를 보유한다. 동일 파일을 별도의 프로세스들이 각자의 주소 공간에 할당할 경우 각자의 vm_area_struct를 통해 메모리 공간을 식별하게 된다.vm_flags
: 메모리 영역 내 페이지에 대한 정보(읽기, 쓰기, 실행 권한 정보 등)를 제공한다.vm_ops
: 메모리 영역을 조작하기 위해 커널이 호출할 수 있는 동작 구조체 vm_operations_struct를 가리킨다. (13절 VFS를 설명할 때 언급했던 ‘동작 객체’ 구조체와 비슷한 개념이다.)
15.3. 실제 메모리 영역 살펴보기
- 간단한 프로그램을 만들고, ‘/proc’ 파일시스템과 pmap 유틸리티를 통해 특정 프로세스의 주소 공간과 메모리 영역을 살펴보자.
[ec2-user@ip-x-x-x-x ~]$ echo -e "int main(int argc, char *argv[]) { while(1); }" > test.c[ec2-user@ip-x-x-x-x ~]$ gcc -o test test.c && ./test &[1] 1024914[ec2-user@ip-x-x-x-x ~]$ cat /proc/1024914/maps55ef85f2b000-55ef85f5a000 r--p 00000000 103:01 2253 /usr/bin/bash55ef86c8c000-55ef86dad000 rw-p 00000000 00:00 0 [heap]7ffb87000000-7ffb94530000 r--p 00000000 103:01 8524092 /usr/lib/locale/locale-archive7ffb94600000-7ffb94ed4000 r--s 00000000 103:01 9692403 /var/lib/sss/mc/passwd7ffb94fab000-7ffb95000000 r--p 00000000 103:01 443 /usr/lib/locale/C.utf8/LC_CTYPE7ffb95000000-7ffb95028000 r--p 00000000 103:01 8524744 /usr/lib64/libc.so.67ffb95230000-7ffb95231000 r--p 00000000 103:01 1600 /usr/lib/locale/C.utf8/LC_NUMERIC7ffb95231000-7ffb95232000 r--p 00000000 103:01 1603 /usr/lib/locale/C.utf8/LC_TIME7ffb95232000-7ffb95233000 r--p 00000000 103:01 442 /usr/lib/locale/C.utf8/LC_COLLATE7ffb95233000-7ffb95234000 r--p 00000000 103:01 446 /usr/lib/locale/C.utf8/LC_MONETARY7ffb95234000-7ffb95235000 r--p 00000000 103:01 8524051 /usr/lib/locale/C.utf8/LC_MESSAGES/SYS_LC_MESSAGES7ffb95235000-7ffb95236000 r--p 00000000 103:01 1601 /usr/lib/locale/C.utf8/LC_PAPER7ffb95236000-7ffb95237000 r--p 00000000 103:01 447 /usr/lib/locale/C.utf8/LC_NAME7ffb95237000-7ffb9523e000 r--s 00000000 103:01 2799 /usr/lib64/gconv/gconv-modules.cache7ffb9523e000-7ffb95240000 r--p 00000000 103:01 9455124 /usr/lib64/libnss_sss.so.27ffb9524e000-7ffb9525c000 r--p 00000000 103:01 8522848 /usr/lib64/libtinfo.so.6.27ffb95283000-7ffb95285000 r--p 00000000 103:01 8524740 /usr/lib64/ld-linux-x86-64.so.27ffed54c8000-7ffed54e9000 rw-p 00000000 00:00 0 [stack]7ffed55e0000-7ffed55e4000 r--p 00000000 00:00 0 [vvar]7ffed55e4000-7ffed55e6000 r-xp 00000000 00:00 0 [vdso]ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
-
/proc/<pid>/maps
파일은 프로세스 주소 공간의 메모리 영역을 출력해준다. -
pmap 유틸리티를 사용하면 위 정보를 조금 더 가독성 있게 표현해준다.
-
지금까지 다룬 구조체의 구조를 깔끔하게 도식화한 그림이다.
-
task_struct
의 mm은 각 프로세스의 메모리 서술자인mm_struct
이다. -
mm_struct
의 mmap은 가상 메모리 영역vm_area_struct
을 표현하는 연결리스트다. -
vm_area_struct
는 프로세스의 실제 메모리 영역(.txt, .data 등)을 나타낸다.
- 알다시피, 커널과 애플리케이션은 가상 주소를 사용하지만, 프로세서는 물리 주소를 사용한다.
- 따라서 프로세서와 애플리케이션이 서로 상호작용하기 위해서는 페이지 테이블을 통해 변환작업이 필요하다.
- 리눅스 커널은 PGD(Global), PMD(Middle), PTE 세 단계의 페이지 테이블을 사용한다.
- 페이지 테이블 구조는 아키텍처에 따라 상당히 다르며
<asm/page.h>
에 정의돼있다.
참고