본문 바로가기
개발공부 개발새발/OS

OS ) 메인 메모리 - 1편

by 휴일이 2025. 2. 14.

기본 하드웨어

CPU 가 직접 접근할 수 있는 유일한 범용 저장 장치는?

  • 메인 메모리
  • 레지스터

→ 따라서 모든 실행되는 명령어와 데이터들은 CPU 가 직접 접근 가능한 메인 메모리와 레지스터에 있어야 함. → 데이터가 메모리에 없다면 일단 메모리로 이동시켜야 함.

레지스터

  • CPU 클록(Clock)의 1사이클 내에 접근 가능.
  • 일부 처리 코어들은 레지스터에 있는 명령어 해독과 간단한 연산을 클록 틱 하나, 또는 그 이상의 속도로 처리함.

메인 메모리

메인 메모리는 레지스터와 다르다.

  • 메인 메모리에 접근을 하기 위해서는 많은 CPU 클록 틱 사이클이 소요된다.
    • 메인 메모리가 레지스터 보다 코어랑 더 머니까요?
  • 이 경우 CPU 가 필요한 데이터가 없어서 명령어를 수행 못하고 지연되는(스톨 stall) 상황이 발생하게 된다.

해결법 : 캐시

CPU 와 메모리 사이에 빠른 속도의 메모리(캐시)를 추가하면 된다.

  • 캐시를 관리하면 하드웨어는 어떠한 운영체제의 도움 없이 메모리 접근 속도를 향상할 수 있다.

메모리의 기능

  1. 물리 메모리의 상대적인 접근 속도 차이를 고려하기
  2. 올바른 동작 보장하기
    • 시스템이 올바르게 동작하기 위해서는 사용자 프로그램으로부터 운영체제를 보호하고, 사용자 프로그램 사이도 서로 보호해야 함.
    • 운영체제가 CPU ↔ 메모리 접근 중에 개입하면 성능이 떨어지니까, 이 보호 기법은 반드시 하드웨어가 지원해야 함.

메모리의 올바른 동작 보장하는 법

개별적인 메모리 공간을 분리하기 위해 특정 프로세스만 접근할 수 있는 합법적인(legal) 메모리 주소 영역을 설정하여 프로세스가 합법적인 영역만을 접근하도록 하는 것이 필요하다.

  • 기준 레지스터상한 레지스터를 이용하여 보호한다.

→ 여기서 합법적이란, 특정 프로세스만 합법적으로 접근할 수 있는 것을 뜻한다. 다른 프로세스가 접근하면 “불법”이 되는 영역이라고 볼 수 있다.

기준 레지스터  가장 작은 합법적인 물리 메모리의 주소 값 저장
상한 레지스터 주어진 영역의 크기를 저장
  • 예시) 기준 레지스터 값이 “300040”, 상한 레지스터 값이 “120900”
    • 프로그램 주소는 30040 ~ 42094 까지

사용자 모드에서 수행되는 프로그램이 운영체제 메모리 공간이나 다른 사용자 프로그램의 메모리 공간에 접근하면?

  • 운영체제는 치명적인 오류로 간주하여 트랩 trap 을 발생시킨다.
  • 우연이든 의도적이든 사용자 프로그램이 운영체제나 다른 사용자프로그램의 코드나 데이터 구조를 수정하는 것을 막는다.

운영체제와 레지스터

  • 기준, 상한 레지스터는 특권 명령을 사용하여 운영체제에 의해서만 적재(load) 된다.
    • 사용자 프로그램이 레지스터 내용을 변경할 수 없다. → 특권 명령은 커널 모드에서만 수행되기 때문에..^^
  • 운영체제는(커널모드에서는) 메모리 영역 / 사용자 메모리 영역 접근에 제약이 없음.

커널의 역할

  • 사용자 프로그램을 사용자 메모리 영역에 적재
  • 오류가 발생하면 프로그램을 덤프(dump out)
  • 시스템 콜의 매개변수 변경
  • 사용자 메모리로부터의 입출력 외에 다른 많은 서비스들..
  • 예) 멀티 프로세스 시스템 운영체제는 “컨텍스트 스위칭” 에서
    • 한 프로세스의 상태를 레지스터 → 메인 메모리로 저장
    • 다음 프로세스의 컨텍스트를 메인 메모리 → 레지스터로 저장

주소 할당

운영체제가 프로세스를 실제로 물리 메모리에 적재하는 방법

  • 원시 프로그램에서 주소는 숫자가 아닌 심볼(변수 count 와 같은 형태) 형태로 표현된다.
  • 컴파일러는 이 심볼 주소를 재배치 가능 주소로 바인딩시킨다.
    • 예) 이 모듈의 첫번째 바이트로부터 열네 번째 바이트 주소
  • 링커(linker) 나 로더(loader)가 재배치 가능 주소를 절대 주소(물리 주소)로 바인딩 시킨다.
    • 예) 74014 번지
    • 바인딩 : 한 주소 공간에서 다른 주소 공간으로 맵핑하는 것

바인딩이 이루어지는 시점으로 구분하기

  • 컴파일 시간 바인딩
    • 절대 코드를 생성
    • 예) 사용자 프로세스가 R번지로부터 시작한다는 걸 미리 안다?
      • 컴파일러는 번역할 코드를 그 위치에서 시작함.
      • 하지만 만약 위치(메모리 위치)가 변경되어야 하면 이 코드는 다시 컴파일되어야 함.
  • 적재 시간 바인딩 (컴파일 시점에 바인딩할 위치를 모른다면)
    • 컴파일러가 이진 코드를 재배치 가능 코드로 만듬.
    • 심볼과 진짜 번지 수와의 바인딩은 “프로그램이 메인메모리에 실제로 적재되는 시간”에 이루어짐.
    • 시작 주소가 변경되어도 아무때나 사용자 코드를 다시 적재하면 됨.
  • 실행 시간 바인딩 (프로세스가 실행하는 중간에 메모리 위치를 옮겨야 한다면)
    • 이걸 위해서는 메모리 관리 장치(MMU) 가 필요하다.

논리 vs 물리 주소 공간

  • 논리 주소 : CPU가 생성하는 주소
  • 물리 주소 : 메모리가 취급하는 주소(메모리 주소 레지스터(MAR)에 주어지는 주소)

가상 주소를 물리 주소로 매핑해주는 MMU

가장 단순한 MMU 기법

 

  • 재배치 레지스터 값이 14000 일 때, 프로세스가 346 번지에 엑세스한다면 → 사실은 14346 번지에 엑세스 하는 것.
  • 사용자 프로그램은 “실제 물리 주소” 에 접근하지 않는다.
    • 그냥 0 ~ 346 번지에 대한 포인터를 생성하고 저장, 연산, 다른 주소와 비교 등을 하는 것.
  • 하지만 사용자 프로그램의 요청으로 어떤 주소로 갈 적에는, 먼저 기준 레지스터에 바인딩되어 실제 주소로 바인딩된다.
    • 사용자 프로그램 → 논리 주소 사용.
    • 메모리 하드웨어 → 논리 주소를 실제 주소로 바꾼 것 사용.
  • 참조된 메모리 주소의 실제 위치는 실제 실행 시간에 결정된다.
💡사용자 프로그램은 논리 주소만을 만들어낼 뿐이니 메모리 위치가 0 에서 max 까지만 있다고 생각할 것이다. 이 논리 주소는 MMU 를 거쳐 실제 물리 주소로 변환되어야 한다. 별도의 물리 주소 공간에 연결되어야 하는 논리 주소 공간의 개념은 올바른 메모리 관리에 핵심 개념이다.

동적 적재 Dynamic Loading

  • 각 루틴이 실제 호출되기 전까지는 메모리에 올라오지 않고, 재배치 가능한 상태로 디스크에서 대기하고 있음.
  • main 프로그램이 메모리에 올라와 실행했을 때
    • 이 루틴이 이미 메모리에 적재됐는지를 조사.
    • 만약 적재되어 있지 않으면, 재배치 가능 연결 적재기가 불려 요구된 루틴을 메모리로 가져옴.
    • 이런 변화를 테이블에 기록
    • CPU 제어가 중단되었던 루틴으로 다시 보내짐.

장점

  • 루틴이 필요한 경우에만 적재 됨.
    • 전체 프로그램 크기가 클 수 있지만 사용되는(적재된) 부분이 훨씬 작을 경우.
    • 오류 처리 루틴과 같은 아주 간혹 발생하지만 실행할 코드가 많은 경우 유용.
💡동적 적재는 운영체제로부터 특별한 지원이 필요없고, 사용자 프로그램이 직접 설계하고 책임진다.

동적 연결 및 공유 라이브러리

동적 연결 라이브러리(DLL)는 사용자 브로그램에 연결되는 시스템 라이브러리

  • 동적 연결은 “연결(linking)” 이 실행 시까지 미루어지는 것.
  • 이 기능이 없다면 시스템의 각 프로그램은 실행 가능 이미지에 해당 언어 라이브러리(또는 최소한 프로그램이 참조하는 루틴)의 사본을 포함해야 함. → 실행 가능 이미지 크기 증가, 메인 메모리 낭비
  • 이런 라이브러리를 여러 프로세스간에 공유도 가능하여 메인 메모리에 DDL 인스턴스 하나만 있으면 됨.

운영체제의 도움이 필요

  • 동적 연결 라이브러리는 라이브러리 갱신(예: 버그수정)으로 확장 가능.
  • 새로운 버전으로 교체 가능하고, 그럼 모든 프로그램은 새로운 라이브러리 버전을 사용하게 될 것.
  • 동적 연결과 공유 라이브러리는 일반적으로 운영체제의 도움이 필요하다.
    • 왜? : 운영체제만이 루틴이 있는지 검사해줄 수 있고, 여러 프로세스가 같은 메모리 주소를 공유하도록 해줄 수 있으니까..

DDL 은 예를 들어…

→ 크롬에서 pdf 파일을 열 수 있지만 크롬을 켰을 때는 당장은 pdf 라이브러리를 연결하지 않고, 실제로 pdf 파일을 열 때만 pdf 라이브러리를 메모리에 올리는 것. 등등

연속 메모리 할당

각 프로세스는 다음 프로세스가 적재된 영역과 인접한 하나의 메모리 영역에 배치된다.

  • 연속 메모리 할당을 위해 메모리 보호 문제를 해결해야 한다.

메모리 보호

프로세스가 자신이 소유하지 못한 메모리를 접근할 수 없게 강제하자.

  • 시스템이 상한 레지스터/재배치 레지스터를 가지고 있다면 가능.
    • 재배치 레지스터 : 가장 작은 물리 주소의 값 저장. (예:10040)
    • 상한 레지스터 : 논리 주소의 범위 값을 저장 (예: 74600)
    • 10040 ~ 74600 이 범위가 프로그램에 할당된 메모리 범위
  • MMU(동적→물리 변환) 는 동적으로 논리 주소에 재배치 레지스터 값을 더함으로서 주소를 변환하는 역할.

디스패처 Dispatcher

  • 컨텍스트 스위칭을 담당
  • 컨텍스트 스위칭의 일환으로 재배치 레지스터와 상한 레지스터에 정확한 값을 적재.
  • CPU 에 의해서 생성되는 모든 주소는 이 레지스터들의 값을 참조해서 확인 작업을 거침.
    • 이것이 운영체제와 다른 사용자 프로그램을 현재 수행 중인 사용자 프로그램의 접근으로부터 보호하는 방법!
💡재배치 레지스터(논리 → 물리 주소 변환)를 사용함으로서 주소는 실행 중에도 얼마든지 변경될 수 있음을 알 수 있다.

메모리 할당

가변 크기 파티션 할당

가장 간단한 방법은 → 프로세스를 메모리의 가변 크기 파티션에 할당한다.

  • 각 파티션에는 정확히 하나의 프로세스만 적재
  • 운영체제가 사용 가능한 메모리 부분과, 사용 중인 부분을 나타내는 테이블 유지.
  • 프로세스가 시스템에 들어오면, 운영체제는 각 프로세스가 메모리를 얼마나 요구하고, 사용 가능한 메모리 공간이 어디에 얼마나 있는지를 고려해 공간을 할당한다.
  • 처음에는 모든 메모리가 사용자 프로세스에 사용 가능하며, 하나의 큰 사용 가능한 메모리 블록인 hole 로 간주한다.
    • hole : 나뉘고 합쳐지는 가용 메모리 블럭

 

 

  • 이처럼 두개의 연속되지 않은 hole 이 생기면, ps5 와 똑같은 양의 메모리를 차지한 ps9 이 있더라도 hole 이 두개로 나뉘어있어 제대로 적재하기 힘든 사태가…

동적 메모리 할당 문제

가용 공간에 메모리 할당을 어떤 식으로 좋게좋게 할 것인가?

  • 최초적합
    • 첫 번째로 사용 가능한 가용 공간 할당.
    • 지난번 검색이 끝났던 곳에서 시작할 수 있으므로 성능 최적화 가능 → 속도가 제일 빠름!
  • 최적 적합
    • 사용 가능한 공간 중 가장 작은 것을 택한다.
    • 처음~끝까지 검색해서 할당 가능한 제일 작은 공간을 사용하는 것.
  • 최악 적합
    • 가장 큰 가용 공간을 택한다.
    • 얘도 처음~끝까지 검색해서 할당 가능한 제일 큰 공간을 사용한다.

→ 최초 적합과 최적 적합이 시간/메모리 이용 효율 측면에서 최악 적합보다 좋다고 입증 됨.

→ 최초 적합/최적 적합은 공간 효율성은 비슷하나 최초 적합은 일반적으로 속도가 더 빠름.

💡다만, 최초 적합과 최적 적합은 모두 외부 단편화(external fragmentation) 로 인해 어려움을 겪는다.

단편화

프로세스들이 메모리에 적재되고 제거되는 일이 반복되면 어떤 가용 공간은 너무 작은 조각이 된다.

  • 외부 단편화는 이처럼 유휴 공간들을 모두 합치면 충분한 공간이 되지만 그것들이 너무 작은 조각들로 여러 곳에 분산되어 있을 때 발생.
    • 최악의 경우 모든 프로세스 사이마다 못 쓰는 가용 공간 발생 ㄷㄷ
    • 이 모든 가용 공간들을 합쳐 하나의 큰 가용 공간을 만들면 여러 프로세스를 실행시킬 수 있을 텐데..

메모리 할당 전략은 단편화 크기에 영향을 받는다.

어떤 시스템에서는 최초 적합이, 다른 시스템에서는 최적 적합이 우수할 수 있음.

  • 어느 쪽(위쪽에서 첫 번째 또는 아래 쪽에서 첫 번째) 가용 공간을 할당할 것인가도 고려해야 함.
  • 그래도 어떤 알고리즘을 사용하더라도 외부 단편화는 문제로 남는다..

내부 단편화

일반적으로는 메모리를 아주 작은 공간들로 분할하고, 프로세스가 요청하면 할당을 항상 이 분할된 크기의 정수배로만 해주는 것이 보통이다.

  • 이 경우 할당된 공간이 요구된 공간보다 약간 더 클 수도?
    • 18,464B 크기의 가용 공간에 18,462 공간을 요구한다면 2B 의 hole 이 남을 것.
      • 이 남는 부분이 바로 내부 단편화

외부 단편화 해결 법

  • 압축
    • 메모리 모든 내용을 한군데로 몰고 모든 가용 공간을 다른 한군데로 몰아서 큰 블록을 만드는 것.
    • 하지만 압축이 항상 가능한 건 아니다 → 재배치가 어셈블 또는 적재 시에 정적으로 행해지면 압축 실행 불가
    • 압축이 가능하더라도 그 비용을 검토해봐야 함.
    • 가장 간단한 압축 알고리즘 → 모든 프로세스를 한쪽 끝으로 이동시켜 몰아넣는 것
      • 비용이 너무 많이 든다.
  • 페이징
    • 한 프로세스의 논리 주소 공간을 여러 개의 비연속적인 공간으로 나눠 필요한 크기의 공간이 가용하다면 물리 메모리를 프로세스에 할당.
    • 걍 한 마디로 메모리를 분할하여 할당하고 페이징하는 것.
    • 그래서 이걸 사용해 문제를 해결한다.

 

 

다음 편에서는 페이징으로 뵙겠습니다.^_^

728x90