프로세스가 아닌 스레드 관점에서 보는 리눅스 스케줄러의 진화
리눅스 운영체제의 핵심 중 하나인 스케줄러는 시스템 내의 여러 프로세스와 스레드가 CPU 자원을 공정하고 효율적으로 사용할 수 있도록 관리하는 역할을 담당합니다. 흔히 프로세스 관점에서 스케줄러를 이해하지만, 현대적인 멀티스레드 애플리케이션의 중요성이 커짐에 따라 스레드 관점에서 스케줄러의 동작 방식을 이해하는 것이 더욱 중요해졌습니다. 이 글에서는 리눅스 스케줄러의 진화를 스레드 관점에서 살펴보고, 실생활에서의 활용 방법과 유용한 팁, 그리고 흔한 오해와 진실 등을 자세히 알아보겠습니다.
스레드와 프로세스, 그리고 리눅스 스케줄러의 역할
프로세스는 실행 중인 프로그램의 인스턴스를 의미하며, 자신만의 메모리 공간과 자원을 가지고 있습니다. 반면 스레드는 프로세스 내에서 실행되는 실행 흐름의 단위로, 프로세스의 자원을 공유합니다. 하나의 프로세스는 여러 개의 스레드를 가질 수 있으며, 이를 통해 병렬성을 높이고 작업 효율을 향상시킬 수 있습니다.
리눅스 스케줄러는 시스템 내의 모든 실행 가능한 프로세스와 스레드 중에서 어떤 것을 다음에 실행할지 결정하는 역할을 합니다. 스케줄러의 목표는 다음과 같습니다.
- 공정성 모든 프로세스와 스레드가 CPU 시간을 공정하게 할당받도록 보장
- 응답성 사용자 인터랙션에 빠르게 반응하여 사용자 경험 향상
- 처리량 CPU 자원을 최대한 활용하여 시스템 전체의 처리량 극대화
- 실시간성 실시간 프로세스의 데드라인을 준수
리눅스 스케줄러의 진화 과정
리눅스 스케줄러는 지속적으로 발전해 왔으며, 각 버전마다 성능 향상과 새로운 기능 추가를 목표로 개선되었습니다. 스레드 관점에서 주요 진화 과정을 살펴보겠습니다.
O(n) 스케줄러 (2.4 버전 이전)
초기 리눅스 커널에서 사용된 스케줄러로, 모든 프로세스/스레드를 순회하며 우선순위에 따라 다음 실행할 것을 선택했습니다. 이 방식은 프로세스/스레드 수가 증가함에 따라 스케줄링 시간이 선형적으로 증가하는 O(n) 복잡도를 가지기 때문에 확장성이 떨어지는 단점이 있었습니다. 스레드가 많은 환경에서는 성능 저하가 더욱 심각했습니다.
O(1) 스케줄러 (2.6 버전)
2.6 버전에서 도입된 O(1) 스케줄러는 실행 가능한 프로세스/스레드를 우선순위별로 관리하는 runqueue를 사용하여 스케줄링 시간을 상수 시간으로 단축했습니다. 이는 시스템 내의 프로세스/스레드 수에 관계없이 스케줄링 시간이 일정하게 유지된다는 의미입니다. 또한, SMP (Symmetric Multi-Processing) 환경을 지원하여 여러 CPU 코어에 프로세스/스레드를 분산시켜 병렬성을 높였습니다. 하지만 인터랙티브한 작업에 대한 공정성 문제가 제기되기도 했습니다.
CFS (Completely Fair Scheduler) (2.6.23 버전 이후)
2.6.23 버전부터 도입된 CFS는 모든 프로세스/스레드가 가상 실행 시간(virtual runtime)을 기준으로 공정하게 CPU 시간을 할당받도록 설계되었습니다. CFS는 레드-블랙 트리(red-black tree) 자료구조를 사용하여 실행 가능한 프로세스/스레드를 관리하며, 가장 작은 가상 실행 시간을 가진 프로세스/스레드를 다음에 실행합니다. 이를 통해 모든 프로세스/스레드가 공정하게 CPU 시간을 할당받을 수 있으며, 인터랙티브한 작업에 대한 응답성도 향상되었습니다. CFS는 스레드 그룹(thread group)이라는 개념을 도입하여 동일한 프로세스에 속한 스레드들을 그룹으로 묶어 관리함으로써, 스레드 간의 공정성을 보장하고 프로세스 단위의 자원 할당을 용이하게 했습니다.
CFS 스케줄러의 동작 방식 상세 분석
CFS 스케줄러는 다음과 같은 주요 개념을 기반으로 동작합니다.
- 가상 실행 시간 (vruntime) 각 프로세스/스레드가 실제로 CPU를 사용한 시간을 보정하여 계산한 값. 우선순위가 높은 프로세스/스레드는 vruntime이 느리게 증가하고, 우선순위가 낮은 프로세스/스레드는 vruntime이 빠르게 증가합니다.
- 레이턴시 (latency) 스케줄러가 각 프로세스/스레드가 최소한 한 번씩 실행되도록 보장하는 시간 간격. 레이턴시 값이 작을수록 인터랙티브한 작업에 대한 응답성이 향상됩니다.
- 가중치 (weight) 각 프로세스/스레드의 우선순위를 나타내는 값. 가중치가 높을수록 CPU 시간을 더 많이 할당받습니다.
CFS 스케줄러는 다음과 같은 단계를 거쳐 다음 실행할 프로세스/스레드를 선택합니다.
- 실행 가능한 모든 프로세스/스레드를 레드-블랙 트리에 삽입합니다. 레드-블랙 트리는 vruntime을 기준으로 정렬됩니다.
- 레드-블랙 트리에서 가장 작은 vruntime을 가진 프로세스/스레드를 선택합니다.
- 선택된 프로세스/스레드를 실행합니다.
- 실행된 프로세스/스레드의 vruntime을 업데이트합니다.
- 다시 1단계로 돌아갑니다.
실생활에서의 활용 방법 및 유용한 팁
리눅스 스케줄러의 동작 방식을 이해하면 애플리케이션의 성능을 최적화하고 시스템 자원을 효율적으로 활용할 수 있습니다. 다음은 실생활에서 활용할 수 있는 몇 가지 팁입니다.
- nice 값 조정: nice 명령어를 사용하여 프로세스의 우선순위를 조정할 수 있습니다. nice 값이 낮을수록 우선순위가 높고, 높을수록 우선순위가 낮습니다. CPU 자원을 많이 사용하는 백그라운드 작업의 nice 값을 높여 다른 중요한 작업에 영향을 주지 않도록 할 수 있습니다.
- cgroups 활용: cgroups (control groups)는 프로세스 그룹의 자원 사용량을 제한하고 관리하는 데 사용됩니다. cgroups를 사용하여 특정 프로세스 그룹의 CPU 사용량을 제한하거나, 특정 프로세스 그룹에 더 많은 CPU 시간을 할당할 수 있습니다.
- pthread_setschedparam 함수 사용: POSIX 스레드 API를 사용하여 스레드의 스케줄링 정책과 우선순위를 설정할 수 있습니다. 실시간 스레드의 경우 SCHED_FIFO 또는 SCHED_RR 정책을 사용하여 데드라인을 준수하도록 할 수 있습니다.
- 프로파일링 도구 활용: perf, gprof 등의 프로파일링 도구를 사용하여 애플리케이션의 CPU 사용량을 분석하고 병목 지점을 찾을 수 있습니다. 이를 통해 불필요한 연산을 제거하거나, 멀티스레딩을 통해 병렬성을 높이는 등의 최적화 작업을 수행할 수 있습니다.
흔한 오해와 사실 관계
리눅스 스케줄러에 대한 몇 가지 흔한 오해와 그에 대한 사실 관계는 다음과 같습니다.
- 오해: nice 값을 높이면 프로세스의 성능이 향상된다.
- 사실: nice 값을 높이는 것은 프로세스의 우선순위를 낮추는 것이며, 다른 프로세스에 더 많은 CPU 시간을 양보하는 것입니다.
- 오해: CFS는 모든 프로세스/스레드에 동일한 CPU 시간을 할당한다.
- 사실: CFS는 가상 실행 시간을 기준으로 공정하게 CPU 시간을 할당하지만, 우선순위에 따라 CPU 시간을 할당받는 비율이 달라집니다.
- 오해: 실시간 스케줄링 정책을 사용하면 항상 데드라인을 준수할 수 있다.
- 사실: 실시간 스케줄링 정책은 데드라인을 준수하기 위한 가능성을 높여주지만, 시스템 부하가 높거나 다른 실시간 프로세스와의 경쟁이 심한 경우에는 데드라인을 놓칠 수도 있습니다.
자주 묻는 질문과 답변
Q: CFS 스케줄러는 어떻게 인터랙티브한 작업을 빠르게 처리하나요?
A: CFS는 레이턴시 값을 작게 설정하여 각 프로세스/스레드가 최소한 한 번씩 실행되도록 보장합니다. 또한, 인터랙티브한 작업은 일반적으로 CPU 사용 시간이 짧기 때문에 vruntime이 작아 다음에 실행될 가능성이 높습니다.
Q: 스레드 풀을 사용하는 것이 항상 성능 향상에 도움이 되나요?
A: 스레드 풀은 작업 생성 및 소멸에 대한 오버헤드를 줄여주지만, 스레드 수가 너무 많으면 컨텍스트 스위칭 오버헤드가 증가하여 오히려 성능이 저하될 수 있습니다. 적절한 스레드 수를 설정하는 것이 중요합니다.
Q: cgroups를 사용하여 특정 프로세스의 CPU 사용량을 제한하는 방법은 무엇인가요?
A: cgroups를 생성하고 해당 프로세스를 cgroups에 추가한 후, cfs_quota_us 및 cfs_period_us 파라미터를 설정하여 CPU 사용량을 제한할 수 있습니다. cfs_quota_us는 프로세스가 사용할 수 있는 CPU 시간 (마이크로초 단위)을 나타내고, cfs_period_us는 시간 간격 (마이크로초 단위)을 나타냅니다.
비용 효율적인 활용 방법
리눅스 스케줄러를 비용 효율적으로 활용하기 위해서는 다음과 같은 사항을 고려해야 합니다.
- 적절한 하드웨어 선택: 애플리케이션의 워크로드 특성에 맞는 CPU 코어 수와 메모리 용량을 가진 하드웨어를 선택해야 합니다.
- 애플리케이션 최적화: CPU 사용량을 줄이고 병렬성을 높이는 방향으로 애플리케이션을 최적화해야 합니다.
- 모니터링 및 튜닝: 시스템 자원 사용량을 지속적으로 모니터링하고, 필요에 따라 스케줄러 관련 파라미터를 튜닝해야 합니다.
- 클라우드 환경 활용: 클라우드 환경에서는 필요에 따라 CPU 코어 수를 동적으로 조절할 수 있으므로, 비용 효율적인 자원 관리가 가능합니다.