CPU에 관하여
CPU의 핵심 구성 요소
-
- CPU의 물리적인 구조
- 물리 회로로 보는, 현재 우리 컴퓨터에 달린 CPU
ALU / Control Unit / 레지스터 / Core / 코어 / 캐시(L1,L2,L3)
- CPU의 물리적인 구조
-
- CPU의 시스템적 구조
- OS와 협력하여 프로그램 실행 과정에서의 구조
(OS가 보는 CPU라 표현해도 옮은 표현)
가상 주소 / 물리 주소 / 페이지 / 페이지 테이블 / TLB / 스케쥴링 시스템 / 문맥 전환
- CPU의 시스템적 구조
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가 사용하기 쉽게 물리적 구조와의 연동점이 존재
‘대표적인 요소’가 밑에서 설명할 ‘캐시’
캐시
간략히 말하자면
‘기존’에 액세스한 데이터의 사본을 보관하는 임시 저장 공간
을 뜻한다
-
느린 저장 공간의 데이터를 빠른 저장 공간의 데이터에
보관하여, 느린 공간까지 갈 필요를 없게 하는 것 -
이러한 작동 방식 덕에
‘데이터를 미리 저장해두어’ 빠른 재사용을 가능케 하는 전략도
‘캐시’라 표현함
캐시의 필요성
-
CPU는 굉장히 빠른 성능을 지녔으며
동시에 아주 작은 저장 용량을 가졌다
(레지스터) - 그렇기에 조금 더 크며, 동시에 저렴한 부품들을
‘단계적’으로 구성하는 방식을 채택
- 단순히 저장만 하는데 CPU 같은 최고급 부품을 사용할 필요가 없으며
이는 경제적으로도 큰 손실
- 단순히 저장만 하는데 CPU 같은 최고급 부품을 사용할 필요가 없으며
- ‘하드웨어’로부터 CPU 까지 가능한 빨리 데이터를 보내기 위해 구성된 방식
- CPU에 가까운 순으로 데이터를 찾으며
데이터가 없다면 점점 더 느린 저장장치로 접근하여 데이터를 찾는다
- CPU에 가까운 순으로 데이터를 찾으며
지역성
이러한 메모리 접근 방식에서 CPU 설계자들은
CPU가 더 효율적으로 메모리를 로드하도록
‘접근 패턴’을 분석함
그리고 이와 같은 2가지 방식을 추가하여
더욱 효율적인 로딩을 가능케 했음
(이것이 지역성의 정체)
-
- 시간 지역성
- 최근 사용한 데이터가 가까운 미래에 또 쓰일 것
- 이미 메모리에 로드된 데이터를 바로 내치는 것이 아니라
가능한 ‘냅둔’ 상태로 한동안 보관함 - LRU 같은 캐시 교체 알고리즘을 적용하여 교체함
- 시간 지역성
-
- 공간 지역성
- 특정 데이터를 로드했을때, 그 주변 데이터들이 같이 쓰일 가능성이 높음
- 따라서 밑의 ‘캐시라인’의 크기만큼 한 번에 가져옴
- 운이 좋으면 한 번에 여러 데이터를 로드할 수 있음
- 공간 지역성
-
이러한 CPU의 예측이 성공하여 데이터를 매우 빠르게 가져오면
‘캐시 히트’ - 예측이 실패하여 DRAM까지 가서 새로이 데이터를 찾아와야 한다면
‘캐시 미스’
- 만약 DRAM에도 없다면 디스크(HDD/SSD)로 가서 찾아옴
(여담이지만 이 타이밍에 OS가 Page Table을 업데이트 하고 명령을 재실행 시킴)
- 만약 DRAM에도 없다면 디스크(HDD/SSD)로 가서 찾아옴
캐시 라인
CPU 캐시에 데이터를 저장하는 ‘최소 단위 블록’
(일반적인 x86 CPU에선 64Byte 사용)
-
어차피 64바이트로 ‘기준’이 잡혀있기에
단순히 int나 포인터 하나만 필요해도 그 근처의 메모리를 싹 긁어온다 - 배열 같은 ‘연속적인’ 메모리를 잡는 자료구조가 ‘캐시 친화적’이란 소리를 듣는 이유!
- 다만 ‘포인터’의 경우 보통 Heap 영역과 연계되기에
배열을 사용한다 하더라도, ‘포인터’에 대한 접근만 친화적임 - 데이터 지향 설계가
캐시 지향적이라는 말을 들을 수 있는 이유이기도 함
(필요한 데이터끼리 묶어 CPU가 연속적으로 필요한 데이터를 읽으므로)
- 다만 ‘포인터’의 경우 보통 Heap 영역과 연계되기에
- 멀티 스레드 환경에서
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) 상태?
- 특정 프로세스가 ‘응답’ 받지 못하고 계속 무시되는 상황
(즉, 자원을 받지 못하는 상황)
- 이러면 해당 프로세스가 ‘멈춘 것’ 처럼 보임
- 기아(Starvation) 상태?
- MLFQ의 경우, 현재 많은 OS의 기반이 된 방식임
프로세스와 스레드
먼저 말하자면
- 프로세스 : OS가 본 ‘프로그램’의 추상화
- 쓰레드 : 프로세스의 ‘실행단위’
먼저 OS에 대하여 좀 더 살펴보고
다시 알아보자
운영체제가 하는 일
운영체제는 크게 3가지 일을 한다
- 자원(하드웨어) 관리
- CPU : 스케쥴링 등
- 메모리 : 가상 메모리, 페이지 교체 등
- 저장장치 : 파일 시스템 , 핸들 등
- 네트워크 : 포트 할당 등
- 입출력 장치
- CPU : 스케쥴링 등
이외에도 여러 자원을 관리하며
‘여러’ 프로세스가 가능한 자원을 공정하게 사용하도록 관리함
(이를 위해 위의 ‘CPU’ 스케쥴링 같은 자원 관리 기법이 필요한 것!)
- 추상화 (Abstraction)의 제공
- 위의 ‘여러 하드웨어’들을 굳이 API 들이 알 필요없게 함
- 필요시 시스템 콜을 통해
API -> 커널 -> 하드웨어로 필요한 자원을 요청하는 방식
- 위의 ‘여러 하드웨어’들을 굳이 API 들이 알 필요없게 함
예를 들자면
-
‘파일 시스템’은 사실 0/1 의 집합이지만
OS는 이를 ‘폴더/파일’의 개념으로 추상화 해줌 -
프로세스 는 사실 ‘여러 명령어 모음(프로그램)’이지만
‘하나’의 ‘실행단위’로서 추상화 -
스레드는 ‘물리적인 CPU 코어’를 추상화하여
여러 ‘실행 흐름’을 표현 -
보호와 고립(Protection & Isolation)
- 프로세스들이 ‘서로’의 메모리 / 자원 을 침범하지 못하게 막음
- 각 프로세스는 자신만의 ‘가상 주소 공간’을 가져
다른 프로세스의 메모리를 볼 수 없게 함 - 커널 / 유저 모드의 분리 및 파일 접근 권한을 통해
해당 프로세스가 ‘실행 중’인 Data 영역에는 접근할 수 없도록 하였다
- 프로세스들이 ‘서로’의 메모리 / 자원 을 침범하지 못하게 막음
이러한 OS의 3가지 작업을 통해
‘프로세스’의 개념이 완성됨
- 각 프로세스는 서로 분리
- 각각의 가상 주소 공간 (선형)을 가짐
-
작업 전환 시, 문맥 교환이 필요한 이유
- 다만, Process 통신이 까다로워졌기에
IPC 같은 별도의 방식을 통해 통신해야 함
참고용 개인 포스팅
프로세스 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 교체 등이 발생
- 비용이 큼 : 물리 메모리, 주소 공간 자체가 바뀌며, MMU의 Page Table 교체 등이 발생
- 스레드 문맥 전환
- 비용이 저렴 : CPU 레지스터와 Stack 정보 등만 바뀌고 프로세스의 주요한 환경은 그대로 이기에 상대적으로 저렴
- 비용이 저렴 : CPU 레지스터와 Stack 정보 등만 바뀌고 프로세스의 주요한 환경은 그대로 이기에 상대적으로 저렴
동시성 이슈
멀티 스레드 환경에서 ‘특정 데이터’에 동시에 접근하는 경우
발생할 수 있는 여러 상황들
임계 영역(Critical Section)
다중 쓰레드 or 프로세스 환경에서
공유된 자원 (변수 등)에 접근하는 ‘코드 영역’을 의미
-
일반적으로 ‘상호 배제’ 원칙이 지켜져야 하며
그렇지 않으면 ‘경쟁 상태’가 발생함 -
두 스레드가 특정 메모리의 변수 값을 동시에 수정하려는 등의 상황에서 발생
(코드는 1줄이라도 내부에선 여러 명령어로 되어 있을 수 있음)
(그래서 Atomic을 사용하는 이유이기도 함)
경쟁 상태(Race Condition)
두 스레드가 동시에 ‘임계 영역’에 접근하여 발생하는 상태
-
A가 값을 쓰고, 저장하려는데 B가 와서 값을 다시 쓴 후
그제서야 A가 저장하여 ‘A의 저장값’이정상적이지 않은 상황 -
정의되지 않은 동작(Undefined Behavior)이란 표현으로도 사용하며
시스템의 ‘무결성’을 훼손하여 피해야 함 -
DB와 같은
데이터 저장시에는 반드시 피해야 하는 상황이기도 함
동기화 도구
상호 배제와 동기화를 위한 구현 방식들
- Mutex
- Lock 과 Unlock의 개념을 사용하는 대표적인 도구
- 임계 영역에 ‘여러 스레드’가 접근하는 것을 막음
- ‘소유권’ 개념이 있음
- Lock 과 Unlock의 개념을 사용하는 대표적인 도구
- Semaphore
- P 연산 / V 연산을 통해 접근되는 스레드를 N개로 제한
- P : 세마포어 값을 감소하고, 0이면 접근한 스레드를 ‘대기’시킴
- V : 세마포어 값을 증가시키고, 대기된 스레드 중 하나를 ‘실행’ 상태로 변경
- P : 세마포어 값을 감소하고, 0이면 접근한 스레드를 ‘대기’시킴
- P 연산 / V 연산을 통해 접근되는 스레드를 N개로 제한
| 항목 | Mutex | Semaphore |
|---|---|---|
| 접근 허용 수 | 1개 | N개 |
| 소유권 | 있음 | 없음 |
| 목적 | 독점적 자원 접근 | 자원 수 제한 |
| binary semaphore와 비교 | mutex와 비슷 | binary는 mutex 유사 |
-
- 조건 변수
- 특정한 조건을 만족할때까지 대기 스레드를 ‘재우는 방식’
- 조건 변수
-
- Atomic 연산
- ‘하나의 스레드’가 ‘완전히 완료’할때까지 독점하는 연산 방식
(키워드 및 자료구조 등으로 제공)
- Atomic 연산
데드락(Dead Lock)
동기화 도구를 ‘잘못’ 사용하여 발생할 수 있는 문제
교착 상태라는 표현으로도 사용된다
- 2개 이상의 프로세스 or 스레드가
서로의 작업이 끝나기를 ‘계속 기다리는 상황’
- 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가 채택하지만, 정말 중요한 시스템이라면 채택하진 않음
(은행/항공/의료 등의 고신뢰가 필요한 경우엔 이걸 사용하면 안됨)
- 방치
댓글남기기