키워드로 정리
1. STL·자료구조·CS
1. STL 벡터의 장점과 단점
장점 : 연속된 메모리, 임의 접근의 빠름, 캐시 지역성이 뛰어남, 끝 삽입 빠름. 단점 : 중간 삽입/삭제가 O(n) / 용량 초과 시 재 할당이 일어남. → 나는 오브젝트를 대량 관리할 때 벡터를 사용했음.(순회가 잦았기 때문에)
2. 맵 컨테이너의 장점과 단점
내부적으로 레드 블릭 트리로 구성되어 있음. 장점 - 삽입 삭제 탐색이 로그N임. 키가 정렬되어서 순회/범위 검색이 가능함. 삽입 삭제에도 반복자가 유지됨. 단점 - 노드 기반이기에 개별 원소마다 할당/포인터 오버헤드 있음. 메모리 지역성이 떨어짐. 자가 균형을 이루는 비용이 추가됨.
3. 맵 컨테이너를 어디에서 활용했는가?
맵 컨테이너는 주로 언오더드 맵을 사용함.
- 리소스 관리하는 관리자에서 키와 리소스 데이터 매핑에 사용.
- 또는 조회와 순회가 둘다 잦은 컨테이너에서 벡터의 인덱스와 키를 매핑하여 팔레트로 사용
- 셰이더 리플렉션을 구현할 때, 셰이더 변수 이름과 컴파일된 이펙트 변수 핸들 매핑에 사용.
4. 맵 컨테이너의 키로 사용할 수 없는 자료형은 무엇이 있는가?
맵 컨테이너는 삽입 시 원소 간에 비교가 필수적임. 따라서 비교 연산자가 정의되지 않은 타입은 불가능. 대표적으로 사용자 정의형 자료형. 비교 연산자를 커스텀하여 구현해주면 키로 사용 가능. 부동 소수점은 정확한 키를 찾지 못할 수 있어서, 사용 지양 필요.
5. 벡터 원소를 포인터/참조로 계속 가리키면 안 되는 이유는?
벡터는 동적 배열이기 때문에 용량이 부족해지면 더 큰 메모리를 새로 할장함. 이때 원소를 전부 이동 시키기 때문에, 기존 원소를 가리키턴 참조,반복자,포인터가 해제된 메모리를 가리키게 됨.
따라서 벡터의 원소는 포인터로 장기 보관하기 보다, 인덱스나 ID로 참조하는 것이 권장됨.
6. 해시 충돌이 생겼을 때에는 어떻게 해결하는가?
해시 충돌의 해결 방법은 크게 두가지. 체이닝과 오픈 어드레싱. 체이닝은 같은 버킷에 들어온 원소를 연결리스트 형태로 저장하는 방식. 오픈 어드레싱은 충돌시 정해진 규칙을 통해 다른 빈 슬롯을 찾아 저장하는 방식. 대표적으로 선형 조사, 이차 조사, 이중 해싱 등이 있음.
두 사항 다 로드 팩터가 커지면 캐시 충돌의 확률이 높아지기 때문에, 리해싱의 과정을 거치게 됨.
8. STL 리스트는 어떤 자료 구조로 구현된 클래스인가?
리스트는 이중 연결 리스트로 구현된 자료형. 직접 사용해보았을 때 순방향과 역방향 모두 순회가 가능하여, 노드가 넥스트와 프리브를 가진다고 추측함. 또한 엔드와 비긴이 연결되어 있지 않았기에 원형은 아니라고 생각함. 메모리가 흩어져 있는 형태라 캐시 성능의 저하로 인해 중간 삽입 삭제도 벡터보다 느린 경우가 많다고 느꼈는데, 이 부분에 대해서는 확인하진 못했음.
9. 배열과 크게 다르지 않음에도 STL 벡터가 가진 장점은 뭔가?
메모리 레이아웃은 배열과 동일하지만(연속 메모리), 그 위에 안전성과 편의가 더해진 것이 가치입니다. ① 동적 크기: 런타임에 자동으로 성장하며 크기를 스스로 압니다(배열은 크기를 따로 들고 다녀야 함). ② RAII: 소멸 시 메모리가 자동 해제되고 예외 상황에서도 누수가 없습니다(new[]/delete[] 수동 관리 불필요). ③ 값 의미론: 복사·이동·비교가 자연스럽고 함수 반환도 안전합니다. ④ STL 생태계: 반복자 기반으로 표준 알고리즘과 결합됩니다. ⑤ at()의 범위 검사 같은 안전장치. 즉 “배열의 성능을 유지하면서 C 배열의 수동 관리 위험을 제거한 것”이 벡터의 본질입니다.
10. push_back과 emplace_back의 차이는 뭔가?
push_back은 완성된 객체를 인자로 받음 → 컨테이너 내부로 복사/이동 emplace_back은 생성자 인자들을 전달 받음. → 컨테이너 메모리 위에서 객체 생성함. 따라서 객체를 새로 만드는 경우에는 emplace_back이 유리함.
11. 리스트 컨테이너의 sort 함수는 어떤 알고리즘으로 구현되어 있는가?
리스트는 알고리즘 헤더가 아닌 내장된 정렬 함수를 사용함. 내부적으로 병합 정렬로 구성되어 있음. 이유는 리스트는 임의 접근이 되지 않기 때문에 퀵소트 계열의 정렬이 어렵고, 노드 포인터를 통해 원소 이동 없이 병합이 가능하기 때문에 병합 정렬을 메모리 공간 낭비 없이 사용 가능하기 때문.
12. 가상 메모리란 무엇인가?
프로세스가 실제 물리 메모리보다 큰 공간을 활용할 수 있게 해주는 메모리 관리 기법. 가상 메모리 블럭에 해당하는 페이지, 물리 메모리 블럭에 해당하는 프레임으로 나뉘어 프로스세스 별로 주소 공간을 할당 받아서 사용하게 됨.
CPU가 가상 주소를 사용하면 MMU가 페이지 테이블을 통해서 물리 주소로 변환하게 됨. 이를 통해 각 프로세스 별로 독립된 주소 공간을 확보할 수 있게 보호해주고, 디스크 스왑을 통해 당장에 필요한 메모리만 사용할 수 있게 해주며 메모리를 연속적으로 배치하여 외부 단편화를 해소할 수 있게 해줌.
13. 가상 메모리의 단점은 무엇인가?
- 가상 주소를 물리 주소로 변환하는 것에 비용이 발생함.
- 현재 필요한 메모리가 메인 메모리에 없으면 디스크에서 페이지를 찾아서 메모리에 적재하는 비용이 발생함
- 페이지 테이블로 인한 메모리 오버헤드가 있음.
- 물리 메모리가 부족하면 디스크 스왑이 많이 일어나 디스I/O 신호가 많이 발생함.
14. 페이지 폴트란 무엇인가?
접근한 가상 주소의 페이지가 물리 메모리에 적재되어 있지 않을 때 발생하는 예외. 운영체제가 디스크 똑는 백업 저장소에서 페이지를 로드하고, 페이지 테이블을 갱신하게 됨. 이후 명령어를 재 시도하는 과정을 거치게 됨.
정상적인 상황에서도 발생 가능함. 처음 접근하는 메모리 ,RAM에 올라오지 않은 코드 영역,디스크로 내려간 페이지 ,지연 로딩된 메모리 등으로 발생할 수 있고, 널포인터, 댕글링 포인터, 잘못된 주소 접근 등으로 인해 프로그램이 비정상 종료되기도 함.
15. 페이지 폴트를 완화하는 법이 무엇인가?
프로그램의 메모리 접근 지역성을 높여야 함. 자주 사용하는 데이터를 연속적으로 배치, 순차적으로 접근하면 페이지가 캐시나 물리 메모리에 남아있을 확률이 커짐. 또한 한번에 너무 많은 데이터를 접근하게 되면 페이지의 수가 증가하여 페이지 교체가 잦게 발생할 수 있음.
16. 프로세스와 스레드의 차이가 무엇인가?
프로세스는 실행 파일의 명령어가 메모리에 적재되며 운영 체제에게 주소, 핸들 자원을 할당받는 하나의 실행 단위. 스레드는 프로세스 내부에서 존재하는 하나 이상의 실행 흐름. 프로세스는 최소 하나 이상의 스레드를 가지고 있고, 각 프로세스 간에 주소를 공유하지 않기에 별도 통신 기법이 필요함. 스레드는 같은 프로세스 내부의 스레드 간에는 스택을 제외한 메모리 공간을 공유하기 때문에 별도 통신이 필요하지 않음. 대신 동기화에 대한 고려가 필요함.
17. 데드락은 무엇이고 방지 방법은 무엇인가?
둘 이상의 스레드가 서로가 점유한 자원을 기다리며 병목에 빠지는 현상. 상호배제, 점유와 대기, 비선점, 순환 대기
임계 영역 보호에 필수적은 상호 배제를 제외한 3가지 중 하나의 조건을 해소하면 예방할 수 있음. 락에 전역 순서를 주어 순환 대기를 해소하거나, 필요한 자원을 모두 획득했을 때만 작업이 진행되게 하거나, 자원을 획득하지 못했을 때, 소유한 자원에 대한 점유를 해제하는 형태로 예방할 수 있습니다.
18. 뮤텍스와 데드락의 상관 관계는 무엇인가?
뮤텍스는 스레드 간 동기화 동구이며, 데드락은 뮤텍스를 잘못 사용했을 때 나타나는 현상입니다.
19. CPU와 GPU의 차이
설계 목표의 차이가 있음. CPU는 지연 최소화 지향. 소수의 강력한 코어에 큰 캐시. 분기 예측, 비 순차 실행. → 조건문과 데이터 의존성이 많은 복잡한 작업을 빠르게 하는 것에 특화
GPU는 처리량 지향. 다수의 코어, 대량의 ALU. 동일 연산을 대량 데이터에 적용.
20. CPU의 역할은 무엇인가?
프로그램의 명령어를 인출-해석-실행(fetch-decode-execute)하며 시스템 전체를 제어하는 중앙 처리 장치입니다. 산술·논리 연산(ALU), 제어 흐름(분기, 함수 호출), 메모리 접근을 수행하고, 인터럽트를 받아 OS와 함께 프로세스 스케줄링·I/O를 관장합니다. 게임 프레임 기준으로 보면 입력 처리, 게임 로직, 물리, AI, 애니메이션 갱신, 컬링, 드로우콜 제출까지가 CPU의 일이고, 그 결과로 만든 명령을 GPU에 넘기는 지휘자 역할입니다.
21. 64비트 체제에서부터 SIMD 연산이 대두되게 되는 이유는 무엇인가?
SIMD는 하나의 명령으로 여러 데이터를 동시에 처리하는 방식. x64 환경에서는 사용할 수 있는 SIMD 전용 벡터 레지스터 수가 늘어나면서 SIMD 연산을 더 효율적으로 처리할 수 있게 됨.
22. 메모리 외부 단편화를 방지할 수 있는 방법은 무엇이 있는가?
외부 단편화는 할당하고자 하는 메모리의 공간이 충분한데도, 메모리들이 조각나 있어서 큰 연속 블록을 할당하지 못하는 상황을 뜻함. OS 단계에서 페이징을 사용하면 물리메모리를 연속된 공간에 배치할 필요가 없기에 메모리 외부 단편화가 해결되어 있음. 다만 프로그래머로서 힙 메모리 외부 단편화 해결에 대해서 생각해보자면, 메모리 풀을 만들어 미리 메모리를 할당하고, 재사용해가며 메모리를 사용하면 단편화을 완화할 수 있음.
23. 물리 메모리 중 메모리 접근 속도가 가장 빠른 순으로 말해보시오
레지스터 → L1 캐시 → L2 캐시 → L3 캐시 → 메인 메모리(DRAM) → (SSD/HDD 저장장치) 순입니다.
24. VRAM의 접근 속도는 어느 정도로 빠를까?
개별 접근의 레이턴시는 RAM보다 길다. 하지만 CPU-GPU접근이 훨씬 느리다. 리소스는 가급적 VRAM에 상주시키고, 매 프레임 CPU→GPU 업로드(상수 버퍼, 다이나믹 버퍼)는 크기를 최소화하며, GPU 결과를 CPU로 읽어오는 readback은 파이프라인 스톨을 유발해 특히 피해야 합니다.
25. 셰이더에 데이터를 전달할 때 왜 가급적 상수 버퍼를 사용하자고 하는가?
상수 버퍼는 드로우 동안 모든 스레드가 읽어들이는 소량 데이터라는 역할을 함. 상수 버퍼를 전용 캐시/레지스터 영역에 적재하여 일반 버퍼보다 빠르게 접근을 처리함. 또한 바인딩/갱신 비용이 정형화 되어 있어 드라이버가 다루기 쉬움.
2. 기술소개서 & 3D 그래픽스
1. 렌더링 패킷 구조를 왜 설계했는가?
2. 왜 팀장이 되었는가? (왜 그 파트를 맡았는가?)
3. 디퍼드 렌더링의 장점이 무엇이길래 사용했는가?
4-1. 렌더링 과정을 추가할 때 디퍼드와 포워드를 어떤 기준으로 선택하겠는가?
4-2. G-Buffer가 무엇인가?
5. 팀 프로젝트 진행 중에 어떤 어려움이 있었는가?
6. 헝가리안 표기법에 대해서 어떻게 생각하는가?
7. 프러스텀 컬링이 중요하게 된 이유는 무엇인가?
.
8. 렌더링 파이프라인이란 무엇인가?
3D 정점 데이터가 화면 상의 픽셀이 되기까지 수행되는 일련의 작업 과정이라고 이해하고 있음. 입력조립기를 통해 CPU의 기하데이터가 인덱스버퍼,정점 버퍼로 GPU에 전송이 되고 버텍스 셰이더를 통해 월드→뷰→클립 스페이스로 전환이 되며 레스터라이저를 통해 클리핑, 후면 컬링이 진행된다. 또한 이 단계에서 원근 나눗셈과 뷰포트 변환을 거쳐 화면 좌표로 변환하고, 삼각형이 차지하는 픽셀 후보를 생성하면서 정점 속성을 보간함.
이후 픽셀 셰이더에서 보간된 데이터를 이용해 각 픽셀 후보의 색상 등을 계산, 출력 병함기에서 깊이 테스트, 알파 블렌딩을 통해 색을 최종적으로 결정함.
9. 짐벌락은 무엇이고 어떻게 해결할 수 있는가?
짐벌락은 3차원 공간 상에서 오일러 회전을 통해 축회전을 진행할 때, 특정 각도에서 두 회전축이 겹치면서 하나의 회전 자유도를 잃는 현상. 보통 +-90도로 회전할 떄 발생.
카메라에서 대표적으로 많이 발생하는 현상인데 해결하기 위해서는 각 축의 회전을 단일 표현하는 것으로 해결할 수 있다.
쿼터니언을 통해 사용하거나, 구면 좌표계를 통해 바라볼 지점(포워드)을 직접 지정하거나 카메라라면 회전의 각을 제한하여 90도를 넘어가지 않게 하여 회피할 수 있다.
꼬리 질문 : 쿼터니언이 뭔가요? 3차원 회전을 표현하기 위한 수학적 표현 방식. 어떤 축을 기준으로 얼마만큼 회전할 지 하나의 값으로 표현한것. 자세한 내부 공식은 알지 못하지만, 2차원 회전을 할때 실수 하나와 허수 하나로 회전을 표현했었던 기억이 있다. 이것을 3차원으로 확장하여 3축에 대한 회전을 허수 3개와 실수 하나로 표현하는 방식이라고 알고 있다다.
10. 벡터의 내적은 무엇인가?
벡터의 내적은 두 벡터가 얼마나 같은 방향을 가리키는가를 나타내는 스칼라값. 두 벡터의 원소를 곱한 합. 같은 방향을 가리킬 수록 1에 가까우며, 수직일 경우 0, 뒤에 있을 경우 음수로 표현이 된다. 이를 통해 전후 관계 파악을 할 때 자주 사용했다.
11. 깊이 테스트란 무엇이고 왜 필요한가?
깊이 테스트는 픽셀을 기록하기 전에 깊이 버퍼에 기록된 깊이와 비교하여, 규칙에 따라 어떤 데이터를 기록할 지 판정하는 것이다. 깊이 테스트를 통해서 기존 렌더링 방식에서 벗어나, 렌더링 순서와 무관하게 가까운 대상을 그릴 수 있게 되었다.
다만 이 깊이 테스트가 있더라도, 픽셀 셰이더 이후에 탈락이 수행되기 때문에 픽셀 셰이더 연산의 비용이 추가될 수 있다. 얼리Z와 같은 방식으로 오버드로우를 방지하기도 한다.
12. 깊이값을 정규화하는 과정은 왜 필요한가?
깊이 버퍼의 포맷이 고정되어 있기 때문이다. 뷰 스페이스의 깊이는 가변적이므로 0~1사이의 값으로 정규화 하여 동일한 버퍼 포맷으로 처리할 필요가 있기 때문이다. 정규화 하게 되면 카메라 설정값과 관계 없이 공통된 형식으로 처리할 수 있음.
다만 원근 투영의 경우, 비선형적으로 깊이가 분포하여 near쪽에 분포되고, 부동소수점은 0에 가까울수록 대소비교가 정밀함. 먼 거리에서는 깊이 값의 차이가 작아져 Z-fighting이 발생하기 쉬움. (perspective devide할때 깊이로 나누는데, 이때 깊이가 클 수록 결국 숫자간의 거리가 짧아지니까, 그래서 멀수록 차이가 작아지는 것임)
13. 투영 행렬의 역할은 무엇인가?
투영행렬은 Near Plane, Far Plane, 시야각, 종횡비를 기준으로 가시 영역을 정의하고, 뷰 공간의 좌표를 클립 공간으로 변환하는 행렬입.
시야각과 종횡비를 이용해 x와 y의 배율을 조정하여 시야 범위의 경계를 클립 공간 경계에 대응시키고, 화면 비율에 따른 왜곡을 방지함. 또한 Near와 Far를 기준으로 z와 w를 구성하여 이후 클리핑, W-Divide, 깊이 정규화가 가능하도록 준비.
14. 원근 투영과 직교 투영의 원리와 차이가 무엇인가?
원근 투영은 시야가 절두체 형태로, 거리에 반비례하게 렌더링 대상을 축소하여 원근감을 나타낸다. 반대로 직교 투형은 시야가 직육면체 형태로 깊이가 동일하기 떄문에 거리와 무관하게 크기가 유지된다.
15. 알파 블렌딩이란 무엇인가?
알파 블렌딩은 현재 출력하려는 픽셀의 색상과 렌더 타깃에 이미 기록된 색상을 알파값에 따라 혼합하는 과정입니다.
이전에 반투명 UI 두 개를 중간 렌더 타깃에 합성한 뒤, 해당 렌더 타깃을 다시 최종 화면에 출력하는 구조를 구현한 적이 있습니다. 이때 일반 알파 블렌딩을 사용하자, 중간 렌더 타깃에 기록하는 과정에서 이미 알파가 적용된 색상에 최종 화면 합성 과정에서 알파가 다시 곱해졌습니다. 그 결과 UI가 겹치는 부분과 외곽이 의도한 것보다 흐리거나 투명하게 보이는 문제가 발생했습니다.
이를 해결하기 위해 색상에 알파값을 미리 곱해 저장하는 프리멀티플라이드 알파 방식을 적용했습니다. 이후 블렌딩 단계에서는 Source 색상에 알파를 다시 곱하지 않고, Destination 색상에만 1에서 Source 알파를 뺀 값을 적용하도록 블렌드 상태를 변경했습니다. 이를 통해 다단계 UI 합성 과정에서 알파가 중복 적용되는 문제를 해결했습니다.
3. C++ 관련
1. 보편 참조란 무엇인가?
보편 참조란, 축약 규칙에 기반하여 우측값이면 우측값 참조, 좌측값이면 좌측값 참조로 동작하는 참조이다. 타입 추론 과정에서 일어나기 때문에 템플릿이나, auto와 같은 문법에서 나타나는데, 우측값 참조와 모양새가 비슷해서 헷갈리기 쉽다.
보편 참조는 퍼펙트 포워딩을 구현할 때 사용된다. 퍼펙트 포워딩은 함수가 전달받은 인자의 타입과 좌측값/우측값 범주까지 보존하여 전달하는 기법이다.
컴포넌트 패턴을 구현할 때 컴포넌트를 추가하는 템플릿 함수에 보편 참조를 적용했다.
컴포넌트 생성에 필요한 매개변수를 가변 인자 템플릿으로 전달받고,
std::forward를 통해 각 인자의 값 범주를 유지한 채 컴포넌트 생성자에 전달했다.
2. 생성자에서 가상함수를 쓰면 안 되는 이유는 무엇인가?
생성자 실행 중에는 객체가 아직 파생 타입이 아닌 기반 타입이기 때문. 따라서 기반 클래스 생성자 실행 중에는 버추얼 테이플 포인터가 기반 클래스 것으로 설정되어 있어, 재정의된 함수가 아닌 기반 클래스의 함수가 호출될 수 있음.
더해서 순수 가상 함수라면 미정의 동작/크래시입니다. 소멸자에서도 동일.
3. 생성자에서 가상함수를 쓰지 않고 가상함수로 안전하게 초기화하는 법은 없는가?
이단계 초기화 패턴을 사용할 수 있다. 생성자를 프라이빗 접근 지정자로 만들고, 객체가 완성된 이후 별도의 가상 함수를 통해 초기화를 호출하는 방법이다. 나는 프로젝트 진행 당시 이 단계를 하나의 생성 함수로 축약하여, 사용자에게는 Create라는 인터페이스를 일관되게 제공했다.
4. 템플릿 비대화란 무엇이고 어떻게 완화하겠는가?
템플릿 비대화는 타입 조합마다 별개로 템플릿이 인스턴스화되어, 바이너리의 크기와 컴파일 시간이 커지고, 명령어의 캐시 효율이 떨어지는 문제. 완화 방법은 타입 무관한 로직을 일반 함수로 분리하고, 템플릿은 얇은 타입 래퍼로 만드는 것이 있다. 또한 타입의 크기등의 불필요한 타임 파라미터를 제거하는 등의 방식이 있따.
5. shared_ptr로 관리하는 객체 내부에서 this 포인터 사용을 지양해야 하는 이유는?
내부에서 this를 통해서 make_shared와 같이 새로운 포인터를 만들게 되면 기존 컨트롤 블록과 무관한 별개의 컨트롤 블록이 생기게 된다. 이 경우 셰어드 포인터의 참조 카운트가 개별로 관리되기 때문에 이중해제가 발생할 수 있다. 따라서 enable_shared_from_this를 상속하여 기존 컨트롤 블록을 공유하는 셰어드 포인터를 얻는 방법이 있습니다.
6. 부동소수점의 오차는 왜 생기는가?
부동소수점은 부호부,지수부,가수부로 값을 근사하게 표현하기 때문이다. 따라서 표현하고자 하는 실수가 2진수로 표현했을 때 가수부의 비트를 초과하거나, 무한소수가 되었다면 정확히 저장할 수 없다. 그렇기에 반올림되어 연산 오차가 누적된다.
8. 클래스 멤버 데이터를 은닉화/캡슐화하는 이유는 무엇인가?
데이터와 데이터를 유지하는 규칙을 한 곳에 강제하기 위함. 멤버를 공개하게 되면 불변되는 식의 안전성을 보장할 수 없고, 문제가 발생했을 때 원인 지점을 특정하기도 어렵다.
접근을 메서드로 한정하게 되면, 내부 표현을 바꾸거나, 디버깅 지점이 한정되어 추적이 쉽다. 즉 책임의 범위를 한정지어 유지 보수성이 뛰어나진다.
9. 게터/세터로 접근 지정자가 무의미해지는 것을 지양해야 하는 이유는?
게터/세터를 통해 퍼블릭 접근 지정자와 다름없이 접근이 가능해지면, 사실상 캡슐화의 의미가 사라진다. 따라서 외부에서 데이터를 수정하고 싶다면, 데이터를 받아와서 수정하는 것보다, 데이터를 수정하는 형태의 메서드를 로출하는 방식으로 프로그래밍 한다.
11. CS 기본 질문
프로세스 스케줄링이란 여러 프로세스가 실행 가능한 환경에서 운영체제가 어떤 프로세스에 CPU를 할당할지 결정하는 정책과 관리 기법입니다.
프로세스 스케줄링은 크게 선점형과 비선점형으로 구분됩니다.
선점형 스케줄링은 실행 중인 프로세스가 있더라도 타임 슬라이스가 만료되거나 더 높은 우선순위의 프로세스가 준비 상태가 되면, 운영체제가 CPU를 강제로 회수해 다른 프로세스에 할당하는 방식입니다. 응답성이 좋고 여러 프로세스에 CPU 시간을 배분하기 유리하지만, 문맥 교환 비용이 발생할 수 있습니다.
비선점형 스케줄링은 한 프로세스에 CPU가 할당되면 해당 프로세스가 종료되거나 I/O 요청 등으로 대기 상태에 들어가 CPU를 자발적으로 반납할 때까지 실행을 보장하는 방식입니다. 구현은 비교적 단순하지만, 실행 시간이 긴 프로세스가 CPU를 오래 점유하면 다른 프로세스의 응답 시간이 길어질 수 있습니다.