일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
- shiba
- 715. Range Module
- 파이썬
- Class
- kaggle
- attribute
- Generator
- data science
- Regular Expression
- 운영체제
- 315. Count of Smaller Numbers After Self
- 밴픽
- Python Code
- 컴퓨터의 구조
- Convert Sorted List to Binary Search Tree
- Python Implementation
- LeetCode
- DWG
- 43. Multiply Strings
- 30. Substring with Concatenation of All Words
- t1
- Decorator
- 시바견
- Substring with Concatenation of All Words
- 프로그래머스
- Protocol
- Python
- 109. Convert Sorted List to Binary Search Tree
- iterator
- concurrency
- Today
- Total
Scribbling
운영체제 - 7 본문
1. 메모리 관리
1.1 메모리 관리
시작하기에 앞서, 이 장에서 메모리는 메인메모리를 의미한다. CPU는 메모리에 있는 내용을 가져오거나 작업 결과를 메모리에 저장하기 위하여 메모리 주소 레지스터(MAR)을 사용한다. 폰노이만 구조의 컴퓨터에서는 메모리가 유일한 작업 공간이므로, 모든 프로그램은 메모리에 올라와야 실행 가능하다. 운영체제도 당연히 메모리에 올라와야 실행이 가능하다. 전원 버튼을 누르면 부팅이 이루어지는데, 이 때 하드디스크에 저장된 운영체제가 메모리에 올라간다.
메모리 관리는 운영체제를 비롯하여 여러 프로세스가 동작할 때 메모리를 어떻게 관리하는가에 관한 문제이다. 이는 메모리 관리 시스템(MMS; Memory Management System)이 담당한다.
1.2 컴파일러와 인터프리터
컴파일러(Compiler)는 소스코드를 기계어로 번역한 후 한꺼번에 실행하는 반면, 인터프리터(Interpreter)는 소스코드를 한 행씩 번역하여 실행한다. 컴파일 과정을 통하여 소스코드에서 오류를 발견할 수 있으며, 이에 더불어 코드를 최적화할 수 있다.
컴파일 과정은 아래 그림과 같다. 컴파일은 사용자가 작성한 소스코드를 목적 코드(Object Code)로 변환한 후 라이브러리를 연결하고 최종 실행 파일(e.g. 윈도우의 .exe 파일)을 만들어 실행하는 과정이다.
컴파일러는 모든 변수 및 함수에 대해 메모리를 확보하고 오류를 찾기 위해 심벌 테이블을 생성하여 유지한다. 심벌 테이블에는 심벌, 주소, 범위(scope) 등이 저장되어 있다. 컴파일러는 이를 이용하여 모든 변수를 메모리 주소로 바꾸어 기계어로 된 실행 파일을 만든다.
목적 코드가 만들어지면 라이브러리에 있는 코드를 목적 코드에 삽입하여 최종 실행 파일을 만든다. 예컨대 컴파일러는 라이브러리 연결 단계에서 printf() 문에 해당하는 기계어 코드를 <stdio.h> 라이브러리에서 가져와 목적 코드에 삽입한다. 이것을 수행하는 것이 바로 링커이다. 실행할 때 삽입되는 함수를 가진 라이버르리를 동적 라이브러리(Dynamic Library)라고 한다. 윈도우의 DLL이 이에 해당된다.
동적 라이브러리는 런타임 라이브러리라고도 불리운다. 동적 라이브러리를 사용하면 함수가 업데이트될 때 마다 새로운 라이브러리를 가져올 필요가 없다는 장점을 갖고 있지만, 이 외에도 다양한 장점이 있다. 특히 다양한 프로세스가 동적 라이브러리에 접근하여 필요할 때에만 불러내어 쓸 수 있기 때문에, 메모리를 절약할 수 있다. 예컨대 <현재 마우스 포인터 위치를 return하는 함수>를 여러 프로세스들이 사용하고 있다고 가정해보자. 만약 이러한 함수가 정적 라이브러리에 포함되어있다면, 이 함수를 사용하고자 하는 모든 프로세스들의 메모리 공간에 해당 함수 또한 올라가 있어야 한다. 그러나 해당 함수가 동적 라이브러리에 포함되어있다면, 위와 같은 불필요한 메모리 사용을 크게 줄일 수 있다.
참고할만한 주소: https://cillic.tistory.com/5
1.3 메모리 관리자의 역할
메모리 관리 유닛(MMU, Memory Manage Unit)은 가져오기(Fetch), 미리 가져오기(Prefetch), 배치(Placement), 재배치(Replacement)등을 한다. 가져오기 작업은 말 그대로 프로세스와 데이터를 하드디스크에서 메모리로 가져오는 작업이다. 배치 작업은 가져온 프로세스와 데이터를 메모리의 특정 위치에 놓는 작업이다. 재배치는 꽉 차 있는 메모리에 새로운 프로세스를 가져오기 위해 오래된 프로세스를 내보내는 작업이다.
2. 메모리 주소
2.1 32bit CPU와 64bit CPU
CPU의 비트는 CPU가 한 번에 다룰 수 있는 데이터의 최대 크기를 의미한다. 32bit CPU는 한 번에 32bit만 다룰 수 있는데, 따라서 32bit CPU를 장착한 컴퓨터는 모든 부품이 32bit 단위로 동작한다. 즉, ALU도 32bit로 처리되며, 각종 버스의 크기도 32bit이고, 대역폭도 32bit이다. CPU의 비트는 메인메모리 주소 공간의 크기와도 연관이 있다. 32bit CPU의 메모리 주소 레지스터(MAR)은 총 32bit를 사용 가능하므로, 2**32개의 주소를 가리킬 수 있다. 이는 약 4GB이다. 반면 64bit CPU는 모든 것이 64bit로 처리된다. 그리고 사용 가능한 메인 메모리의 크기는 2**64B로, 그 크기에 거의 제약이 없다고 보아도 무방하다.
2.2 절대 주소와 상대 주소
메모리 관리의 핵심은 프로세스(혹은 프로그램)간에 주어진 메모리 공간을 침범하지 않도록 관리하는 것이다. 경계 레지스터는 프로세스간의 경계 지점의 주소를 가진 레지스터이다. 메모리 관리자는 User Program이 경계 레지스터 값을 벗어나는 위치에 접근하려는 경우, 해당 프로세스를 종료시킨다.
절대 주소(Absolute Address)는 실제 물리 주소(Physical Address)를 의미한다. 이는 메모리 관리자 측면에서 서술하는 메모리 주소로, 컴퓨터의 메인 메모리의 실제 주소로 생각할 수 있다. 반면 상대 주소(Relative Address)는 User Program 입장에서 바라보는 주소로, 항상 0번지로부터 시작한다. 모든 프로그램은 메인 메모리에 실제로 올라가기 전에는 프로세스가 어느 물리 주소에 배치될지 알 수 없기 때문에, 0번지라는 상대적 주소부터 동작하는 것을 가정한다. 이러한 개념을 논리 주소 공간(Logical Address)이라고 한다.
User Program이 특정 상대 주소에 접근하려고 하면, 메모리 관리자는 재배치 레지스터를 사용하여 상대 주소를 절대 주소로 변환하여 해당 데이터를 가져온다.
3. 단일 프로그래밍 환경에서의 메모리 할당
3.1 메모리 오버레이
모든 프로그램은 메인 메모리에 올라가야만 하는데, 메모리보다 큰 용량의 프로그램은 어떻게 실행할 수 있을까? 프로그램의 크기가 전체 메모리보다 크면 전체 프로그램을 메모리에 가져오는 대신, 적당한 크기로 잘라서 가져오는데 이를 메모리 오버레이(Memory Overlay)라고 한다. 즉, 실행하는 데 필수적인 모듈만 올려놓고 나머지는 필요할 때마다 메모리에 가져와 사용하는 것이다.
메모리 오버레이에서 어떤 모듈을 가져오거나 내보낼지는 CPU 레지스터 중 하나인 프로그램 카운터가 결정한다. 프로그램 카운터가 가리키고 있는 명령어를 실행하기 위한 모듈이 메모리에 없으면 메모리 관리자에게 요청하여 메모리로 가져온다.
3.2 스왑
메모리 오버레이를 사용하여 메모리보다 큰 프로그램이 실행 가능하다. 그러나 메모리가 가득찬다면 어떤 모듈은 메모리에서 쫓아내야만 한다. 이 때 쫓겨난 모듈 혹은 프로세스는 저장장치의 특별한 공간에 모아두는데, 이러한 영역을 스왑 영역(Swap Area)라고 한다. 그리고 스왑 영역에서 메모리로 데이터를 가져오는 것을 스왑 인(Swap In), 반대로 메모리에서 스왑 영역으로 데이터를 내보내는 작업을 스왑 아웃(Swap Out)이라고 한다. 스왑 영역 역시 메모리 관리자가 관리한다.
스왑 영역은 마치 메모리의 일부같이 인식된다. 윈도우에서는 이러한 스왑 영역을 확인할 수 있는데, 컴퓨터의 전원 메뉴 중 [최대 절전 모드]가 한 예시이다. [최대 절전 모드]는 현재 작업 중인 상태로 쉬게 하는 것으로, CPU와 메모리의 전력 공급을 끊기 때문에 메모리에 있는 모든 내용이 사라진다. 그러므로 원래 메인 메모리에 있던 내용을 어딘가에 저장해야하는데, 이 때 옮겨 두는 곳이 스왑 영역이다.
스왑 영역의 크기는 사용자가 임의로 정할 수 있으나, 기본적으로는 하드디스크의 남은 공간과 메모리 사용량을 고려하여 유동적으로 정해진다. 때문에 윈도우 운영체제일 경우, 하드디스크가 충분히 여유 공간을 갖고 있는 것이 성능에 좋은 영향을 줄 수 있다.
4. 다중 프로그래밍 환경에서의 메모리 할당
4.1 메모리 분할 방식
여러 프로세스들이 메모리에 올라오는 경우, 메모리를 나누는 방식이 핵심이다. 배치 정책은 가변 분할 방식(Variable Size Partitioning)과 고정 분할 방식(Fixed Size Partitioning)으로 나뉜다. 가변 분할 방식은 프로세스의 크기를 고려하여 나누는 방식이고, 고정 분할 방식은 프로세스의 크기 상관없이 메모리를 같은 크기로 나눈다.
가변 분할 방식은 프로세스의 크기에 맞게 메모리가 분할되고, 한 프로세스가 연속 공간에 배치되므로 연속 메모리 할당 방식이다. 그러나 이는 메모리 관리가 매우 복잡하다. 반면 고정 분할 방식은 비연속 메모리 할당이지만 메모리 관리가 수월하다. 현대 운영체제에서 메모리 관리는 기본적으로 고정 분할 방식을 사용하며 일부분만 가변 분할 방식을 사용한다.
4.2 가변 분할 방식
가상 메모리 시스템에서는 가변 분할 방식을 세그멘테이션 기법이라고 한다. 가변 분할 방식은 외부 단편화(External Fragmentation) 문제를 일으킨다. 이는 프로세스를 배치다하보면 프로세스와 프로세스 사이에 작은 조각들이 생기는 현상이다. 이러한 문제를 최소화하기 위해 프로세스를 배치하는 효율적인 알고리즘이 필요할 뿐 아니라, 외부 단편화가 심해지면 작은 조각을 모아서 하나의 큰 덩어리로 만드는 '조각 모음' 또한 필요하다. 여기서 '조각 모음'은 작은 조각들을 모으기 위해 프로세스들을 재배치시키는 것을 의미한다. 조각 모음을 하려면 프로세스를 중지하고, 이동하고, 주소를 바꾸고, 다시 시작하는 등의 오버헤드가 필요하다. 이러한 이유로 현대 운영체제는 가변 분할 방식을 잘 사용하지 않는다.
* 하드 디스크 조각 모음: 하드 디스크 조각 모음도 위의 '조각 모음'과 비슷한 개념이다. 하드 디스크에 데이터를 저장하고 삭제하다보면 작은 조각들이 데이터 사이 사이에 발생하게 된다. 이러다 보면 붙어 있어야 하는 데이터들이 조각 조각에 저장되면서 멀리 떨어지게 되는데, 이는 당연히 입출력 성능을 저하시킨다. 하드 디스크 조각 모음은 이렇게 붙어 있어야 하는 데이터를 다시 연속적으로 배치하고, 조각을 모아 큰 조각을 만듦으로써 전체 성능을 회복시킨다.
4.3 고정 분할 방식
고정 분할 방식은 관리가 수월하다. 다만 내부 단편화 문제가 발생한다. 내부 단편화(Internal Fragmentation)은 고정된 크기보다 작은 프로세스가 배치되는 경우 발생한다. 때문에 고정 분할 방식에서는 분할되는 공간의 크기를 조절하는 것이 매우 중요하다.
'Computer Science > Computer Knowledge' 카테고리의 다른 글
운영체제 - 9 (0) | 2021.10.22 |
---|---|
운영체제 - 8 (0) | 2021.10.20 |
운영체제 - 6 (0) | 2021.10.11 |
운영체제 - 5 (0) | 2021.10.08 |
운영체제 - 4 (0) | 2021.10.05 |