2023 스터디

컴퓨터구조+운영체제 스터디

EYR 2023. 11. 3. 14:19

 

이 글은 지난 8월 진행되었던 컴퓨터구조/운영체제 스터디의 백업본입니다.


사용 교재: 혼자 공부하는 컴퓨터구조 운영체제

 

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

 

www.yes24.com

 


[컴퓨터 구조]

 

1단원

더보기

1단원:컴퓨터 구조 시작하기

1-1:컴퓨터 구조를 알아야 하는 이유

컴퓨터 구조를 배워야 하는 이유

: 더 많은 이해는 개발자에게 문제 해결의 실마리

: 성능/용량/비용 의 더 깊은 수준의 고려가 가능


1-2:컴퓨터 구조의 큰 그림

정보(데이터와 명령어)를 0과 1로 표현

데이터: 컴퓨터가 이해하는 정적인 정보(숫자, 문자, 이미지, 동영상)

명령어instruction: 컴퓨터를 작동시키는 정보

컴퓨터의 핵심 부품

중앙처리장치CPU/주기억장치Memory/보조기억장치(RAM/ROM)/입출력장치IO

기억장치들은 작을수록 빠르고, 클수록 느리다는 특징을 가지고 있다.

메모리: 현재 실행되는 프로그램의 정보(명령어/데이터)를 저장

값을 빠르게 접근하기 위해 주소를 사용

메모리는 메인 보드 안에, CPU 밖에 존재

CPU: 명령어 해석/실행

-산술논리연산장치ALU: 계산 수행

-레지스터: 임시 저장 장치

-제어장치: 제어 신호를 내보냄. 명령어를 해석

보조기억장치: 전원이 꺼져도 내용을 저장

(하드 디스크, SSD, USB메모리, DVD, CD-ROM 등)

입출력장치: 컴퓨터 외부에서 내부와 정보를 교환하는 장치

보조기억장치는 입출력장치의 일종

main board/mother board: 컴퓨터의 핵심 부품들이 연결되는 판.

시스템 버스로 서로 정보를 주고 받는다.

-주소 버스 / 데이터 버스 / 제어 버스

2단원

더보기

2단원: 데이터

2-1: 0과 1로 숫자를 표현하는 방법

2-1-1 단위

비트bit: 0과 1을 나타내는 가장 작은 정보 단위

n개의 비트로 표현할 수 있는 상태 2^n 가지

바이트byte: 비트 8개

KB(byte * 1000) / MB(KB * 1000) / GB(MB * 1000) / TB(GB * 1000)

워드word: CPU가 한 번에 처리할 수 있는 데이터 크기 (32bit/64bit…)

2-1-2 이진법

이진법binary

음수 표현: 2의 보수를 구한 값을 음수로 간주한다.

2의 보수) 어떤 수를 그보다 큰 2^n에서 뺀 값

0과 1을 뒤집고 거기에 1을 더한 값

ex) 11 → 00 → 01

플래그 비트: 음수와 양수를 구분하기 위해 사용되는 bit.

2-1-3 16진법

0 1 2 3 4 5 6 7 8 9 A B C D E F

0x를 붙여 구분한다.

16진수 → 2진수 변환: 16진수 한 수를 4bit를 가진 2진수로 변환한다.

2진수 → 16진수 변환: 2진수를 4자리씩 끊어 16진수로 변환한다.

2-2: 0과 1로 문자를 표현하는 방법

문자 집합: 컴퓨터가 인식하고 표현할 수 있는 문자의 집합

문자 인코딩encoding: 문자 → 이진수

문자 디코딩decoding: 이진수 → 문자

아스키ASCII 코드:

초창기 문자 집합. 알파벳과 숫자 특수 문자를 포함한다.

각각 7bit로 표현되며 128개의 문자가 있다.

(8bit의 확장 아스키 존재)

한글 인코딩

  1. 완성형 인코딩: 한글 한 글자 한글자가 고유함.(EUC-KR, CP949)
  2. 조합형 인코딩: 초성/중성/종성을 조합해 비트열을 할당해 하나의 코드를 완성.

유니코드: 현대 문자를 표현할 때 가장 많이 사용되는 표준 문자 집합.

3단원

더보기

3단원: 명령어

3-1: 소스 코드와 명령어

고급 언어: 사람이 이해하기 쉽게 만들어진 언어(C, Java, Python…)

저급 언어: 컴퓨터가 이해하기 쉽게 만들어진 언어

-기계어: 0,1의 명령어 bit로 이루어진 언어

-어셈블리어: 0,1의 명령어를 사람이 읽기 편한 형태로 번역한 언어

컴파일

: 소스 코드 전체가 저급 언어로 변환되어 실행

소스 코드 → 목적 코드(저급 언어)

인터프리터

: 한 줄씩 변환되어 실행

컴파일 언어가 인터프리터 언어에 비해 빠른 이유. 통으로 번역하기 때문에 그 번역 과정 중에 저급 언어(어셈블리어)에서 실행되기 좋은 방식으로 변환되기도 한다. 이렇게 조정하면 한 클락 동안 동시에 처리할 수 있는 instruction이 늘어나기 때문에 효율적이다.

 

3-2: 명령어의 구조

-연산 코드(연산자) op code: 명령어가 수행할 연산

-오퍼랜드(피연산자): 연산에 사용할 데이터

데이터 뿐만 아니라 데이터가 담긴 위치를 포함하기도 한다.(주소 필드)

주소 지정 방식

-즉시 주소 지정 방식: 연산에 사용될 데이터를 직접 명시

숫자 등의 데이터를 직접 명시. 표현할 수 있는 bit만큼 데이터의 크기가 작아짐. 메모리에 접근할 필요가 없으므로 빠르다.

-직접 주소 지정 방식: 연산에 사용될 데이터를 유효 주소로 명시

데이터의 크기가 크다. 유효 주소가 있는 메모리를 가리킴

-간접 주소 지정 방식: 유효 주소의 주소를 명시

두 번의 메모리 접근이 필요해 느리다.

-레지스터 주소 지정 방식: 연산에 이용할 데이터가 담긴 레지스터를 명시

CPU 내에 있으므로 일반적인 메모리 접근보단 빠르다.

-레지스터 간접 주소 지정 방식: 데이터를 메모리에 저장하고, 그 주소를 레지스터에 저장한다. 그 주소를 명시한다.

4단원

더보기

04:CPU의 작동 원리

04-1:ALU와 제어장치

ALU

https://ko.wikipedia.org/wiki/산술_논리_장치

input: 피연산자, 제어 신호(Opcode)

-산술 연산/논리 연산 등을 수행

output: 계산 결과, 플래그(추가적인 상태 정보 Status)

플래그 → 플래그 레지스터에 저장

플래그 종류 의미
부호 플래그 연산한 결과의 부호
제로 플래그 연산 결과가 0인지의 여부
캐리 플래그 올림수나 빌림수가 발생했는지의 여부
오버플로우 플래그 오버플로우의 여부
인터럽트 플래그 인터럽트가 가능한지의 여부
수퍼바이저 플래그 어떤 모드로 실행중인지의 여부(커널 모드/사용자 모드)

제어장치

input: 클럭 신호, 명령어, 플래그 값, 제어 신호

-클럭 신호clock: 컴퓨터에서 사용되는 시간 단위.

클럭에 맞춰서 신호가 전달되거나 받아들여지지 않으면 신호의 혼란이 생길 수 있다.

항상 모든 신호가 같은 타이밍에 전달되는 것이 아니므로…

-명령어: 명령어 레지스터에 저장된 CPU가 해석해야 할 명령어.

-플래그 값

-제어 신호: 제어 시스템 버스로부터 전달된 제어 신호.

output: CPU 외부에 전달하는 제어 신호 / CPU 내부에 전달하는 제어 신호

외부 - 제어 버스로 제어 신호를 내보낸다.

내부 - ALU / 레지스터

04-2:레지스터

8개의 레지스터

  1. 프로그램 카운터(PC) : 메모리에서 읽어 들일 명령어의 주소를 저장.
  2. 명령어 레지스터: 해석될 명령어를 저장.
  3. 메모리 주소 레지스터: 메모리의 주소를 저장. CPU가 읽어들이고자 하는 메모리의 주소 값이 이곳에 저장된다. 주소 값을 주소 버스로 보낼 때 이곳을 거친다.
  4. 메모리 버퍼 레지스터: 메모리와 주고받을 값을 저장. 데이터 버스로 주고받을 값이 이곳을 거친다.
  5. 플래그 레지스터: 연산 결과/CPU 상태에 대한 부가적인 정보를 저장.
  6. 범용 레지스터: 다양한 상황에서 범용으로 사용된다. 데이터와 주소 모두 저장할 수 있는 레지스터. 하나가 아니라 여러 개가 존재한다.
  7. 스택 포인터: ‘스택 주소 지정 방식’에 사용된다.
  8. 베이스 레지스터: ‘변위 주소 지정 방식’에 프로그램 카운터와 함께 사용된다.

스택/베이스 레지스터 - 주소 지정에 사용되는 레지스터들.

스택 주소 지정 방식: 스택 포인터가 스택의 맨 위를 가리킨다.

변위 주소 지정 방식: 변위의 값과 특정 레지스터의 값을 더하여 유효 주소를 얻어낸다.

참조 레지스터의 값 + 오퍼랜드의 주소 값 = 유효 주소

-상대 주소 지정 방식: 레지스터가 프로그램 카운터인 경우.

실행할 명령어로부터 +프로그램 카운터의 값 만큼

-베이스 레지스터 주소 지정 방식: 레지스터가 베이스 레지스터 인 경우.

기준 주소와 오퍼랜드(기준 주소로부터 떨어진 거리) 를 합한 주소의 값

04-3:명령어 사이클과 인터럽트

명령어 사이클: CPU가 흐름에 따라 명령어를 처리해 가는 것

-인출 사이클: 메모리에 있는 명령어 → CPU로 인출

-실행 사이클: CPU로 가져온 명령어를 실행하는 단계

-간접 사이클: ‘간접 주소 지정 방식’ 에서 메모리 접근을 하는 단계

인터럽트interrupt: 명령어 사이클이 끊어지는 것

더 급한 일 때문에 먼저 CPU를 사용해야 하는 상황

-동기 인터럽트: CPU에 의해 발생. 예기치 못한 상황에 마주쳤을 때 발생. Exception(예외)

-비동기 인터럽트: 입출력장치(I/O)에 의해 발생. 알림 역할을 한다. 하드웨어 인터럽트


하드웨어 인터럽트

: 입출력 장치의 작업 도중에도 효율적으로 명령어를 사용하기 위한 알림.

(작업 완료 여부 확인 등)

처리 순서

  1. 입출력 장치 -(인터럽트 요청 신호)→ CPU
  2. CPU 매 사이클 마다 인터럽트 여부 확인
  3. CPU는 인터럽트 플래그를 보내 받아들일 수 있는지 여부 체크
  4. 가능하다면 지금까지의 작업 백업
  5. CPU는 인터럽트 벡터 참조 → 인터럽트 서비스 루틴 실행
  6. 실행이 끝나면 작업을 복구하여 실행 재개

5단원

더보기

05:CPU 성능 향상 기법

05-1:빠른 CPU를 위한 설계 기법

클럭 속도: 단위-헤르츠(Hz), 클럭 속도가 높을수록 CPU의 성능이 좋다.

하지만 클럭 속도가 높아지면 CPU의 발열이 심해진다는 문제가 있다.

코어: 명령어를 실행하는 부품. 오늘날의 CPU에선 여러개가 존재할 수 있다.

-멀티코어: 코어가 여러개인 CPU. 멀티코어 프로세서

코어가 많을 수록 CPU의 처리 속도가 빠르다.

코어마다 처리할 연산이 적절히 분배되지 않는다면 수에 비례하여 연산 속도가 증가되진 않는다. 작업량이 적어도 마찬가지이다.

스레드: 실행 흐름의 단위.

-하드웨어적 스레드: CPU에서 사용된다. 하나의 코어가 동시에 처리하는 명령어 단위.

하나의 코어로 여러 명령어를 동시에 처리하는 CPU → 멀티스레드 프로세서

-소프트웨어적 스레드: 하나의 프로그램에서 독립적으로 실행되는 명령어 단위.


멀티스레드 프로세서

명령어 처리에 필요한 레지스터를 여러개 소유하고 있으면 여려 명령어의 처리가 가능하다.

프로그램 입장에서는 코어와 다를 바가 없기도 해 논리 프로세서라고 부르기도 한다.

05-2:명령어 병렬 처리 기법

명령어 병렬 처리 기법

  1. 명령어 파이프라인
  2. 슈퍼스칼라
  3. 비순차적 명령어 처리

명령어 파이프라인

명령어 인출 → 명령어 해석 → 명령어 실행 → 결과 저장

같은 단계가 겹치지 않으면 CPU는 각 단계를 동시에 실행할 수 있다.

파이프라인 위험

  1. 데이터 위험: 명령어 간에 ‘데이터 의존성’ 이 있을때 앞 명령어가 끝나지 않은 상태에서 데이터 의존성이 있는 뒤 명령어가 실행되었을 때 잘못된 데이터를 얻거나 할 수 있는 위험.
  2. 제어 위험: 프로그램 카운터가 갑자기 변화해 다음 명령이 바뀌었을 경우 처리 중인 명령은 쓸모가 없어진다. if문 등으로 교체되는 경우 등.(다음 명령어로 무엇이 올지 예측할 수 없음)
  3. 구조적 위험: 서로 다른 명령어가 동시에 같은 부품을 사용하려고 할 때. 자원 위험.

슈퍼스칼라

CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조.

(명령어 파이프라인을 여러 개 두는 것)


비순차적 명령어 처리

명령어들을 순차적으로 실행하지 않는 기법

순서를 바꾸어 실행해도 문제가 될 게 없는, 데이터 의존성이 없는 명령어들을 먼저 실행하여 파이프라인이 멈추는 것을 방지한다.

05-3:CISC와 RISC

명령어 집합ISA: CPU가 이해할 수 있는 명령어들의 모음

ISA가 같은 CPU끼리 서로의 명령어를 이해할 수 있다.

CISC: 복잡하고 다양한 명령어들을 활용하는 CPU 설계 방식.

-가변 길이 명령어

-적은 수의 명령어로도 프로그램 실행 가능 → 메모리 절약

-명령어 수행 시간이 길고 규격화되어있지 않기 때문에 파이프라인이 어려움.

RISC: 명령어의 종류가 적은 CPU 설계 방식

-고정 길이 명령어

-명령어가 규격화되어 있다. 파이프라인 최적화.

6단원

더보기

6단원: 메모리와 캐시 메모리

06-1: RAM의 특징과 종류

💡 RAM : 실행할 프로그램의 명령어와 데이터가 저장되는 휘발성 저장 장치

 

-실행할 프로그램의 명령어와 데이터가 저장된다.

-전원을 끄면 저장된 내용이 사라지는 휘발성 저장 장치

-‘실행할 대상’ 이 저장된다.

용량: 클 수록 좋다. (용량과 성능이 항상 비례하진 않는다. 한계가 있다.)

작을 수록 보조기억장치에서 실행할 프로그램을 가져오는 일이 잦아진다.

💡 *비휘발성 저장 장치: 보조기억장치(하드 디스크, SSD, CD-ROM, USB 메모리)

 

-전원을 꺼도 저장된 내용이 유지된다.

-CPU가 직접 접근하지 못한다.

-‘보관할 대상’ 이 저장된다.


종류

(1) DRAM(Dynamic RAM)

저장된 데이터가 동적으로 변하는 RAM. 시간이 지나면 저장된 데이터가 사라진다. 일정 주기로 데이터 재저장 필요. 저렴하다. 주로 사용된다.

(2) SRAM(Static RAM)

저장된 데이터가 변하지 않는 RAM(정적). DRAM보다 속도가 빠르다. 비싸다. 소용량의 빠른 속도가 필요한 장치, 캐시 메모리 등에서 사용된다.


  DRAM SRAM
재충전 필요 불필요
속도 느림 빠름
가격 저렴 비쌈
집적도 높음 낮음
소비 전력 적음 많음
사용 용도 주기억장치RAM 캐시 메모리

(3) SDRAM(Synchronous Dynamic RAM)

클럭 신호와 동기화된 RAM. (동기식 DRAM)

(클럭 신호에 맞춰 CPU와 정보를 주고 받는다.)

(4) DDR SDRAM(Double Data Rate SDRAM)

대역폭(데이터를 주고받는 길의 너비)을 넓힌 SDRAM.

SDR SDRAM(SDRAM) → DDR SDRAM → DDR2 SDRAM → DDR3 SDRAM → DDR4 SDRAM

(한 클럭당 1) → (2) → (4) → (8) → (16)

06-2: 메모리의 주소 공간

물리 주소(Physucal address)

메모리 하드웨어가 사용하는 주소. 정보가 실제로 저장된 하드웨어 상의 주소.

논리 주소(Logical address)

CPU와 실행 중인 프로그램이 사용하는(이해하는) 자신만을 위한 주소. 실행중인 프로그램 각각에게 할당된 0부터 시작되는 새 주소.

💡 CPU ↔ 메모리 사이의 상호작용을 위해선 논리 주소 ↔ 물리 주소 간의 변환이 필요하다.


메모리 관리 장치(MMU): 논리 주소 ↔ 물리 주소 변환. CPU와 주소 버스 사이에 위치해 있다.

 

CPU가 발생시킨 논리 주소 + 베이스 레지스터 → 물리 주소

sequenceDiagram 

CPU->>MMU:(logical address)4958
MMU->>Memory:4958 + 6000(base register)

한계 레지스터: 논리 주소 범위를 벗어나는 명령어 실행을 방지해 다른 프로그램에 영향을 받지 않도록 보호한다.

 


메모리
—베이스 레지스터 값—
 
프로그램
(한계 레지스터 값)
—베이스 레지스터 +
한계 레지스터 값—
 

베이스 레지스터: 프로그램의 가장 작은 물리 주소 범위

한계 레지스터: 논리 주소의 최대 크기

06-3: 캐시 메모리

저장 장치 계층 구조 memory hierarchy

  1. CPU에 가까울수록 빠르고 멀수록 느리다
  2. 속도가 빠를수록 용량이 작고 비싸다
가격이 비싸다
레지스터   CPU와 가깝다   속도가 빠르다  
캐시 메모리        
메모리        
보조기억장치   CPU와 멀다 속도가 느리다 가격이 싸다

캐시 메모리 : CPU ↔ 메모리 사이에 위치. 레지스터와 메모리 사이의 SRAM 장치

CPU의 연산 속도와 메모리 접근 속도의 차이를 줄이기 위해 사용된다.

[(코어 내부)코어 ↔ L1 캐시 ↔ L2 캐시] ↔ (코어 외부)L3 캐시

←←← 속도가 빠르고/용량이 작고/가격이 비싸다

캐시 메모리는 CPU가 사용할 법한 대상을 예측하여 저장. (용량이 작기 때문)

이것이 실제로 들어맞았을 때 캐시 히트 cache hit 라고 부름.

예측이 틀려 데이터를 가져와야 하는 경우를 캐시 미스 chache miss 라고 부름.

$$ 캐시 적중률 = \frac{캐시히트횟수} {(캐시 히트 횟수 + 캐시 미스 횟수)} $$

적중률은 대략 85~95%

참조 지역성의 원리

  1. 시간 지역성 : CPU는 최근에 접근했던 메모리 공간다시 접근하려는 경향이 있다.
  2. 공간 지역성 : CPU는 접근한 메모리 공간 근처를 접근하려는 경향이 있다.

https://itwiki.kr/w/참조_지역성

*순차 지역성: 데이터가 순차적으로 액세스되는 경향(명령어는 순차적이기에)

7단원

더보기

07: 보조기억장치

07-1: 다양한 보조기억장치

하드 디스크HDD

  • 자기적인 방식으로 데이터를 저장(자기 디스크의 일종)
  • 대용량 저장 장치가 필요한 작업에 사용됨
  • 동그란 원판에 데이터를 저장하고, 이를 회전시켜 값을 읽음.

https://ko.wikipedia.org/wiki/하드_디스크_드라이브

  • Platter플래터: 실질적으로 데이터가 저장되는 곳
  • Spindle스핀들: 플래터를 회전시킴(속도단위: RPM)
  • Head헤드: 데이터를 읽고 쓰는 곳
  • Actuator(disk) arm디스크 암: 헤드를 원하는 위치로 이동시킴

[데이터가 저장되는 과정]

  • 트랙/섹터 단위로 데이터를 저장.
  • 트랙: 플래터를 여러 동심원으로 나눈 하나의 원
  • 섹터: 트랙을 나눈 조각
  • 실린더: 같은 트랙이 위치하는 곳

[저장된 데이터에 접근하는 과정]

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

플래시 메모리flash memory

USB메모리, SD카드, SSD

  • 전기적으로 데이터를 읽고 쓰는 반도체 기반
  • 다양한 곳에서 널리 사용
  • NAND / NOR 로 나뉘어진다.
  • 데이터를 저장하는 단위: 셀
    • 한 셀에 1비트: SLC
    • 한 셀에 2비트: MLC
    • 한 셀에 3비트: TLC

[SLC]

  • 한 셀로 0, 1 두 개의 정보를 표현
  • MLC/TLC에 비해 빠른 입출력, 긴 수명, 높은 가격
  • 고성능의 저장 장치로 사용됨

[MLC]

  • 한 셀로 4개의 정보를 표현
  • SLC보다 속도와 수명은 떨어짐.
  • 대용화하기 유리
  • SLC보다 저렴함.

[TLC]

  • 한 셀로 8개의 정보를 표현
  • 대용량화에 유리.
  • 수명과 속도가 떨어지지만 용량 대비 가격이 저렴함.

[플래시 메모리의 단위]

  • 셸 → 페이지 → 블록 → 플레인 → 다이
  • 읽기/쓰기는 페이지 단위로 실행
  • 삭제는 블록 단위로 실행
  • 페이지: Free/Valid/Invalid
    • Free: 어떠한 데이터도 저장하고 있지 않아 새 데이터 저장 가능
    • Vaild: 유효한 데이터를 저장하고 있는 상태
    • Invalid: 유효하지 않은 데이터(쓰레기값)을 저장하고 있는 상태
  • 덮어쓰기가 불가능해 Invalid상태에서 새 데이터를 저장할 수 없다.

07-2: RAID의 정의와 종류

RAID: 여러 개의 물리적 보조기억장치를 하나의 논리적 보조기억장치처럼 사용하는 기술

RAID 레벨: RAID 구성 방법

  • RAID 0: 데이터를 단순히 나누어 저장. 이론상 4배가량 빠름. 디스크 중 하나에 문제가 생기면 모든 정보를 읽을 수 없음

스트라이핑: 분산하여 저장하는 것

  • RAID 1: 복사본을 만들어서 저장. 동일한 내용을 복사해 두 군데에 저장. 이론상 2배가량 빠름. 많은 양이 필요하고 비용이 증가함.
  • RAID 4: 오류를 검출하는 패리티 비트를 둠. 새로운 데이터를 저장할 때 마다 패리티를 저장하는 디스크에도 데이터를 쓰므로 병목 현상이 발생한다.
  • RAID 5: 패리티 정보를 분산하여 저장한다.
  • RAID 6: 기본적으로 RAID 5와 같으나, 서로 다른 두 개의 패리티를 둔다. 오류를 검출할 때 더욱 안전하다.

8단원

더보기

8단원: 입출력장치

08-1: 장치 컨트롤러와 장치 드라이버

입출력장치의 전송률은 CPU와 메모리에 비해서 낮다. 이는 통신을 어렵게 한다.

때문에 컴퓨터에 직접 연결되지 않고 장치 컨트롤러를 통해 연결된다.

장치 컨트롤러의 역할

  • CPU와 입출력장치 간의 통신 중개
  • 오류 검출
  • 데이터 버퍼링

*버퍼링: 데이터를 버퍼에 저장해 놓았다가 조금씩 내보내며 전송률을 맞추는 방식.

장치 컨트롤러의 내부

  • 데이터 레지스터: CPU ↔ 입출력장치 사이에 주고받을 데이터가 담기는 레지스터. 버퍼링의 버퍼 역할을 한다.
  • 상태 레지스터: 입출력 장치에 대한 상태 정보가 저장된다.
  • 제어 레지스터: 수행할 내용에 대한 제어정보/명령을 저장한다.

장치 드라이버: 장치 컨트롤러를 제어해 컴퓨터와 정보를 주고받는 프로그램. 입출력장치를 연결하기 위한 소프트웨어

08-2: 다양한 입출력 방법

CPU↔장치 컨트롤러의 정보 주고받기

  1. 프로그램 입출력: 프로그램 속 명령어로 입출력장치 제어
    • 장치 컨트롤러가 메모리를 다루는 방식
      1. 메모리 맵 입출력: 메모리에 접근하기 위한 주소 공간과 입출력 장치에 접근하기 위한 주소 공간을 하나로 간주.
        1. 메모리 주소 공간 축소
        2. 같은 명령어 사용 가능
      2. 고립형 입출력: 메모리에 접근하기 위한 주소 공간과 입출력 장치에 접근하기 위한 주소 공간을 분리.
        1. 공간이 축소되지 않음
        2. 전용 명령어 사용 필요
  2. 인터럽트 기반 입출력: 인터럽트를 기반으로 하는 입출력. 장치 컨트롤러에 의해 발생하며 CPU가 장치 컨트롤러에게 명령하면 컨트롤러가 입출력 장치를 제어함.
    • 다중 인터럽트 처리 방법
    *다중 인터럽트: 여러 입출력 장치에서 인터럽트가 동시에 발생한 경우.
    • 무시할 수 없는 인터럽트 NMI가 발생한 경우: 우선순위가 높은 것부터 처리
    • PIC를 이용해 우선순위를 판별.
    •  
    PIC의 다중 인터럽트 처리 과정
    1. 장치 컨트롤러 -인터럽트 요청 신호→ PIC
    2. PIC(인터럽트 우선순위 판단) -우선적으로 처리할 신호→CPU
    3. CPU-인터럽트 확인 신호→CPU
    4. PIC-(데이터버스)인터럽트 벡터→CPU
    5. CPU가 인터럽트 서비스 루틴을 실행
    sequenceDiagram
    
    장치 컨트롤러 ->> PIC: 인터럽트 요청 신호
    장치 컨트롤러 ->> PIC: 인터럽트 요청 신호1
    장치 컨트롤러 ->> PIC: 인터럽트 요청 신호2
    Note over PIC: (우선순위 판단) 
    PIC ->> CPU: 처리할 인터럽트 요청 신호
    CPU ->> PIC: 인터럽트 확인 신호
    PIC ->> CPU: 인터럽트 벡터
    Note over PIC, CPU: (데이터 버스)
    Note over CPU: 인터럽트 서비스 루틴 실행
    

 

 3. DMA 입출력: 입출력 장치와 메모리가 CPU를 거치지 않고 상호작용하는 방식. DMA 컨트롤러가 필요하다.

  • DMA 입출력 과정
    1. CPU → DMA 컨트롤러에 입출력 작업을 명령
    2. DMA 컨트롤러는 CPU 대신 장치 컨트롤러와 상호작용 하여 입출력 작업을 수행
    3. 입출력 작업이 끝나면 DMA 컨트롤러는 CPU에 인터럽트를 건다.

 

  • DMA 입출력 버스: DMA 컨트롤러와 장치 컨트롤러들은 입출력 버스라는 별도의 버스에 연결되어 있다.
    • PCL 버스
    • PCL Express(PCle) 버스

 

운영체제

https://github.com/mingimouse/HIPS-OS

 

 

12단원 : 프로세스 동기화

https://github.com/mingimouse/HIPS-OS/blob/main/Chapter12/1.md

 

더보기

다른 프로세스들과 영향을 주고 받는 협력적 프로세스데이터의 일관성을 유지하기 위해 서로 동기화가 될 필요가 있다.

다음 협력적 프로세스들를 살펴보자.

생산자 - 소비자 문제

생산자(Producer)

while(true){
	/* 생산 */
	while (counter == BUFFER_SIZE);
	/* do noting */
	buffer[in] = next_produced;
	in = (in + 1) % BUFFER_SIZE;
	counter ++
}
	
 

소비자(Consumer)

while(true) {
	while(counter == 0);
	/* do noting */
	next_consumed = buffer[out];
	counter --;
	/* 소비 */
}
 

자원을 만들기만 하는 생산자와 자원을 사용하기만 하는 소비자가 존재한다.

두 프로세스가 사용하는 버퍼는 수치적으로 보았을 때 counter 라는 변수에 의존한다.

그러나 두 프로세스가 병렬적으로 수행될 때 저수준 언어에서는 counter+- 가 한 줄로만 실행되지 않는다. 레지스터에 값을 저장하려면 레지스터를 불러오고, 값을 변경하고, 다시 집어넣는 방식으로 변환될 수 있다.

exsample

counter++
->
register = counter
register = register + 1
counter = register1
 

이렇게 세 단계로 구분되기 때문에 섞인다면 결과가 완전히 뒤바껴버릴 수 있다.


Race condition(경쟁 상황) : 실행 결과가 접근이 발생한 특정 순서에 의존하는 상황

이와 같은 상황을 막기 위해 한 순간에 하나의 프로세스만이 count에 접근할 수 있도록 해야 한다. 그러기 위해선 두 프로세스는 동기화가 되어야 한다.

동기화 - ‘작업들 사이의 수행 시기를 맞추는 것’ 프로세스 동기화란 아래의 두 가지를 의미한다.

  • 실행 순서 제어: 프로세스를 올바른 순서대로 실행하기
  • 상호 배제: 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기.

임계구역(critical section)

프로세스에는 임계구역이라는 코드부분이 있다. 이 부분은 여러 프로세스들이 정보를 수정할 수 있는 공간이라 한 프로세스가 들어가면 다른 프로세스가 접근할 수 없게끔 상호배제가 이루어져야 한다.

상호배제가 이루어지려면 진입할 때 진입 허가를 요청해야 한다.

진입 구역(entry section): 진입 요청을 구현하는 코드 부분

퇴출 구역(exit section): 임계구역 뒤의 부분

나머지 구역(remainder section): 나머지 일반적인 부분

while (true) {

/* -- 진입 구역 -- */

(임계 구역)

/* -- 퇴출 구역 -- */

(나머지 구역)
 

상호배제 동기화를 통해 임계구역을 설정하려면 다음과 같은 원칙이 지켜져야 한다.

  1. 상호 배제(mutual exclusion): 한 프로세스가 임계구역에서 실행되는 동안, 다른 프로세스는 자신의 임계구역에서 실행될 수 없다.
  2. 진행(progress): 임계구역에서 실행되는 프로세스가 없고, 진입하려고 하는 프로세스가 있다면 누가 임계구역으로 진입할 수 있을지 결정할 수 있으며 이 선택은 연기될 수 있다.
  3. 한정된 대기(bounded waiting): 프로세스가 임계구역에 진입하고 싶어한다면 그 프로세스는 언젠가 들어올 수 있어야 한다. (계속 대기하고 있으면 안된다.)

동기화 예시

[1] Peterson의 해결안

  • 고전적인 소프트웨어-기반 해결책
  • 이 해결안은 두 개의 프로세스가 있을 때로 조건이 한정된다.

[초기 조건]

  • 두 프로세스는 아래의 변수들를 공유한다.
int turn 
// 누가 임계 구역에 들어갈 턴인지를 결정
Boolean flag[2]
// (array) 임계구역에 들어갈 준비가 되었는지(들어가고 싶은지)
// 를 나타낸다.
 

[임계 구역에 진입하는 방법]

  1. flag[i] 를 true 로 만든다.
  2. turn을 j로 바꾼다.

[코드]

while(true){
flag[i] = true;
turn = j;
while (flag[j] && turn == j);
	/* critical section */
flag[i] = false;
	/* remainder section */
}
 
while (flag[j] && turn == j);
 

둘 중 하나가 False가 되었을 때 while 문을 빠져나오게 된다.

j도 같은 코드를 가지고 있다. (flag와 turn 지정만 다른)

하지만 만약 j와 i가 동시에 실행되었다고 하더라도 turn의 값은 하나이기 때문에 while 문을 통과할 프로세스는 하나 뿐이다.

(예시)

/* start */

[i] -> flag[i] = true
[j] -> flag[j] = true
[i] -> turn = j
[j] -> turn = i

/* status flag[i,j] = true / turn = i */

[i] 진입요청
flag[j] = true && turn != j
while -> false
/* critical section */

[j] 진입요청
flag[i] = true && turn == i
while -> true
/* wait */

[i] 가 나온 뒤
flag[i] = false; 를 실행하기 때문에
[j]의 while 문이 풀리게 되어 실행 가능하다.
 

그러나 위 해결안은 소프트웨어 기반이고, 오래 전에 이야기 되었기 때문에 최신 컴퓨터 아키텍쳐에선 작용하지 않을 수 있다.

[2] 메모리 장벽

메모리 모델: 컴퓨터 아키텍처가 응용 프로그램에게 제공하는 메모리 접근 시 보장되는 사항을 결정한 방식.

1) 강한 순서: 한 프로세서의 메모리 변경 결과가 다른 모든 프로세서에 즉시 보임.

2) 약한 순서: 한 프로세서의 메모리 변경 결과가 다른 프로세서에 즉시 보이지 않음.

이 유형은 프로세스마다 다르다. 다른 프로세서들에게 보이지 않으면 메모리 변경 결과를 알 수 없기 때문에 컴퓨터 아키텍쳐는 메모리의 모든 변경 사항을 다른 모든 프로세서로 전파하는 명령어를 제공한다. (메모리 장벽 memory barriers)

메모리 장벽: 명령어가 저수준 언어 레벨에서 재정렬되더라도 메모리 장벽은 항후 적재 또는 저장 작업이 수행되기 전에 저장 작업이 메모리에서 완료되어 다른 프로세서들에게 보이도록 한다.

T1

while (! flag)
	memory_barrier();
print x;
 
T2

x = 100;
memory_barrier();
flag = true;
 

flag 값이 x보다 먼저 적재되도록 보장한다.


[3] 하드웨어 명령어

여러 줄의 명령어들을 atomic(원자적)으로 만들면 저수준 언어에서도 쪼개지지 않는다. 수행 도중에 인터럽트 당하지 않는다는 뜻이다. 프로세스가 실행이 되거나/되지 않거나 가 완전히 보장된다.

[3-1] TAS 방식 (test_and_set)

[초기 조건]

test_and_set 함수

boolean test_and_set (boolean *target)
{
	boolean rv = *target;
	*target = TRUE;
	return rv;
}
 

이 명령어는 원자적으로 실행된다. (This instruction is executend stomically)

프로세스들은 다음의 변수를 공유한다.

boolean lock; // initialized False
 

(실행 코드)

do{
	while(test_and_set(&lock));
	/* do nothing */

	/* critical section */

	lock = false;

	/* remainder section */
} while (true);
 

test_and_set 함수가 거짓이 되면 while문을 빠져나온다.

처음 lock에 들어간 false은 false를 반환함으로서 while문을 나오지만 lock의 값은 true로 바뀌게 된다. 때문에 다음 번에 이 while문을 통과하려는 프로세스는 나올 수 없다.

초기값이 false인 lock값을 원자적으로 true로 만듬으로서 상호 배제를 구현하는 것이다.


[3-2] CAS 방식 (compare_and_swap)

[초기 조건]

compare_and_swap 함수

int compare_and_swap(int *value, int expected, int new_value)
{
	int temp = *value;
	if (*value == expected)
		*value = new_value;
	return temp;
"
 

이 명령어는 원자적으로 실행된다. (This instruction is executend stomically)

다음의 변수를 공유한다

Boolean lock; // initalized to 0;
 

(실행)

while (true){
	while (compare_and_swap(&lock, 0, 1) != 0);
		/* do nothing */

		/* critical section */

		lock = 0;
		
		/* remainder section */

}
 

expected = 0

new_value= 1

피연산자 value는 오직 *(value == expected) 이 참일 때에만 new_value로 지정된다.

즉, 예측한 값이 참일 때만 새 값으로 바뀌게 된다.


[4] Mutex Locks (뮤텍스 락) (spinlock)

하드웨어 기반 해결책은 응용 소프트웨어 프로그래머들이 사용할 수 없다. 대신 mutex 락이라는 상위 수준 도구를 사용한다.

프로세스는 임계구역에 들어가기 전에 반드시 락을 획득해야 하고, 빠져나올 때 반환해야 한다.

[초기 조건]

아래의 두 함수는 반드시 원자적(atomic) 이여야 한다.

acquire(): 락을 획득한다.

acquire(){
	while (!available);
	/* busy wait */
	available = false;
}
 

release(): 락을 반환한다.

release() {
	acailable = true;
}
 

(사용)

while (true) {
	/* -- acquire lock -- */
		critical section
	/* -- release lock --*/
		remainder section
 

여기까지의 구현 방식은 바쁜 대기busy waiting 가 필요하다.

바쁜 대기: 기다리기 위해 loop문을 CPU가 계속 체크한다. CPU의 자원 낭비가 된다.


[4] 세마포(Semaphores)

바쁜 대기 문제를 해결하기 위해 일시적으로 휴면 상태로 전환한 후 락을 사용할 수 있게 되면 깨워 바쁜 대기를 피한다.

세마포 S는 정수 변수wait()와 signal() 함수만이 접근할 수 있다.

(두 함수는 원자적이여야 한다.)

[초기 조건]

wait()

wait(S){
	while (S <= 0);
	S--;
}
 

signal()

signal(S){
	S++;
}
 

세마포 사용법

  • 이진 세마포: 값의 범위, 0과 1사이의 값만 가능.

(mutex 락과 유사하게 동작)

  • counting 세마포: 값의 범위, 제한 없다.

(유한한 개수를 가진 자원에 대한 접근 제어. 세마포는 사용 가능한 자원의 개수로 초기화된다.)

  1. 임계 구역에 접근하려는 프로세스는 세마포에 wait() 연산을 수행한다.
  2. (1) 로 인해 세마포의 값이 감소한다.
  3. 임계 구역에서 나왔을 때, signal() 연산을 수행한다.
  4. (3) 로 인해 세마포는 증가한다.
  5. 세마포의 값이 0이 되면 모든 자원이 사용 중임을 뜻한다.

(실행)

  • P1과 P2가 병렬적으로 진행하고 있다. P1이 먼저 수행되고 P2가 이후에 수행되어야 한다고 가정하자.
  • 두 프로세스는 세마포 synch를 공유한다. 이를 0으로 초기화한다.
P1

S1;
signal(S);
 
P2

wait(S);
S2;
 

하지만 위의 방식으로도 바쁜 대기를 막을 순 없다.

세마포를 다음과 같이 구현하면 바쁜 대기를 막을 수 있다.

세마포 S

typedef struct{
	int value;
	struct process *list;
} semaphore;
 

wait()

wait(semaphore *S){
	S-> value--;
	if (S -> value < 0) {
		add this process to S->list;
		sleep();
	}
}
 

세마포 값이 양수가 아닌 것을 발견하면, 프로세스는 반드시 대기한다. 바쁜 대기 대신 프로세스는 잠시 중단된다.(sleep)

signal()

signal(semaphore *S){
	S -> value++;
	if (S-> value <= 0) {
		remove a process P from S-> list;
		wakeup(P);
	}
}
 

다른 프로세스가 signal() 연산을 실행하면 다시 시작된다. (wakeup)

프로세스 리스트에 있는 리스트 중 하나를 꺼낸다.


[5] 모니터monitor

세마포를 사용해도 시간-순서 적 오류가 생길 수 있다.

또한 함수를 설정해야 하기 때문에 번거롭기도 하다.

모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스를 묶어 관리한다. 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근 가능하다.

조건 변수: 특정 조건을 바탕으로 프로세스를 실행하고 일시 중단하기 위해 사용. 실행 순서를 제어하는 특별한 변수.

wait: 호출한 프로세스의 상태를 대기로 전환하고 일시적으로 조건 변수에 대한 대기 큐에 삽입하는 연산. (위 그림의 큐와는 다르다)

(조건 변수의 동작)

signal: wait 연산으로 일시 중지된 프로세스를 재개하는 연산. 이 호출을 통해 대기 상태에 있던 프로세스가 깨어나 모니터 안으로 다시 돌아온다.


'2023 스터디' 카테고리의 다른 글

[웹 스터디] 미니 프로젝트: 클리커 게임  (0) 2023.11.03