본문 바로가기

CS/면접을 위한 CS 전공노트

[운영체제] 운영체제와 컴퓨터/메모리

반응형

2023년 4월 21일 134p~157p

 

3.1 운영체제와 컴퓨터

운영체제(Operation System)는 사용자가 컴퓨터를 쉽게 다루게 해주는 인터페이스로, 한정된 메모리나 시스템 자원을 효율적으로 분배하게 해주는 일꾼이다. 참고로 운영체제와 유사하지만 소프트웨어를 추가로 설치할 수 없는 것을 펌웨어라고 한다.

3.1.1 운영체제의 역할과 구조

1. 프로세스 관리

  • 프로세스, 스레드
  • 스케줄링
  • 동기화
  • IPC 통신

운영체제에서 작동하는 응용 프로그램을 관리하는 기능이다.

어떤 의미에서는 프로세서(CPU)를 관리하는 것이라고 볼 수도 있다. 현재 CPU를 점유해야 할 프로세스를 결정하고, 실제로 CPU를 프로세스에 할당하며, 이 프로세스 간 공유 자원 접근과 통신 등을 관리하게 된다.

2. 저장장치 관리

  • 메모리 관리
  • 가상 메모리
  • 파일 시스템

1차 저장장치에 해당하는 메인 메모리와 2차 저장장치에 해당하는 하드디스크, NAND 등을 관리하는 기능이다.

- 1차 저장장치(Main Memory)
프로세스에 할당하는 메모리 영역의 할당과 해제
각 메모리 영역 간의 침범 방지
메인 메모리의 효율적 활용을 위한 가상 메모리 기능

- 2차 저장장치(HDD, NAND Flash Memory 등)
파일 형식의 데이터 저장
이런 파일 데이터 관리를 위한 파일 시스템을 OS에서 관리
FAT, NTFS, EXT2, JFS, XFS 등 많은 파일 시스템들이 개발되어 사용 중

3. 네트워킹

  • TCP/IP
  • 기타 프로토콜

네트워킹은 컴퓨터 활용의 핵심과도 같아졌다.

TCP/IP 기반의 인터넷에 연결하거나, 응용 프로그램이 네트워크를 사용하려면 운영체제에서 네트워크 프로토콜을 지원해야 한다. 현재 상용 OS들은 다양하고 많은 네트워크 프로토콜을 지원한다.

이처럼 운영체제는 사용자와 컴퓨터 하드웨어 사이에 위치해서, 하드웨어를 운영 및 관리하고 명령어를 제어하여 응용 프로그램 및 하드웨어를 소프트웨어적으로 제어 및 관리 해야한다.

4. 사용자 관리

  • 계정 관리
  • 접근권한 관리

우리가 사용하는 PC를 여러 사람이 사용하는 경우가 많다. 그래서 운영체제는 한 컴퓨터를 여러 사람이 사용하는 환경도 지원해야 한다. 가족들이 각자의 계정을 만들어 PC를 사용한다면, 이는 하나의 컴퓨터를 여러 명이 사용한다고 말할 수 있다.

따라서, 운영체제는 각 계정을 관리할 수 있는 기능이 필요하다. 사용자 별로 프라이버시와 보안을 위해 개인 파일에 대해선 다른 사용자가 접근할 수 없도록 해야 한다. 이 밖에도 파일이나 시스템 자원에 접근 권한을 지정할 수 있도록 지원하는 것이 사용자 관리 기능이다.

5. 디바이스 드라이버

  • 순차접근 장치
  • 임의접근 장치
  • 네트워크 장치

운영체제는 시스템의 자원, 하드웨어를 관리한다. 시스템에는 여러 하드웨어가 붙어있는데, 이들을 운영체제에서 인식하고 관리하게 만들어 응용 프로그램이 하드웨어를 사용할 수 있게 만들어야 한다.

따라서, 운영체제 안에 하드웨어를 추상화 해주는 계층이 필요하다. 이 계층이 바로 디바이스 드라이버라고 불린다. 하드웨어의 종류가 많은 만큼, 운영체제 내부의 디바이스 드라이버도 많이 존재한다.

이러한 수많은 디바이스 드라이버들을 관리하는 기능 또한 운영체제가 맡고 있다.

 

운영체제의 구조는 다음과 같다.

운영체제는 크게 사용자와 응용 프로그램에 인접하여 커널에 명령을 전달하고 실행 결과를 돌려주는 '인터페이스(GUI)'와 운영체제의 핵심 기능을 모아놓은 '커널', 두 부분으로 나뉜다.

1. 인터페이스

사용자가 전자장치와 상호작용할 수 있도록 하는 사용자 인터페이스의 한 형태, 단순 명령어 창이 아닌 아이콘을 마우스로 클릭하는 단순한 동작으로 컴퓨터와 상호작용할 수 있도록 한다.

2. 시스템콜

운영체제가 커널에 접근하기 위한 인터페이스이며 유저 프로그램이 운영체제의 서비스를 받기 위해 커널 함수를 호출할 때 쓴다.

프로세스나 스레드에서 운영체제로 어떠한 요청을 할 때 시스템콜 인터페이스와 커널을 거쳐 운영체제에 전달된다. 

https://velog.io/@jsb12302/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%9D%98-%EC%97%AD%ED%95%A0%EA%B3%BC-%EA%B5%AC%EC%A1%B0

- modebit

시스템 콜이 작동될 때 modebit을 참고해서 유저 모드와 커널 모드를 구분한다. modebit은 0,1을 가지는 플래그 변수로 modebit 0은 커널 모드, 1은 유저 모드다.

유저 프로그램이 I/O요청으로 트랩을 발동하면 올바른 I/O요청인지 확인한 후 유저 모드가 시스템 콜을 통해 커널 모드로 변환되어 실행된다.

유저모드 : 유저가 접근할 수 있는 영역을 제한적으로 두며 컴퓨터 자원에 함부로 침범하지 못하는 모드
커널모드 : 모든 컴퓨터 자원에 접근할 수 있는 모드

https://transferhwang.tistory.com/56

다음은 '직접 접근'과 '시스템콜을 통한 접근'의 차이점을 보여주는 예시이다.

'직접 접근'은 위의 그림처럼 두 응용 프로그램이 자신이 원하는 위치에 데이터를 저장할 수 있다. 그러나 이 과정에서 다른 사람의 데이터를 지울 수도 있고, 자신의 데이터가 다른 사람에 의해 지워질 수도 있다.

그러나 '시스템콜을 통한 접근'은 직접 하드디스크에 데이터를 저장하지 않고, 커널이 제공하는 write() 함수를 사용하여 데이터 저장을 요청한다. 응용 프로그램은 데이터가 어느 위치에 어떤 방식으로 저장되는지 알 수 없고, 이를 확인하기 위해서 read() 함수로 시스템콜을 이용하여 가져와야 한다. 따라서 컴퓨터 자원을 관리하기가 수월하다.

- 시스템 콜의 장점

  • 유저 프로그램은 시스템콜을 기반으로 커널과 분리가 되어 복잡한 파일 시스템과 프로세스 생성 등에 대한 내부동작을 신경 쓸 필요가 없다.
  • 유저모드에서 시스템 콜을 통해서만 커널모드로 진입할 수 있게 했기때문에 시스템의 안정성과 보안이 강화된다. 

3. 커널

프로세스 관리, 메모리 관리, 입출력장치 관리, 파일 관리와 같은 운영체제의 핵심적인 기능을 모아놓은 곳이다. 커널은 자동차의 엔진과 같은 존재로 운영체제의 성능은 커널이 좌우한다.

운영체제는 커널과 인터페이스를 분리하여, 같은 커널을 사용하더라도 다른 인터페이스를 가진 형태로 제작할 수 있다. 따라서 사용자에게 다른 운영체제처럼 보이게 할 수 있다.

4. 드라이버

하드웨어를 제어하기 위한 소프트웨어다.

 

3.1.2 컴퓨터의 요소

컴퓨터는 CPU, DMA 컨트롤러, 메모리, 타이머, 디바이스 컨트롤러 등으로 이루어져 있다.

https://velog.io/@jsb12302/%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EC%9A%94%EC%86%8C

1. CPU

CPU(Central Processing Unit)는 산술논리연산장치, 제어장치, 레지스터로 구성되어 있다. 인터럽트에 의해 메모리에 존재하는 명령어를 해석해 실행한다.

관리자 역할을 하는 운영체제의 커널이 프로그램을 메모리에 올려 프로세스로 만들면 일꾼인 CPU가 처리한다.

2. 제어장치

제어장치는 프로세스 조작을 지시하는 CPU의 한 부품이다. 입출력장치 간 통신을 제어하고 명령어들을 읽고 해석하며 데이터 처리를 위한 순서를 결정한다.

3. 레지스터

레지스터는 CPU 안에 있는 매우 빠른 임시기억장치이다. CPU와 직접 연결되어 있어 연산 속도가 메모리보다 수십 배에서 수백 배까지 빠르다. CPU는 자체적으로 데이터를 저장할 방법이 없기 때문에 레지스터를 거쳐 데이터를 전달한다.

4. 산술논리연산장치

산술논리연산장치(ALU)는 덧셈, 뺄셈 같은 두 숫자의 산술 연산과 베타적 논리합, 논리곱 같은 논리 연산을 처리하는 디지털 회로다.

- CPU의 연산 처리 과정

CPU에서 제어장치, 레지스터, 산술논리연산장치를 통해 연산하는 과정이다.


1. 제어장치가 메모리에 계산할 값을 로드한다. 또한 레지스터에도 로드한다.
2. 제어장치가 레지스터에 있는 값을 계산하라고 산술논리연산장치에 명령한다.
3. 제어장치가 계산된 값을 다시 레지스터에서 메모리로 저장한다.

- 인터럽트

인터럽트는 어떤 신호가 들어왔을 때 CPU를 잠시 정지시키는 것을 말한다. 

프로그램을 실행하는 도중에 예기치 않은 상황이 발생할 경우 현재 실행 중인 작업을 즉시 중단하고, 발생된 상황에 대한 우선 처리가 필요함을 CPU에게 알리는 것이다. 지금 수행 중인 일보다 더 중요한 일(ex. 입출력, 우선 순위 연산 등)이 발생하면 그 일을 먼저 처리하고 나서 하던 일을 계속해야한다. 키보드, 마우스 등 I/O 디바이스로 인한 인터럽트, 0으로 숫자를 나누는 산술 연산에서의 인터럽트, 프로세스 오류 등으로 인해 발생한다.

인터럽트가 발생되면 인터럽트 핸들러 함수가 모여 있는 인터럽트 벡터로 가서 인터럽트 핸들러 함수가 실행된다. 인터럽트 간에는 우선순위가 있고 우선순위에 따라 실행되며, 인터럽트는 하드웨어 인터럽트, 소프트웨어 인터럽트 두 가지로 나뉜다.

1) 하드웨어 인터럽트 : 키보드를 연결한다거나 마우스를 연결하는 일 등의 I/O 디바이스에서 발생하는 인터럽트다. 이때 인터럽트 라인이 설계된 이후 순차적인 인터럽트 실행을 중지하고, 운영체제에 시스템콜을 요청해서 원하는 디바이스로 향해 디바이스에 있는 작은 로컬 버퍼에 접근하여 일을 수행한다.

2) 소프트웨어 인터럽트 : '트랩'이라고도 한다. 프로세스 오류 등으로 프로세스가 시스템콜을 호출할 때 발동한다.

* 인터럽트 발생 처리 과정

주 프로그램이 실행되다가 인터럽트가 발생했다.

현재 수행 중인 프로그램을 멈추고, 상태 레지스터와 PC 등을 스택에 잠시 저장한 뒤에 인터럽트 서비스 루틴으로 간다(잠시 저장하는 이유는, 인터럽트 서비스 루틴이 끝난 뒤 다시 원래 작업으로 돌아와야 하기 때문).

만약 인터럽트 기능이 없었다면, 컨트롤러는 특정한 어떤 일을 할 시기를 알기 위해 계속 체크를 해야 하며, 이를 폴링(Polling)이라고 한다.

폴링을 하는 시간에는 원래 하던 일에 집중할 수가 없게 되어 많은 기능을 제대로 수행하지 못하는 단점이 있었다.

인터럽트 우선순위를 다루는 이유는 인터럽트가 2개 이상 발생하였을 때 먼저 처리할 인터럽트를 정하기 위함이다.

<인터럽트 우선 순위>
1. 전원 공급 이상
2. CPU의 기계적인 오류
3. 외부 신호에 의한 인터럽트
4. 입출력 전송 요청 및 전송 완료, 전송 오류
5. 명령어 오류
6. 프로그램 검사
7. 슈퍼바이저 호출(SVC)

컨트롤러가 입력을 받아들이는 방법(우선순위 판별방법)에는 두 가지가 있다. 둘의 차이는 이벤트를 기다리는 방식이다.

1. 폴링 방식 : 사용자가 명령어를 사용해 입력 핀의 값을 계속 읽어 변화를 알아내는 방식(정기적으로 상태를 조사하는 방식)

소프트웨어적으로 인터럽트 우선순위를 판별하는 방법이다. 우선순위 변경이 용이하고 회로가 간단하고 융통성이 있다는 장점이 있다. 인터럽트가 많은 경우 반응시간이 느리다는 단점이 있다.

2. 인터럽트 방식 : 마이크로컨트롤러 MCU 자체가 하드웨어적으로 변화를 체크하여 변화 시에만 일정한 동작을 하는 방식

  • Daisy Chain
    하드웨어적으로 인터럽트 우선순위를 판별하는 방법으로, 직렬 우선순위 부여 방식이다. 인터럽트가 발생하는 모든 장치를 한 개의 회선에 직렬로 연결하며 우선순위가 높은 장치를 맨 앞에 위치시키며 우선 순위에 따라 연결한다.
  • 병렬 우선순위 부여
    각 장치마다 별개의 회선으로 연결한다. 

인터럽트 방식은 하드웨어로 지원을 받아야 하는 제약이 있지만, 폴링에 비해 신속하게 대응하는 것이 가능하다. 따라서 실시간 대응이 필요할 때는 필수적인 기능이다. 즉, 인터럽트는 발생시기를 예측하기 힘든 경우에 컨트롤러가 가장 빠르게 대응할 수 있는 방법이다.

- DMA 컨트롤러

DMA(Direct Memory Access) 컨트롤러는 I/O 디바이스가 메모리에 직접 접근할 수 있도록 하는 하드웨어 장치를 뜻한다.
CPU에만 너무 많은 인터럽트 요청이 들어오기 때문에 CPU 부하를 막아준다. 또한 하나의 작업을 CPU와 DMA 컨트롤러가 동시에 하는 것을 방지한다.

- 메모리

메모리는 전자회로에서 데이터나 상태, 명령어 등을 기록하는 장치를 말한다. 보통 RAM을 일컬어 메모리라고도 한다. CPU는 계산을, 메모리는 기억을 담당한다.

- 타이머

몇 초안에는 작업이 끝나야 한다는 것을 정하고 특정 프로그램에 시간 제한을 다는 역할을 한다. 시간이 많이 걸리는 프로그램이 작동할 때 제한을 걸기 위해 존재한다.

- 디바이스 컨트롤러

컴퓨터와 연결되어 있는 I/O 디바이스들의 작은 CPU를 말하고, 옆에 붙어 있는 로컬 버퍼는 각 디바이스에서 데이터를 임시로 저장하기 위한 작은 메모리를 뜻한다.


3.2 메모리

CPU는 단지 메모리에 올라와 있는 프로그램의 명령어들을 실행할 뿐이다.

레지스터 : CPU 안에 있는 작은 메모리, 휘발성, 속도가 가장 빠름, 기억 용량이 작음
캐시 : L1, L2 캐시 지칭. 휘발성, 속도 빠름, 기억 용량 작음
주기억장치 : RAM 지칭, 휘발성, 속도 보통, 기억 용량 보통
보조기억장치 : HDD, SDD 지칭, 속도 낮음, 기억 용량 많음

LAM은 하드디스크로부터 일정량의 데이터를 복사해서 임시 저장하고 이를 필요 시마다 CPU에 빠르게 전달하는 역할을 한다. 계층 위로 올라갈수록 가격은 비싸지는데 용량은 작아지고 속도는 빨라진다. 이러한 계층이 있는 이유는 경제성과 캐시 때문이다. 

'로딩 중'이라는 메시지가 나올 때, 이는 하드디스크 또는 인터넷에서 데이터를 읽어 RAM으로 전송하는 과정이 아직 끝나지 않음을 의미한다.

 

# 캐시

캐시는 데이터를 미리 복사해 놓은 임시 저장소이자 빠른 장치와 느린 장치의 속도 차이를 줄이기 위한 메모리 이다. 메모리와 CPU 사이의 속도 차이가 너무 크기 때문에 그 중간에 레지스터 계층을 뒤서 속도 차이를 해결한다.

속도 차이를 해결하기 위해 계층과 계층 사이에 있는 계층을 캐싱 계층이라고 한다. 예를 들어 캐시 메모리와 보조기억장치 사이에 있는 주기억장치를 보조기억장치의 캐싱 계층이라고 할 수 있다.

이러한 역할을 수행하기 위해서는 CPU 가 어떤 데이터를 원할 것인가를 어느 정도 예측할 수 있어야 한다. 캐시의 성능은 작은 용량의 캐시 메모리에 CPU 가 이후에 참조할, 쓸모 있는 정보가 어느 정도 들어있느냐에 따라 좌우되기 때문이다.

이 때 적중율(Hit rate)을 극대화 시키기 위해 데이터 지역성(Locality)의 원리를 사용한다. 지역성의 전제조건으로 프로그램은 모든 코드나 데이터를 균등하게 Access 하지 않는다는 특성을 기본으로 한다. 즉, Locality란 기억 장치 내의 정보를 균일하게 Access 하는 것이 아닌 어느 한 순간에 특정 부분을 집중적으로 참조하는 특성인 것이다.

* 지역성
시간 지역성 : 최근 사용한 데이터에 다시 접근하려는 특성
공간 지역성 : 최근 접근한 데이터를 이루고 있는 공간이나 그 가까운 공간에 접근하는 특성

- 캐시히트캐시미스

캐시에서 원하는 데이터를 찾았다면 캐시히트라고 하며, 해당 데이터가 없다면 메모리로 가서 데이터를 찾아오는 것을 캐시 미스라고 한다.

캐시히트를 하게 되면 해당 데이터를 제어장치를 거쳐 가져오게 된다. 캐시히트의 경우 위치도 가깝고 CPU 내부 버스를 기반으로 작동하기 때문에 빠르다. 반면에 캐시미스가 발생되면 메모리에서 가져오게 되는데 이는 시스템 버스를 기반으로 작동하기 때문에 느리다.

- 캐싱 라인

언급했듯이 캐시(cache)는 프로세서 가까이에 위치하면서 빈번하게 사용되는 데이터를 저장하는 장소이다. 하지만 캐시가 아무리 가까이 있더라도 찾고자 하는 데이터가 어느 곳에 저장되어 있는지 몰라 모든 데이터를 순회해야 한다면 시간이 오래 걸리게 된다. 즉, 캐시에 목적 데이터가 저장되어 있다면 바로 접근하여 출력할 수 있어야 캐시가 의미 있어진다는 것이다.

그렇기 때문에 캐시에 데이터를 저장할 때 특정 자료구조를 사용하여 묶음으로 저장하게 되는데 이를 캐싱 라인 이라고 한다. 프로세스는 다양한 주소에 있는 데이터를 사용하므로 빈번하게 사용하는 데이터의 주소 또한 흩어져 있다. 따라서 캐시에 저장하는 데이터에는 데이터의 메모리 주소 등을 기록해 둔 태그를 달아놓을 필요가 있다. 이러한 태그들의 묶음을 캐싱 라인이라고 하고, 메모리로부터 가져올 때도 캐싱 라인을 기준으로 가져온다. 종류로는 대표적으로 세 가지 방식이 존재한다.

- 캐시 매핑

캐시가 히트되기 전에 매핑하는 방법을 말하며 CPU의 레지스터와 주 메모리 간에 데이터를 주고받을 때를 기반으로 설명한다.

<캐싱 내 메모리 기법>

1. 직접매핑(Direct Mapping) 
메모리 주소와 캐시의 순서를 일치시킨다. 메모리가 1~100까지 있고 캐시가 1~10까지 있다면 1~10까지의 메모리는 캐시의 1에 위치하고 11~20까지의 메모리는 캐시의 2에 위치시키는 것이다. 구현이 간단하고 처리는 빠르지만 저 규칙을 만족시켜서 캐시를 넣다 보면 캐시가 비효율적으로 자꾸 교체되어야 하는 일이 생긴다. 예를 들면 30~40에 해당하는 값을 자꾸 불러다 사용해야 하는데 이를 저장할 캐시 공간은 3 하나 뿐이므로 매번 캐시 교체가 일어나게 된다. 즉 적중률이 낮고 성능이 낮은 단순한 방식이다. 

2. 연관매핑(Associative Mapping) 
순서를 일치시키지 않는다. 필요한 메모리값을 캐시의 어디든 편하게 저장 될 수 있다. 당연히 찾는 과정은 복잡하고 느릴 수 있지만 정말 필요한 캐시들 위주로 저장할 수 있기 때문에 적중률은 높다. 캐시가 일반 메모리보다 속도가 훨씬 빠르므로 캐시의 검색량보단 적중률이 높은게 성능이 더 좋다.

3. 직접연관매핑(Set Associative Mapping) 
연관매핑에 직접매핑을 합쳐 놓은 방식이다. 순서를 일치시키고 편하게 저장하되, 일정 그룹을 두어 그 그룹 내에서 편하게 저장시키는 것이다. 예를 들면 메모리가 1~100까지 있고 캐시가 1~10까지 있다면 캐시 1~5에는 1~50의 데이터를 무작위로 저장시키는 것이다. 블록화가 되어 있기 때문에 검색에는 좀 더 효율적이고, 직접매핑처럼 저장 위치에 대한 큰 제약이 있는건 아니기 때문에 적중률이 많이 떨어지지도 않는다. 

- 웹 브라우저의 캐시

소프트웨어적인 대표적인 캐시로는 웹 브라우저의 작은 저장소 쿠키, 로컬 스토리지, 세션 스토리지가 있다.

1) 쿠키

쿠키는 만료기한이 있는 키-값 저장소이다. 4KB까지 데이터를 저장할 수 있고 만료기한을 정할 수 있다.

2) 로컬 스토리지

로컬 스토리지는 만료 기한이 없는 키-값 저장소이다. 10MB까지 저장할 수 있으며 웹 브라우저를 닫아도 유지되고 도메인 단위로 저장, 생성되며 클라이언트에서만 수정 가능하다.

3) 세션 스토리지

세션 스토리지는 만료기한이 없는 키-값 저장소이다. 탭 단위로 세션 스토리지를 생성하며 탭을 닫을 때 해당 데이터가 삭제된다. 5MB까지 저장 가능하며 클라이언트에서만 수정 가능하다.

- 데이터베이스의 캐싱 계층

데이터베이스 시스템을 구축할 때도 메인 데이터베이스 위에 레디스 데이터 베이스 계층을 캐싱 계층으로 둬서 성능을 향상시키기도 한다.

 

3.2.2 메모리 관리

프로그램이 CPU에서 사용되기 위해서는 메인 메모리(RAM)에 적재되어 있어야 한다. 하지만 점점 커지는 프로그램에 의해 여러 프로세스를 동시에 실행하는 시스템에서는 메모리 용량 부족 이슈가 발생했고 이를 해결하고자 가상 메모리란 기법이 생겨나게 되었다.

1) 가상 메모리(virtual memory)

메모리 관리 기법의 하나로, 컴퓨터가 실제로 이용 가능한 메모리 자원을 추상화하여 이를 사용하는 사용자들에게 매우 큰 메모리로 보이게 만들어 물리적 메모리 부족을 보완한다. 

보조기억(HDD)의 일부를 주기억(RAM)으로 활용하게끔 하여 실제 주기억장치보다 큰 메모리 영역을 제공하는 방법으로도 사용된다.

가상 메모리 기법을 사용하면 메인메모리(RAM)의 속도와 하드디스크의 용량의 장점을 가질 수 있다.

가상 메모리의 핵심은 어떤 프로세스가 실행될 때 메모리에 해당 프로세스 전체가 올라가지 않고 실행에 필요한 일부분만 메모리 올라간다는 점으로 프로세스들의 내용(페이지) 중 현재 실행에서 덜 중요한 것들을 하드 디스크의 공간에 옮겨 놓음으로써 적은 양의 메모리로 큰 효율을 낼 수 있다.

프로세스는 가상 주소(프로그램 상에서 사용자가 보는 주소 공간)를 사용하고, 실제 해당 주소에서 데이터를 읽고 쓸 때만 물리 주소로 바꿔준다.

virtual address (가상 주소): 프로세스가 참조하는 주소 (0~4GB)
physical address (물리 주소): 실제 메모리 주소

페이지 : 메모리를 효율적으로 사용하고자 프로세스를 일정 크기로 나눈 단위. 가상 메모리를 사용하는 최소 크기 단위
프레임 : 실제 메모리를 사용하는 최소 크기 단위

CPU가 특정 프로세스의 어떤 공간을 참조할 때는 우선 가상 주소를 먼저 참조하고, 가상 주소에 해당하는 실제 물리 주소를 참조하게 된다.
가상 주소를 참조할 때마다 매번 물리 주소로 변환되어야하니, 시간을 줄이기 위해 MMU라는 하드웨어 칩의 지원을 받는다. MMU는 가상 주소를 물리 주소로 빠르게 변환해주는 역할을 한다. 

이 덕분에 사용자는 실제 주소를 의식할 필요 없이 프로그램을 구축할 수 있게 된다.

* MMU의 역할

  • 논리 주소를 물리주소로 변환
  • 메모리 보호나 캐시 관리 등 CPU가 메모리에 접근하는 것을 관리하는 하드웨어

* MMU의 메모리 보호

프로세스는 독립적인 메모리 공간을 가져야 되고, 자신의 공간만 접근해야 한다. 따라서 한 프로세스에게 합법적인 주소 영역을 설정하고, 잘못된 접근이 오면 trap을 발생시키며 보호한다.

base와 limit 레지스터를 활용한 메모리 보호 기법

base 레지스터는 메모리상의 프로세스 시작주소를 물리 주소로 저장하고, limit 레지스터는 프로세스의 사이즈를 저장한다.

이로써 프로세스의 접근 가능한 합법적인 메모리 영역(x)은 다음과 같다.

base <= x < base+limit

이 영역 밖에서 접근을 요구하면 trap을 발생시킨다. 안전성을 위해 base와 limit 레지스터는 커널 모드에서만 수정 가능하도록 설계되었다 (사용자 모드에서는 직접 변경할 수 없도록).

다음과 같이 가상주소가 MMU에 의해 실제 주소로 변환할 때 매핑되어있는 페이지 테이블 리스트를 기반으로 변환된다.

TLB

MMU가 물리주소를 찾을 때 바로 페이지 테이블로 가지 않고, 작은 페이지 테이블인 TLB에서 찾아 변환하도록 한다. 메모리와 CPU 사이에 있는 주소 변환을 위한 캐시이다. 페이지 테이블에 있는 리스트를 보관하며 CPU가 페이지 테이블까지 가지 않도록 하여 속도를 향상시킬 수 있는 캐시 계층이다.

* Page table

하드 디스크에 옮겨놓은 내용(페이지)들을 사용하려고 할 때는 Page table을 사용하여 관리한다.

하나의 프로세스는 하나의 페이지 테이블을 가진다. Page table 은 Index 를 키로 해당 페이지에 할당된 메모리(Frame)의 시작 주소를 Value 로 저장하고 있다. valid bit를 사용하여 물리적 메모리에 올라가 있는지 아닌지를 나타낸다.

위의 그림을 보면 page table에는 페이지의 일부만 물리적 메모리에 올라가 있는 것을 볼 수 있다. 

이는 프로그램을 실행 시 프로세스를 구성하는 모든 페이지를 물리적 메모리에 올리지 않고 당장 필요한 페이지만 메모리에 올리기 때문이며, 이런 방식을 요구 페이징(Demand paging)이라고도 한다. 요구 페이징 방식을 사용함으로써 다음과 같은 장점을 얻게 된다.

  • 현재 필요한 페이지만 메모리에 적재하기 때문에 메모리 사용량이 감소한다.
  • 물리적 메모리 용량의 제약에서 벗어날 수 있게 해준다.
  • 프로세스 전체를 메모리에 올리지 않기 때문에 입출력의 오버헤드가 줄어든다.

그리고 요구 페이징 방식을 사용하기 때문에 CPU가 프로그램을 실행하면서 필요한 페이지가 물리적 메모리에 없는 경우도 생기게 되는데 이것을 페이지 폴트(Page Fault)라고 한다. 이 경우 보통은 스왑 영역(가상 메모리라고 생각해도 무방)에서 페이지를 찾아서 물리적 메모리에 로드하게 된다.

스와핑

가상 메모리에는 존재하지만 실제 메모리에는 존재하지 않는 데이터나 코드에 접근할 경우 페이지 폴트가 발생하는데, 스와핑을 통해 마치 페이지 폴트가 발생하지 않은 것처럼 만든다. 스와핑이란 메모리에서 당장 사용하지 않는 영역을 하드디스크로 옮기고, 하드디스크의 일부분을 마치 메모리처럼 불러와 쓰는 것이다.

페이지 교체가 이루어지면 아무일도 없던 것 처럼 프로세스를 계속 수행시켜주면서 사용자가 알지 못하도록 해야 한다. 이때, 아무일도 일어나지 않은 것처럼 하려면, 페이지 교체 당시 오버헤드를 최대한 줄여야 한다.

1. 프로세스 실행 도중 페이지 부재 발생
2. 페이지 폴트를 발생시킨 페이지 위치를 디스크에서 찾음
3. 메모리에 빈 프레임이 있는지 확인. 빈 프레임이 있으면 해당 프레임을 사용
4. 빈 프레임이 없으면, victim 프레임을 선정해 디스크에 기록하고 페이지 테이블을 업데이트함
5. 빈 프레임에 페이지 폴트가 발생한 페이지를 올리고, 페이지 테이블 업데이트

이처럼 빈 프레임이 없는 상황에서 victim 프레임을 비울 때와 원하는 페이지를 프레임으로 올릴 때, 총 두 번의 디스크 접근이 이루어진다. 페이지 교체가 많이 이루어지면, 이처럼 입출력 연산이 많이 발생하게 되면서 오버헤드 문제가 발생한다.

<해결 방법 1>
변경비트를 모든 페이지마다 둬서, victim 페이지가 정해지면 해당 페이지의 비트를 확인
해당 비트가 set 상태면? → 해당 페이지 내용이 디스크 상의 페이지 내용과 달라졌다는 뜻 (즉, 페이지가 메모리 올라온 이후 한번이라도 수정이 일어났던 것. 따라서 이건 디스크에 기록해야 함)
bit가 clear 상태라면? → 디스크 상의 페이지 내용과 메모리 상의 페이지가 정확히 일치하는 상황 (즉, 디스크와 내용이 같아서 기록할 필요가 없음)

=> 비트를 활용해 디스크에 기록하는 횟수를 줄이면서 오버헤드에 대한 수를 최대 절반으로 감소시키는 방법

< 해결 방법2>
페이지 교체 알고리즘을 상황에 따라 잘 선택해야 함
현재 상황에서 페이지 폴트를 발생할 확률을 최대한 줄여줄 수 있는 교체 알고리즘을 사용
FIFO, OPT, LRU...

페이지 폴트(page fault)

프로세스의 주소 공간에는 존재하지만 지금 이 컴퓨터의 물리메모리(RAM)에는 없는 데이터에 접근했을 경우 발생하고, 그 이후 스와핑이 작동한다.

  1. invalid 페이지에 접근하면 MMU가 trap을 발생하여 운영체제에 알린다.
  2. 운영체제는 CPU의 동작을 잠시 멈춘다.
  3. 운영체제는 요구된 페이지를 가상 메모리에서 찾는다.
  4. 해당 페이지를 물리적 메모리의 비어있는 프레임에 로드한다.
  5. 페이지 테이블을 최신화한다.
  6. 중단되었던 CPU를 다시 시작한다.

스레싱(thrasing)

페이지 폴트율이 높은 것을 의미하고 컴퓨터의 심각한 성능 저하를 초래한다. 메모리에 너무 많은 프로세스가 동시에 올라가게 되면 스와핑이 많이 일어나 발생하게 된다.

페이지 폴트가 일어나면 CPU 이용률이 낮아진다. CPU 이용률이 낮아지게 되면 운영체제는 'CPU가 한가한가?'라고 생각하여 가용성을 더 높이기 위해 더 많은 프로세스를 메모리에 올리게 된다. 이같은 악순환이 반복되며 스레싱이 일어나게 된다.

이를 해결하기 위해 메모리를 늘리거나, HDD를 SDD로 바꾸는 방법이 있다.

운영체제에서 해결할 수 있는 방법은 작업세트와 PFF가 있다.

- 작업 세트(working set)

프로세스의 과거 사용 이력인 지역성(locality)를 통해 결정된 페이지 집합을 만들어서 미리 메모리에 로드하는 것이다. 탐색에 드는 비용 및 스와핑을 줄일 수 있다.

- PFF(Page Falut Frequency)


페이지 폴트 빈도를 조절하는 방법으로 상한선과 하한선을 만드는 방법이다. 만약 상한선에 도달하면 프레임을 늘리고, 하한선에 도달하면 프레임을 줄이는 것이다.

2) 메모리 할당

메모리에 프로그램을 할당할 때는 시작 메모리 위치, 메모리의 할당 크기를 기반으로 할당하는데, 연속 할당과 불연속 할당으로 나뉜다.

연속 할당

메모리에 연속적으로 공간을 할당하는 것을 말한다.

  • 고정 분할 방식 : 메모리를 미리 나누어 관리 -> 내부 단편화 발생
  • 가변 분할 방식 : 매 시점 프로그램의 크기에 맞게 동적으로 메모리를 나눠 사용 -> 외부 단편화 발생

가변 분할 방식 종류는 다음과 같다.

  • 최초적합 : 최초로 찾은 홀 할당
  • 최적적합 : 프로세스의 크기보다 큰 공간 중 가장 작은 홀 할당
  • 최악적합 : 프로세스의 크기와 가장 많이 차이나는 홀 할당
* Memory Fragmentation (메모리 단편화)
컴퓨터에서 어떤 프로그램을 실행할 때, 메모리의 공간을 연속적인 형태로 할당하여 사용하게 된다. 이렇기에 프로그램이 메모리에 할당되고, 해제되고, 다시 새로운 프로그램이 할당되고, 해제되고를 반복하다 보면 메모리 공간이 조각조각 나뉘게 되어 실제로는 사용가능한 메모리가 충분히 존재하지만 할당이 불가능한 상태가 발생하게 된다.
이를 메모리 단편화 (Memory Fragmentation) 라 하고, 내부 단편화와 외부 단편화로 나눈다.
- 내부 단편화
메모리를 할당할 때 프로세스가 필요한 양보다 더 큰 메모리가 할당되어서 프로세스에서 사용하는 메모리 공간이 낭비되는 상황
ex) 만약 어떠한 프로그램에 OS 가 5MB 만큼의 메모리를 할당하였지만 실제로는 1MB 만큼의 메모리를 사용하고 있을 경우 -> 4MB 만큼 내부 단편화 발생
- 외부 단편화
메모리가 할당되고 해제되는 작업이 반복적으로 일어날 때 할당된 메모리와 메모리 사이에 중간 중간 사용하지 않는 작은 메모리가 생기는 상황. 메모리 공간을 충분하지만 실제로는 할당할 수 없는 문제 발생


홀 : 할당할 수 있는 비어 있는 메모리 공간

불연속 할당

현대 운영체제가 쓰는 방법으로 메모리를 연속적으로 할당하지 않는다.

메모리 단편화 해결 방법은 다음과 같다.

참고)  페이징(paging), 세그멘테이션(segmentation)은 대표적인 가상 메모리 기법이다.

- 페이징

동일한 크기의 페이지 단위로 나누어 메모리의 서로 다른 위치에 프로세스를 할당한다. 홀의 크기가 균일하지 않은 문제가 없어지지만 주소 변환이 복잡해진다(외부 단편화 해결. 내부 단편화 문제).

  • 프로세스는 페이지로 나누어지고, 물리 메모리는 프레임으로 나뉘어짐. 프레임과 페이지의 크기는 같음
  • 페이지 테이블에는 각 페이지 번호해당 페이지가 할당된 프레임의 시작 물리 주소를 저장
  • CPU는 논리 주소로 명령을 내리고, 이는 메모리로 가기 전에 각 페이지의 실제 메모리 주소가 저장되어 있는 페이지 테이블에서 물리 주소로 변경
  • 할당은 항상 프레임의 정수 배로 할당되는데, 만약 프로세스가 프레임의 정수 배보다 살짝 작다면 할당된 마지막 프레임은 전부 사용되지 않고 남아버리는 문제 발생 (페이지가 클수록 내부 단편화 커짐)
    ex) 페이지 크기가 1,024B 이고 프로세스 A 가 3,172B 의 메모리를 요구한다면 3 개의 페이지 프레임(1,024 * 3 = 3,072) 하고도 100B 가 남기때문에 총 4 개의 페이지 프레임이 필요한 것이다. 결론적으로 4 번째 페이지 프레임에는 924B(1,024 - 100)의 여유 공간이 남게 되는 내부 단편화 문제가 발생

* 페이징 기법에서의 주소 바인딩 과정

<P, d> -> <f, d>
  • P : 페이지 번호
  • d : 변위
  • f : 프레임 번호(또는 프레임 시작 주소)
  1. CPU에서 사용하는 logical address는 페이지 번호(P)와 변위(d)로 구성
  2. Page Table에서 페이지 번호에 해당하는 프레임 시작 주소를 찾는다.
  3. 프레임 시작 주소(f) + 변위(d)를 통해 물리 주소를 계산하여 실제 물리 주소에 접근

이러한 변환 과정은 MMU에서 이루어 지며, 페이지 정보가 캐싱 되어 있을 경우 TLB를 통해 빠르게 접근하도록 한다.

페이지 테이블은 PTE라고 하는 레코드를 갖는데, 이 PTE를 통해 캐싱 되어 있는지 알 수 있다.

* 페이지 테이블 엔트리(PTE, Page Table Entry)

페이지 테이블의 레코드로 프레임 번호와 여러 플레그로 이루어져 구성되어 있다.

포함 정보

  • Frame Number : 프레임 번호
  • Present/Absent : 메인 메모리에 페이지가 존재하는지 확인하는 비트 필드 => 이를 통해 page fault 판별이 가능
  • Protection : 읽기만 가능한 경우 0, 읽기 쓰기 모두 가능한 경우 1
  • Reference : 참조 비트 (최근 참조 됐는지 판단하여 페이지 교체 알고리즘을 적용 시킬 수 있음)
  • Caching : 해당 페이지를 캐싱할지 선택
  • Dirty (or modified bit) : 오염 또는 수정 여부를 판단하는 비트로, 페이지 내용이 변경됐음을 알려 페이지 교체시 하드 디스크에 다시 기록하게 한다.

- 세그멘테이션 

페이징과 다르게 고정된 크기가 아닌 서로 다른 크기의 논리적 단위인 세그먼트(Segment)로 분할한다. 공유와 보안 측면에서 장점을 가지지만 홀 크기가 균일하지 않다(내부 단편화 해결. 외부 단편화 문제).

  • 사용자/프로그래머 관점의 메모리 관리 기법
  • Segment는 페이지 같은 개념이지만, 프로세스를 논리적 내용을 기반으로 나눠서 메모리에 배치
  • 프로세스를 Code, Data, Stack으로 나누는 것 
  • 사용자가 두 개의 주소로 지정(세그먼트 번호 + 변위)
  • 세그먼트 테이블에는 각 세그먼트의 기준(세그먼트의 시작 물리 주소)과 한계(세그먼트의 길이)를 저장
  • CPU에서 해당 세그먼트의 크기를 넘어서는 주소가 들어오면 인터럽트가 발생해서 해당 프로세스를 강제로 종료

- 페이지드 세그멘테이션

프로그램을 의미 단위인 세그먼트로 나눠 공유나 보안 측면에 강점을 두고, 임의의 길이가 아닌 동일한 크기의 페이지 단위로 나눔

  • segment table을 통해 code, data, stack, heap과 같은 세그먼트를 구분하여 page table을 찾음
  • page table을 통해 해당 page에 해당하는 frame을 찾음
  • frame 번호와 변위(offset)을 통해 실제 메모리 주소를 찾음

- 메모리 풀

필요한 공간을 직접 지정하여 미리 할당 받아 사용하고 반납하는 방식이다.

Paging 과 Segmentation 차이
paging 은 고정 크기를 가짐. segmentation 은 가변 크기를 가짐.
paging은 내부 단편화 발생 가능, segmentation 은 외부 단편화 발생 가능.


Paging 과 Segmentation 을 사용하는 이유
메모리 단편화를 해결하기 위한 방법. 다중 프로그래밍 시스템에서 여러 프로세스를 수용하기 위해 메모리를 동적 분할하는 메모리 관리 기법이 필요함.

3) 페이지 교체 알고리즘

메모리는 한정되어 있기 때문에 스와핑이 많이 일어난다. 스와핑이 많이 일어나지 않도록 설계되어야 하며, 이는 페이지 교체 알고리즘을 기반으로 한다.

페이지 부재 발생 → 새로운 페이지를 할당해야 함 → 현재 할당된 페이지 중 어떤 것 교체할 지 결정하는 방법

가상 메모리는 요구 페이지 기법을 통해 필요한 페이지만 메모리에 적재하고 사용하지 않는 부분은 그대로 둔다. 하지만 필요한 페이지만 올려도 메모리는 결국 가득 차게 되고, 올라와있던 페이지가 사용이 다 된 후에도 자리만 차지하고 있을 수 있다.

따라서 메모리가 가득 차면, 추가로 페이지를 가져오기 위해서 쓰지않는 페이지는 out하고, 해당 공간에 현재 필요한 페이지를 in 시켜야 한다. 여기서 어떤 페이지를 out 시켜야할 지 정해야 한다. 이때 out 되는 페이지를 victim page라고 부른다.

기왕이면 수정이 되지 않는 페이지를 선택해야 좋다. 만약 수정되면 메인 메모리에서 내보낼 때, 하드디스크에서 또 수정을 진행해야 하므로 시간이 오래 걸리기 때문이다.

* Page Reference String
CPU는 논리 주소를 통해 특정 주소를 요구한다.
메인 메모리에 올라와 있는 주소들은 페이지의 단위로 가져오기 때문에 페이지 번호가 연속되어 나타나게 되면 페이지 결함이 발생 하지 않는다. 따라서 CPU의 주소 요구에 따라 페이지 결함이 일어나지 않는 부분은 생략하여 표시하는 방법이 바로 Page Reference String이다.

- FIFO

가장 먼저 온 페이지를 가장 먼저 교체한다. 가장 간단한 방법으로, 특히 초기화 코드에서 적절한 방법이다.

victim page : 가장 먼저 메모리에 올라온 페이지

초기화 코드 : 처음 프로세스가 실행될 때 최초 초기화를 시키는 역할만 진행하고 다른 역할은 수행하지 않으므로, 메인 메모리에서 빼도 괜찮다. 하지만 처음 프로세스 실행 시에는 무조건 필요한 코드이므로, FIFO 알고리즘을 사용하면 초기화를 시켜준 후 가장 먼저 내보내는 것이 가능하다.

- OPT(Optimal Page Replacement)

앞으로 가장 사용하지 않을 페이지를 가장 우선적으로 내보낸다.

FIFO에 비해 페이지 결함의 횟수를 많이 감소시킬 수 있다. 하지만, 실질적으로 페이지가 앞으로 잘 사용되지 않을 것이라는 보장이 없기 때문에 수행하기 어려운 알고리즘이다.


- LRU(Least Recently Used)

가장 오랫동안 참조되지 않은 페이지를 교체한다. 보통 해시 테이블과 이중 연결 리스트로 구현한다. 

  • 해시 테이블은 이중 연결 리스트에서 빠른 검색을 위해 사용된다.
  • 이중 연결 리스트는 최근 사용된 요소를 추적하기 위해 사용된다.

리스트의 맨 앞은 가장 최근 사용된 아이템이다. 맨 끝은 가장 적게 사용된 아이템이다.

OPT의 경우 미래 예측이지만, LRU의 경우는 과거를 보고 판단하므로 실질적으로 사용이 가능한 알고리즘(실제로도 최근에 사용하지 않은 페이지는 앞으로도 사용하지 않을 확률이 높다이다. OPT보다는 페이지 결함이 더 일어날 수 있지만, 실제로 사용할 수 있는 페이지 교체 알고리즘에서는 가장 좋은 방법 중 하나이다.

* 교체 방식

  1. Global 교체 : 메모리 상의 모든 프로세스 페이지에 대해 교체하는 방식
  2. Local 교체 : 메모리 상의 자기 프로세스 페이지에서만 교체하는 방식

다중 프로그래밍의 경우, 메인 메모리에 다양한 프로세스가 동시에 올라올 수 있다. 따라서, 다양한 프로세스의 페이지가 메모리에 존재한다.

페이지 교체 시, 다양한 페이지 교체 알고리즘을 활용해 victim page를 선정하는데, 선정 기준을 Global로 하느냐, Local로 하느냐에 대한 차이가 있다.

→ 실제로는 전체를 기준으로 페이지를 교체하는 것이 더 효율적이라고 한다. 자기 프로세스 페이지에서만 교체를 하면, 교체를 해야할 때 각각 모두 교체를 진행해야 하므로 비효율적이다.

- NUR(Not Used Recently)

일명 clock 알고리즘이며, 0과 1을 가진 비트를 둔다. 1은 최근에 참조되었고 0은 참조되지 않음을 나타낸다. 시계 방향으로 돌면서 0을 찾고 0을 찾은 순간 해당 프로세스를 교체하고 1로 바꾼다.



- LFU(Least Frequently Used) 

가장 참조 횟수가 적은 페이지를 교체한다. 즉 많이 사용되지 않은 것을 교체한다.

반응형