[OS] 주기억장치

Jay·2021년 5월 1일
0

Computer Science

목록 보기
48/50
post-thumbnail

OS에서 CPU자원을 관리하는 프로세스 관리 뿐 아니라 메모리 관리 역시 매우 중요한 사항이다!

현재에도 여전히 메모리를 최대한 효율적으로 사용하기 위한 여러 방법들이 연구되고 OS 기능에서 매우 중요한 부분을 차지 한다.

이 글은 KOCW 운영체제 강의를 토대로 작성되는 내용입니다.

1. 메모리에 프로그램 할당하기

메모리는 기본적으로 주소(Address), 데이터(Data)로 구성되어있다.

CPU와 메모리는 양방향으로 위 그림처럼 정보를 주고 받게 된다.
CPU는 주소를 가지고 메인 메모리에 요청을 하거나 해당 주소에 계산 결과를 저장하고,
메모리는 요구하는 주소에 저장되어 있는 데이터를 CPU에게 전달한다.

프로그램의 빌드 과정은 소스파일 ➡️ 목적 파일 ➡️ 실행 파일 순이다.

  • 소스 파일 : 고수준언어 또는 어셈블리어
  • 목적 파일 : 컴파일 또는 어셈블 결과
  • 실행 파일 : 링크 결과

위 그림은 프로그램이 만들어지는 과정을 표현한 그림이다.

1. 소스파일은 컴파일러에 의해 컴파일 수행 결과로 목적 파일을 생성한다.
프로그래밍을 하면 외부 라이브러리를 사용하는 경우가 빈번한데, 컴파일 타임에 이를 추가하지 않기에 목적 파일에는 이에 대한 정보는 없다.
2. 링크 단계에서 하드디스크에서 프로그래머가 추가한 라이브러리를 찾아 정보를 추가하여, 실행 파일을 만든다.
이 프로개름을 실행하면 로더(loader)에 의해 메인 메로리에 할당 된다.

그리고 생성된 프로그램은 Code, Data, Stack 영역으로 나뉜다.
단순 생성된 프로그램은 Code, Data 영역만 존재하며,
메모리에 적재되었을 때는 실제로 실행해야 하기에 stack영역이 추가된다.

실제로 프로그램을 메모리에 올리려면 이 프로그램을 메모리의 몇 번지에 할당할까?

만약 OS가 없다면 프로그래머가 이를 직접 해줘야 한다.(고수준언어에서는 직접 주소를 다루지 않는 경우가 많다)

다중 프로그래밍 환경에서는 여러 프로그래밍이 메모리에 적재되고 교체 되는 과정이 있기에 한 프로그램은 고정적인 공간을 차지 할 수 없다.
위의 문제를 해결해주는 것이 바로 MMU이다.
MMU에는 프로그램이 메모리에 할당될 때마다 다른 주소 공간을 사용하기에 재배치 레지스터가 별도로 존재한다.

MMU의 존재

위의 그림처럼 MMU가 존재함으로 인해서 프로그램은 메인 메모리에 해당 주소를 사용할 수 있는지 여부를 생각하지 않고 주소를 사용한다.

🙌 해당 프로그램이 사용하는 시작 주소가 0번지라고 할 때, 실제 메인 메모리에서는 할당되는 주소가 유동적이기에 0번지라는 주소를 실제 할당된 주소로 변경해줘야 한다.

이게 재배치 레지스터의 역할이다.
마치 필터링을 해주듯이 맵핑을 해주게 된다.

MMU의 기능에서 보면,
이전에 메모리 보호를 위해 base, limit레지스터가 있었다.
CPU에서 주소를 사용하는데 이 주소가 해당 프로그램의 base~limit범위를 벗어나면 인터럽트를 발생시켜 프로그램을 강제 종료 시킨다.

base/limit 기능 이외에도 위에서 말한 reallocation 기능을 통해 프로그램이 어느 주소를 사용하더라도 실제 메인 메모리에서 할당된 주소를 찾아갈 수 있도록 address traslation 동작을 수행한다.

⭐️ 즉, CPU는 프로그램에 설정된 주소를 계속 사용하고 메모리에 명령을 보내지만, MMU에 의해 실제로 프로그램이 할당된 메모리 주소로 변환해서 사용할 수 있는 것이다.


MMU에 의해 위 그림과 같이 주소는 2가지로 구분된다.
CPU에서 사용하는 주소는 논리 주소라고 하고
메모리가 사용하는 주소는 물리 주소라고 한다.

2. 메모리 낭비 방지

OS에서 메모리 효율은 매우 중요한 부분이다.

동적 적재(Dynamic Loading)

  • 프로그램이 실행하는 데 필요한 루틴/ 데이터만 적재하는 것.

오류처리 구문은 if문과 같이 오류가 발생한 순간에만 실행하면 되기에 미리 메모리에 올려두지 않고 오류가 발생한 순간 내부 코드를 실행시키면 된다.

데이터 역시 모든 데이터가 필요하지 않다. 배열과 클래스의 경우 필요한 부분만 메모리에 적재하고 실행 도중 필요해지면 그때 찾아서 추가적으로 메모리에 적재한다.

그럼 당연히 반대로 모든 루틴과 데이터를 적재하고 실행하는 것을 정적 적재(static loading)이라고 한다.

동적 연결(Dynamic Linking)

동적 연결은 여러 프로그램에 공통으로 사용되는 라이브러리를 중복으로 메모리에 올리지 않고 하나만 올리도록 하는 것이다.

// P1
int a = 1;
int b = 2;
printf("%d\n", a + b);

// P2
int a = 1;
int b = 2;
printf("%d\n", a * b);

위의 코드처럼 p1, p2 프로세스가 있지만 둘을 실행시키면 둘은 모두 printf()를 사용하는 라이브러리를 메모리에 적재하게 된다. 이는 중복 적재이다.

같은 라이브러리를 메모리에 중복으로 적재하면 메모리 낭비가 발생한다.

그렇기에 프로그램이 메모리에 적대된 후에 링크(link)작업을 수행한다.
기존에는 실행 파일이 만들어지기 전에 링크 과정을 수행하였는데, 이를 정적 연결이라고 한다.

공통 라이브러리(printf())를 연결한 모습이다.
리눅스에서는 이러한 라이브러리를 공유 라이브러리
윈도우에서는 동적 연결 라이브러리 라고 한다.

Swapping

  • 메모리에 적재되어 있는 프로세스 중 오랫동안 사용하지 않은 프로세스를 프로세스 이미지 형태로 만든 후 하드디스크에 내려보낸다.
    메모리에서 Backing Store로 가는 것을 swap-out,
    다시 Backing Store에서 메모리로 가는 것을 swap-in이라고 한다.

프로세스 이미지는 해당 프로그램이 메모리에 적재된 후 실행되면서 데이터를 추가하거나 변경하는 등의 과정을 거치는데, 현재 데이터의 상태를 프로세스 이미지라고 부른다.

swapping 과정으로 인한 프로세스 이미지를 저장하기 위해 하드 디스크 일부분을 분리하여 사용하는데 이를 backing store 혹은 swap device라고 부른다.

Backing Store의 크기는 대략 메인 메모리 크기 정도로 예상할 수 있다.
메모리의 모든 프로세스가 쫓겨난다고 해도 메인 메모리 크기를 넘지 않기 때문이다.
메인 메모리 크기가 크지 않는 PC나 스마트폰은 하드디스크의 일부를 backing store로 사용하지만 메모리 크기가 크다면 따로 하드디스크 자체를 backing store로 사용하는 경우도 있다.

Swap-out된 프로세스가 다시 swap-in될때 이전의 메모리 주소가 아닌 새로운 주소로 갈 수 있다. 이는 해당 프로세스가 backing store에 있는 동안 다른 프로세스가 해당 주소공간을 사용할 수 있기 때문이다.
하지만 이는 MMU의 재배치 레지스터로 인해 어디에 적재되어도 상관없이 정상동작이 가능하다.

프로세스의 크기가 커지고, 하드디스크는 메인 메모리보다 속도면에서 매우 느리고 swapping 동작의 오버헤드가 매우크다.
하지만 이로 인해 얻는 이득이 더 많으므로 대부분의 운영체제는 이를 사용하고 있고, 속도가 중요한 서버 컴퓨터나 슈퍼 컴퓨터는 backing store를 하드 디스크가 아닌 좀 더 빠른 저장 자칠르 사용하기도 한다.

연속 메모리 할당

현재는 메모리에 여러 프로세스가 할당되는 다중 프로그래밍 환경이다.

부팅 직후 메모리 상태를 보면, OS만 할당되어 있고 비어 있다.
이러한 비어있는 공간을 hole이라고 부른다.

즉, 부팅 직후 운영체제와 big single hole이 존재한다.

시간이 지나면서 프로세스가 생성되고 종료하고를 반복하면, 여러 곳에 서로 다른 크기의 홀이 존재한다. 이러한 상태를 scattered holes라고 한다.

부팅 직후 여러 프로세스들이 생성, 종료를 반복한 후의 상태이다.
이와 같이 hole들이 불연속하게 흩어진 상태를 메모리 단편화라고 한다.

메모리 단편화로 인해 여러 곳에 Hole이 흩어진 상태에서 하나의 프로세스가 메모리에 할당되려면 문제가 발생할 수 있다.

hole이 3개가 있고 각 크기는 50byte, 50byte, 80byte이다.
근데 할당하려는 프로세스 크기가 150byte이다. 각 홀들을 하나로 합치면 230byte로 이 프로세스를 할당할 수 있지만 실제로는 나뉘어져 있기에 할당되지 못한다.

이러한 현상을 외부 단편화라고 한다.

연속 메모리 할당 방식

외부 단편화의 해결 방법을 살펴보기 전 연속 메모리 할당 방식을 먼저 보자.
연속 메모리 할당 바식에는 3가지가 있다.
First-fit, Best-fit, Worst-fit

  • First-fit(최초 적합) : 최초 적합은 할당할 프로세스 크기보다 크거나 같은 hole을 탐색하는 순서 중에서 가장 먼저 찾은 hole에 프로세스를 할당하는 것.

  • Best-fit(최적 적합) : 최적 적합은 할당할 프로세스 크기와 hole 크기의 차이가 가장 작은 hole에 프로세스를 할당하는 것. (hole크기는 프로세스 크기보다 반드시 커야한다)

  • Worst-fit(최악 적합) : 최적 적합과 반대로 할당할 프로세스 크기와 hole크기 차이가 가장 큰 hole에 프로세스를 할당하는 것.

Hole은 100kb, 500kb, 600kb, 300kb, 200kb 총 5개가 있고
프로세스는 P1, P2, P3, P4 총 4개가 있다.
프로세스의 각 크기는 212kb, 417kb, 112kb, 426kb이다.

  • First-fit

  • Best-fit

  • Worst-fit

각 3가지 방식대로 프로세스를 할당하였다.

결과를 보면 best-fit은 4개의 프로세스를 모두 할당할 수 있고
나머지 전략은 마지막 프로세스를 할당하지 못한다.
모든 hole을 합치면 p4를 할당할 수 있지만 hole들은 나눠져 있기에 할당할 수 없다 = 외부 단편화.

각 할당 방식의 일반적인 성능 비교를 보면,
속도면에서는 first-fit이 가장 빠르다.
메모리 이용률면에서는 first-fit, best-fit이 비슷하다.

하지만 여러 실험을 통해 best-fit을 사용하더라도 외부 단편화로 인해 전체 메모리의 1/3정도를 낭비한다고 한다.
거의 사용불가 수준.

Compaction

이를 해결하는 방법 중 하나는 Compaction이다.
여러 곳에 흩어진 hole들을 강제로 하나로 합치는 것이다.
하지만 hole을 옮기는 오버헤드가 너무 크고, 어느 hole을 옮겨야 빠르게 합칠 수 있는지에 대한 최적 알고리즘이 존재하지 않는 큰 단점이 있다.

Ref

profile
Android Developer - Come to my medium (https://medium.com/@wodbs135)

0개의 댓글