가상주소확인

test.c

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
#include <stdlib.h>
#include <stdio.h>
 
int glob1, glob2;
 
int func2(){
    int f2_local1,f2_local2;
    printf("func2 local : \n\t%p,\n\t%p\n",&f2_local1,&f2_local2); 
}
 
int func1(){
    int f1_local1,f1_local2;
    printf("func1 local: \n\t%p,\n\t%p\n",&f1_local1,&f1_local1);
    func2();
}
 
main()
{
    int m_local1,m_local2;
    int *dynamic_addr;
    printf("main local:\n\t%p,\n\t%p\n",&m_local1,&m_local2);
    func1();
    dynamic_addr=malloc(16);
    printf("dynamic: \n\t%p\n",dynamic_addr);
    printf("global:\n\t%p,\n\t%p\n",&glob2,&glob1);
    printf("functions: \n\t%p, \n\t%p, \n\t%p\n",main,func1,func2);
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

 

Chapter 4 메모리 관리

 

1. 메모리 관리 기법과 가상 메모리

가상 메모리(virtual memory) : 물리 메모리의 한계를 극복하기 위한 기법, 가장 성공적이며 대부분의 시스템에서 사용

실제 시스템에 존재하는 물리 메모리의 크기와 관계없이 가상적인 주소 공간을 사용자 태스크에게 제공.

32비트 CPU : 2의32승 4GB 주소 공간을 사용자에게 제공

64비트 CPU : 2의64승 16EB 주소 공간을 사용자에게 제공

 

주의해야할점은 물리적으로 4GB의 메모리를 모두 사용자 태스크에게 제공하는 것은 아니다. 프로그래머에게 개념적으로 제공되는 공간, 실제로는 사용자가 필요한 만큼의 물리 메모리를 제공한다.

 

결국 가상 메모리는 사용자에게 개념적으로 4GB의 큰 공간을 제공함과 동시에 물리 메모리는 필요한 만큼의 메모리만 사용되므로 가능한 많은 태스크가 동시에 수행될 수 있다는 장점을 제공. 이외에도 메모리 배치 정책이 불필요하고, 태스크 간 메모리 공유/보호가 쉽고, 태스크의 빠른 생성이 가능하다는 장점이 있다.

 

 

2. 물리 메모리 관리 자료 구조

리눅스는 시스템에 존재하는 전체 물리 메모리에 대한 정보를 가지고 있어야 한다.

메모리라는 물리적 자원이 리눅스에서 어떻게 표현되고 있는가?

한정된 용량의 메모리를 효율적으로 사용하기 위해 어떤 정책을 사용하고 있는가?

 

복수 개의 CPU를 가지고 있는 컴퓨터 시스템 중 모든 CPU가 메모리와 입출력 버스 등을 공유하는 구조를 SMP(Symmetric Multiprocessing)라 부른다. 그런데 복수 개의 CPU가 메모리 등의 자원을 공유하기 때문에 성능상 병목 현상이 발생할 수 있다. 따라서 CPU들을 몇 개의 그룹으로 나누고 각각의 그룹에게 별도의 지역 메모리를 주는 구조가 생겨났는데 이러한 구조를 NUMA(Non-Uniform Memory Access)라 부르고 이에 반해 기존 시스템을 UMA(Uniform Memory Access)라고 한다.

 

UMA 구조라면 별 상관이 없겠지만, NUMA구조에서는 CPU에서 어떤 메모리에 접근하느냐에 따라 성능 차이가 발생 할 수 있다. 리눅스는 다양한 하드웨어 지원을 목표로 하며, 리눅스개발자는 UMA 구조에서도 NUMA 구조에서도 모두 효율적으로 수해될 수 있도록 리눅스를 설계하였다.

 

 

2-1 Node

리눅스에선 접근 속도가 같은 메모리의 집합을 뱅크(bank)라 부른다. UMA구조라면 한 개의 뱅크, NUMA구조라면 복수개의 뱅크가 존재. 뱅크를 표현하는 구조가 노드(node,~/include/linux/mmzone.h>)이다.

UMA구조의 노드는 전역 변수인 contig_page_data를 통해 접근 가능

NUMA구조의 시스템에서는 복수 개의 노드가 존재, 복수 개의 노드는 리스트를 통해 관리, pgdat_list라는 이름의 배열을 통해 접근 가능

 

리눅스는 하드웨어 시스템에 관계없이 노드라는 일관된 자료구조를 통해서 전체 물리메모리를 접근할 수 있게 된다.

 

UMA, NUMA 둘다 하나의 노드는 pg_data_t 구조체를 통해 표현된다. 이 구조체는 해당 노드에 속해있는 물리 메모리의 실제 양(node_present_pages)이나, 해당 물리메모리가 메모리 맵의 몇 번지에 위치하고 있는지를 나타내기 위한 변수(node_start_pfn) 등이 정의되어 있다. zone 구조체를 담기 위한 배열(node_zones)과, zone의 개수를 담는 변수(nr_zones) 등이 선언되어 있다.

 

만약 리눅스가 물리 메모리의 할당 요철을 받게 되면, 되도록 할당을 요청한 태스크가 수행되고 있는 CPU와 가까운 노드에서 메모리를 할당하려 한다. 리눅스는 태스크가 되도록 이전에 수행되었던 CPU에서 다시 수행되도록 하기 때문에 이러한 정책을 통해 보다 높은 성능을 얻을 수 있게 된다.

 

2-2 Zone

노드 안에 존재하고 있는 메모리는 모두 어떠한 용도로도 사용될 수 있어야 한다.

그런데 일부 ISA 버스 기반 디바이스의 경우 정상적인 동작을 위해서 반드시 물리 메모리중 16MB이하 부분을 할당해 줘야 하는 경우가 있다. 따라서 노드에 존재하는 물리메모리 중 16MB이하 부분은 특별화게 관리하는데, 이를 위해 node의 일부분을 따로 관리할 수 있도록 자료구조를 만들어 놓았다. (zone(~/include/linux/mmzone.h)이라 부른다.)

zone은 동일한 속성을 가지며, 다른 zone의 메모리와는 별도로 관리되어야 하는 메모리의 집합. 16MB 이하 부분은 ZONE_DMA, ZONE_DMA32라는 이름으로 불린다. 16MB이상은 ZONE_NORMAL

 

리눅스에서는 물리메모리가 1GB이상이라면 896MB까지를 커널의 가상주소공간과 1:1로 연결해주고, 나머지 부분은 필요할 때 동적으로 연결하여 사용하는 구조를 채택. 896MB이상의 메모리 영역을 ZONE_HIGHMEM이라 부름

 

주의할 점은 모든 시스템에서 언제나 DMA, NORMAL, HIGHMEM 세 개의 zone이 존재하는 것은 아니다. 

 

각각의 zone은 자신에게 속해있는 물리 메모리를 관리하기 위해 zone 구조체를 사용. 이 구조체에는 해당 zone에 속해있는 물리 메모리의 시작주소와 크기, 추후에 설명될 버디 할당자가 사용할 free_area 구조체를 담는 변수 등이 존재.

watermark와 vm_stat를 통해 남아있는 빈 공간이 부족한 경우 적절한 메모리 해제 정책을 결정하게 된다. 또한 프로세스가 zone에 메모리 할당요청을 하였으나, free페이지가 부족하여 할당해주지 못한 경우 이러한 프로세스들을 wait_queue에 넣고, 이를 해싱(hashing)하여 wait_table변수가 가리키게 한다.

현재의 시스템의 zone 관련 사항은 "cat /proc/zoneinfo" 명령을 통해 확인 가능

 

2-3 Page frame

각각의 zone은 자신에 속해 있는 물리 메모리들을 관리, 바로 이 물리 메모리의 관리 최소 단위를 페이지 프레임(page frame)이라 부른다.

각각의 페이지 프레임은 page(~/include/linux/mm_types.h)라는 이름의 구조체에 의해 관리된다.

리눅스는 시스템 내의 모든 물리 메모리에 접근 가능해야 한다. 이를 위해 모든 페이지 프레임 당 하나씩 page 구조체가 존재한다.

이는 시스템이 부팅되는 순간에 구축되어 역시 물리 메모리 특정 위치에 저장된다. 이 위치는 mem_map 이라는 전역배열을 통해 접근 가능하다.

 

결국 복수 개의 페이지 프레임이 zone을 구성, 때에 따라 하나 혹은 그 이상의 zone이 node를 구성하며, 역시 시스템의 구조에 따라 하나 혹은 그 이상의 node가 존재하는 것이 리눅스의 전체 물리 메모리 관리 구조이다.

 

 

3. Buddy와 Slab

리눅스는 page frame, zone, node라는 구조를 통해 시스템에 존재하는 전체 물리 메모리를 관리. 그렇다면 리눅스는 가지고 있는 물리 메모리를 어떻게 할당 또는 해제하는가 ?

 

리눅스는 물리 메모리의 최소 관리 단위인 페이지 프레임 단위로 할당하도록 결정. 4KB가 최소 할당단위가 된다.

 

만약 4KB보다 작은 크기를 요청한다면? 30Byte처럼 작은 크기를 요청할 경우 4KB를 할당해주면 내부 단편화(internal Fragmentation) 문제가 발생. 이를 해결하기 위해 슬랩 할당자(Slab Allocator)를 도입

 

만약 4KB보다 큰 크기를 요청 한다면? 10KB를 요청할 경우, 세 개의 페이지 프레임을 할당하면 내부 단편화를 최소화 시킬 수 있도록 할당 가능, 하지만 리눅스는 이 요청에 대해 16KB를 할당해주는 버디 할당자(Buddy Allocator)를 사용한다.

버디 할당자가 메모리 관리의 부하가 적으며 외부 단편화(External Fragmentation)를 줄일 수 있다는 장점을 제공하기 때문

 

 

 

 

3-1 버디 할당자 (Buddy Allocator)

버디 할당자는 zone 구조체에 존재하는 free_area[]배열을 통해 구축된다. 따라서 버디는 zone 당 하나씩 존재

free_area라는 배열의 각 엔트리는 free_area라는 이름의 구조체, 이 구조체는 free_list와 map 필드를 갖는다.

 

일반적인 버디, free_area 배열은 10개의 엔트리를 가진다. 0~9까지 각각의 숫자는 해당 free_area가 관리하는 할당의 크기를 나타냄. 버디는 2의 정수 승 개수의 페이지 프레임들을 할당해주며 리눅스 구현상 최대 할당 크기는 4MB(2^10 x 4KB)

 

free_area구조체는 free_list 변수를 통해 자신에게 할당된 free 페이지 프레임을 list로 관리. 자신이 관리하는 수준에서 페이지의 상태를 map변수를 통해 비트맵(bitmap)으로 관리

 

버디 할당자는 요청된 크기를 만족하는 최소의 order에서 페이지 프레임을 할당해주고 그 order에 가용한 페이지 프레임이 존재하지 않으면 상위 order에서 페이지 프레임을 할당받아 두 부분으로 나누어, 한 부분은 할당해주고 나머지 부분은 하위 order에서 가용 페이지 프레임으로 관리한다. 이때 나누어진 두 부분을 친구(Buddy)라고 부르며 이 때문에 이 할당자를 버디 할당자라고 부른다.

 

해제 작업은 할당의 반대 작업. 예를들어 11번 페이지 프레임을 해제하면 비트맵과 free_list의 내용이 변경. 8~15까지의 페이지 프레임이 free상태가 되고, 해당 비트맵은 1에서 0으로 변경

 

3-2 Lazy Buddy

커널 버전 2.6.19 이후 free_area의 구조와 버디 할당자의 구현이 조금 바뀜 이를 Lazy 버디라고 부른다.

 

기존 버디에서는 한 페이지 프레임을 할당/해제 반복을 할때, 필요시 페이지 프레임을 할당해주기 위해서는 큰 페이지를 쪼개서(비트맵 수정 필요) 할당해줘야 한다. 그 뒤 해제한다면 다시 큰 페이지로 합쳐서(비트맵 수정 필요) 관리해야한다. 이러한 작업이 반복된다면 많은 오버헤드가 동반, 따라서 할당되었던 페이지 프레임을 합치지말고, 곧 다시 할당 될 테니 되도록 합치는 작업을 미루는것이 어떨까하는것이 Lazy 버디의 등장 배경

 

페이지 상태를 관리하기 위해 사용되던 비트매 포인터가 nr_free라는 변수로 바뀜. nr_free 변수는 자신이 관리하는 zone내에서 비사용중인 페이지 프레임의 개수이다.

 

버디 동작 : 버디는 zone마다 유지되고 있는 watermark(high,low,min)값과 현재 사용가능 한 페이지의 수를 비교한다.

이를 통해 zone에 가용 메모리가 충분한 경우 해제된 페이지의 병합 작업을 최대한 뒤로 미룬다. 만약 가용메모리가 부족해지는 경우 다음과 같이 병합 작업을 수행한다. 버디에 메모리를 반납하는 함수인 __free_pages() 함수는 내부적으로 __free_one_page()라는 함수를 호출하는데 이 함수는 MAX_ORDER만큼 루프를 돌면서 현재 해제하는 페이지가 버디와 합쳐져서 상위 order에서 관리될 수 있는지 확인한다. 가능한 경우 현재 order의 nr_free를 감소시키고, 상위로 페이지를 이동 시킨 뒤, 상위 order의 nr_free를 증가시킨다. 이러한 작업을 반복하여 전체 order의 버디를 원활히 동작시켜 주게 된다.

버디 할당자로부터 페이지를 할당받는 커널 내부 함수 중 가장 저수준 함수의 이름은 __alloc_pages()이며 반대로 해제하는 하무의 이름은 __free_pages()이다.

 

2의 승수 크기의 페이지를 연속적으로 관리하고 있다가 요청이 들어오면 이들을 할당/해제하며 관리하는 것이 버디 할당자이므로 함수호출의 인자는 할당받고자 하는 메모리 크기를 2의 승수로 지정해 주어야 한다.

 

복수 개의 zone에 각각의 버디 할당자가 동작하고 있을 수 있기 때문에 어늦 zone에서 메모리를 할당 받을 것인지와 함께 몇 가지 속성을 지정해 주어야 한다. 현재 시스템의 버디 할당자 관련 정보는 "cat /proc/buddyinfo" 명령을 통해 확인 가능

 

 

3-3 슬랩 할당자(Slab Allocator)

페이지 프레임 크기가 클수록 내부 단편화로 인한 낭비되는 공간 역시 증가될 것이다.

 

페이지 프레임의 크기가 4KB라고 가정할 때, 미리 4KB 페이지 프레임을 한 개 하당받은 뒤, 이 공간을 64Byte 크기로 분할 해 둔다. 그러면 64Byte 크기의 공간이 64개 있다고 생각해볼 수 있다. 그런 뒤 사용자가 64Byte의 공간을 요청하면 버디 할당자로부터 할당받아오는 것이 아니라 미리 할당받아 분할하여 관리하고 있던 바로 이 공간에서 떼어 준다. 추후 사용자가 할당받았던 64Byte의 공간을 해제한다면 역시 버디로 반납하는 것이 아니라 미리 할당받아 관리하던 공간에서 다시 가지고 있으면 된다.

일종의 캐시로 사용하는 것이다. 이러한 cache의 집합을 통해 메모리를 관리하는 정책을 바로 슬랩 할당자라 부른다.

현재 시스템의 슬랩 할당자와 관련된 정보는 "cat /proc/slabinfo" 명령을 통해 확인 가능 

 

어떤 크기의 cache를 가지고 있어야 하는가? 자주 할당되고 해제되는 크기의 cache를 가지고 있어야 내부 단편화 최소 가능. 태스크가 생성되고 제거될 때마다 할당/해제되어야 하는 task_struct를 위한 공간처럼 커널 내부에서 자주 할당/해제되는 자려구조의 크기를 위한 cache를 유지한다. 또한 일반적인 메모리 할당 요청에 대비하기 위해 32Byte에서부터 시작되는 2의 승수 크기의 cache를 128KB(최근에는 4MB) 크기까지 유지한다.

 

각 cache는 슬랩들로 구성되고 슬랩은 다시 객체(Object)들로 구성된다. 예를 들어 64Byte cache라면 64Byte 공간들이 각각 객체에 대응되고 이 객체들이 모여서 슬랩이 되고, 다시 슬랩이 모여 cache가 되는 것이다. 슬랩은 구성하고 있는 객체들의 상태에 따라 Full, Free, Partial로 구분된다. Free 슬랩은 모든 객체가 사용 가능한 상태이고, Full 스랩은 모든 객체가 이미 사용 중인 상태이며, Partial 슬랩은 일부는 사용 중이고 일부는 비사용 중인 상태이다.

슬랩 할당자에게 메모리 공간의 할당 요청이 들어온다면 가장 적합한 크기의 캐시를 찾아가서, partial 슬랩으로부터 객체를 할당해준다.

 

리눅스는 다양한 크기의 캐시를 효율적으로 관리하기 위해 kmem_cache라는 자료구조를 정의.

이 구조체는 각각의 캐시가 담고 있는 객체 크기는 얼마인지 등의 정보를 표현. 새로운 캐시 생성하기 위해서는 keme_cache라는 구조체로부터 할당받아야 한다.

 

버디 할당자로부터 받는다면 4KB sizeof(kmem_cache)만큼의 공간 낭비, 슬랩 할당자로 할당받아야한다. kmem_cache구조체 크기의 객체를 담고 있는 캐시의 이름이 바로 cache_cache이다.

따라서 cache_cache는 다른 캐시들 보다 먼저 생성되어야, kmem_cache를 위한 공간을 할당받아 다양한 캐시를 생성할수 있다.

 

슬랩할당자로부터 객체를 할당받는 저수준 함수는 kmem_cache_alloc()이며 해제하는 함수는 kmem_cache_free()이다. 특정 크기의 공간을 위한 캐시를 유지하는 것이 슬랩 할당자이므로 이들 함수는 어느 캐시에서 공간을 할당받고, 해제시켜야 하는지를 지정해야함. 더 이상 할당할 공간이 없다면 슬랩 할당자는 버디로부터 페이지 프레임을 더 할당받아야 하는데 이때는 kmem_cache_grow()같은 함수를 호출하여 슬랩을 확장한다.

 

슬랩 할당자는 위의 함수 외에도 외부 인터페이스 함수로 kmalloc(), kfree()를 제공, 슬랩 할당자로부터 임의의 크기의 메모리 공간을 할당받을 수 있다. 할당된 공간은 물리적으로 연속이라는 특징을 가짐

 

 

4. 가상 메모리 관리 기법

 

먼저 태스크 당 하나씩 존재하는 task_struct 자료구조와 태스크의 가상 주소 공간의 관계에 대해 살펴본다. 그런뒤 리눅스가 어떻게 가상 주소 공간을 할당/해제하는지에 대해 살펴본다.

 

태스크는 자신의 고유한 가상 메모리를 갖는다. 따라서 커널은 태스크의 가상 메모리가 어디에 존재하는지 관리를 해야 한다.

task_struct에서 태스크의 메모리와 관련된 내용은 mm이라는 이름의 필드에 담겨있다. 이 필드는 mm_struct(~/include/linux/mm_types.h)라는 구조체를 가리킨다.

 

mm_strcut 자료구조가 관리하는 정보들은 크게 3부분으로 구분할 수 있다

  1. 태스크를 구성하고 있는 vm_area_struct 구조체들. 리눅스 커널은 가상메모리 공간 중 같은 속성을 가지며 연속인 영역을 region 이라는 이름으로 부른다(세그먼트). 리눅스는 각각의 region을 vm_area_struct라는 자료구조를 통해 관리한다. 같은 태스크에 속한 vm_area_struct들은 효율적인 관리를 위해 레드-블랙 트리로 연결. mm_struct에는 바로 이 트리의 시작을 가리키는 변수인 mm_rb와 최근에 접근한 vm_area_struct를 가리키는 mmap_cache변수 등이 존재
  2. 주소 변환을 위한 페이지 디렉터리의 시작점 주소를 pgd라는 이름의 변수에 유지한다.
  3. 가상 메모리의 구조에 대한 변수들을 갖는다. (start_code,start_data,start_stack 등)

태스크는 텍스트, 데이터, 스택 등의 region으로 구성. 텍스트는 가상 메모리의 0번지에서부터 시작하며(start_code) 자신의 크기만큼 공간 차지. 텍스트의 끝(code_end)부터 데이터가 차지한다(start_data) 그리고 데이터의 끝(end_data)부터 힙이 차지하며(start_brk), 힙의 끝은 brk라는 변수가 가리킨다. 태스크에 새로운 메모리 공간이 동적으로 할당되면 brk부터 힙이 위쪽 방향으로 자라게 된다.

 

스택은 커널과 사용자 영역의 경계인 3GB 바로 아랫부분에 존재. 스택의 끝은 환경변수(environment variable)와 초기 인자(initial argument)가 차지. 환경 변수와 초기인자란 main(argc,argv,env)함수가 호출될 때 인자로 전달되는 env와 argv를 의미. env아래에는 argv가 지정되며, arg_end부터 main()함수의 지역 변수가 차지, 이후 main() 함수가 다른 함수를 호출할 때 스택은 아래방향으로 자람

 

vm_area_struct 자료구조가 가리키는 세그먼트의 시작 주소(vm_start), 끝 주소(vm_end), 그리고 region의 접근 제어(읽기만 가능, 읽기 쓰기 모두 가능 등)등을 기록하는 플래그(vm_flags)등의 변수를 갖는다. 또한 이 세그먼트가 실제 실행 파일의 어느 위치에 있는지에 대한 정보를 vm_file, vm_offset 변수로 관리. 이 변수들은 페이지 폴트가 발생했을 때 어떤 파일의 어느 부분을 읽어야 하는지 결정할 때 사용.

 

물리 메모리 관리기법과 마찬가지의 이유로 가상 메모리 역시 고정된 크기의 할당 단위로 관리

리눅스에선 보통 4KB 페이지라고 부름. 공통의 속성을 갖는 페이지 들이 모여 vm_area를 구성, 이를 vm_area_struct 라는 자료구조로 관리. 또한 같은 태스크에 속한 vm_area_struct 들이 모여 하나의 mm_struct 내에서 관리

 

가상 메모리 할당/해제는 vm_area_struct의 할당/해제 문제와 page의 할당/해제 문제로 구분.

 

vm_area_struct의 할당 해제 :

하나의 태스크에는 여러 개의 vm_area_struct가 존재할 수 있다. 이들은 겹쳐지지 않으며 새로 사용하려는 가상 주소 공간이 인접한 vm_area_struct와 같은 속성을 가진다면 합쳐져서 관리될 수 있다.

 

세 개의 vm_area_struct가 가상 주소 공간을 관리하고 있다고 가정, 새로운 네 개의 페이지 공간을 사용하려 할 때 우선 사용 가능한(물리 메모리와 매핑되어 사용 중이지 않은) 가상 주소 공강을 찾아야한다. 원하는 size를 인자로 주고 사용 가능한 연속한 가상 주소 공간을 찾는 함수는 arch_get_unmapped_area()

지3066~3069번의 페이지가 할당된다고 가정. 새로 할당받은 가상 주소 공간을 관리하는 vm_area_struct가 새로 만들어져야 하는데, 만약 인접한 vm_area_struct와 속성이 같다면 세 개가 존재해야 하는 vm_area_struct는 하나로 통합되어 관리 가능. 동적 분할(dynamic partition) 메모리 관리 시스템에서 메모리 병합(coaloasing)기법과 유사.

새로 할당 받은 공간을 위한 vm_area_struct를 구성하고 기존 vm_area_struct와 연결해주는 등의 작업을 담당하는 함수는 do_mmap_pgoff()

 

 

5. 가상 메모리와 물리 메모리의 연결 및 변환

프로그램이 시작되는 과정부터 살펴보자.

컴파일 되어 디스크에 저장되어 있는 프로그램을 수행시키기 위해서는 태스크를 하나 생성해야한다. 생성된 태스크에게 가상 주소 공간을 제공, 필요하다면 물리 메모리의 일부를 할당해 준 뒤, 태스크가 원하는 디스크 상의 내용을 읽어 물리 메모리에 올려놓고, 이 물리 메모리의 실제 주소와 태스크의 가상 주소 공간을 연결 해 준다.

 

디스크에 저장되어 있는 실행 파일의 어느 부분을 읽어서 물리 메모리에 올릴것인가는 ELF 포맷 파일의 헤더를 읽음으로써 가능, 이를 가상 주소 몇 번지와 연결해 줄 것인가는 미리 정해져 있는 규칙(~/include/linux/elf.h)을 따름

 

sys_execve()라는 시스템 호출을 분석해 리눅스가 시스템 자원 메모리를 어떻게 관리하는지 알아보고, 이를 각 태스크마다 주어지는 4GB의 가상 주소공간과 task_struct구조체를 연결지어보자. 이 시스템 호출은 ~fs/exec.c 파일에 구현되어 있으며, 인자로 전달된 프로그램을 메모리에 적재하는 일을 수행한다.

 

가정) ELF 파일을 인자로 sys_execve()가 호출, 이 실행파일은 ELF 헤더와 3개의 phdr(프로그램헤더), 그리고 3개의 section으로 구성, 각 section은 텍스트(12KB)와 데이터(16KB) 그리고 스택(8KB). 인텔 CPU

 

태스크의 가상 주소 공간 중 텍스트 영역(region)은 3개의 페이지로, 데이터 영역은 4개의 페이지, 그리고 스택 영역은 2개의 페이지로 구성. 텍스트의 각 페이지들을 t1 t2 t3 데이터와 스택의 각 페이지들을 d1 d2 d3 d4 s1 s2라고 지칭.

t1 : 가상 주소 0x0번지부터 시작, 크기는 4KB

t2 : 가상 주소 0x1000(십진수로 4096) 번지부터 시작

d1 : 가상 주소 0x3000 시작 (텍스트 크기 이후부터)

s1 : 가상 주소 3GB 아래 방향을 쌓인다. 3GB-4KB 

s2 : 3GB-8KB

 

sys_execve() 시스템 호출은 우선 free한 페이지 프레임들을 할당 받는다. 그리고 파일시스템에게 수행하려는 파일의 일부 내용을 읽어달라고 요청. 읽혀진 내용을 할당받은 페이지 프레임에 적재.

 

적재된 실행 파일을 수행시킬 수 있을까? 아직은 아니다. CPU는 프로그램의 가상 주소를 사용, 물리 메모리에서 명령어나 데이터를 읽어 오려면 가상 주소를 물리 주소로 변활할 수 있는 방법이 있어야 한다. 이때 사용되는 것이 페이지 테이블(page table). 페이지 테이블은 가상 주소를 물리 주소로 변환하는 주소 변환 정보를 기록한 테이블이다.

 

리눅스 커널은 t1 t2 d1 s1 등의 페이지들을 페이지 프레임에 적재할 때 주소변환을 위한 페이지 테이블도 함께 생성. 태스크를 실행할 때 이 테이블을 이용해 주소 변환을 수행.

 

 

-----------118p

+ Recent posts