Anatomy of a Program in Memory

Original url : http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory


Memory 관리는 OS 의 중요한 feature 이다. 같은 사이트를 번역한 글은 모두 x86 기반으로 분석된 글이며 다른 architecture은 다른 방식을 채택했을 수 있다. 


Multi-tasking OS 는 각 process 가 자신 만의 memory sandbox를 갖고 실행된다. 이 sandbox는 virtual address space 이며 32bit system에서는 4GB 를 cover 할 수 있다. 이 virtual address 는 physical memory 를 page table에 의해 mapping 된다. 조금 더 추가하자면 process가 virtual address를 통해 read/write 하게되면 physical memory에 disk 등에서 부터 memory로 읽어온 physical address로 mapping 및 접근 가능하도록 만들어주고 사용 할 수 있도록 한다. 아래의 그림은 Intel architecture(32bit)을 사용하는 machine의 process address space(virtual address space)를 그린 그림이다.



I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

Process Address Space(Virtual)

User 영역에서 page-falut 가 나면 kernel 이 physical 영역에 사용허가(?) 를 내어주고 그 virtual address와 mapping 시켜 준다. kernel space 모든 process 의 virtual address 상에 똑같이 존재한다. kernel code와 data는 항상 addressable 하며 interrupt 처리와 system call을 언제든지 실행 하능하도록 준비되어 있다. 반대로 user made의 mapping은 process가 변경될 때마다 address space는 변경되며 사용되는 address도 다를 수 있다. 참고로 intel x86 architecture는 TLB(Translation lookable buffer)라 불리는 곳에 virtual to physical 을 기록하게 된다. 한번의 page fault 로 user address 와 physical address가 MMU 에 의해 연결이 되면 TLB 에 관련된 내용을 쓰고 다음 같은 virtual address를 접근 시 virtual to physical address 변환 과정(이 변환 과정은 비싼 명령이라고 함)이 필요 없이 바로 가져다 쓸수있도록 하는 cache인 것이다. 근데 이 x86은 TLB 에 특별한 tag 같은 것이 없어 공통으로 쓰는 TLB cache에 모든 process 가 사용한다. 그래서 process 간 TLB를 쓰더라도 구별할 방법이 없으며, context switch 와 동시에 모두 flush 를 하게된다. (서로 다른 process 가 같은 virtual address를 이용할 때 실제 physical memory를 다를 수 있기 때문임.)

실제 Context Switch 시 많은 일들이 일어나지만 일부만 기록한다.


I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

Context Switching


짙은 녹색 부분이 physical memory 로 mapping된 virtual address 영역이다. 위에서 보듯이 FireFox 는 메모리를 엄청 쓰는 application 중에 하나이다.(물론 그림 상으로는 많이 쓴다 안쓴다 하긴 어렵지만) 분리된 메모리 영역은 heap, stack 등으로 나뉘어지는 segment  가 될 것이다. 아래의 그림은 linux process 가 일반적으로 사용하는 segment layout이다.


I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

Process Virtual address space


모든 process는 위의 그림와 같은 방법으로 virtual address space를 운영한다.  process의 top 에서는 stack 이 사용한다. 이는 local 변수나 function parameter 등을 위해 사용된다. 함수나 method를 호출 할 때마다 stack 영역에 새로운 stack frame을 넣게 된다. 새로 넣는다기 보다는 stack base pointer를 새로운 함수에 맞추는 것이라고 보면 된다. function 수행이 완료되고 난 뒤에 생성된 stack frame은 제거된다. 이것은 Stack 은 LIFO(Last In, First Out)에 따라 simple 하게 동작한다. 또한  지속적인 stack 영역의 사용은 cpu cache 내부의 활성화된 stack memory 를 접근/사용 가능하니 performance가 좋다고 한다. 각 process의 thread 들은 자신만의 stack을 갖는다. 


만약 지정된 size 보다 많은 data를 pushing 하는 것이 가능하다. 이것은 expand_stack() 함수 호출로 linux 내부에서 page fault handling 할 때 진행될 수 있다. 그 뒤에 acct_stack_growth() 로 stack 이 알맞게 키워질 수 있는지 확인한다.  만약 stack size 가 RLIMIT_STACK(보통 8MB)보다 작으면 stack 를 키우고 program은 그냥 진행하면 된다. 요구하는 정도에 따라 stack을 키우고 줄일 수 있기 때문이다. 하지만 stack size가 최대치가 된다면, stack overflow가 발생하고 program은 segment fault 를 받게된다. 


stack 의 동적 증가는 unmapped memory region 인(위의 그림에서 stack 아래에 있는 흰 부분) memory를 access 하고 그 공간을 사용할 수 있을 때만 동작한다. 어떤 unmapped memory 의 모든 접근은 일단 segment falut 이 후에 page fault handling을 통해 memory map 이후에 사용하도록 하는 것이다. 어떤 경우에 이 map된 영역은 read-only가 될 수 있고 write operation이 허용되지 않을 수 있다. 


위의 그림에서 memory mapping segment 를 살펴보자. kernel 은 file의 내용을 direct mapping을 이 영역으로 map 해준다. 어떤 application이든 linux 의 mmap() system call을 통해 이와 같은 mapping을 kernel에게 요청할 수 있다. 이런 memory mapping은 file I/O를 위한 방법으로 편리하고 고성능의 방법이 된다. 그래서 shared libary 는 이와 같은 방법으로 user address space에 필요시 mapping하여 사용한다. 또한 anonymous memory mapping 을 사용하여 file을 mapping 하지는 않지만 program data를 위한 영역으로 사용할 수 있다. (Android 에서 property를 저장하는 공간으로 사용되는 ashmem이 대표적인 예라고 할 수 있을 듯.) linux 에서 malloc()을 통해 큰 block의 memory를 요청하면 C library는 heap 영역을 사용하는 대신 anonymous mapping 공간을 사용하여 확보한다. 여기서 "큰 block" 이라고 하면, M_MMAP_THRESHOLD 로 define된 값보다 큰 경우라고 보면된다.(default 128KB, 이것은 mallopt()를 통해 조절가능하다)


이제 heap 영역으로 넘어가자. heap은 stack 처럼 program이 runtime에 제공받을 수 있도록 만들어놓은 영역이다. 하지만 stack과는 다르게 명시적으로 해제를 시켜주지 않는한 만들어진 function 밖에서도 사용될 수 있다. 거의 모든 언어들은 heap memory management를 program에게 제공한다. memory 요청을 만족시키기 위해 kernel 과 code runtime 사이에 동기화(?) 같은 것이 필요하다. C 에서는 heap 할당은 malloc()을 사용하고 C++, C#과 같은 gabage-collected 언어에서는 new keyword를 사용한다. 


만약 memory 요청에 heap에 공간이 충분하다면 보통의 언어들은 kernel의 개입없이 handling 이 가능하다. 하지만 heap의 영역을 확장이 필요한 경우 brk() system call(kernel 구현 부)을 이용하여 요청된 block을 위한 공간을 만들어야 한다. Heap management는 복잡하며, program의 무질서한 할당 요청 및 해제등을 효휼적이며 빠르게 하기 위해서는 정교한 알고리즘이 요구된다. 


마지막으로 memory 가장 아래에 있는 BSS, data, program text 를 살펴보자. BSS 와 data의 영역은 C 언어에서는 static(global) 변수를 위한 곳이다. BSS와 data의 차이점은 BSS는 초기화 되지 않은 static 변수를 갖는다는 것이다. BSS memory는 anonymous : 어떤 파일과 mapping되어 있지 않다는 의미. 만약 static int A; 라고 code에서 사용하면 A라는 변수는 BSS 영역에 들어 있다는 것이다. 


data segment는 초기화된 static 변수를 갖게 된다. 이 memory는 anonymous 영역이 아니다. source code에서 주어진 초기화된 static 값을 포함하는 program의 binary image의 일부를 mapping 하게 되는 것이다. 예를 들어 static int B = 10; 이라고 선언하면 B은 data segment에 있는 것이며 10이라는 값으로 초기화 된다는 것이다. data segment가 file의 일부(program의 binary 이미지) mapping 하여 사용하지만 private memory mapping type으로 변수의 값이 변하더라도 실제 file(binary image)에 반영이 되진 않는다.


아래 그림에서 특별한 예제를 살펴본다. (pointer 변수 사용 경우) 이 경우 pointer 라는 변수에 address 를 갖고 초기화하는 것을 만든다.(32bit 이니 4byte memory address를 가짐, 이는 초기화 되는 global 변수이니 data segment 에 들어간다). 이 point는 실제 string이 있는 address를 갖고 있는데 이 address는 text segment에 있는 것이다. test segment는 read-only 이며 이미지화(?)된 code모두를 mapping 하고 있는 것으므로 write operation일 경우 segment fault 를 받게 될 것이다. 이런 segment falut 는 pointer bug로 부터 보호하기 위해 있다. 이 단락의 내용을 diagram으로 표현하면,



I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image

segment falut with address pointer

테스트를 해본다면 linux 에서는 /proc/<pid>/maps file을 읽어보면 memory 영역을 알수가 있다. segment 내에서도 여러개의 영역을 포함할 수 있다는 것을 기억해두자. 예를 들어 각 일반적인 memory mapped file은 mmap segment 에 자신만의 영역을 가진다. 그리고 동적 library는 BSS 와 data 영역과 비슷한 추가적인 공간을 가지고도 있다.


nm 이나 objdump 로 binary 의 symbol 들을 살펴 볼 수 있으며 그 symbol들의 address 또한 알수 있다. 마지막으로 linux 32bit system에서의 classic 한 virtual memory layout을 살펴 본다.


I confirmed to re-use of image with gustavo-web@duartes.org, If you want to see a original image, just click this image


+ Recent posts