티스토리 뷰
메모리는 주소와 데이터로 구성되어 있다. CPU가 원하는 데이터의 주소를 메모리에 보내주게 되면 메모리는 CPU에게 해당하는 데이터를 보내준다. 또한 CPU에서 계산된 결과를 메모리의 특정 주소에 저장하고 명령을 보내면 메모리에 해당 주소에 데이터를 저장한다.
프로그램을 개발할 때는 여러 가지의 파일 형태를 가진다. 소스 파일은 고수준언어 또는 어셈블리언어로 개발된 파일을 말한다. 소스 파일은 컴파일러와 어셈블러에 의해 목적 파일로 전환된다. 목적 파일은 소스 파일에 대한 컴파일 또는 어셈블 결과를 나타내는 파일로 기계어로 나타내어진다. 목적 파일을 링크가 실행파일로 바꾼다. 실행파일은 링크의 결과로 나타난 파일이다. 링크는 하드디스크에 들어가 있는 다양한 내장 함수(Library)들을 실행하기 위해 연결을 해주는 과정이다. 만들어진 실행 파일을 메모리에 적재하는 과정을 하는 것은 로더이다. 실행 파일은 로더에 의해 적재되어야만 실행이 가능하다.
(소스파일 - [컴파일러, 어셈블리] -> 목적파일 - [ 링크 ] -> 실행파일)
하나의 프로그램이 실행되기 위해서는 코드와 데이터, 스택을 가지고 있어야한다. 코드의 경우에는 앞에서 작성한 파일들이 될 수 있다. 데이터의 경우는 프로그램이 실행될 때 넣어주어야 할 값이 된다. 예를 들어 두 수 중 큰 수를 나타내어 주는 프로그램이 있으면 큰 수를 나타내게 해주는 작업을 해주는 부분이 코드이고 두 수를 입력하는 값이 데이터이다. 스택은 함수를 호출 했을 때 돌아오는 주소와 지역변수를 저장하는 공간이다.
실행 파일이 만들어지면 로더에 의해 메모리에 올려 진다고 하였다. 운영체제는 이 실행 파일을 메모리의 어디 부분에 올릴지를 결정한다. 다중 프로그램의 환경에서 메모리에 프로그램을 넣어주고 다시 하드디스크로 보내주고 하는 과정들을 모두 운영체제에서 담당하여 처리를 한다. 사실 고수준언어를 작성할 때에는 주소를 사용하여 작성하지 않지만 목적 파일로 바뀌어 실행 파일을 사용하면 주소의 값을 통해 코드를 이동하고 작동을 하게 된다. 따라서 메모리에 적재할 때 적절한 메모리 위치에 프로그램을 넣지 않으면 문제가 발생할 수 있다. 이러한 문제를 해결하는 것이 MMU이다. MMU 운영체제의 역할을 설명할 때 같이 설명하였는데 주소의 영역을 정해주는 역할을 한다. MMU에는 재배치 레지스터가 존재하는데 이는 코드가 원하는 주소를 만들어주는 역할을 한다. 예를 들어 코드에서 0번지에 들어가야 하는 프로그램이 작성되어 있는데 메모리에서 빈 공간이 주소가 500번지 있다고 한다. 그러면 재배치 레지스터에서 500이라는 값을 더해주어 코드가 500번지에서 실행하는 것이 맞게 해주는 것이다.
- 메모리 낭비 방지 -
하드디스크에 존재하는 프로그램들은 메모리의 특정 부분에 적재되어 실행이 된다. 프로그램이 만들어지기 까지 소스 파일, 목적 파일, 실행 파일을 거쳐 로더에 의해 메모리에 올려 지게 되는데 적절한 위치에 적재를 해야 실행을 시킬 수 있다. 앞 장에서 MMU의 재배치 레지스터를 이용하면 부분적으로 적절한 위치에 프로그램을 올릴 수 있다. 이번 장에는 이를 더 넘어가서 어떻게 하면 더 효율적으로 프로그램을 올릴 수 있는지, 메모리 보호를 위해서 메모리를 낭비하는 것을 방지하는 어떤 방법을 사용해야 하는지에 대해 학습 할 것이다.
MMU가 메모리 관리를 하는 방법에 대해서 조금 더 상세히 들어가 보자. MMU는 프로그램이 동작할 때 필요한 데이터나 동작들을 메모리의 특정 주소 위치로부터 가지고 와야 하는데 이는 프로그램이 만들어질 때 코드화 되어 있다. 따라서 항상 같은 위치의 메모리 주소로부터 데이터를 불러오게 된다. 하지만 메모리에 프로그램을 적재할 때 항상 같은 위치에 프로그램을 올릴 수는 없다. 어떤 날에는 0번지에 올리기도 하고 다른 날에는 500번지에 올리기도 한다. 이는 메모리 공간에서 빈 공간을 찾아 프로그램을 올리기 때문에 알 수가 없다. 그래서 이를 맞추어 조절해주는 역할을 하는 것이 바로 MMU의 재배치 레지스터이다. 재배치 레지스터는 CPU가 프로그램을 실행시킬 때 코드를 읽어 들어가는데 코드에서 특정 위치의 메모리 주소로부터 데이터를 가지고 오라는 동작을 할 때 도움을 준다. CPU는 분명 특정 위치를 가르쳐 주었는데 재배치 레지스터는 이의 주소에서 메모리에 프로그램이 있는 위치 주소를 더해서 계산을 해서 CPU에서 원하는 데이터 위치와 메모리상에 프로그램의 위치를 맞게 맞추어 준다. 여기서 CPU에서 MMU로 보내는 주소를 논리 주소라고 하고 MMU에서 메모리로 보내는 주소는 물리 주소라고 한다.
동적 적재는 프로그램 실행에 반드시 필요한 루틴/데이터만 적재하는 것을 의미한다. 적재는 프로그램을 메인 메모리에 올리는 동작을 의미한다. 우리가 프로그램을 작성할 때 다양한 코드를 작성한다. 실제 실행이 되는 부분도 있지만 예외의 상황을 처리하는 경우 실행이 되지 않는 부분이 있을 수도 있다. 모든 루틴이 다 사용되는 것이 아니고 모든 데이터가 다 사용되는 것도 아니다. 따라서 프로그램의 모든 동작을 메인 메모리에 다 적재를 하면 차지하는 공간이 많아지게 된다. 동적 적재는 이런 문제를 해결하기 위해 실제 실행되는 반드시 필요한 루틴/데이터만 적재를 하는 것이다. 예시로 오류처리나 사용하지 않는 데이터와 같은 부분은 사용을 할 경우에만 메모리로 적재를 하고 아닐 경우에는 적재시키지 않는다. 이를 통해 메모리의 효율을 높일 수 있다.
동적 연결은 여러 프로그램에 공통 사용되는 라이브러리를 관리하는 방법이다. 여러 가지의 프로그램이 메모리상에 존재할 때 공통으로 사용하는 라이브러리가 존재한다. 예를 들면 C언어로 만들어진 프로그램이 여러 가지가 있는데 여기서 prinf()와 같은 C언어에서 사용하는 공통 라이브러리가 존재한다. 하지만 동적 연결을 하지 않으면 각각의 프로그램에 필요한 라이브러리가 각각 메모리에 적재되게 된다. 공통 라이브러리 루틴을 메모리에 중복으로 올리는 것은 메모리에 낭비가 된다. 라이브러리 루틴 연결을 실행 시까지 미루고 오직 하나의 라이브러리 루틴만 메모리에 적재해서 이 루틴과 연결을 하도록 하는 방법을 해서 적용시키는 방식인 동적 연결을 하게 되면 이렇게 낭비되는 메모리를 줄여 효율을 높일 수 있다.
Swapping이라는 기술은 메모리에 적재되어 있으나 현재 사용되지 않고 있는 프로세스를 관리하는 역할을 한다. 메모리 활용도 높이기 위해 Swapping을 이용하면 현재 사용되지 않는 프로세스를 Backing store로 몰아낸다. Backing store는 하드디스크의 일부분이다. 이런 과정을 swap-out 이라고 한다. 이와 반대로 필요한 부분이 생기면 그 부분에 대해 메모리로 적재해서 올려주는 swap-in이라고 한다. MMU의 재배치 레지스터를 사용하므로 적재 위치는 메모리의 빈 공간 어디에도 적재가 가능하다. 이런 작용으로 메모리의 활용도를 높일 수 있어 효율이 높아진다. 하지만 프로세스의 크기가 크면 Backing store 입출력에 따른 부담이 크다.
- 연속 메모리 할당 -
컴퓨터의 구조가 발달되어 오면서 운영체제는 다중 프로그래밍 환경을 조성하게 된다. 부팅 직후 메모리에 운영체제가 하드디스크로부터 적재되고 컴퓨터가 동작을 하는 것을 기다린다. 그 후 여러 가지의 프로그램이 동시에 올라와서 스케줄링에 의해 CPU나 I/O를 할당받는다. 처음에 부팅 직후에는 운영체제만 적재되어 있으므로 운영체제를 제외한 공간이 모두 비어 있게 된다. 그래서 big single hole이 생기게 된다. 이 후 프로세스가 생성되고 종료되는 작업을 반복하며 컴퓨터가 동작하게 된다. 이 때는 프로세스들은 여러 위치의 메모리에 적재되기 때문에 scattered holes가 생성되게 된다. 프로세스가 순서대로 쌓이더라도 작업이 끝나는 순서는 정해져 있지 않고 다시 들어오는 프로세스와도 메모리 공간 차지 사이즈가 다르기 때문에 메모리의 빈 공간이 중간 중간에 계속 생성될 것이다.
scattered holes가 계속 발생하게 되면 hole 들이 불연속하게 흩어져 있게 되어 메모리 단편화 현상이 발생하게 된다. 이렇게 되면 메모리의 빈 공간은 많은데 사이즈가 작은 빈 공간이 많아 다른 프로세스를 적재할 수 없는 현상이 발생한다. 이러한 현상을 외부 단편화라고 한다. hole들의 크기를 모두 합치면 프로세스를 충분히 적재할 수 있는데 떨어져 있어서 프로세스가 못 들어가는 현상이다. 메모리 관리자 입장에서 보면 메모리가 충분히 비어 있는데도 프로세스를 넣지 못하니 매우 억울한 상황이다.
(외부단편화 : 구멍을 합치면 사용가능 한데, 구멍들의 크기가 작아 사용하기 곤란한 상황
내부단편화 : 메모리의 영역을 사용하고 남은 공간)
외부 단편화를 없애는 방식으로 연속 메모리 할당 방식을 사용한다. 연속 메모리 할당 방식에도 3가지의 방법이 존재한다.
최초 적합(First-fit)은 메모리를 순차적으로 탐색하여 제일 먼저 발견한 적절하게 들어갈 수 있는 곳을 찾아 프로세스를 적재하는 방법이다.
최적 적합(Best-fit)은 메모리를 탐색하여 메모리 공간 중에서 제일 적절하게 들어갈 수 있는 곳을 찾아 프로세스를 적재하는 방법이다.
최악 적합(Worst-fit)은 메모리에 넣는데 크기와 제일 안 맞는 공간(프로세스보다 큰 메모리 공간 중에서)에 프로세스를 넣는 방식이다.
할당 방식에 따른 성능을 비교해보면 속도 측면에서는 최초 적합이 좋다. 최초 적합은 메모리를 순서대로 탐색하여 가장 먼저 발견되는 공간에 적재하기 때문에 속도 면에서는 가장 빠르다고 할 수 있다. 이용률의 측면에서 보면 최적 적합이 좋다. 속도는 조금 떨어지지만 정확하게 맞는 위치에 들어가는 방식을 취하기 때문에 애매한 빈 공간을 창출할 일이 많이 없다. 최초 적합의 경우는 자신의 프로세스 크기보다 메모리 공간이 더 큰 경우가 발생하면 무조건 프로세스를 넣지만 최적 적합은 빈 공간을 제일 안 만드는 측면으로 프로세스를 적재한다. 하지만 대부분의 경우를 비교해보면 최초 적합과 최적 적합의 이용률의 차이가 많이 발생하지는 않는다.
연속 메모리 할당의 방식을 사용하더라도 외부 단편화로 인한 메모리 낭비가 1/3 정도 발생한다. 그러므로 비싼 메모리의 공간을 많이 사용할 수 없으니 문제가 크다고 할 수 있다. 이런 방식을 해결하는 또 다른 방법이 Compaction이다. Compaction은 hole 들을 한 곳으로 모으겠다는 방식이라고 할 수 있다. 한 곳으로 모으기 위해서는 메모리를 움직여야한다. 그래서 메모리 계산에 대한 부담이 크다. 또한 hole을 어디로 움직이는 게 좋은 가 에 대한 문제가 발생한다. 이렇기 때문에 hole을 움직이거나 프로세스를 움직이는 Compaction 방식의 최적의 알고리즘은 존재하지 않는다. 하지만 이런 방식이 있다는 것은 기억하는 것이 좋을 것 같다. 그렇다면 이렇게 메모리 낭비가 심한데 어떻게 해결을 한 것일까? 그 방법이 바로 페이징이다. 페이징은 다음 장에서 설명할 것이다.
'OS' 카테고리의 다른 글
9. 운영체제 가상메모리 (0) | 2018.11.02 |
---|---|
8-2. 페이징, 세그먼테이션 (0) | 2018.11.02 |
7. 운영체제 교착상태 (0) | 2018.11.02 |
6-2. Semaphore, 임계구역, 식사하는 철학자들, 생산자-소비자 문제 풀이 (0) | 2018.11.02 |
6. 운영체제 프로세스 동기화 (0) | 2018.11.02 |