오늘부터 짧게라도 하루 하나씩 CS 지식을 좀 정리하려고 한다
이걸 하게된 계기는 내가 좀 어설프게 아는 것들이 많다는 것을 느꼈고, 정리를 한번 해야함을 느꼈음
오늘할 것은 Context Switching이다
간단히 알고 있던 내용은 필요한 데이터를 메모리에 적재할 때, 그 교환을 의미하는 것으로 알고 있음
Context Switching 정의
OS(운영체제)에서 CPU가 현재 실행 중인 프로세스 또는 스레드의 상태(Context)를 저장하고, 새로운 프로세스 또는 스레드의 상태로 전환하는 과정을 의미
이해하기 쉽도록 비유하자면?
1) 작업대에 현재 차량 정비에 필요한 공구들을 두고 사용 중
2) 해당 작업대에서 PC를 조립을 해야하는 상황
3) 이때 작업대에 쓰던 공구는 한쪽으로 잘 정리해두고, PC 조립에 필요한 공구를 올려두는 과정
위 과정을 Context Switching이라고 한다
Context Switching, 언제 발생하나?
크게 아래 세가지 상황에서 발생한다
1) 멀티 태스킹
2) 인터럽트 처리
3) 커멀 모드 <-> 유저 모드 전환
1) 멀티태스킹
- 하나의 CPU에서 여러 프로세스를 번갈아가며 실행하면, 기존의 프로세스는 저장해둬야 다음 프로세스를 실행할 수 있으므로 Context Switching이 발생하는 것이다
2) 인터럽트(Interrupt) 처리
- I/O 요청이 발생하면, CPU는 다른 프로세스를 실행하다가, 요청이 완료되면 다시 원래 프로세스로 복귀
3) 커널 모드 <-> 유저 모드 전환
- 시스템 콜(System Call) 실행 시 발생
시스템 콜이란?
쉽게 말하면, 유저 프로그램이 OS에게 “이거 좀 해줘” 하고 요청하는 인터페이스
with open("hello.txt", "r") as f:
print(f.read())
위와 같은 코드가 있다면? 실제로는 OS에게 아래와 같은 일들을 요청하는 것이다
- 파일 열기 (open)
- 파일 읽기 (read)
- 파일 닫기 (close)
Context Switching의 동작 과정
1) 현재 실행 중인 프로세스의 Context(문맥)을 저장한다
2) 새롭게 실행할 프로세스의 이전 상태를 복원한다
3) CPU가 새로운 프로세스를 실행한다
Context란?
프로세스가 실행되는 동안 필요한 상태 정보들이 모여있는 것이 Context임
CPU가 실행될 때 필요한 지스터, 프로그램 카운터, 스택 포인터, 메모리 맵 등을 의미함
- 레지스터 : CPU의 내부 저장소로, 데이터나 명령어의 주소 등을 담고 있는 작은 저장 공간
- 프로그램 카운터 (PC) : 현재 실행 중인 명령어의 주소를 가리키는 포인터
- 스택 포인터 : 함수 호출 시 사용되는 스택 메모리 주소
- 메모리 맵 : 프로세스의 메모리 할당 상태를 나타냄
즉, CPU가 한 프로세스를 끝내고 다른 프로세스를 전환할 때
- 현재 프로세스의 Context는 저장
- 실행할 프로세스의 Context는 불러옴
위 과정에서 전환되는 Context 과정이 Context Switching을 의미한다
Context Switching이 과도하게 일어나면?
1) 추가적인 CPU 연산 필요
2) 캐시 리셋 (Cache Miss)
1) 일단 추가적인 CPU 연산이 필요하다
- 즉, 과도한 Context Switching은 성능 저하를 유발할 수 있음
- 작업대 위에 공구를 너무 자주 바꾸면 찾으러가고, 치워두고 하는 과정이 비효율적이기 때문이다
2) Cache Miss가 일어날 수 있다
- CPU에 다른 것을 작업처리할 수 있도록 도와주어야 하니까, 캐시에 올려둔 데이터를 매번 갈아끼워야 한다
- 이러면 Chache Miss 발생
Cache Miss란?
- CPU나 프로그램이 데이터를 캐시에 찾으려고 했지만, 캐시에 없음
- 더 느린 메모리(e.g. RAM)나 디스크에서 데이터를 다시 불러와야 하는 상황을 의미
Context Switching 최적화 방법
1) 스레드 사용
2) 프로세서 바인딩
3) 스케줄링 최적화
4) 하드웨어 지원
1) 스레드 지원
- 프로세스보다 스레드가 더 가벼운 단위
- Context 전환 비용도 더 적음
2) 프로세서 바인딩
- 프로세스를 특정 CPU 코어에 바인딩, 캐시 메모리를 재사용할 수 있도로 하여 Context 전환을 줄일 수 있음
- 실시간성 또는 성능이 중요한 앱에 해당 (e.g. 게임, ML 등)
- 멀티코어에 CUP 캐시 친화적으로 동작하도록 최적화 하는 경우
- HW 인터럽트나 I/O 바인딩을 줄이기 위해, 네트워크/디스크 워커를 특정 코어에 고정할 때
아래는 문득 정리하고 공부하다 든 생각...
Q. OS 프로세스를 바인딩하면 좋을까?
A. 답은 아니다
- OS는 시스템 전반에서 작동해야하는데, 특정 코어에만 묶이면, 병목이나 부작용 발생 가능
- 또한, OS는 기본적으로 Load Balancer(Scheduler)를 통해 프로세스들을 효율적으로 분배함
- 근데 특정 코어에 묶는다? 스케줄러가 최적화해둔 흐름을 망칠 수 있음
- 모든 CPU를 사용하는게 자연스러운 동작 방식인데, 이걸 못하게 하는 것이니까 성능 저하도 있을 수 있음
- 고성능 서버 튜닝처럼, 특정 코어를 OS 전용으로 두고, 나머지를 앱 전용으로 설정하는 경우는 고려해볼 수도 있겠다 (서버는 코어 수가 훨씬 더 많을테니)
- OS 프로세스 바인딩이 성능 개선에 목적을 두고 있다면, 오히려 사용자 앱 쪽에 바인딩을 고려하는 것이 일반적이다
3) 스케줄링 최적화
- 프로세스가 자주 전환되지 않도록, OS에서 스케줄링 알고리즘을 최적화하는 것이 중요함
- 우선순위 기반 스케줄링 or 라운드로빈(RR) 알고리즘 등 사용
4) 하드웨어 지원
- 하드웨어 레벨에서 멀티태스킹을 지원도 있는데, 이런 하드웨어 최적화를 활용하면 Context 전환이 더 빠르고 효율적일 수 있다