옆히

혼자 공부하는 컴퓨터 구조 + 운영체제 본문

개인공부용1/cs

혼자 공부하는 컴퓨터 구조 + 운영체제

옆집히드라 2024. 6. 18. 21:48
혼자 공부하는 컴퓨터 구조 + 운영체제 - 10점
강민철 지음/한빛미디어

 

더보기

Chapter 01 컴퓨터 구조 시작하기

01-1 구조를 알아야 하는 이유
__문제 해결
__성능, 용량, 비용
[2가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

01-2 컴퓨터 구조의 큰 그림
__컴퓨터가 이해하는 정보
__컴퓨터의 4가지 핵심 부품
[7가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 02 데이터

02-1 0과 1로 숫자를 표현하는 방법
__정보 단위
__이진법
__십육진법
[5가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

02-2 0과 1로 문자를 표현하는 방법
__문자 집합과 인코딩
__아스키 코드
__EUC-KR
__유니코드와 UTF-8
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 03 명령어

03-1 소스 코드와 명령어
__고급 언어와 저급 언어
__컴파일 언어와 인터프리터 언어
[좀 더 알아보기] 목적 파일 vs 실행 파일
[6가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

03-2 명령어의 구조
__연산 코드와 오퍼랜드
__주소 지정 방식
[좀 더 알아보기] 스택과 큐
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 04 CPU와 작동 원리

04-1 ALU와 제어장치
__ALU
__제어장치
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

04-2 레지스터
__반드시 알아야 할 레지스터
__특정 레지스터를 이용한 주소 지정 방식(1): 스택 주소 지정 방식
__특정 레지스터를 이용한 주소 지정 방식(2): 변위 주소 지정 방식
[좀 더 알아보기] 상용화된 CPU 속 레지스터 및 주소 지정 방식
[8가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

04-3 명령어 사이클과 인터럽트
__명령어 사이클
__인터럽트
[좀 더 알아보기] 예외의 종류
[5가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 05 CPU 성능 향상 기법

05-1 빠른 CPU를 위한 설계 기법
__클럭
__코어와 멀티 코어
__스레드와 멀티스레드
[5가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

05-2 명령어 병렬 처리 기법 1
__명령어 파이프라인
__슈퍼스칼라
__비순차적 명령어 처리
[3가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

05-3 CISC와 RISC
__명령어 집합
__CISC
__RISC
[3가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 06 메모리와 캐시 메모리

06-1 RAM의 특징과 종류
__RAM의 특징
__RAM의 용량과 성능
__RAM의 종류
[6가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

06-2 메모리의 주소 공간
__물리 주소와 논리 주소
__메모리 보호 기법
[5가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

06-3 캐시 메모리
__저장 장치 계층 구조
__캐시 메모리
__참조 지역성 원리
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 07 보조기억장치

07-1 다양한 보조기억장치
__하드 디스크
__플래시 메모리
[6가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

07-2 RAID의 정의와 종류
__RAID의 정의
__RAID의 종류
[6가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 08 입출력장치

08-1 장치 컨트롤러와 장치 드라이버
__장치 컨트롤러
__장치 드라이버
[2가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

08-2 다양한 입출력 방법
__프로그램 입출력
__인터럽트 기반 입출력
__ DMA 입출력
[6가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 09 운영체제 시작하기

09-1 운영체제를 알아야 하는 이유
__운영체제란
__운영체제를 알아야 하는 이유
[2가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

09-2 운영체제의 큰 그림
__운영체제의 심장, 커널
__이중 모드와 시스템 호출
__운영체제의 핵심 서비스
[좀 더 알아보기] 가상 머신과 이중 모드의 발전
[좀 더 알아보기] 시스템 호출의 종류
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 10 프로세스와 스레드

10-1 프로세스 개요
__프로세스 직접 확인하기
__프로세스 제어 블록
__문맥 교환
__프로세스의 메모리 영역
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

10-2 프로세스 상태와 계층 구조
__프로세스 상태
__프로세스 계층 구조
__프로세스 생성 기법
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

10-3 스레드
__프로세스와 스레드
__멀티프로세스와 멀티스레드
[3가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 11 CPU 스케줄링

11-1 CPU 스케줄링 개요
__ 프로세스 우선순위
__스케줄링 큐
__선점형과 비선점형 스케줄링
[7가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

11-2 CPU 스케줄링 알고리즘
__스케줄링 알고리즘의 종류
[5가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 12 프로세스 동기화

12-1 동기화란
__동기화의 의미
__ 생산자와 소비자 문제
__공유 자원과 임계 구역
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

12-2 공유 자원과 임계 구역
__뮤텍스 락
__세마포
__모니터
[3가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 13 교착 상태

13-1 교착 상태란
__식사하는 철학자 문제
__자원 할당 그래프
__교착 상태 발생 조건
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

13-2 교착 상태 해결 방법
__교착 상태 예방
__교착 상태 회피
__교착 상태 검출 후 회복
[3가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 14 가상 메모리

14-1 연속 메모리 할당
__스와핑
__메모리 할당
__외부 단편화
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

14-2 페이징을 통한 가상 메모리 관리
__페이징이란
__페이지 테이블
__페이징에서의 주소 변환
__페이지 테이블 엔트리
[좀 더 알아보기] 페이징의 이점 - 쓰기 시 복사
[좀 더 알아보기] 계층적 페이징
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

14-3 페이지 교체와 프레임 할당
__요구 페이징
__페이지 교체 알고리즘
__스래싱과 프레임 할당
[4가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

Chapter 15 파일 시스템

15-1 파일과 디렉터리
__파일
__디렉터리
[좀 더 알아보기] 상대 경로를 나타내는 또 다른 방법
[7가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

15-2 파일 시스템
__파티셔닝과 포매팅
__파일 할당 방법
__파일 시스템 살펴보기
[좀 더 알아보기] 저널링 파일 시스템
[좀 더 알아보기] 마운트
[7가지 키워드로 정리하는 핵심 포인트]
[확인 문제]

____정답 및 해설
____찾아보기

```메모록```

chapter 01 컴퓨터 구조 시작하기

컴퓨터 구조를 이해하면 문법만으로는 알기 어려운 성능/용량/비용을 고려하며 개발할 수 있습니다.

컴퓨터가 이해하는 정보에는 데이터와 명령어가 있음

  • 메모리: 현재 실행되는 프로그램의 명령어와 데이터를 저장하는 부품
  • CPU: 메모리에 저장된 명령어를 읽어 들이고, 해석하고, 실행하는 부품
  • 보조기억장치: 전원이 꺼져도 보관할 프로그램을 저장하는 부품
  • 입출력장치: 컴퓨터 외부에 연결되어 컴퓨터 내부와 정보를 교환할 수 있는 부품
  • 시스템 버스: 컴퓨터의 네 가지 핵심 부품들이 서로 정보를 주고받는 통로; 메인보드

#메모리는 현재 실행되는 프로그램의 명령어와 데이터를 저장하는 부품이다. 즉, 프로그램이 실행되려면 반드시 메모리에 저장되어 있어야 한다. 메모리 - 실행할 정보를 저장; 보조기억장치 - 보관할 정보를 저장

 

#CPU의 내부 구성 요소

  • 산술논리연산장치(ALU: Arithmetic Logic Unit): 계산하는 장치
  • 레지스터(register): 임시 저장 장치
  • 제어장치(CU: Control Unit): 제어신호(control signal)라는 전기 신호(메모리 읽기 || 메모리 쓰기)를 내보내고 명령어를 해석하는 장치

#시스템 버스의 구성 요소

  • 주소 버스(address bus)
  • 데이터 버스(data bus)
  • 제어 버스(control bus)

chapter 02 데이터

1바이트 -> 10^3 킬로바이트 -> 10^6 메가바이트 -> ... ^(n+3) ...

1024개씩 묶은 단위 -> KiB, MiB, GiB

 

워드(word): CPU가 한 번에 처리할 수 있는 정보의 크기 단위

half word(워드 절반), full word(워드 크기), double word(워드의 두 배 크기)

 

#2의 보수(two's complement): 어떤 수를 그보다 큰 2^n에서 뺀 값

  1. 이진수의 0과 1을 뒤집는다(1의 보수 구함)
  2. 구한 수에 1을 더하면 2의 보수를 구함

2의 보수를 사용해서 0과 1만으로 음수를 표현할 수 있지만 0000, 1111, 2^n 형태의 이진수에는 원하는 음수 값을 구할 수 없다.

 

문자 집합(character set): 컴퓨터가 인식하고 표현할 수 있는 문자의 모음

문자 인코딩(character encoding): A -> 1000 0001

문자 디코딩(character decoding): 1000 0001 -> A

c.f.) 코드 포인트(code point)

 

#utf-8 인코딩

UTF(Unicode Transformation Format) == 유니코드 인코딩 방법

가변 길이 인코딩: 인코딩 결과가 1바이트 ~ 4바이트

인코딩 결과가 몇 바이트가 될지는 유니코드에 부여된 값에 따라 다름.

chapter 03 명령어

고급 언어 <-> 저급 언어(기계어(machine code), 어셈블리어)

하드웨어와 밀접하게 맞닿아 있는 프로그램을 개발하는 임베디드 개발자, 게임 개발자, 정보 보안 분야 등의 개발자는 어셈블리어를 많이 이용합니다. 그리고 이러한 분야의 개발자들에게 어셈블리어란 '작성의 대상'일 뿐만 아니라 매우 중요한 '관찰의 대상'이기도 합니다. 어셈블리어를 읽으면 컴퓨터가 프로그램을 어떤 과정으로 실행하는지, 즉 프로그램이 어떤 절차로 작동하는지를 가장 근본적인 단계에서부터 하나하나 추적하고 관찰할 수 있기 때문입니다.

 

컴파일러 언어 <-> 인터프리터 언어

 

#소스코드(고급 언어) -> 컴파일러(컴파일) -> 목적 코드(저급 언어)

컴파일 언어로 작성된 소스 코드는 컴파일러에 의해 저급 언어로 변환되고(이 과정을 컴파일이라고 합니다), 컴파일 결과로 저급 언어인 목적 코드가 생성됩니다.

목적 파일 -> 링킹 -> 실행 파일

 

현대의 많은 프로그래밍 언어 중에는 컴파일 언어와 인터프리터 언어 간의 경계가 모호한 경우가 많다.

 

명령어는 연산코드와 오퍼랜드로 구성; 명령어 = operation code + operand; 연산 코드 필드(주소 필드) - 오퍼랜드 필드

명령어에 있는 오퍼랜드의 개수에 따라서 0-주소 명령어, 1-주소 명령어, 2-주소 명령어, 3-주소 명령어

 

#기본적인 연산 코드의 유형

1.데이터 전송

  1. MOVE: 데이터를 옮겨라
  2. STORE: 메모리에 저장하라
  3. LOAD(FETCH): 메모리에서 CPU로 데이터를 가져와라
  4. PUSH: 스택에 데이터를 저장하라
  5. POP: 스택의 최상단 데이터를 가져와라

2.산술/논리 연산

  1. ADD / SUBTRACT / MULTIPLY / DIVIDE: 덧셈 / 뺄셈 / 곱셈 / 나눗셈을 수행하라
  2. INCREMENT / DECREMENT: 오퍼랜드에 1을 더하라 / 오퍼랜드에 1을 빼라
  3. AND / OR / NOT: AND / NOT: OR / NOT 논리 연산을 수행하라
  4. COMPARE: 두 개의 숫자 또는 TRUE / FALSE 값을 비교하라

3.제어 흐름 변경

  1. JUMP: 특정 주소로 실행 순서를 옮겨라
  2. CONDITIONAL JUMP: 조건에 부합할 때 특정 주소로 실행 순서를 옮겨라
  3. HALT: 프로그램의 실행을 멈춰라
  4. CALL: 되돌아올 주소를 저장한 채 특정 주소로 실행 순서를 옮겨라
  5. RETUREN: CALL을 호출할 때 저장했던 주소로 돌아가라

4.입출력 제어

  1. READ(INPUT): 특정 입출력 장치로부터 데이터를 읽어라
  2. WRITE(OUTPUT): 특정 입출력 장치로 데이터를 써라
  3. START IO: 입출력 장치를 시작하라
  4. TEST IO: 입출력 장치의 상태를 확인하라

 

연산 코드에 사용할 데이터가 저장된 위치, 즉 연산의 대상이 되는 데이터가 저장된 위치를 유효주소(effective address)라고 하고, 오퍼랜드 필드에 데이터가 저장된 위치를 명시할 때 연산에 사용할 데이터 위치를 찾는 방법을 주소 지정 방식(addressing mode)라고 한다.

#주소 지정 방식(addressing mode)

  • 즉시 주소 지정 방식(immediate addresing mode)
  • 직접 주소 지정 방식(direct addresing mode)
  • 간접 주소 지정 방식(indirect addresing mode)
  • 레지스터 주소 지정 방식(register addresing mode)
  • 레지스터 간접 주소 지정 방식(register indirect addresing mode): 유효 주소를 찾는 과정이 간접 주소 지정 방식과 비슷하지만, 메모리에 접근하는 횟수가 한 번으로 줄어듬

Compiler Explorer (godbolt.org)

 

chapter 04 CPU의 작동 원리

플래그 종류 의미 사용 예시
부호 플래그 연산한 결과의 부호를 나타낸다 부호플래그가 1일 경우 계산 결과는 음수, 0일 경우 계산 결과는 양수를 의미한다
제로 플래그 연산 결과가 0인지 여부를 나타낸다 제로 플래그가 1일 경우 연산결과는 0, 0일 경우 연산 결과는 0이 아님을 의미한다
캐리 플래그 연산 결과 올림수나 빌림수가 발생했는지를 나타낸다 캐리 플래그가 1일 경우 올림수나 빌림수가 발생했음을 의미하고, 0일 경우 발생하지 않았음을 의미한다
오버플로우 플래그 오버플로우가 발생했는지를 나타낸다 오버플로우 플래그가 1일 경우 오버플로우가 발생했음을 의미하고, 0일 경우 발생하지 않았음을 의미한다
인터럽트 플래그  인터럽트가 가능한지를 나타낸다. 인터럽트 플래그가 1일 경우 인터럽트가 가능함을 의미하고, 0일 경우 인터럽트가 불가능함을 의미한다.
슈퍼바이저 플래그 커널 모드로 실행 중인지, 사용자 모드로 실행 중인지를 나타낸다. 슈퍼바이저 플래그가 1일 경우 커널 모드로 실행 중임을 의미하고, 0일 경우 사용자 모드로 실행 중임을 의미한다

#제어장치는 CPU 내부와 외부로 제어 신호를 내보내고, 명령어를 해석하는 부품이다; 제어 신호는 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호

  1. 제어장치는 클럭 신호를 받아들인다; 컴퓨터의 모든 부품이 클럭 신호에 맞춰 작동한다
  2. 제어장치는 '해석해야 할 명령어'를 받아들인다; 명령어 레지스터에 저장
  3. 제어장치는 플래그 레지스터 속 플래그 값을 받아들인다
  4. 제어장치는 시스템 버스, 그중에서 제어 버스로 전달된 제어 신호를 받아들인다

#반드시 알아야 할 레지스터

  1. 프로그램 카운터(PC: Program Counter): 메모리에서 가져올 명령어의 주소, 즉 메모리에서 읽어 들일 명령어의 주소를 저장함; 명령어 포인터(IP: Instruction Pointer)라고 부르는 CPU도 있다; 메모리 버퍼 레지스터로 데이터가 전송되면 PC는 증가
  2. 명령어 레지스터(IR:Instruction Register): 해석할 명령어, 즉 방금 메모리에서 읽어 들인 명령어를 저장하는 레지스터임; 제어장치는 명령어 레지스터 속 명령어를 받아들이고 이를 해석한 뒤 제어신호를 내보낸다
  3. 메모리 주소 레지스터(MAR:Memory Address Register):메모리의 주소를 저장하는 레지스터이다; CPU가 읽어 들이고자 하는 주소 값을 주소 버스로 보낼 때 메모리 주소 레지스터를 거치게 된다
  4. 메모리 버퍼 레지스터(MBR:Memory Buffer Register): 메모리와 주고받을 값(데이터와 명령어)을 저장하는 레지스터이다; 데이터 버스로 주고받을 값은 메모리 버퍼 레지스터를 거친다; 메모리 데이터 레지스터(MDR:Memory Data Register)라고도 부른다
  5. 범용 레지스터(general purpose register): 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터
  6. 플래그 레지스터(flag register): ALU 연산 결과에 따른 플래그를 플래그 레즈스터에 저장
  7. 스택 포인터(stack pointer): 스택의 꼭대기를 가리키는 레지스터
  8. 베이스 레지스터(base register)

#스택 주소 지정 방식: 스택과 스택 포인터를 이용한 주소 지정 방식; 메모리 안에 스택 영역을 사용

 

#변위 주소 지정 방식(displacement addressing mode): 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식임

  1. 상대 주소 지정 방식(relative addressing mode): 오퍼랜드와 프로그램 카운터(PC)의 값을 더하여 유효 주소를 얻음
  2. 베이스 레지스터 주소 지정 방식(base-register addressing mode): 오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는 방식임

kangtegong/self-learning-cs: 『혼자 공부하는 컴퓨터구조 & 운영체제』 (한빛미디어) 심화자료 (github.com)

 

GitHub - kangtegong/self-learning-cs: 『혼자 공부하는 컴퓨터구조 & 운영체제』 (한빛미디어) 심화자료

『혼자 공부하는 컴퓨터구조 & 운영체제』 (한빛미디어) 심화자료. Contribute to kangtegong/self-learning-cs development by creating an account on GitHub.

github.com

 

#명령어 사이클(instruction cycle): 실행하는 프로그램은 수많은 명령어로 이루어져 있고, CPU는 이 명령어들을 하나씩 실행한다. 이때 프로그램 속 각각의 명령어들은 일정한 주기가 반복되며 실행되는데, 이 주기를 명령어 사이클이라 한다.

  • 인출 사이클(fetch cycle): 메모리에 있는 명령어를 CPU로 가지고 오는 단계
  • 실행 사이클(execution cycle): CPU로 가져온 명령어를 실행하는 단계
  • 간접 사이클(indirect cycle): 간접 주소 지정 방식은 오퍼랜드 필드에 유효 주소의 주소를 명시하는데, 이 경우 명령어를 인출하여 CPU로 가져왔다 하더라도 바로 실행 사이클에 돌입이 불가하다 따라서 메모리 접근을 한 번 더 해야 하는데 이 단계가 간접 사이클이다
  • 인터럽트 사이클(interrupt cycle)

#인터럽트(interrupt): CPU의 작업을 방해하는 신호

  1. 동기 인터럽트(synchronous interrupt): CPU에 의해 발생하는 인터럽트; CPU가 명령어들을 수행하다가 예상치 못한 상황에 마주쳤을 때, 가령 CPU가 실행하는 프로그래밍상의 오류와 같은 예외적인 상황에 마주쳤을 때 발생하는 인터럽트가 동기 인터럽트이다; 예외(exception)라고도 부름
  2. 비동기 인터럽트(asynchronous interrupt): 주로 입출력장치에 의해 발생하는 인터럽트; 하드웨어 인터럽트(본 책에서 부르는 용어)

CPU가 인터럽트 요청을 수용하기 위해서는 플래그 레지스터의 인터럽트 플래그interrupt flag)가 활성화되어 있어야 한다.

 

하드웨어 인터럽트에는 인터럽트 플래그로 막을 수 있는 인터럽트(maskable interrupt)와 막을 수 없는 인터럽트(non maskable interrupt)(정전이나 하드웨어 고장 등)가 있다

 

#하드웨어 인터럽트 처리 순서

  1. 입출력장치는 CPU에 인터럽트 요청 신호를 보낸다
  2. CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인한다
  3. CPU는 인터럽트 요청을 확인하고 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 여부를 확인한다
  4. 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업한다
  5. CPU는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행한다
  6. 인터럽트 서비스 루틴 실행이 끝나면 4에서 백업해 둔 작업을 복구하여 실행을 재개한다

인터럽트 서비스 루틴(ISR: Interrupt Service Routine)은 인터럽트를 처리하기 위한 프로그램으로 인터럽트 핸들러(interrupt handler)라고도 부른다

인터럽트 벡터(interrupt vector)는 인터럽트 서비스 루틴을 식별하기 위한 정보이다; CPU는 하드웨어 인터럽트 요청을 보낸 대상으로부터 데이터 버스를 통해 인터럽트 벡터를 전달받는다

 

#예외의 종류

예외가 발생하면 CPU는 하던 일을 중단하고 해당 예외를 처리한다. 예외를 처리하고 나면 CPU는 다시 본래 하던 작업으로 되돌아와 실행을 재개한다.

  • 폴트(falut): 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개하는 예외; ex)CPU가 한 명령어를 실행하려 하는데, 이 명령어를 실행하기 위해 꼭 필요한 데이터가 메모리가 아닌 보조기억장치에 있다고 하면 CPU는 폴트를 발생시키고 필요한 데이터를 다시 메모리로 가져와 저장한 뒤 폴트가 발생한 명령어부터 실행을 재개한다.
  • 트랩(trap): 예외를 처리한 직후 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외; ex)디버깅
  • 중단(abort): CPU가 실행 중인 프로그램을 강제로 중단시킬 수밖에 없는 심각한 오류를 발견했을 때 발생하는 예외
  • 소프트웨어 인터럽트(software interrupt): 시스템 호출이 발생했을 때 나타남 -> 9장

chapter 05 CPU 성능 향상 기법

CPU는 계속 일정한 클럭 속도를 유지하기보다는 고성능을 요하는 순간에는 순간적으로 클럭 속도를 높이고, 그렇지 않을 때는 유연하게 클럭 속도를 낮추기도 합니다. 최대 클럭 속도를 강제로 더 끌어올릴 수도 있는데, 이런 기법을 오버클럭킹(overclocking)이라고 합니다.

 

우리가 지금까지 CPU의 정의로 알고 있었던 '명령어를 실행하는 부품'은 오늘날 코어(core)라는 용어로 사용됩니다. 오늘날의 CPU는 단순히 '명령어를 실행하는 부품'에서 '명령어를 실행하는 부품을 여러 개 포함하는 부품'으로 명칭의 범위가 확장되었습니다. 코어를 여러 개 포함하고 있는 CPU를 멀티코어(multi-core) CPU 또는 멀티코어 프로세서라고 부릅니다.

 

#스레드(thread): 사전적 의미는 '실행 흐름의 단위'

  • 하드웨어적 스레드: 하나의 코어가 동시에 처리하는 명령어 단위'
  • 소프트웨어적 스레드: 하나의 프로그램에서 독립적으로 실행되는 단위

하나의 코어로 여러 명령어를 동시에 처리하는 CPU를 멀티스레드(multisthread) 프로세서 또는 멀티스레드 CPU라고 한다. //인텔의 멀티스레드 기술 -> 하이퍼스레딩(hyper-threading)

하드웨어 스레드를 논리 프로세서(logical processor)라고도 부른다

 

코어는 명령어를 실행할 수 있는 '하드웨어 부품'이고, 스레드는 '명령어를 실행하는 단위'이다; 멀티코어 프로세서는 명령어를 실행할 수 있는 하드웨어 부품이 CPU 안에 두 개 이상 있는 CPU를 의미하고, 멀티스레드 프로세서는 하나의 코어로 여러개의 명령어를 동시에 실행할 수 있는 CPU를 의미한다.

 

슈퍼스칼라

 

#명령어 병렬 처리 기법(ILP:Instruction-Level Parallelism)

  • 명령어 파이프 라이닝
  • 슈퍼스칼라
  • 비순차적 명령어 처리

#명령어 처리 과정을 클럭 단위로 나누어 보면 일반적으로 다음과 같이 나눌 수 있다

  1. 명령어 인출(Instruction Fetch)
  2. 명령어 해석(Instruction Decode)
  3. 명령어 실행(Execute Instruction)
  4. 결과 저장(Write Back)

#파이프라인 위험(pipeline hazard): 파이프라이닝이 높은 성능을 가져오기는 하지만, 특정 상황에서는 성능 향상에 실패하는 경우도 있음

  • 데이터 위험(data hazard): 명령어 간 '데이터 의존성'에 의해 발생함. ex)R1 <- R2 + R3; R4 <- R1 + R2 와 같은 두 명령어는 순서가 바뀌면 제대로 작동하지 않음
  • 제어 위험(control hazard): 주로 분기 등으로 인한 '프로그램 카운터의 갑작스러운 변화'에 의해 발생함; 이를 방지하기 위해 사용하는 기술 중 하나가 분기 예측(branch prediction)
  • 구조적 위험(structural hazard): 명령어를 겹쳐 실행하는 과정에서 서로 다른 명령어가 동시에 ALU, 레지스터 등과 같은 CPU 부품을 사용하려고 할 때 발생함. 구조적 위험은 자원 위험(resource hazard)이라고도 부른다.

#슈퍼스칼라(superscalar): CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조; 슈퍼스칼라 구조로 명령어 처리가 가능한 CPU를 슈퍼스칼라 프로세서 또는 슈퍼스칼라 CPU라고 한다. 이론적으로 파이프라인 개수에 비례하여 프로그램 처리 속도가 빨라지지만 여러 개의 파이프라인을 이용하면 하나의 파이프라인을 사용할 때보다 데이터 위험, 제어 위험, 자원 위험을 피하기가 더욱 까다롭다.

 

#비순차적 명령어 처리(OoOE: Out-of-order execution): 명령어를 순차적으로만 실행하지 않고 순서를 바꿔 실행해도 무방한 명령어를 먼저 실행하여 명령어 파이프라인이 멈추는 것을 방지하는 기법; 파이프라인 위험으로 인한 파이프라인의 중단을 방지하고 파이프라인을 계속 가동시켜준다

 

#CISC(Complex Instruction Set Computer): 명령어 집합이 복잡하고 다양한 기능을 제공해줘서 적은 수의 명령으로 프로그램을 동작시키고 메모리를 절약할 수 있지만, 명령어의 규격화가 어려워 파이프라이닝이 어렵다

#RISC(Reduced Instruction Set Computer): 짧고 규격화된 명령어, 되도록 1클럭 내외로 실행되는 명령어를 지향함

 

RISC는 메모리에 직접 접근하는 명령어를 load, store 두 개로 제한할 만큼 메모리 접근을 단순화하고 최소화를 추구한다.; 이런 점에서 RISC를 load-store 구조라고도 부른다

chapter 06 메모리와 캐시 메모리

  • 휘발성 저장 장치(volatile memory): RAM
  • 비휘발성 저장 장치(non-volatile memory): SSD, CD-ROM, USB 

RAM에는 실행할 프로그램의 명령어와 데이터가 저장됨

#RAM의 종류

  • DRAM: Dynamic RAM의 준말로 시간이 지나면 저장된 데이터가 점차 사라져서 데이터의 소멸을 막기 위해 일정 주기로 데이터를 재활성화(다시 저장)해야 함 -> 소비 전력이 비교적 낮고, 저렴하고, 집적도가 높아서 대용량 설계 용이; 주기억장치(RAM)
  • SRAM: Static RAM의 준말로 저장된 데이터가 변하지 않음 -> 일반적으로 DRAM보다 속도가 빠르다; 캐시 메모리
  • SDRAM: Synchronous Dynamic RAM은 클럭 신호와 동기화됨(클럭 타이밍에 맞춰 CPU와 정보를 주고받을 수 있음)
  • DDR SDRAM: Double Data Rate SDRAM 대역폭(data rate)을 넓혀 속도를 빠르게 만듬 <-> 한 클럭당 하나씩 데이터를 주고받을 수 있는 SDRAM을 SDR(Single Data Rate) SDRAM이라고 부르기도 함

SDR -> DDR -> DDR2 -> DDR4 // SDR 보다 1 -> 2^1 -> 2^2 -> 2^4 숫자배 넓음

 

chapter 07 보조기억장치

#하드 디스크가 데이터의 접근하는 시간

  • 탐색 시간(seek time): 접근하려는 데이터가 저장된 트랙까지 헤드를 이동시키는 시간
  • 회전 지연(rotational latency): 헤드가 있는 곳으로 플래터를 회전시키는 시간
  • 전송 시간(transfer time): 하드 디스크와 컴퓨터 간에 데이터를 전송하는 시간

 

  • 단일 헤드 디스크(single-head disk) = 이동 헤드 디스크(movable-head disk)
  • 다중 헤드 디스크(multiple-head disk) = 고정 헤드 디스크(fixed-head disk): 탐색 시간 = 0

 

  • NAND 플래시 메모리
  • NOR 플래시 메모리

#플래시 메모리에는 셀(cell)이라는 단위가 있다. 한 셀에 저장할 수 있는 비트의 수마다 

SLC(Single Level Cell), MLC(Multiple Level Cell), TLC(Triple Level Cell) 타입이 있다

셀당 bit가 늘수록 수명이 짧아지고 읽기/쓰기 속도가 느려지는 대신 용량 대비 가격이 낮다.

 

#셀(cell)들이 모여 만들어진 단위를 페이지(page), 그리고 페이지가 모여 만들어진 단위를 블록(block)이라고 한다. 블록이 모여 플레인(plane), 플레인이 모여 다이(die)가 된다.

 

플래시 메모리에서 읽기와 쓰기는 페이지 단위로 이루어진다. 하지만 삭제는 페이지보다 큰 블록 단위로 이루어진다. 이때 페이지는 세 개의 상태를 가질 수 있다.

  • Free 상태: 어떠한 데이터를 저장하고 있지 않아 새로운 데이터를 저장할 수 있는 상태
  • Valid 상태: 이미 유효한 데이터를 저장하고 있는 상태
  • Invalid 상태: 쓰레기값이라 부르는 유효하지 않은 데이터를 저장하고 있는 상태

SSD를 비롯한 플래시 메모리는 페이지 단위로 기록하지만 삭제는 블록 단위여서 페이지 내 값을 수정할려면 수정하려는 값은 Invalid 상태로 변경하고 새로 페이지를 저장해야 함

#가비지 컬렉션

  1. 유효한 페이지들만을 새로운 블럭으로 복사함
  2. 기존의 블록을 삭제함

RAID(Redundant Array of Independent Disks): 주로 하드 디스크와 SSD를 사용하는 기술로, 데이터의 안전성 혹은 높은 성능을 위해 여러 개의 물리적 보조기억장치를 마치 하나의 논리적 보조기억장치처럼 사용하는 기술을 의미함.

  • RAID 0: 여러 개의 보조기억장치에 데이터를 단순히 나누어 저장하는 구성 방식 -> 여러 번에 걸쳐 읽고 썼을 데이터를 동시에 읽고 쓸수 있어서 빠르지만 저장된 정보가 안전하지 않다. ++줄무늬처럼 분산되어 저장된 데이터를 스트라입(stripe)이라 하고, 분산하여 저장하는 것을 스트라이핑(striping)이라고 함
  • RAID 1: 복사본을 만드는 방식으로 완전한 복사본을 만드는 구성이기에 미러링(mirroring)이라고도 부른다; 쓰기 속도는 RAID 0보다 느리지만 복구가 매우 간단하다. 그러나 사용 가능한 용량은 적어짐
  • RAID 4: RAID 1처럼 완전한 복사본을 만드는 대신 오류를 검출하고 복구하기 위한 정보를 저장한 장치를 두는 구성 방식이다. 이때 '오류를 검출하고 복구하기 위한 정보'를 패리티 비트(parity bit)라고 한다; RAID 4에서는 패리티를 저장한 장치를 이용해 다른 장치들의 오류를 검출하고, 오류가 있다면 복구한다. RAID 1보다 적은 하드 디스크로도 데이터를 안전하게 보관할 수 있다.
  • RAID 5: RAID 4에서 어떤 새로운 데이터가 저장될 때마다 패리티를 저장하는 디스크에도 데이터를 쓰게 되므로 패리티를 저장하는 장치에 병목 현상이 발생함 -> 이를 해결하기 위해 RAID 5에서는 패리티를 분산하여 저장한다.
  • RAID 6: 기본적인 구성은 RAID 5와 같으나 서로 다른 두 개의 패리티를 두는 방식이다. -> RAID 4, 5 보다 안전한 구성이나 새로운 정보를 저장할 패리티가 두 개이므로, 쓰기 속도는 RAID 5보다 느리다. -> 저장속도를 조금 희생해서 데이터를 더욱 안전하게 보관하고 싶을 때 사용한다.

 

#오류를 검출하는 패리티 비트

  1. RAID 4에서는 패리티 정보를 저장한 장치로써 나머지 장치들의 오류를 검출.복구한다.
  2. 패리티 비트는 본래 오류 검출용 정보지만 RAID에서는 오류 복구도 가능하다.

여러 RAID 레벨을 혼합한 방식을 Nested RAID라고 함

 

RAID 0는 데이터를 단순히 병렬로 분산하여 저장하고, RAID 1은 완전한 복사본을 만든다.

RAID 4는 패리티를 저장한 장치를 따로 두는 방식이고, RAID 5는 패리티를 분산하여 저장하는 방식이다.

RAID 6는 서로 다른 두 개의 패리티를 두는 방식이다.

 

chapter 08 입출력장치

입출력장치는  1)종류가 다양하고 2)일반적으로 CPU와 메모리의 전송률(transfer rate) 차이로 컴퓨터에 직접 연결되지 않고 장치 컨트롤러(device controller)라는 하드웨어를 통해 연결됨(입출력 제어기(I/O controller) 또는 입출력 모듈(I/O module) 등으로 다양하게 불리기도 함.

 

#장치 컨트롤러의 역할

  1. CPU와 입출력장치 간의 통신 중개
  2. 오류 검출
  3. 데이터 버퍼링: 버퍼링(buffering)이란 전송률이 높은 장치와 낮은 장치 사이에 주고받는 데이터를 버퍼(buffer)라는 임시 저장 공간에 저장하여 전송률을 비슷하게 맞추는 방법

장치 컨트롤러의 간략화된 내부 구조

 

  • 데이터 레지스터(data register): CPU와 입출력 장치 사이에 주고받을 데이터가 담기는 레지스터 (버퍼 역할); 최근 주고받는 데이터가 많은 입출력 장치에서는 레지스터 대신 RAM을 사용하기도 함
  • 상태 레지스터(status register): 입출력장치가 입출력 작업을 할 준비가 되었는지, 입출력 작업이 완료되었는지, 입출력장치에 오류는 없는지 등의 상태 정보가 저장됨
  • 제어 레지스터(control register): 입출력장치가 수행할 내용에 대한 제어 정보와 명령을 저장

#장치 드라이버(device driver)란 장치 컨트롤러의 동작을 감지하고 제어함으로써 장치 컨트롤러가 컴퓨터 내부와 정보를 주고받을 수 있게 하는 프로그램

 

#CPU와 장치 컨트롤러가 정보를 주고받는 방법

  • 프로그램 입출력(programmed I/O)
    • 메모리 맵 입출력(memory-mapped I/O)
    • 고립형 입출력(isolated I/O)
  • 인터럽트 기반 입출력(Interrupt-Driven I/O)
  • DMA 입출력(Direct Memory Access I/O): 입출력 장치와 메모리가 CPU를 거치지 않고도 상호작용할 수 있는 방법
메모리 맵 입출력 고립형 입출력
메모리와 입출력장치는 같은 주소 공간 사용 메모리와 입출력장치는 분리된 주소 공간 사용
메모리 주소 공간이 축소됨 메모리 주소 공간이 축소되지 않음
메모리와 입출력장치에 같은 명령어 사용 가능 입출력 전용 명령어 사용

 

인터럽트 <-> 폴링(polling): 입출력장치의 상태는 어떤지, 처리할 데이터가 있는지를 주기적으로 확인하는 방식

 

플레그 레지스터 속 인터럽트 비트가 활성화되어 있는 경우, 혹은 인터럽트 비트를 비활성화해도 무시할 수 없는 인터럽트인 NMI(Non-Maskable Interrupt)가 발생한 경우 CPU는 우선순위가 높은 인터럽트부터 처리한다.

많은 컴퓨터에서 다중 인터럽트를 처리할 때 프로그래머블 인터럽트 컨트롤러(PIC: Programmable Interrupt Controller)라는 하드웨어를 사용한다.

 

PIC: 여러 장치 컨트롤러에 연결되어 장치 컨트롤러에서 보낸 하드웨어 인터럽트 요청들의 우선순위를 판별한 뒤 CPU에 지금 처리해야 할 하드웨어 인터럽트는 무엇인지를 알려주는 장치(NMI는 우선순위를 판별하지 않는다.)

 

#DMA 입출력 과정

  1. CPU는 DMA 컨트롤러에 입출력장치의 주소, 수행할 연산(읽기/쓰기), 읽거나 쓸 메모리의 주소 등과 같은 정보로 입출력 작업을 명령함
  2. DMA 컨트롤러는 CPU 대신 장치 컨트롤러와 상호작용하며 입출력 작업을 수행함. 이때 DMA 컨트롤러는 필요한 경우 메모리에 직접 접근하여 정보를 읽거나 씀
  3. 입출력 작업이 끝나면 DMA 컨트롤러는 CPU에 인터럽트를 걸어 작업이 끝났음을 알림

->CPU는 DMA 컨트롤러에게 입출력 작업 명령을 내리고, 인터럽트만 받으면 되기 때문에 작업 부담을 훨씬 줄일 수 있다. 오로지 시작과 끝에만 관여하면 됨

 

CPU와 DMA 컨트롤러는 시스템 버스를 공유해서 쓰는데 DMA 컨트롤러는 CPU가 시스템 버스를 이용하지 않을 때마다 조금씩 시스템 버스를 이용하거나, CPU가 일시적으로 시스템 버스를 이용하지 않도록 허락을 구하고 시스템 버스를 집중적으로 이용함(사이클 스틸링(cycle stealing)이라고도 부름)

 

앞선 방법에서 DMA를 위해 시스템 버스를 너무 자주 사용하면 그만큼 CPU가 시스템 버스를 이용하지 못하는 문제를 해결하기 위해 DMA컨트롤러와 장치 컨트롤러들을 입출력 버스(input/output bus)라는 별도의 버스에 연결하여 해결할 수 있다. DMA 장치 컨트롤러와 장치 컨트롤러가 서로 데이터를 전송할 대는 시스템 버스를 이용할 필요가 없으므로 시스템 버스의 사용 빈도를 줄일 수 있음

 

입출력 버스에는 PCI(Perpheral Component Interconnect) 버스, PCI Express(PCIe) 버스 등 여러 종류가 있다.

 

최근에는 메모리에 직접 접근할 뿐만 아니라 입출력 명령어를 직접 인출하고, 해석, 실행하는 일종의 입출력 전용 CPU도 만들어졌는데, 이를 입출력 프로세서(IOP: Input/Output Channel)이라고 부른다.

 


chapter 09 운영체제 시작하기

#운영체제는 실행할 프로그램에 필요한 자원을 할당하고, 프로그램이 올바르게 실행되도록 돕는 특별한 프로그램입니다.

 

#프로그램 실행에 마땅히 필요한 요소들을 가리켜 시스템 자원이라 함

 

#운영체제는 커널 영역(kernel space), 사용자가 이용하는 응용 프로그램(application software) 사용자 영역(user space)에 적재됨

 

#운영체제의 핵심 서비스를 담당하는 부분을 커널(kernel)이라고 함

 

사용자 인터페이스(UI: User Interface) - 그래픽 유저 인터페이스(GUI: Graphical User Interface), 커맨드 라인 인터페이스(CLI: Command Line Interface)는 커널에 포함되지 않는 서비스

 

#이중 모드(dual mode): 사용자 모드(운영체제 서비스를 제공받을 수 없는 실행 모드), 커널 모드(운영체제 서비스를 제공받을 수 있는 실행 모드)를 구분하는 방식

 

#운영체제 서비스를 제공받기 위한 요청을 시스템 호출(system call)이라고 함; 사용자 모드 -> 커널 모드로 전환

 

#시스템 호출은 일종의 인터럽트; 인터럽트는 입출력장치에 의해 발생하기도 하지만 인터럽트를 발생시키는 특정 명령어에 의해 발생하기도 하는데, 이를 소프트웨어 인터럽트라고 함.

 

시스템 호출을 발생시키는 명령어가 실행되면 CPU는 지금까지의 작업을 백업하고, 커널 영역 내에 시스템 호출을 수행하는 코드(인터럽트 서비스 루틴)를 실행한 뒤 다시 기존에 실행하던 응용 프로그램으로 복귀하여 실행을 계속해 나감

1.시스템 호출 -> 2.운영체제 코드 실행 -> 3.시스템 호출 복귀

이 과정에서 빈번하게 시스템 호출을 발생시키고 사용자 모드와 커널 모드를 오가며 실행됨

 

#운영체제의 핵심 서비스

  • 프로세스 관리: 프로세스 동기화, 교착 상태 해결
  • 자원 접근 및 할당(CPU, 메모리, 입출력장치): CPU 스케줄링(여러 프로세스에게 CPU 할당하는 방법)
  • 파일 시스템 관리

실행 중인 프로그램을 프로세스(process)라고 함

인터럽트 서비스 루틴 -> 커널 영역에 있음

 

가상화를 지원하는 CPU는 커널 모드와 사용자 모드 이외에 가상 머신을 위한 모드인 하이퍼바이저 모드를 따로 둠 -> 가상 머신 상에서 작동하는 응용 프로그램들은 하이퍼바이저 모드로써 가상 머신에 설치된 운영체제로부터 운영체제 서비스를 받을 수 있음

 

#시스템 호출의 종류

개발자가 작성하는 프로그래밍 언어들은 내부적으로 위와 같은 시스템 호출을 통해 실행됨. C에서 printf, scanf도 내부적으로 시스템 호출을 통해 실행됨


chapter 10 프로세스와 스레드

프로그램은 실행되기 전까지는 그저 보조기억장치에 있는 데이터 덩어리일 뿐이지만, 보조기억장치에 저장된 프로그램을 메모리에 적재하고 실행하는 순간 그 프로그램은 프로세스가 됨 -> 프로세스를 생성한다

 

  • 포그라운드 프로세스(foreground process): 사용자가 보는 앞에서 실행되는 프로세스
  • 백그라운드 프로세스(background process)

백그라운드 프로세스 중 사용자와 상호작용하지 않고 그저 묵묵히 정해진 일만 수행하는 백그라운드 프로세스를 유닉스 운영체제에서는 데몬(demon), 윈도우 운영체제에서는 서비스(service)라고 부름

 

#타이머 인터럽트는 클럭 신호를 발생시키는 장치에 의해 주기적으로 발생하는 하드웨어 인터럽트입니다. 타임아웃 인터럽트라고도 부릅니다.

 

운영체제는 빠르게 번갈아 수행되는 프로세스의 실행 순서를 관리하고, 프로세스에 CPU를 비롯한 자원을 배분함. 이때 운영체제는 프로세스 제어 블록(PCB: Process Control Block)을 이용함.

 

PCB는 프로세스와 관련된 정보를 저장하는 자료 구조이고 커널 영역에 저장된다.

PCB는 프로세스 생성 시에 만들어지고 실행이 끝나면 폐기됨

 

#PCB에 담기는 정보

  • 프로세스 ID(PID): 프로세스를 식별하기 위해 부여하는 고유한 번호
  • 레지스터 값: 프로세스는 자신의 실행 차례가 돌아오면 이전까지 사용했던 레지스터의 중간값들을 모두 복원함
  • 프로세스 상태: 현재 프로세스가 입출력장치를 사용하기 위해 기다리고 있는 상태인지, CPU를 사용하기 위해 기다리고 있는 상태인지, 아니면 CPU를 이용하고 있는 상태인지 등의 프로세스 상태 정보가 PCB에 저장됨
  • CPU 스케줄링 정보: 어떤 순서로 CPU를 할당받을지에 대한 정보
  • 메모리 관리 정보: 프로세스마다 메모리에 저장된 위치가 다름. 그래서 PCB에는 프로세스가 어느 주소에 저장되어 있는지에 대한 정보가 있어야 함. PCB에는 베이스 레지스터, 한계 레지스터 값과 같은 정보들이 담김 또한 프로세스의 주소를 알기 위한 또 다른 중요 정보 중 하나인 페이지 테이블 정보도 PCB에 담김
  • 사용한 파일과 입출력장치 목록: 프로세스가 실행 과정에서 특정 입출력장치나 파일을 사용하면 PCB에 해당 내용이 명시됨

#문맥(context): 하나의 프로세스 수행을 재개하기 위해 기억해야 할 정보(각종 레지스터 값, 메모리 정보 등의 중간정보)

 

하나의 프로세스 문맥은 해당 프로세스의 PCB에 표현되어 있습니다. PCB에 기록되는 정보들을 문맥이라고 봐도 무방합니다.

 

#문맥 교환(context switching): 기존 프로세스의 문맥을 PCB에 백업하고, 새로운 프로세스를 실행하기 위해 문맥을 PCB로부터 복구하여 새로운 프로세스를 실행

 

문맥교환이 잦으면 오버헤드로 인해 성능이 저하될 수 있음

 

#프로세스의 메모리 영역

  • 코드 영역: 텍스트 영역이라고도 부름. 기계어로 이루어진 명령어가 저장되었으며, 코드 영역에는 데이터가 아닌 CPU가 실행할 명령어가 담겨 있기 때문에 쓰기가 금지되어 있다. read-only 공간
  • 데이터 영역: 전역 변수와 같은 프로그램이 실행되는 동안 유지할 데이터가 저장된느 공간.
  • 힙 영역: 프로그래머가 직접 할당할 수 있는 저장 공간, 메모리 공간을 할당 받았다면 언젠가는 해당 공간을 반환해야 함 그렇지 않으면 memory leak 문제가 발생
  • 스택 영역: 데이터를 일시적으로 저장하는 공간임. 매개변수, 지역 변수가 대표적

#코드 영역과 데이터 영역은 '크기가 고정된 영역'이라는 점에서 정적 할당 영역이라고도 부름.  반면 힙 영역과 스택 영역은 프로세스 실행 과정에서 그 크기가 변할 수 있는 영역이라는 점에서 동적 할당 영역이라고도 부름

 

#일반적으로 힙 영역은 메모리의 낮은 주소에서 높은 주소로 할당되고, 스택 영역은 높은 주소에서 낮은 주소로 할당됨.-> 힙 영역과 스택 영역에 데이터가 쌓여도 새롭게 할당되는 주소가 겹칠 일이 없다.

 

#프로세스 상태

  • 생성 상태: 이제 막 메모리에 적재되어 PCB를 할당받은 상태
  • 준비 상태: 당장이라도 CPU를 할당받아 실행할 수 있지만, 아직 자신의 차례가 아니기에 기다리고 있는 상태, 준비 상태인 프로세스가 실행 상태로 전환되는 것을 dispatch라고 함
  • 실행 상태: CPU를 할당받아 실행 중인 상태; 타이머 인터럽트가 발생하면 준비 상태가 되고, 실행 도중 입출력장치를 사용하면 입출력장치의 작업이 끝날 때까지 대기 상태가 됨
  • 대기 상태: 대기 상태 프로세스는 입출력 완료 인터럽트를 받을 때까지 기다려야함. 입출력장치의 작업을 기다리는 상태를 대기 상태(blocked)라고 함. //일반적으로 표현하면 특정 이벤트가 일어나길 기다릴 때 프로세스는 대기 상태가 됨
  • 종료 상태(terminated): 프로세스가 종료된 상태. 프로세스가 종료되면 운영체제는 PCB와 프로세스가 사용한 메모리를 정리함.

process state diagram

 

  • 부모 프로세스(parent process)
  • 자식 프로세스(child process)

PPID: Parent PID

프로세스 계층 구조

#프로세스 생성 기법

부모 프로세스는 fork를 통해 자신의 복사본을 자식 프로세스로 생성해내고, 만들어진 복사본(자식 프로세스)은 exec를 통해 자신의 메모리 공간을 다른 프로그램과 교체한다.

 

#스레드(thread): 프로스세스를 구성하는 실행의 흐름 단위

프로세스의 스레드들은 실행에 필요한 최소한의 정보(프로그램 카운터를 포함한 레지스터, 스택)만을 유지한 채 프로세스 자원을 공유하며 실행됨

 

#리눅스는 프로세서와 스레드라는 말 대신 태스크(task)라는 이름으로 통일하여 명명

fork를 한 직후 같은 프로세스를 통째로 메모리에 중복 저장하지 않으면서 동시에 프로세스끼리 자원을 공유하지 않는 방법 -> 쓰기 시 복사(copy on write)

 

스레드끼리는 프로세스 내의 자원을 공유함

 

#프로세스 간 통신(IPC; Inter-Process Communication): 프로세스 간의 자원을 공유하고 데이터를 주고받는 것

  • 파일을 통한 프로세스 간 통신
  • 공유 메모리(shared memory)
  • 그외에도 소켓, 파이프 등을 통해 통신 가능

chapter 11 CPU 스케줄링

CPU 스케줄링(CPU scheduling): 운영체제가 프로세스들에게 공정하고 합리적으로 CPU 자원을 배분하는 것

 

디스크 재생이나 디스크 백업 작업을 담당하는 프로세스와 같이 입출력 잡업이 많은 프로세스를 입출력 집중 프로세스(I/O bound process)라고 하고, 복잡한 수학 연산, 컴파일, 그래픽 처리 작업을 담당하는 프로세스와 같이 CPU 작업이 많은 프로세스를 CPU 집중 프로세스(CPU bound process)라고 한다. 대기 시간과 실행 시간이 다름

 

CPU를 이용하는 작업을 CPU 버스트(CPU burst)라 하고, 입출력장치를 기다리는 작업을 입출력 버스트(I/O burst)라 부른다.

 

입출력 집중 프로세스를 먼저 실행하고 대기 시간 동안 CPU 집중 프로세스를 실행시키는게 효율적임. 상황에 맞게, 그리고 프로세스의 중요도에 맞게 프로세스가 CPU를 이용할 수 있도록 하기 위해 운영체제는 프로세스마다 우선순위(priority)를 부여함 -> 우선순위가 높은 프로세스는 더 빨리, 더 자주 실행

 

#운영체제는 프로세스를 CPU 사용, 메모리 적재, 입출력장치 사용 분류에 따라 줄세워 관리함 -> 스케줄링 큐(scheduling queue)(스케줄링에서 이야기하는 큐는 반드시 선입선출일 필요는 없음)

같은 장치를 요구한 프로세스들은 같은 대기 큐에서 기다림; 줄 서는 동시에 높은 우선순위부터 실행함

 

  • 선점형 스케줄링(preemptive scheduling): 프로세스가 CPU를 비롯한 자원을 사용하고 있더라도 운영체제가 프로세스로부터 자원을 강제로 빼앗아 다른 프로세스에 할당할 수 있는 스케줄링 방식을 의미함; 타이머 인터럽트를 통한 방식이 이에 해당
  • 비선점형 스케줄링(non-preemptive scheduling): 하나의 프로세스가 자원을 사용하고 있다면 그 프로세스가 종료되거나 스스로 대기 상태에 접어들기 전까진 다른 프로세스가 끼어들 수 없는 스케줄링 방식을 의미함

선점형 스케줄링은 한 프로세스의 자원 독점을 막고 프로세스들에 골고루 자원을 배분할 수 있다는 장점이 있지만, 그만큼 문맥 교환 과정에서 오버헤드가 발생할 수 있다.

비선점형 스케줄링은 문맥 교환의 횟수가 선점형 스케줄링보다 적기 때문에 문맥 교환에서 발생하는 오버헤드는 선점형 스케줄링보다 적지만, 하나의 프로세스가 자원을 사용 중이라면 당장 자원을 사용해야 하는 상황에서도 무작정 기다리는 수밖에 없다.

 

  • 선입 선처리 스케줄링(FCFS(First Come First Served) scheduling ): 준비 큐에 삽입된 순서대로 CPU를 할당함
  • 최단 작업 우선 스케줄링(SJF(Shortest Job First) scheduling ): 준비 큐에 삽입된 프로세스들 중 CPU 사용 시간의 길이가 가장 짧은 프로세스부터 CPU를 할당함
  • 라운드 로빈 스케줄링(Round Robin scheduling): 정해진 시간(타임 슬라이스)만큼만 돌아가며 CPU를 할당함; 타임 슬라이스가 지나치게 크면 사실상 선입 선처리 스케줄링과 다를 바 없어 호위 효과가 생길 여지가 있고, 타임 슬라이스가 지나치게 작으면 문맥 교환에 발생하는 비용이 커 CPU는 프로세스를 처리하는 일보다 프로세스를 전환하는 데에 온 힘을 다 쓸 여지가 있음
  • 최소 잔여 시간 우선 스케줄링(SRT(Shortest Remaining Time) scheduling): SJF + RR; 최소 잔여 시간 우선 스케줄링 하에서 프로세스들은 정해진 타임 슬라이스만큼 CPU를 사용하되, CPU를 사용할 다음 프로세스로는 남아있는 작업 시간이 가장 적은 프로세스가 선택됨
  • 우선순위 스케줄링(SRT(Shortest Remainning Time) scheduling): 가장 높은 우선순위를 가진 프로세스에 CPU를 할당함. 앞선 SRT, SJF 스케줄링은 넓은 의미에서 우선순위 스케줄링의 일종임(우선순위를 어떻게 두냐에 차이)
  • 다단계 큐 스케줄링(multilevel queue scheduling): 우선순위 스케줄링의 발전된 형태로 우선순위별로 준비 큐를 여러 개 사용하는 스케줄링 방식임. 다단계 큐 스케줄링 하에서는 우선순위가 가장 높은 큐에 있는 프로세스들을 먼저 처리하고, 우선순위가 가장 높은 큐가 비어 있으면 그다음 우선순위 큐에 있는 프로세스들을 처리함. 이때 큐마다 다른  타임 슬라이스를 적용할 수도 있고, 다른 스케줄링 알고리즘을 적용할 수도 있다.
  • 다단계 피드백 큐 스케줄링(multilevel feedback queue scheduling): 다단계 큐 스케줄링은 starvation 현상이 발생할 수 있음; 프로세스들이 큐 사이를 이동할 수 있는 다단계 큐 스케줄링임

다단계 피드백 큐 스케줄링: 구현이 복잡하지만, 가장 일반적인 CPU 스케줄링 알고리즘

타임 슬라이스 동안 실행을 다 못끝내면 낮은 우선순위로 이동시킨다. -> CPU 집중 프로세스는 자연스럽게 우선순위가 낮아지고 입출력 집중 프로세스들은 자연스레 우선순위가 높은 큐에서 실행이 끝난다. 또한 우선순위가 낮은 큐에서 너무 오랫동안 대기하고 있는 프로세스를 에이징 기법을 사용해서 우선순위가 높은 큐로 이동시킬 수도 있다.

 

#호위 효과(convoy effect): FCFS 스케줄링에서 대기시간과 실행시간 간에 괴리로 평균 대기 시간이 길게 잡힘

#기아 현상(starvation): 우선순위가 높은 프로세스만 계속 먼저 실행되어서 우선순위가 낮은 프로세스의 실행이 계속 뒤로 밀림

#에이징(aging): 오랫동안 대기한 프로세스의 우선순위를 점차 높이는 방식; starvation 방지 기법

#타임 슬라이스: 각 프로세스가 CPU를 사용할 수 있는 정해진 시간을 의미함


chapter 12 프로세스 동기화

협력하여 실행되는 프로세스들은 실행 순서와 자원의 일관성을 보장해야 하기에 반드시 동기화(synchronization) 되어야 한다.

 

프로세스 동기화란 프로세스들 사이의 수행 시기를 맞추는 것을 의미한다.

 

#프로세스 동기화

실행 순서 제어를 위한 동기화: 프로세스를 올바른 순서대로 실행하기

상호 배제를 위한 동기화: 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기

 

#생산자와 소비자 문제

 

#용어

  • 공유자원(shared resource): 공동으로 이용하는 변수, 파일, 장치 등의 자원
  • 임계 구역(critical section): 공유 자원에 접근하는 코드 중 동시에 실행하면 문제가 발생하는 코드 영역

#레이스 컨디션(race condition): 여러 프로세스가 동시 다발적으로 임계 구역의 코드를 실행하여 문제가 발생하는 경우 -> 데이터의 일관성이 깨짐

 

총합++; 같은 명령어도 "1)총합 변수를 레지스터에 저장, 2)레지스터 값 1 증가, 3)레지스터 값을 총합 변수에 저장"과 같이 여러 줄의 저급언언어로 변환되기 때문에 고급 언어 한 줄을 실행하는 과정에서 문맥 교환이 일어날 수 있다.

 

#운영체제에서 상호 배제를 위한 동기화를 위해서 지켜야 하는 세 가지 원칙

  • 상호 배제(mutual exclusion): 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임계 구역에 들어올 수 없다.
  • 진행(progress): 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
  • 유한 대기(bounded waiting): 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다.(임계 구역에 들어오기 위해 무한정 대기해서는 안 된다).

#뮤텍스 락(Mutex Locks; MUTual): 프로세스들이 공유하는 전역 변수 lock(자물쇠 역할), 임계 구역을 잠그는 역할(acquire 함수), 임계 구역의 잠금을 해제하는 역할(release 함수)

 

#바쁜 대기(busy wait): 임계 구역이 잠겨있는지 반복적으로 확인함

 

#세마포(semaphore: 수기 신호): 하나의 프로세스만 이용 가능한 프린터 세 대가 있는 상황처럼 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구; 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)를 나타내는 전역 변수 S, 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는 wait 함수, 임계 구역 앞에서 기다리는 프로세스에 '이제 가도 좋다'고 신호를 주는 signal 함수로 구현 가능

  • 이진 세마포(binary semaphore): 뮤텍스락이랑 비슷한 개념임
  • 카운팅 세마포(counting semaphore):

busy wait 말고도 이런 방법도 있다;  wait 함수는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고, 그 프로세스의 PCB를 세마포를 위한 대기큐에 짚어넣습니다. 그리고 프로세스가 임계 구역에서의 작업이 끝나고 signal 함수를 호출하면 signal 함수는 대기 중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨줍니다.

 

세마포에서 signal(), wait() 위치를 조정해서 실행 순서 제어도 가능함

 

#모니터(동기화도구): 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리함; 프로세스나 스레드의 실행 순서를 제어하기 위해 조건 변수(conditional variable)도 사용함(모니터랑 별개의 개념)

 

 


chapter 13 교착 상태

#식사하는 철학자 문제(dining philosophers problem)

 

#교착 상태(deadlock): 일어나지 않을 사건을 기다리며 진행이 멈춰 버리는 현상

 

교착 상태는 자원 할당 그래프(resource-allocation graph)를 통해 단순하게 표현할 수 있다.

 

#교착 상태 발생 조건: 아래 조건이 모두 만족될 때 교착 상태가 발생할 가능성이 생김

  • 상호 배제: 한 프로세스가 사용하는 자원을 다른 프로세스가 사용할 수 없음
  • 점유와 대기(hold and wait): 자원을 할당받은 상태에서 다른 자원을 할당받기를 기다리는 상태 
  • 비선점(nonpreemptive):  다른 프로세스의 자원을 강제로 빼앗지 못해서 프로세스 종료할 때까지 기다려야 함
  • 원형 대기(circular wait): 프로세스들과 프로세스가 요청 및 할당받은 자원이 원의 형태를 이룸

#교착 상태 해결 방법

  • 교착 상태가 일어나지 않도록 교착 상태 발생 조건에 부합하지 않게 자원을 분해하여 교착 상태를 예방
  • 교착 상태가 발생하지 않을 정도로 조금씩 자원을 할당하다가 교착 상태의 위험이 있다면 자원을 할당하지 않는 방식으로 교착상태 회피
  • 자원을 제약 없이 할당하다가 교착 상태 검출 후 회복
  • 타조 알고리즘(ostrich algorithm): 어차피 회복될텐데 왜 해결함? ...

#교착 상태 예방

상호 배제를 없앰 -> 모든 자원 공유 -> 비현실적

점유와 대기를 없앰 -> 특정 프로세스에 자원을 모두 할당하거나, 아예 할당하지 않음 -> 당장 자원이 필요해도 기다릴 수밖에 없는 프로세스와 사용되지 않으면서 오랫동안 할당되는 자원을 다수 양산해서 자원의 활용률이 낮아짐 또한 많은 자원을 사용하는 프로세스는 자원을 적게 사용하는 프로세스에 비해 동시에 자원을 사용할 타이밍을 확보하기가 어려워져서 기아 현상을 야기할 수도 있음

비선점 조건을 없앰 -> 선점하여 사용할 수 있는 일부 자원에 대해서는 효과적임(CPU처럼) 하지만 한 프로세스의 작업이 끝날 때까지 다른 프로세스가 기다려야 하는 자원도 많다 -> 범용성이 떨어짐

원형 대기 조건을 없앰 -> 모든 자원에 번호를 붙이고, 오름차순으로 자원을 할당하면 원형 대기는 발생하지 않음 -> 원형을 일렬로 늘이는 효과; 다른 방식보다는 현실적이고 실용적인 방식이지만 모든 컴퓨터 시스템 내에 존재하는 수많은 자원에 번호를 붙이는 일은 간단한 작업이 아니고 각 자원에 어떤 번호를 붙이는지에 따라 특정 자원의 활용률이 떨어질 수 있음

 

이처럼 교착 상태를 예방하는 방식은 교착 상태가 발생하지 않음을 보장할 수는 있지만 여러 부작용이 따름

 

#교착 상태 회피

교착 상태 회피 방식에서는 교착 상태를 한정된 자원의 무분별한 할당으로 인해 발생하는 문제로 간주함

프로세스들에 배분할 수 있는 자원의 양을 고려하여 교착 상태가 발생하지 않을 정도의 양만큼만 자원을 배분하는 방법

 

  • 안전 상태(safe state): 교착 상태가 발생하지 않고 모든 프로세스가 정상적으로 자원을 할당받고 종료될 수 있는 상태; 안전 순서열이 존재하는 상태
  • 불안전 상태(unsafe state): 교착 상태가 발생할 수도 있는 상황; 안전 순서열이 없는 상태

#안전 순서열(safe sequence): 교착 상태 없이 안전하게 프로세스들에 자원을 할당할 수 있는 순서를 의미; 교착 상태를 회피하기 위해 안전 상태를 유지하도록 자원을 할당하는 방식

 

프로세스와 스레드는 자원을 사용하기 위해 1)우선 자원을 운영체제에게 요청하고, 2)운영체제로부터 자원을 할당받아 사용하고, 3)자원의 사용이 끝났으면 자원을 반환함

 

#교착 상태 검출 후 회복

검출 후 회복 방식에서 운영체제는 프로세스들이 자우너을 요구할 때마다 그때그때 모두 할당하며, 교착 상태 발생 여부를 주기적으로 검사함

  • 선점을 통한 회복 -> 교착 상태가 해결될 때가지 한 프로세스씩 자원을 몰아주는 방식. 교착 상태가 해결될 때까지 다른 프로세스로부터 자원을 강제로 빼앗고 한 프로세스에 할당하는 방식임
  • 프로세스 강제 종료를 통한 회복 -> 운영체제는 교착 상태에 놓인 프로세스를 모두 강제 종료할 수도 있고, 교착 상태가 없어질 때까지 한 프로세스씩 강제 종료할 수도 있습니다. 전자는 한 방에 교착 상태를 해결할 수 있는 가장 확실한 방식이지만 그만큼 많은 프로세스들이 작업 내역을 잃게 될 가능성이 있고, 후자는 작업 내역을 잃는 프로세스는 최대한 줄일 수 있지만 교착 상태가 없어졌는 여부를 확인하는 과정에서 오버헤드를 야기함
더보기

 

  • 메모리 접근 오버헤드:
    • CPU가 메모리에서 데이터를 읽거나 쓸 때 소요되는 시간.
    • 캐시 미스(Cache miss)로 인해 발생하는 지연 시간.
  • I/O 오버헤드:
    • CPU가 데이터를 디스크나 네트워크를 통해 전송할 때 소요되는 시간.
    • 데이터 전송 과정에서의 프로토콜 처리 시간.
  • 컨텍스트 스위칭 오버헤드:
    • CPU가 하나의 스레드나 프로세스에서 다른 스레드나 프로세스로 전환할 때 발생하는 시간.
    • 이 과정에서 레지스터 상태를 저장하고 복원하는 데 필요한 시간.
  • 함수 호출 오버헤드:
    • 함수 호출 시 스택 프레임을 설정하고 해제하는 데 필요한 시간.
    • 파라미터 전달 및 리턴값 처리 시간.
  • 동기화 오버헤드:
    • 멀티스레드 환경에서 동기화를 위해 락(lock)을 획득하고 해제하는 데 필요한 시간.
    • 경쟁 조건(Race condition)을 피하기 위한 메커니즘에 소요되는 시간.

오버헤드는 시스템 자원(시간, 메모리, CPU 등)을 추가적으로 소비하는 모든 작업을 포함합니다. CPU가 메모리나 보조기억장치에 데이터를 이동할 때 발생하는 시간은 오버헤드의 한 유형이지만, 오버헤드는 그 외에도 여러 상황에서 발생할 수 있는 더 넓은 개념입니다.

 

더보기

동기화 메커니즘과 오버헤드

  1. 뮤텍스(Mutex)
    • 오버헤드: 뮤텍스는 한 번에 하나의 스레드만 자원에 접근할 수 있도록 보장합니다. 스레드가 뮤텍스를 획득하기 위해서는 커널 모드 전환이 필요할 수 있으며, 이로 인해 시간이 소요됩니다.
    • 장점: 강력한 동기화 기능을 제공하며, 프로세스 간 동기화에도 사용할 수 있습니다.
  2. 락(Lock)
    • 오버헤드: C#의 lock 키워드는 내부적으로 모니터(Monitor) 클래스를 사용합니다. 모니터는 한 번에 하나의 스레드만 특정 코드 블록을 실행하도록 보장합니다. 락을 획득하고 해제하는 과정에서 CPU 시간과 시스템 리소스가 사용됩니다.
    • 장점: 사용하기 쉽고 효율적이며, 코드 블록 전체를 보호할 수 있습니다.
  3. 스핀락(Spinlock)
    • 오버헤드: 스핀락은 짧은 시간 동안 락을 획득하기 위해 반복적으로 락 상태를 확인하는 방식입니다. 이 방식은 컨텍스트 스위칭 오버헤드를 줄일 수 있지만, CPU 자원을 많이 소비할 수 있습니다.
    • 장점: 락이 짧은 시간 내에 해제될 것으로 예상될 때 유용합니다.
  4. 세마포어(Semaphore)
    • 오버헤드: 세마포어는 지정된 최대 스레드 수만큼 자원에 접근할 수 있도록 제어합니다. 세마포어를 획득하고 해제하는 과정에서도 시스템 호출이 발생하며, 이로 인해 오버헤드가 발생합니다.
    • 장점: 제한된 수의 스레드가 자원에 접근해야 할 때 유용합니다.
  5. 이벤트 객체(Event)
    • 오버헤드: 이벤트 객체는 특정 조건이 발생할 때까지 하나 이상의 스레드를 대기 상태로 만들고, 조건이 충족되면 대기 중인 스레드를 실행 상태로 전환합니다. 이벤트 객체를 설정하거나 리셋하는 과정에서 오버헤드가 발생합니다.
    • 장점: 이벤트를 사용하면 스레드 간의 신호를 효과적으로 처리할 수 있습니다.

chapter 14 가상 메모리

프로세스에 연속적인 메모리 공간을 할당하는 방식을 연속 메모리 할당 방식이라고 함

 

#스와핑

입출력 작업의 요구로 대기 상태가 된 프로세스라던지, 오랫동안 사용되지 않은 프로세스를 임시로 보조기억장치 일부 영역으로 쫓아내고, 그렇게 해서 생긴 메모리상의 빈 공간에 또 다른 프로세스를 적재하여 실행하는 방식을 스와핑(swapping)이라고 한다.

메모리에서 사용되지 않는 일부 프로세스를 보조기억장치로 내보내고 실행할 프로세스를 메모리로 들여보내는 메모리 관리 기법입니다.

  • 스왑 영역(swap space): 프로세스들이 쫓겨나는 보조기억장치의 일부 영역
  • 스왑 아웃(swap-out): 프로세스가 메모리에서 스왑 영역으로 옮겨지는 것
  • 스왑 인(swap-in): 프로세스가 다시 메모리로 옮겨오는 것

스와핑을 이용하면 프로세스들이 요구하는 메모리 주소 공간의 크기가 실제 메모리 크기보다 큰 경우에도 프로세스들을 동시 실행할 수 있다.

 

 

#메모리 할당(연속)

  • 최초 적합(first fit): 운영체제가 메모리 내의 빈 공간을 순서대로 검색하다가 적재할 수 있는 공간을 발견하면 그 공간에 프로세스를 배치하는 방식이다. 프로세스가 적재될 수 있는 공간을 발견하는 즉시 메모리를 할당하는 방식이므로 검색을 최소화할 수 있고 결과적으로 빠른 할당이 가능하다.
  • 최적 적합(best fit): 운영체제가 빈 공간을 모두 검색해 본 후, 프로세스가 적재될 수 있는 공간 중 가장 작은 공간에 프로세스를 배치하는 방식이다.
  • 최악 적합(worst fit): 운영체제가 빈 공간을 모두 검색해 본 후, 프로세스가 적재될 수 있는 공간 중 가장 큰 공간에 프로세슬 배치하는 방식이다.

#외부 단편화(external fragmentation)

프로세스를 메모리에 연속적으로 배치하는 연속 메모리 할당은 외부 단편화(프로세스를 할당하기 어려울 만큼 작은 메모리 공간들로 인해 메모리가 낭비되는 현상) 문제를 내포한다.

 

외부 단편화를 해결할 수 있는 대표적인 방안으로 메모리를 압축(compacation)하는 방법이 있다. 메모리 조각 모음이라고 부른다. 압축은 여기저기 흩어져 있는 빈 공간들을 하나로 모으는 방식으로 메모리 내에 저장된 프로세스를 적당히 재배치 시켜 여기저기 흩어져 있는 작은 빈 공간들을 하나의 큰 공간으로 만드는 방법이다.

 

압축 방식은 여러 단점이 있는데, 작은 빈 공간들을 하나로 모으는 동안 시스템은 하던 일을 중지해야 하고, 메모리에 있는 내용을 옮기는 작업은 많은 오버헤드를 야기하며, 어떤 프로세스를 어떻게 움직여야 오버헤드를 최소화하며 압축할 수 있는지에 대한 명확한 방법을 결정하기 어렵다.

 

압축 방식 말고도 외부 단편화를 없앨 수 있는 또 다른 해결 방안이 등장했는데, 이것이 오늘날까지도 사용되는 가상 메모리 기법, 그 중에서도 페이징 기법이다.

 

기존 연속 메모리 할당의 문제점

  • 외부 단편화
  • 물리 메모리보다 큰 프로세스 실행 불가능함

#가상 메모리(virtual memory)는 실행하고 하는 프로그램을 일부만 메모리에 적재하여 실제 물리 메모리 크기보다 더 큰 프로세스를 실행할 수 있게 하는 기술이다. 이를 가능케 하는 가상 메모리 관리 기법에는 크게 페이징세그멘테이션이 있다.

 

#페이징(paging)은 프로세스의 논리 주소 공간을 페이지(page)라는 일정한 단위로 자르고, 메모리 물리 주소 공간을 프레임(frame)이라는 페이지와 동일한 크기의 일정한 단위로 자른 뒤 페이지를 프레임에 할당하는 가상 메모리 관리 기법이다.

 

페이징을 사용하는 시스템에서는 프로세스 전체가 스왑 아웃/스완 인되는 것이 아닌 페이지 단위로 스왑 아웃/스왑 인 된다. 즉, 메모리에 적재될 필요가 없는 페이지들은 보조기억장치로 스왑 아웃되고, 실행에 필요한 페이지들은 메모리로 스왑 인 된다. 페이징 시스템에서의 스왑 아웃은 페이지 아웃(page out), 스왑 인은 페이지 인(page in)이라고 부르기도 한다.

 

#한 프로세스를 실행하기 위해 프로세스 전체가 메모리에 적재될 필요가 없다. 실행에 필요한 페이지만 메모리에 적재함으로써 물리 메모리보다 큰 프로세스를 실행 가능하게 한다.

 

프로세스가 메모리에 불연속적으로 배치되면 CPU 입장에서는 '다음에 실행할 명령어 위치'를 찾기가 어려워진다. 이를 해결하기 위해서  페이징 시스템은 프로세스가 비록 (실제 메모리 내의 주소인) 물리 주소에 불연속적으로 배치되더라도 (CPU가 바라보는 주소인) 논리 주소에는 연속적으로 배치되도록 페이징 테이블(page table)을 이용합니다.

 

페이지 테이블은 페이지 번호와 프레임 번호를 짝지어 주는 일종의 이정표이다. CPU로 하여금 페이지 번호만 보고 해당 페이지가 적재된 프레임을 찾을 수 있게 한다. 물리 주소상에서는 프로세스들이 분산되어 저장되어 있더라도 CPU 입장에서 바라본 논리 주소는 연속적으로 보일 수 있다. 프로세스들이 메모리에 분산되어 저장되어 있더라도 CPU는 논리 주소를 그저 순차적으로 실행하면 된다.

 

페이징은 외부 단편화 문제를 해결할 수 있지만, 내부 단편화(inter fragmentation)라는 문제를 야기할 수 있습니다. 페이징은 프로세스의 논리 주소 공간을 페이지라는 일정한 크기 단위로 자르는데 모든 프로세스 크기가 반드시 페이지의 배수가 아니기 때문에 마지막 페이지에 크기가 남을 수 있다.

내부 단편화는 하나의 페이지 크기보다 작은 크기로 발생함. 내부 단편화를 적당히 방지하면서 너무 크지 않은 페이지 테이블이 만들어지도록 페이지의 크기를 조정하는 것이 중요하다.(참고로 리눅스를 포함한 일부 운영체제에서는 위와 같이 기본적으로 설정된 페이지 크기보다 더 큰 크기의 페이지도 일부 허용하며 메모리에 유지하는 경우도 있다. 기본적으로 설정된 페이지보다 큰 페이지를 대형 페이지(huge page)라고 한다.

 

프로세스마다 각자의 페이지 테이블을 가지고 있고 각 프로세스의 페이지 테이블들은 메모리에 적재되어 있다. 그리고 CPU 내의 페이지 테이블 베이스 레지스터(PTBR: Page Table Bace Register)는 각 프로세스의 페이지 테이블이 적재된 주소를 가리키고 있다.

 

각 프로세스들의 페이지 테이블 정보들은 각 프로세스의 PCB에 기록된다. 그리고 프로세스의 문맥 교환이 일어날 때 다른 레지스터와 마찬가지로 함께 변경된다.

 

페이지 테이블을 메모리에 두면 메모리 접근 시간이 두 배로 늘어남. 이와 같은 문제를 해결하기 위해 CPU 곁에 (일반적으로 MMU 내에) TLB(Translation Lookaside Buffer)라는 페이지 테이블의 캐시 메모리를 둔다.

 

TLB는 페이지 테이블의 캐시이기 때문에 페이지 테이블의 일부 내용을 저장한다. 참조 지역성에 근거해 주로 최근에 사용된 페이지 위주로 가져와 저장한다.

 

CPU가 발생한 논리 주소에 대한 페이지 번호가 TLB에 있을 경우 이를 TLB 히트(TLB hit)라고 한다. 이 경우에는 페이지가 적재된 프레임을 알기 위해 메모리에 접근할 필요가 없다. 따라서 메모리 접근을 한 번만 하면 된다.

만일 페이지 번호가 TLB에 없을 경우 어쩔 수 없이 페이지가 적재된 프레임을 알기 위해 메모리 내의 페이지 테이블에 접근하는 수 밖에 없다. 이를 TLB 미스(TLB miss)라고 한다.

 

페이징 시스템에서는 모든 논리 주소가 기본적으로 페이지 번호(page number)와 변위(offset)로 이루어져 있다. 가령 CPU가 32비트 주소를 내보냈다면 이중 N비트는 페이지 번호, 32-N비트는 변위, 이런 식으로 되어있다.

 

#페이지 번호: 접근하고자 하는 페이지 번호; 페이지 테이블에서 해당 페이지 번호를 찾으면 페이지가 어떤 프레임에 할당되었는지를 알 수 있다.

#변위: 접근하려는 주소가 프레임의 시작 번지로부터 얼만큼 떨어져 있는지를 알기 위한 정보

 

논리 주소 <페이지 번호, 변위>는 페이지 테이블을 통해 물리 주소 <프레임 번호, 변위>로 변환됨.

 

#페이지 테이블 엔트리

페이지 테이블의 각 엔트리, 다시 말해  페이지 테이블의 각각의 행들을 페이지 테이블 엔트리(PTE: Page Table Entry)라고 한다.

 

페이지 테이블 엔트리

#페이지 테이블 엔트리에 담기는 정보

  1. 유효 비트(valid bit): 해당 페이지에 접근 가능한지 여부를 알려줌; 현재 페이지가 메모리에 적재되어 있으면 1, 아니라면 0임. 만일 CPU가 유효 비트가 0인 메모리에 적재되어 있지 않은 페이지로 접근하려고 하면 페이지 폴트(page fault)라는 예외(Exception)가 발생한다. (1.CPU는 기존 작업을 백업 -> 2.페이지 폴트 처리 루틴을 실행 -> 3.페이지 처리 루틴은 원하는 페이지를 메모리로 가져온 뒤 유효 비트를 1로 변경함 -> 4.페이지 폴트를 처리했다면 이제 CPU는 해당 페이지에 접근할 수 있게 됨)
  2. 보호 비트(protection bit): 보호 비트를 통해 해당 페이지가 읽기 전용0인지, 읽기/쓰기 전용1인지 등을 판단할 수 있다.(예를 들어 코드 영역을 읽기 전용이다.)
  3. 참조 비트(reference bit): CPU가 이 페이지에 접근한 적이 있는지 여부를 나타냄. 적재 이후 CPU가 읽거나 쓴 페이지는 참조 비트가 1로 세팅되고, 적재 이후 한 번도 읽거나 쓴 적이 없는 페이지는 0으로 유지됨
  4. 수정 비트(modified bit; 더티 비트(dirty bit)): 해당 페이지에 데이터를 쓴 적이 있는지 없는지 수정 여부를 알려줌. 1이면 변경된 적이 있는 페이지, 0이면 변경된 적이 없는 페이지(한 번도 접근한 적 없거나 읽기만 했던 페이지)임을 나타냄

수정 비트는 페이지가 메모리에서 사라질 때 보조기억장치에 쓰기 작업을 해야 하는지, 할 필요가 없는지를 판단하기 위해 존재한다.

 

#페이징의 이점 - 쓰기 시 복사(copy on write)

fork() 시스템 호출로 인해 부모 프로세스에서 복사된 자식 프로세스는 부모 프로세스와 똑같은 페이지 테이블을 가져서 동일한 프레임을 가리킨다. 이때 자식 프로세스에서 페이지에 쓰기 작업을 하면 (프로세스 간의 자원을 공유하지 않으므로) 그 순간 해당 페이지가 별도의 공간으로 복제된다. 각 프로세스는 자신의 고유한 페이지가 할당된 프레임을 가리키게 되는데 이것을 쓰기 시 복사라고 한다. -> 프로세스 생성 시간을 줄이면서 동시에 메모리 공간 절약도 가능함

 

#계층적 페이징(hierarchical paging): 다단계 페이지 테이블(multilevel page table) 기법이라고도 부른다.

페이지 테이블의 크기는 생각보다 작지 않다. 프로세스의 크기가 커지면 자연히 프로세스 테이블의 크기도 커지기 때문에 메모리 낭비를 막기 위해 프로세스를 이루는 모든 페이지 테이블 엔트리를 항상 메모리에 유지하지 않을 수 있는 방법이 등장했는데, 이것이 계층적 페이징이다.

 

페이지 테이블을 나누어 다른 페이지 테이블이 이를 관리함으로써 전체 페이지 테이블이 메모리에 없어도 되게 함(일부는 보조기억장치로)

 

# CPU가 발생하는 논리 주소

계층적 페이징을 사용하지 않는 경우 -> 변위-페이지번호

계층적 페이징을 사용하는 경우 -> 바깥 페이지 번호-안쪽 페이지 번호-변위

  1. 바깥 페이지 번호를 통해 페이지 테이블의 페이지를 찾기
  2. 페이지 테이블의 페이지를 통해 프레임 번호를 찾고 변위를 더함으로서 물리 주소 얻기

#요구 페이징(demand paging): 프로세스를 메모리에 적재할 때 처음부터 모든 패이지를 적재하지 않고 필요한 페이지만을 메모리에 적재하는 기법

  1. CPU가 특정 페이지에 접근하는 명령어를 실행한다.
  2. 해당 페이지가 현재 메모리에 있을 경우 (유효 비트가 1일 경우) CPU는 페이지가 적재된 프레임에 접근한다.
  3. 해당 페이지가 현재 메모리에 없을 경우 (유효 비트가 0일 경우) 페이지 폴트가 발생한다.
  4. 페이지 폴트 처리 루틴은 해당 페이지를 메모리로 적재하고 유효비트를 1로 설정한다.
  5. 다시 1번을 수행한다.

아무런 페이지를 적재하지 않은 채 무작정 실행 -> 순수 요구 페이징(pure demand paging)

 

요구 페이징 시스템이 안정적으로 작동하려면 필연적으로 다음 두 가지를 해결해야 합니다. 하나는 페이지 교체이고, 다른 하나는 프레임 할당입니다.

 

요구 페이징 기법으로 페이지들을 메모리에 적재하다 보면 언젠가 메모리가 가득참 -> 메모리에서 어떤 페이지를 내보낼 지 결정하는 방법을 페이지 교체 알고리즘이라고 함

 

페이지 폴트를 가장 적게 일으킬수록 좋은 페이지 교체 알고리즘

 

#페이지 참조열(page reference string): CPU가 참조하는 페이지들 중 연속된 페이지(중복된 페이지를 참조하는 행위는 페이지 폴트를 발생시키지 않음)를 생략한 페이지열 -> 페이지 폴트 횟수를 알 수 있다.

 

#FIFO 페이지 교체 알고리즘(First-In-First-Out Page Replacement Algorithm): 적재된 페이지 순서대로 교체함

 

#2차 기회 페이지 교체 알고리즘(second chance page replacement algorithm): FIFO 페이지 교체 알고리즘과 동일하게 가장 오래 머물렀던 페이지를 대상으로 하되, 만일 참조 비트가 1일 경우, 당장 내쫓지 않고 참조 비트를 0으로 만든 뒤 현재 시간을 적재 시간으로 설정함 -> 기회를 한 번 더 줌

 

#최적 페이지 교체 알고리즘: 가장 오랫동안 사용되지 않을 알고리즘; 실제 구현이 어렵다. 이론상 성능을 평가하기 위한 목적으로 사용

 

#LRU 페이지 교체 알고리즘(LRU: Least Recently Used Replacement Algorithm): 가장 오랫동안 사용하지 않은 페이지를 교체함 페이지마다 마지막으로 사용한 시간을 토대로 최근에 가장 사용이 적었던 페이지를 교체한다.

 

메모리가 적어서 프레임이 부족하면 CPU는 페이지 폴트가 자주 발생할 수밖에 없다.

 

프로세스가 실제 실행되는 시간보다 페이징에 더 많은 시간을 소요하여 성능이 저해되는 문제를 스레싱(thrashing)이라고 한다.

 

멀디프로그래밍의 정도: 메모리에서 동시 실행되는 프로세스의 수

 

실행되는 프로세스의 수(멀티프로그래밍의 정도)를 늘린다고 해서 CPU 이용률이 그에 비례해서 증가하는 것이 아니다. 동시에 실행되는 프로세스 수가 어느 정도 증가하면 CPU 이용률이 높아지지만 필요 이상으로 늘리면 각 프로세스들이 사용할 수 있는 프레임 수가 적어지기 때문에 페이지 폴트가 지나치게 빈번히 발생하고, 이에 따라 CPU 이용률이 떨어져 전체적인 성능이 저해된다.

 

스레싱이 발생하는 근본적인 원인은 각 프로세스가 필요로 하는 최소한의 프레임 수가 보장되지 않았기 때문이다. -> 프레임 할당 방식 고려

 

#정적 할당 방식: 프로세스의 실행 과정을 고려하지 않고 단순히 프로세스의 크기와 물리 메모리의 크기만을 고려한 방식

  • 균등 할당(equal allocation)
  • 비례 할당(proportional allocation)

#동적 할당 방식

  • 작업 집합 모델(working set model): 프로세스가 일정 기간 동안 참조한 페이지 집합을 기억하며 빈번한 페이지 교체를 방지한다.
  • 페이지 폴트 빈도(PFF: Page-Fault Frequency): 페이지 폴트율이 너무 높으면 그 프로세스는 너무 적은 프레임을 갖고 있고, 페이지 폴트율이 너무 낮다면 그 프로세스는 너무 많은 프레임을 갖고 있다. -> 페이지 폴트율에 상한선과 하한선을 정하고, 이 범위 안에서만 프레임을 할당하는 방식임.

실행 중인 프로세스가 일정 시간 동안 참조한 페이지의 집합을 작업 집합(working set)이라고 한다. CPU가 과거에 주로 참조한 페이지를 작업 집합에 포함한다면 운영체제는 작업 집합의 크기만큼만 프레임을 할당해 주면 됨.


chapter 15 파일 시스템

파일(file)이란 하드 디스크나 SSD와 같은 보조기억장치에 저장된 관련 정보의 집합을 의미함

 

파일 관련 부가 정보: 속성(Attrubute) 또는 메타데이터(metadata)

 

#파일 속성

속성 이름 의미
유형 운영체제가 인지하는 파일의 종류를 나타낸다.(확장자(extension) 이용)
크기 파일의 현재 크기와 허용 가능한 최대 크기를 나타낸다.
보호 어떤 사용자가 해당 파일을 읽고, 쓰고, 실행할 수 있는지를 나타낸다.
생성 날짜 파일이 생성된 날짜를 나타낸다.
마지막 접근 날짜  파일에 마지막으로 접근한 날짜를 나타낸다.
마지막 수정 날짜 파일이 마지막으로 수저오딘 날짜를 나타낸다.
생성자 파일을 생성한 사용자를 나타낸다.
소유자 파일을 소유한 사용자를 나타낸다.
위치 파일의 보조기억장치상의 현재 위치를 나타낸다.

 

파일을 다루는 모든 작업은 운영체제에 의해 이루어진다. 어떤 ㅡㅇ용 프로그램도 임의로 파일을 조작할 수 없으며 파일을 다루려면 운영체제에 부탁해야함 -> 이를 위해 운영체제는 파일 연산을 위한 시스템 호출을 제공함(파일 생성 & 삭제, 열기 & 닫기, 읽기 &  쓰기)

 

파일들을 일목 요연하게 관리하기 위해 디렉터리(directory)를 이용(윈도우 운영체제에서는 폴더(folder)라고 부름)

 

경로(Path)는 디렉터리를 이용해 위치를 특정 짓는 정보임

 

1단계 디렉터리(single-level directory)

트리 구조 디렉터리(tree-structured directory)

루트 디렉터리(root directory): 최상위 디렉터리, 슬래시(/)로 표현 (윈도우에서는 C:\)

구분자 / vs 윈도우에서 구분자 \

 

  • 절대 경로(absolute path): 루트 디렉터리부터 시작하는 경로
  • 상대 경로(relative path): 현재 디렉터리부터 시작하는 경로

#디렉터리 엔트리

많은 운영체제에서는 디렉터리를 그저 '특별한 형태의 파일'로 간주함. 파일 내부에 해당 파일고 ㅏ관련된 정보를 담고 있다면, 디렉터리는 내부에 해당 디렉터리에 담겨 있는 대상과 관련된 정보를 담고 있다. 그리고 이 정보는 보통 테이블 형태로 구성된다. 즉, 디렉터리는 보조기억장치에 테이블 형태의 정보로 저장됨

 

각각의 엔트리(행)에 담기는 정보는 파일 시스템마다 차이가 있다.

 

대부분의 운영체제는 현재 작업 디렉터리를 마침표(.)로 나타내고, 현재 작업 디렉터리의 상위 디렉터리(부모 디렉터리)를 마침표 두 번(..)으로 나타낸다. 그리고 흔히 이 기호를 이용해 상대 경로를 표현한다.

 

 

#파일 시스템은 파일과 디렉터리를 보조기억장치에 일목요연하게 저장하고 접근할 수 있게 하는 운영체제 내부 프로그램이다.

 

보기억장치를 사용하려면 파티션을 나누는 작업(파티셔닝)과 포맷 작업(포매팅)을 거쳐야 함

 

#파티셔닝(partioning)은 저장 장치의 논리적인 영역을 구획하는 작업을 의미한다. 파티셔닝 작업을 통해 나누어진 영역 하나하나를 파티션(partion)이라고 한다.

 

#포매팅(formatting)이란 파일 시스템을 설정하여 어떤 방식으로 파일을 저장하고 관리할 것인지를 결정하고, 새로운 데이터를 쓸 준비를 하는 작업을 의미한다.

포매팅의 종류에는 저수준 포매팅(저장 장치를 생성할 당시 공장에서 수행되는 물리적인 포매팅), 논리적 포매팅(파일 시스템을 생성하는 포매팅)이 있다.

 

파일 시스템에는 여러 종류가 있고, 파티션 마다 다른 파일 시스템을 설정할 수 있다. 파티셔닝과 포매팅을 마치고 파일 시스템을 설정했다면 파일과 디렉터리를 생성할 수 있다.

 

운영체제는 파일과 디렉터리를 블록(block)로 읽고 쓴다. 즉, 하나의 파일이 보조기억장치에 저장될 때는 하나 이상의 블록에 걸쳐 저장된다. 하드 디스크의 가장 작은 저장 단위는 섹터이지만, 운영체제는 하나 이상의 섹터를 블록이라는 단위로 묶은 뒤 블록 단위로 파일과 디렉터리를 관리한다.

 

파일을 보조기억장치에 할당하는 방법

 

  • 연속 할당(contiguous allocation): 보조기억장치 내 연속적인 블록에 파일을 할당하는 방식; 연속 할당을 사용하는 파일 시스템에서는 디렉터리 엔트리에 파일 이름과 더불어 첫 번째 블록 주소와 블록 단위의 길이를 명시함; 구현하기 쉬우나 외부 단편화를 야기함
  • 연결 할당(linked allocation): 각 블록 일부에 다음 블록의 주소를 저장하여 각 블록이 다음 블록을 가리키는 형태로 할당하는 방식. 즉, 파일을 이루는 데이터를 연결 리스트로 관리한다; 외부 단편화 문제를 해결하지만 1)첫 번째 블록부터 하나씩 차례대로 읽어야 해서 임의 접근(random access) 속도가 매우 느리고, 2)하드웨어 고장이나 오류 발생시 해당 블록 이후 블록은 접근할 수 없다는 단점이 있다. -> 연결 할당을 변형한 FAT 파일 시스템을 사용
  • 색인 할당(indexed allocation_: 몯느 블록 주소를 색인 블록(index block)이라는 하나의 블록에 모아 관리하는 방식; 파일 시스템에서는 디렉터리 엔트리에 파일 이름과 더불어 색인 블록 주소를 명시함 -> 유닉스 파일 시스템이 색인 할당을 기반으로 만들어짐

FAT는 하드 디스크 파티션의 시작 부분에 있지만, 실행하는 도중 FAT가 메모리에 캐시될 수 있다. -> 임의 접근 성능 개선

  • FAT 파일 시스템: 연결 할당 방식을 개선; 각 블록에 포함된 다음 블록의 주소들을 한데 모아 테이블 형태로 관리한다. 이러한 테이블을 파일 할당 테이블(FAT: File Allocation Table)이라고 부른다. 저용량 저장 장치용 파일 시스템으로 많이 이용되고 있으며 FAT 뒤에 붙는 숫자는 블록을 표현하는 비트수를 의미한다.(윈도우에서는 블록이라는 용어 대신 클러스터를 사용함)
  • 유닉스 파일 시스템:  색인 할당 기반; 유닉스 파일 시스템에서는 색인 블록을 i-node(index node)라고 부른다. i node 에는 파일 속성 정보와 열다섯 개의 블록 주소가 저장될 수 있다.

 

유한개인 i-node 블록 주소 저장 공간을 해결하는 법

  1. 블록 주소 중 열두 개에는 직접 블록(direct block) 주소를 저장함
  2. 첫 단계로 충분하지 않다면 열세 번째 주소에 단일 간접 블록(single indirect block) 주소를 저장함
  3. 두번째 단계로도 충분하지 않다면 열네 번째 주소에 이중 간접 블록(double indirect block) 주소를 저장함
  4. 세번째 단계로도 충분하지 않다면 열다 섯번째 주소에 삼중 간접 블록(triple indirect block) 주소를 저장함

이외에도 윈도우 운영체제에서 사용하는 NTFS, 리눅스 운영체제에서 사용하는 ext 파일 시스템 등이 있다.

 

#저널링(jourmaling) 파일 시스템: 저널링 기법이란 작업 로그를 통해 시스템 크래시가 발생했을 때 빠르게 복구하기 위한 방법.

  1. 작업 직전 파티션의 로그 영역에 수행하는 작업(변경 사항)에 대한 로그를 남긴다.
  2. 로그를 남긴 후 작업을 수행한다.
  3. 작업이 끝났다면 로그를 삭제한다.

작업 도중 시스템 크래시가 발생하여 다시 부팅을 해야 한다면 파일 시스템 전체를 검사할 필요 없이 로그 영역에 남긴 로그만 검사해도 된다. 즉, 저널링 파일 시스템은 시스템 크래시가 발생한 직후에 로그 영역을 읽어 크래시가 발생한 당시 어떤 작업을 실행 중이었는지 알아낸 다음 해당 작업을 완료한다.

 

저장 장치를 마운트(mount) 한다. -> 한 저장 장치의 파일 시스템에서 다른 저장 장치의 파일 시스템에 접근할 수 있도록 시스템을 편입 시키는 작업을 의미한다. /mount/USBDirectoryA/file.txt