9 분 소요

CPU의 핵심 구성 요소

  • CPU의 물리적인 구조
    물리 회로로 보는, 현재 우리 컴퓨터에 달린 CPU
    ALU / Control Unit / 레지스터 / Core / 코어 / 캐시(L1,L2,L3)
  • CPU의 시스템적 구조
    OS와 협력하여 프로그램 실행 과정에서의 구조
    (OS가 보는 CPU라 표현해도 옮은 표현)
    가상 주소 / 물리 주소 / 페이지 / 페이지 테이블 / TLB / 스케쥴링 시스템 / 문맥 전환

CPU의 물리적 구조

구성 요소 물리적 역할 (하드웨어 레벨)
ALU 산술/논리 연산 수행
CU 명령어 해석 및 제어 신호 발생
레지스터 초고속 저장공간
파이프라인 명령어를 여러 단계로 분해하여 병렬 처리
코어 ALU+CU+레지스터+L1 캐시 등 포함한 독립 실행 유닛
버스(내부 Bus) 레지스터·ALU·캐시 사이에 신호 전달
캐시(L1/L2/L3) CPU 근접 고속 메모리 (다음 챕터에서 자세히)
MMU (Memory Management Unit) 가상주소 → 물리주소 변환, 페이지 처리
TLB (Translation Lookaside Buffer) MMU 캐시 (주소 변환의 캐시)
  • 자신에게 주어지는 ‘명령어’를 해석 및 처리하기 위한 장치들부터
    그러한 작업 처리에 도움이 되는 각종 장치들

CPU의 시스템적 구조

시스템 요소 개념 CPU와의 관계
프로세스(Process) 독립적인 실행 단위 CPU의 실행 컨텍스트를 가짐
스레드(Thread) 프로세스 내 실행 흐름 레지스터 세트와 Stack 필요
문맥 전환(Context Switch) 실행 중인 작업 교체 레지스터·PC·Flags 저장/복원
가상 메모리(Virtual Memory) 각 프로세스에 독립적인 주소 공간 제공 CPU는 MMU를 통해 접근
페이지(Page) 가상 메모리의 일정 크기 블록 (보통 4KB) TLB에 캐싱됨
페이지 테이블(Page Table) 가상→물리 매핑 정보 CPU/MMU가 참조
TLB 페이지 테이블의 캐시 변환 속도 극대화
세그먼트(Segment) 코드/데이터/스택 등 메모리 분할 논리 현대 시스템에서는 거의 페이징 중심
시스템 콜(System Call) 커널 기능 요청 인터페이스 CPU 모드 전환 필요
스케줄러(Scheduler) 어떤 스레드가 CPU를 쓸지 결정 문맥 전환 수행
  • OS가 CPU라는 자원을 관리하는 방법
    또한, OS가 사용하기 쉽게 물리적 구조와의 연동점이 존재
    ‘대표적인 요소’가 밑에서 설명할 ‘캐시’

캐시

간략히 말하자면
‘기존’에 액세스한 데이터의 사본을 보관하는 임시 저장 공간
을 뜻한다

  • 느린 저장 공간의 데이터를 빠른 저장 공간의 데이터에
    보관하여, 느린 공간까지 갈 필요를 없게 하는 것

  • 이러한 작동 방식 덕에
    ‘데이터를 미리 저장해두어’ 빠른 재사용을 가능케 하는 전략도
    ‘캐시’라 표현함

캐시의 필요성

Image

  • CPU는 굉장히 빠른 성능을 지녔으며
    동시에 아주 작은 저장 용량을 가졌다
    (레지스터)

  • 그렇기에 조금 더 크며, 동시에 저렴한 부품들을
    ‘단계적’으로 구성하는 방식을 채택
    • 단순히 저장만 하는데 CPU 같은 최고급 부품을 사용할 필요가 없으며
      이는 경제적으로도 큰 손실
  • ‘하드웨어’로부터 CPU 까지 가능한 빨리 데이터를 보내기 위해 구성된 방식
    • CPU에 가까운 순으로 데이터를 찾으며
      데이터가 없다면 점점 더 느린 저장장치로 접근하여 데이터를 찾는다

지역성

이러한 메모리 접근 방식에서 CPU 설계자들은
CPU가 더 효율적으로 메모리를 로드하도록
‘접근 패턴’을 분석함

그리고 이와 같은 2가지 방식을 추가하여
더욱 효율적인 로딩을 가능케 했음
(이것이 지역성의 정체)

  • 시간 지역성
    최근 사용한 데이터가 가까운 미래에 또 쓰일 것
    • 이미 메모리에 로드된 데이터를 바로 내치는 것이 아니라
      가능한 ‘냅둔’ 상태로 한동안 보관함
    • LRU 같은 캐시 교체 알고리즘을 적용하여 교체함
  • 공간 지역성
    특정 데이터를 로드했을때, 그 주변 데이터들이 같이 쓰일 가능성이 높음
    • 따라서 밑의 ‘캐시라인’의 크기만큼 한 번에 가져옴
    • 운이 좋으면 한 번에 여러 데이터를 로드할 수 있음
  • 이러한 CPU의 예측이 성공하여 데이터를 매우 빠르게 가져오면
    ‘캐시 히트’

  • 예측이 실패하여 DRAM까지 가서 새로이 데이터를 찾아와야 한다면
    ‘캐시 미스’
    • 만약 DRAM에도 없다면 디스크(HDD/SSD)로 가서 찾아옴
      (여담이지만 이 타이밍에 OS가 Page Table을 업데이트 하고 명령을 재실행 시킴)

캐시 라인

CPU 캐시에 데이터를 저장하는 ‘최소 단위 블록’
(일반적인 x86 CPU에선 64Byte 사용)

  • 어차피 64바이트로 ‘기준’이 잡혀있기에
    단순히 int나 포인터 하나만 필요해도 그 근처의 메모리를 싹 긁어온다

  • 배열 같은 ‘연속적인’ 메모리를 잡는 자료구조가 ‘캐시 친화적’이란 소리를 듣는 이유!
    • 다만 ‘포인터’의 경우 보통 Heap 영역과 연계되기에
      배열을 사용한다 하더라도, ‘포인터’에 대한 접근만 친화적임
    • 데이터 지향 설계
      캐시 지향적이라는 말을 들을 수 있는 이유이기도 함
      (필요한 데이터끼리 묶어 CPU가 연속적으로 필요한 데이터를 읽으므로)
  • 멀티 스레드 환경에서
    False Sharing 문제를 유의하여 사용해야 함
    • 다른 쓰레드 끼리 쓰는 변수임에도 ‘같은 캐시 라인’에 포함되면
      서로 캐시 라인을 뺏으려 할 수 있기에 문제가 될 수 있음

CPU 스케쥴링

하나 이상의 CPU(코어)에서
여러 스레드를 어떤 순서로 실행할지 결정하는 작업

  • OS가 실행하는 하나의 ‘정책’ 중 하나
    • 비교적 추상화가 잘 되어 있기에 ‘고수준’ 정책이라고도 표현함
    • ‘저수준’ 정책은 밑에서 얘기할 ‘문맥 전환’
      (메모리 주소와 밀접한 관련이 있기에)
    • 사실 문맥 전환과 매우 밀접한 연관성이 있음
      (‘작업 교체’시 ‘문맥 전환’이 되기에)
  • CPU가 가능한 여러 작업을 ‘동시에’, ‘공정하게’ 진행해야 하기에
    이러한 정책을 사용
    • ‘자원’을 가능한 효율적이고 공평하게 사용하기 위한 것이다

스케쥴링 알고리즘

먼저 스케쥴링 알고리즘은 ‘선점형(Preemptive)’와
‘비선점형(Non-Preemptive)’으로 나뉨

  • 선점형
    OS가 CPU의 작업을 중단시키고 새로운 작업을 실행시키도록 함
    (현대 OS들이 기본적으로 사용하는 방식)
    (여기에 ‘우선순위’를 조정하는 로직을 추가하여 사용)
    • 보통 Timer 같은 ‘하드웨어 인터럽트’를 통해 ‘중단’시킴
  • 비선점형
    CPU의 작업 완료를 대기하도록 함
    • 하드웨어 등의 인터럽트로 중단되지 않음

스케쥴링 알고리즘 정리 표

스케줄링 방식 알고리즘 이름 선점 여부 특징 요약 장점 단점
비선점형 FCFS (First Come First Served) 도착 순서대로 실행 구현 간단, 공정성(순서 기준) 긴 작업 뒤에 짧은 작업이 오면 오래 지연(Convoy Effect)
비선점형 SJF (Shortest Job First) CPU 버스트가 가장 짧은 작업 먼저 평균 대기 시간 최소화(이론적 최적) 실제 실행 시간을 예측하기 어려움, 기아 상태 발생 가능
비선점형 Priority Scheduling (Non-Preemptive) 우선순위 높은 작업 먼저 중요 작업 먼저 수행 가능 낮은 우선순위 starvation(기아현상) 가능
비선점형 Multi-Level Queue (Non-Preemptive Mode) 여러 큐로 분류 후 고정 우선순위로 처리 프로세스 그룹별 최적화 큐 사이 이동 불가 → 유연성 부족
선점형 SRTF (Shortest Remaining Time First) SJF의 선점형 버전 짧은 작업 처리에 유리, 효율적 긴 작업은 starvation 위험
선점형 Round Robin (RR) 고정 Time Slice로 순환 공정성 최고, 인터랙티브 좋음 Time Slice 설정 난이도 / 과도한 스위칭 가능
선점형 Priority Scheduling (Preemptive) 높은 우선순위가 CPU 즉시 차지 실시간 대응 ↑ 낮은 우선순위의 starvation
선점형 MLFQ (Multi-Level Feedback Queue) CPU 사용량/행동 기반으로 우선순위 자동 조정 현대 OS 핵심 → 반응성 + 성능 조화 스케줄러 설계/튜닝 복잡
  • 기아(Starvation) 상태?
    특정 프로세스가 ‘응답’ 받지 못하고 계속 무시되는 상황
    (즉, 자원을 받지 못하는 상황)
    • 이러면 해당 프로세스가 ‘멈춘 것’ 처럼 보임
  • MLFQ의 경우, 현재 많은 OS의 기반이 된 방식임

프로세스와 스레드

먼저 말하자면

  • 프로세스 : OS가 본 ‘프로그램’의 추상화
  • 쓰레드 : 프로세스의 ‘실행단위’

먼저 OS에 대하여 좀 더 살펴보고
다시 알아보자

운영체제가 하는 일

Image

운영체제는 크게 3가지 일을 한다

  • 자원(하드웨어) 관리
    • CPU : 스케쥴링 등
    • 메모리 : 가상 메모리, 페이지 교체 등
    • 저장장치 : 파일 시스템 , 핸들 등
    • 네트워크 : 포트 할당 등
    • 입출력 장치

이외에도 여러 자원을 관리하며
‘여러’ 프로세스가 가능한 자원을 공정하게 사용하도록 관리함
(이를 위해 위의 ‘CPU’ 스케쥴링 같은 자원 관리 기법이 필요한 것!)

  • 추상화 (Abstraction)의 제공
    • 위의 ‘여러 하드웨어’들을 굳이 API 들이 알 필요없게 함
    • 필요시 시스템 콜을 통해 API -> 커널 -> 하드웨어로 필요한 자원을 요청하는 방식

예를 들자면

  • ‘파일 시스템’은 사실 0/1 의 집합이지만
    OS는 이를 ‘폴더/파일’의 개념으로 추상화 해줌

  • 프로세스 는 사실 ‘여러 명령어 모음(프로그램)’이지만
    ‘하나’의 ‘실행단위’로서 추상화

  • 스레드는 ‘물리적인 CPU 코어’를 추상화하여
    여러 ‘실행 흐름’을 표현

  • 보호와 고립(Protection & Isolation)

    • 프로세스들이 ‘서로’의 메모리 / 자원 을 침범하지 못하게 막음
    • 각 프로세스는 자신만의 ‘가상 주소 공간’을 가져
      다른 프로세스의 메모리를 볼 수 없게 함
    • 커널 / 유저 모드의 분리 및 파일 접근 권한을 통해
      해당 프로세스가 ‘실행 중’인 Data 영역에는 접근할 수 없도록 하였다

이러한 OS의 3가지 작업을 통해
‘프로세스’의 개념이 완성됨

  • 각 프로세스는 서로 분리
  • 각각의 가상 주소 공간 (선형)을 가짐
  • 작업 전환 시, 문맥 교환이 필요한 이유

  • 다만, Process 통신이 까다로워졌기에
    IPC 같은 별도의 방식을 통해 통신해야 함

참고용 개인 포스팅

운영체제의 가상화에 대하여 자세히 알고 싶다면

OS와 추상화
OS와 보호/고립,자원 관리

프로세스 vs 스레드

  • 프로세스
    각 API가 ‘혼자서’ 실행되도록 착각한 추상화 개념
    (OS가 추상화한 ‘실행 중인 프로그램’이라고도 표현함)
    • 고유한 가상 메모리 영역을 가지며
      이들은 Code/Heap/Stack/Data/BSS 영역 등으로 구분됨
    • 격리된 구조이기에, 다른 프로세스가 죽더라도
      현재 프로세스에는 영향을 주지 않음
    • 프로세스의 상태는 PCB(Process Control Block)으로 관리됨
    • 문맥 전환의 비용이 높은 편
      (보통 Page Table을 바꿔야 하기에 비용이 높은 편)
  • 스레드
    프로세스의 실행 단위
    • 프로세스의 자원을 공유하며, 각각 독립적으로 실행 됨
    • 고유한 Stack 공간을 지니지만, 나머지는 프로세스의 메모리 공간을 공유함
    • 프로세스 생성에 비하여 스레드는 훨씬 가벼우며
      CPU의 코어에서 병렬처리가 가능함
    • TCB를 통해 스레드의 상태 관리
    • 문맥 전환의 비용이 낮은 편
      (같은 프로세스 내부라서, Page Table을 그대로 사용하여 오버헤드가 적음)

표로 정리

구분 프로세스(Process) 스레드(Thread)
기본 개념 실행 중인 프로그램 프로세스 내부의 실행 흐름
메모리 공간 독립적 공유
Heap 공유 안함 공유함
Global 변수 공유 안함 공유함
Stack 개별적 개별적
Context Switching 비용 높음 낮음
생성 비용 작음
충돌(Deadlock) 위험 낮음 높음 (메모리 공유 때문)
IPC 필요 여부 필요 (별개 프로세스끼리) 필요 없음
안정성 한 프로세스 죽어도 다른 프로세스 영향 없음 한 스레드가 죽으면 프로세스 전체가 죽음

문맥 전환 (Context Switching)

CPU가 현재 실행 중인 작업 (Process Or Thread)의 상태를 저장하고
다른 작업의 상태를 불러와 실행하는 것

  • CPU 하나가 여러 작업을 동시에 처리하는 것처럼 보일 수 있는 이유

  • 문맥?
    CPU가 스레드를 실행하기 위해 필요한 정보
    • Thread Context : CPU 레지스터 값, 프로그램 카운터, 스택 포인터 등
    • Process Context : Page Table, 파일 핸들 등
      -> 스레드는 ‘실행 흐름’만을, 프로세스는 거기에 ‘환경’까지 포함하기에 무거움
  • 프로세스 문맥 전환
    • 비용이 큼 : 물리 메모리, 주소 공간 자체가 바뀌며, MMU의 Page Table 교체 등이 발생
  • 스레드 문맥 전환
    • 비용이 저렴 : CPU 레지스터와 Stack 정보 등만 바뀌고 프로세스의 주요한 환경은 그대로 이기에 상대적으로 저렴

동시성 이슈

멀티 스레드 환경에서 ‘특정 데이터’에 동시에 접근하는 경우
발생할 수 있는 여러 상황들

임계 영역(Critical Section)

다중 쓰레드 or 프로세스 환경에서
공유된 자원 (변수 등)에 접근하는 ‘코드 영역’을 의미

  • 일반적으로 ‘상호 배제’ 원칙이 지켜져야 하며
    그렇지 않으면 ‘경쟁 상태’가 발생함

  • 두 스레드가 특정 메모리의 변수 값을 동시에 수정하려는 등의 상황에서 발생
    (코드는 1줄이라도 내부에선 여러 명령어로 되어 있을 수 있음)
    (그래서 Atomic을 사용하는 이유이기도 함)

경쟁 상태(Race Condition)

두 스레드가 동시에 ‘임계 영역’에 접근하여 발생하는 상태

  • A가 값을 쓰고, 저장하려는데 B가 와서 값을 다시 쓴 후
    그제서야 A가 저장하여 ‘A의 저장값’이 정상적이지 않은 상황

  • 정의되지 않은 동작(Undefined Behavior)이란 표현으로도 사용하며
    시스템의 ‘무결성’을 훼손하여 피해야 함

  • DB와 같은 데이터 저장 시에는 반드시 피해야 하는 상황이기도 함

동기화 도구

상호 배제와 동기화를 위한 구현 방식들

  • Mutex
    • Lock 과 Unlock의 개념을 사용하는 대표적인 도구
    • 임계 영역에 ‘여러 스레드’가 접근하는 것을 막음
    • ‘소유권’ 개념이 있음
  • Semaphore
    • P 연산 / V 연산을 통해 접근되는 스레드를 N개로 제한
      • P : 세마포어 값을 감소하고, 0이면 접근한 스레드를 ‘대기’시킴
      • V : 세마포어 값을 증가시키고, 대기된 스레드 중 하나를 ‘실행’ 상태로 변경
항목 Mutex Semaphore
접근 허용 수 1개 N개
소유권 있음 없음
목적 독점적 자원 접근 자원 수 제한
binary semaphore와 비교 mutex와 비슷 binary는 mutex 유사
  • 조건 변수
    특정한 조건을 만족할때까지 대기 스레드를 ‘재우는 방식’
  • Atomic 연산
    ‘하나의 스레드’가 ‘완전히 완료’할때까지 독점하는 연산 방식
    (키워드 및 자료구조 등으로 제공)

데드락(Dead Lock)

동기화 도구를 ‘잘못’ 사용하여 발생할 수 있는 문제

  • 교착 상태라는 표현으로도 사용된다
    • 2개 이상의 프로세스 or 스레드가
      서로의 작업이 끝나기를 ‘계속 기다리는 상황’

데드락의 발생 원인 4가지

조건 의미
1) 상호 배제(Mutual Exclusion) 한 자원은 한 번에 한 프로세스만 사용
2) 점유와 대기(Hold and Wait) 자원을 가진 상태에서 다른 자원을 기다림
3) 비선점(No Preemption) 자원을 강제로 빼앗을 수 없음
4) 순환 대기(Circular Wait) A→B→C→A 형태로 서로 대기

데드락에 대한 대처 방식

  • 예방
    데드락이 생길 수 없도록 위의 4가지 조건 중 하나를 깨는 방식
    (OS가 아닌 프로그래머가 고려해야 하는 상황임)
    • 락 순서를 통일한다던가… 등
  • 회피
    OS가 시스템 상태를 체크하고 데드락이 발생 가능하다면 자원 요청 거절
    (은행원 알고리즘 이라는 유명한 알고리즘 존재)
    (다만 현대 OS에서는 거의 쓰지는 않음)
    • 자원 할당 -> 할당 가능/데드락 발생 여부 등을 모두 검사해야 하기에 규모가 큰 시스템에 부적합
  • 탐지 및 회복
    데드락이 발생했는지를 체크하고, 복구함
    • 탐지 : 자원 할당 그래프를 통해 Cycle 확인
    • 회복 : 문제가 있는 프로세스 등을 종료하거나 자원을 빼았는 등의 구현
  • 방치
    데드락이 발생할 가능성이 낮다면 ‘허용’하는 방식
    (또는 회복 비용이 비싼 경우)
    • 보통 탐지/해결 비용이 비싸며, 데드락의 발생 가능성도 높지 않기에
    • 피해 또한 대부분 1~2 개의 프로세스 정도
    • 의외로 많은 일반 PC의 OS가 채택하지만, 정말 중요한 시스템이라면 채택하진 않음
      (은행/항공/의료 등의 고신뢰가 필요한 경우엔 이걸 사용하면 안됨)

태그:

카테고리:

업데이트:

댓글남기기