프로그램을 실행시키기 위해서는 프로그램과 접근 데이터를 메인 메모리에 올려야한다.

컴퓨터 시스템은 시스템 실행 중에 여러 프로세스를 메모리에 유지할 수 있다. 

아래 그림과 같이 메모리는 각 프로세스가 사용할 뿐만 아니라 커널 자체도 메모리를 사용한다.

 

 

메모리의 통계 정보

시스템의 총 메모리의 양과 사용중인 메모리의 양은 `free` 명령어를 통해 확인할 수 있다. (단위는 Kbyte)

free

  • total: 시스템에 탑재된 전체 메모리 용량
  • free: 이용하지 않는 메모리
  • buff/cache: 버퍼 캐시 또는 페이지 캐시가 이용하는 메모리. 시스템의 free 값이 부족하면 커널이 해제한다.
  • available: 실질적으로 사용 가능한 메모리 = (free) + (buff/cache)
  • swap
    • total: 설정된 스왑 총 크기
    • used: 사용중인 스왑 크기
    • free: 사용되지 않은 스왑 크기

htop을 사용하면 실시간으로 좀 더 눈에 잘 들어오는 형태로 모니터링 할 수 있다.

htop

 

메모리 사용량이 늘어나면 free 의 크기가 줄어든다.

이러한 상태가 되면 메모리 관리 시스템은 커널 내부의 해제 가능한 메모리 영역을 해제한다.

이후에도 메모리 사용량이 계속 증하가면 시스템은 메모리가 부족해 동작할 수 없는 메모리 부족 상태가 된다 = Out of Memory

이러한 경우 메모리 관리 시스템에서는 적절한 프로세스를 강제 종료(kill) 시켜 메모리 영역을 해제한다.

 

 

메모리 주소의 할당

커널이 프로세스에 메모리를 할당하는 경우는 크게 두 가지 타이밍에 벌어진다.

1. 프로세스를 생성할 때

2. 프로세스를 생성한 뒤 추가로 동적 메모리를 할당할때

 

좀 더 자세히 살펴보자.

[운영체제 10판] 9장 메인 메모리

c.f. 파이썬의 경우 인터프리터 언어라서 조금 다를 수 있을 것 같아서 찾아보니,
https://stackoverflow.com/questions/19791353/linking-and-loading-in-interpreted-languages
- 파이썬은 컴파일 과정이 없다.
- 실행 파일(예: /usr/bin/python)이 실제로 실행되는 프로그램이고, 실행할 스크립트를 읽으며 한 줄씩 실행하게 된다.
- 그 과정에서 다른 파일이나 모듈(예: /usr/python/lib/math.py)에 대한 참조를 만날 수 있으며 그 때, 이를 읽고 해석한다.
- 바이너리 라이브러리들은 런타임에 코드에서 요청 시, 해당 라이브러리를 로드한 다음 사용할 수 있다.
- 파일을 처음 실행할 때 바이트코드(.pyc 파일)로 컴파일되고, 다음에 모듈을 가져오거나 실행할 때 코드 실행이 향상된다.

 

물리 주소를 논리 주소로 변환하는 방법

[운영체제 10판] 9장 메인 메모리

  • Dynamic Loading: 프로세스가 실행되기 위해 프로세스 전체가 메모리에 미리 올라와 있을 필요는 없다. 메모리 공간을 더 효율적으로 이용하기 위해 동적 적재를 할 수 있다. 동적 적재에서 각 루틴은 실제 호출 전까지 메모리에 올라오지 않고 재배치 가능한 상태로 디스크에 대기하고 있다. 동적 적재는 운영 체제로부터 특별한 지원이 필요없으며, 사용자 자신이 프로그램의 설계를 책임져야 한다. 운영체제는 동적 적재를 구현하는 라이브러리 루틴을 제공해 줄 수는 있다.
    • 예: main 프로그램이 메모리에 올라와 실행된다. 이 루틴이 다른 루틴을 호출하게 되면 호출한 루틴이 이미 메모리에 적재됐는지 조사한다. 만약 적재되어 있지 않으면 재배치 가능 연결 적재기(relocatable linking loader)가 불려 요구된 루틴을 메모리로 가져오고, 이러한 변화를 테이블에 기록해둔다. 그 후 CPU 제어는 중단되었던 루틴으로 보내진다.
  • Dynamic Linking and Shared Library: 동적 연결 라이브러리(DLL)은 사용자 프로그램이 실행될 때, 사용자 프로그램에 연결되는 시스템 라이브러리이다. 동적 연결에서는 linking이 실행 시기까지 미루어진다. 동적 연결은 주로 표준 C 언어 라이브러리와 같은 시스템 라이브러리에 사용된다. DLL은 여러 프로세스 간 공유될 수 있어 메일 메모리에 DLL 인스턴스가 하나만 있을 수 있으며 공유 라이브러리라고도 한다. 프로그램이 동적 라이브러리에 있는 루틴을 참조하면 로더는 DLL을 찾아 필요한 경우 메모리에 적재한다. 그런 다음 동적 라이브러리의 함수를 참조하는 주소를 DLL이 저장된 메모리의 위치로 조정한다. 동적 로딩과 달리 동적 연결과 공유 라이브러리는 일반적으로 운영체제의 도움이 필요하다. 메모리에 있는 프로세스들이 각자의 공간을 자기만 엑세스 할 수 있도록 보호된다면 운영체제만이 메모리 공간에 루틴이 있는지 검사해 줄 수 있고, 운영체제만이 여러 프로세스가 같은 메모리 주소를 공용할 수 있도록 해줄 수 있다. 
    • c.f. 정적 연결(static linking): 라이브러리가 프로그램의 이진 프로그램 이미지에 끼어 들어간다.

동적 메모리를 할당할 때, 추가적으로 메모리가 필요한 경우 프로세스는 커널에 메모리 확보용 시스템 콜을 호출하여 메모리 할당을 요청한다. 커널은 메모리 할당 요청을 받으면 필요한 사이즈를 빈 메모리 영역으로부터 잘라내, 그 영역의 시작 주소값을 반환한다.

 

논리 주소를 물리 주소로 변환하는 방법

가상 주소를 물리 주소로 변환하는 과정은 커널 내부에 보관되어 있는 `페이지 테이블`을 사용한다.

가상 메모리는 전체 메모리를 페이지 단위로 나누어 관리하고 있으므로 변환은 페이지 단위로 이루어진다.

(c.f. 물리 메모리는 프레임 단위로 불리는 동일한 크기의 블록으로 나누어진다.)

이때, 페이지 사이즈는 CPU 구조에 따라 다른다. 흔히 사용하는 x86_64의 페이지 사이즈는 4kb이다.

리눅스 시스템에서 페이지 크기를 알아내고 싶다면 `getconf PAGESIZE` 명령을 입력하면 된다. (단위는 바이트)

 

CPU 에서 나오는 모든 주소는 (페이지 번호) + (페이지 오프셋) 두 개의 부분으로 나누어진다.

  • 페이지 번호: 페이지 테이블 접근 시 사용된다. 페이지 테이블은 물리 메모리의 각 프레임 시작 주소를 저장한다.
  • 페이지 오프셋: 참조되는 프레임 안에서의 위치를 나타낸다.

만약 페이지 테이블에 매핑되지 않은 영역대의 가상 주소에 접근하게 되면 CPU 에는 `페이지 폴트` 인터럽트가 발생한다.

페이지 폴트에 의해 현재 실행 중인 명령이 중단되고, 커널 내의 `페이지 폴트 핸들러` 라는 인터럽트 핸들러가 동작하게 된다.

커널은 프로세스로부터 메모리 접근이 잘못되었다는 내용을 페이지 폴트 핸들러에 알려준 후, `SIGSEGV` 시그널을 프로세스에 통지한다. (이 시그널을 받은 프로세스는 강제 종료된다.)

 

실제 메모리 할당(Linux)

## C

C언어에서는 표준 라이브러리에 있는 `malloc()` 함수가 메모리 확보 함수이다.

리눅스에서는 내부적으로 `malloc()` 함수에서 `mmap()` 또는 `brk()` 함수를 호출하여 메모리 할당을 구현한다.

## Python

파이썬같이 직접 메모리 관리를 하지 않는 스크립트 언어의 오브젝트 생성에도

최종적으로는 내부에서 C언어의 malloc 함수를 사용하고 있다.

 

mmap 함수는 페이지 단위로 메모리를 확보하지만, malloc 함수는 바이트 단위로 메모리를 확보한다.

즉, glibc에서는 사전에 mmap 시스템 콜을 이용하여 메모리 풀을 확보한 뒤, 프로그램에서 malloc이 호출되면 메모리 풀로부터 필요한 양을 바이트 단위로 잘라내어 반환하는 처리를 한다. 풀로 만들어 둔 메모리에 더 이상 빈 공간이 없으면 다시 mmap 을 호출하여 새로운 메모리 영역을 확보한다.

glibc의 메모리 풀

사용하는 메모리 양을 체크하여 알려주는 기능의 프로그램들이 있으나, 그 프로그램이 알려주는 사용량과 실제 리눅스에서 사용하는 메모리의 양이 서로 다른 경우가 매우 많다. (일반적으로 리눅스에서 프로세스가 사용하는 메모리 양이 더 많음)

왜냐하면 리눅스에서 측정한 메모리 양은 프로세스가 생성될 때, mmap 함수를 호출했을 때 할당한 메모리 전부를 더한 값을 나타내고, 프로그램이 체크한 메모리 사용량은 malloc 함수 등으로 획득한 바이트 수의 총합을 나타내기 때문에 서로 다르게 표시되기 때문이다. 이러한 프로그램이 체크한 메모리 사용량이 정확히 무엇을 나타내는지는 프로그램에 따라 다르므로 확인해봐야 한다.

 

 

'CS > OS + Linux' 카테고리의 다른 글

[운영체제] 리눅스 커널이란  (0) 2023.01.10

+ Recent posts