오랜만에 Windows Kernel에 대한 블로그가 올라와서 정리를 해 보았습니다.
One Windows Kernel
https://techcommunity.microsoft.com/t5/Windows-Kernel-Internals/One-Windows-Kernel/ba-p/267142
Windows는 x86, x64, ARM, ARM64를 지원하고 예전에는 Itanium, PowerPC, DEC Alpha, MIPS를 지원할 정도로 다양한 하드웨어를 지원하고 Data center, Laptop, xbox, phone, IOT device를 지원할 정도로 다양한 SKU를 가지고 있습니다.
아래 이미지는 896 코어에 1792 개의 logical processor와 2TB RAM을 지원하는 Windows DataCenter 버전의 작업 관리자 이미지 입니다. (저도 이렇게 큰 장비를 본 적은 없습니다. 대형 장비 이지만 Windows 10을 쓰는 방법과 거의 유사하게 시스템을 관리할 수 있어 금방 적응할 수 있습니다.)
Windows refactoring 에 대해서 먼저 이야기 하고 있습니다. 다양한 SKU의 Windows을 동일한 DLL로 지원을 하지만 일부 코드를 다르게 구현할 수 있도록 API set (https://docs.microsoft.com/en-us/windows/desktop/apiindex/windows-apisets) 이라는 것을 만들었습니다. 예를 들면 kernel32.dll을 그대로 사용하면서 실제 구현은 다른 DLL에 되어 있는 것입니다. (저도 API set을 자세히 분석해 본 적은 아직 없어서 자세한 내용은 나중에 정리해 봐야 할것 같습니다.)
Kernel Components
Windows NT는 마이크로 커널 유형으로 되어 있습니다. (완전한 마이크로 커널은 아닙니다.) 코어 커널 모듈이 있고 실행부라는 영역이 커널에 존재 합니다. 커널은 스레드 디스패칭, 멀티프로세서 동기화, 하드웨어 예외 핸들링 등을 처리 하고 실행부에서는 IO, 객체 관리, 메모리 관리, 프로세스 서브 시스템 등을 담당합니다.
윈도우 컴포넌트의 크기를 코드수로 보면 아래와 같습니다. 역시 메모리가 가장 많은 코드를 가지고 있고 커널 순으로 이어집니다. 좀 더 자세한 내용을 학고 싶으면 Windows Internals 책을 구입해서 읽어보면 됩니다.
Kernel subsystems | Lines of code |
Memory Manager | 501, 000 |
Registry | 211,000 |
Power | 238,000 |
Executive | 157,000 |
Security | 135,000 |
Kernel | 339,000 |
Process sub-system | 116,000 |
Scheduler
스레드는 프로그램 코드가 실행되는 가장 기본 유닛으로 Windows 스케줄러가 스케줄 합니다. 윈도우 스케줄러는 스레드 우선순위에 따라서 높은 우선순위를 가진 스레드 부터 스케줄링 합니다. 스레드는 주어진 시간 (Quantum time) 동안 실행되고 다음 스레드가 실행됩니다. 만약 높은 우선순위를 가지고 있는 스레드가 계속 실행되고 있어서 낮은 우선 순위를 가지는 스레드가 실행될 기회를 얻지 못한다면 낮은 우선순위를 가진 스레드의 우선순위가 올라가게 됩니다.
최초에 Windows 스케줄러는 하나의 레디큐를 가지고 있었습니다. 하지만 많은 프로세서를 지원하기 시작하면서 하나의 레디큐가 병목현상을 보이게 되었고 Windows Server 2003 에서 프로세서마다 하나의 레디큐를 가지게 디자인이 변경 되었습니다. 하나의 레디큐를 사용하면서 발생했던 글로벌 락 문제가 사라졌습니다. 가장 높은 우선순위를 가지는 스레드가 실행되는 것은 보장할 수 있지만 N 개의 core를 가지는 시스템에서 최대 N 개의 높은 우선순위를 가지는 스레드가 실행된다고 말 할 수는 없습니다. (현재 실행되는 스레드보다 높은 스레드가 다른 코어에 대기 상태로 있을 수 있습니다.) Windows가 laptop 이나 태블릿 같은 CPU 파워가 낮은 시스템에 적용되면서 UI가 느려지는 현상등이 발생하게 되었습니다. 그래서 Windows 8.1 에서 스케줄러가 프로세서당 레디큐를 가지는 것과 프로세서간 공유하는 레디큐를 가지는 하디브리드 형태로 변경 되었습니다. 다른 아키텍처의 변경이 있었기 때문에 스케줄로 변경으로 인한 성능 향상이 크게 보이지는 않았습니다.
Windows 7 에서 동적인 공정 공유 스케줄러 (Dynamic Fair Share Scheduler)가 도입되었습니다. 이 기능은 터미널 서버를 위해서 도입되었습니다. 하나의 터미널 세션이 과도한 CPU 자원을 사용해서 다른 터미널 세션에 영향을 주는 것을 방지하기 위해 도입 되었습니다. 기존의 스케줄링은 세션을 고려하지 않고 스레드 우선순위만 고려해서 스케줄링 했는데 이 기능이 도입 되어 다른 세션에 영향을 주지 않게 되었습니다. Linux의 Completely Fair Scheduler와 유사한 것 입니다. Windows 8 에서 이 개념이 스케줄러 그룹으로 일반화 되었고 각 세션마다 윈도우 스케줄러가 사용됩니다. 스케줄러 그룹은 스레드 우선순위에서도 2차 인덱스 역할을 해서 어떤 스레드가 다음에 실행될지를 결정 합니다. 터미널 세션에서 모든 스케줄러 그룹은 동일한 양의 스케줄링을 받게 됩니다. 그리고 Job 오브젝트가 향상되어 CPU rate control (https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_jobobject_cpu_rate_control_information) 기능을 지원하게되어 프로세스가 사용할 수 있는 CPU 양의 hard cap과 soft cap을 제한할수 있습니다. 이 기능은 리눅스의 cgroups 과 유사 합니다.
Windows 7 이후 부터 Windows 서버는 64개 이상의 프로세서를 지원하게 됩니다. 많은 프로세서를 지원하기 위해 프로세서 그룹 이라는 개념이 도입되었습니다. 프로세서 그룹은 최대 64개 까지의 프로세서를 하나의 그룹으로 묶어서 하나의 스케줄링 단위로 만드는 것으로 부팅하는 시점에 커널에서 어떤 프로세서가 어떤 그룹에 설정될지를 결정 합니다. (64개 이하의 코어를 가질 경우 프로세서 그룹은 적합하지 않습니다.) 단일 프로세스 (SQL Server)는 프로세서 그룹에 확장될 수 있으나 개별 스레드는 하나의스케줄링 그룹에서만 실행될 수 있습니다. (응용 프로그램이 프로세서 그룹이나 NUMA에 적합하지 않을 경우 BIOS에서 프로세서를 하나의 그룹으로 인식 시킬 수 있습니다.)
64개 이상의 코어를 가지는 시스템에서 코어 수를 늘렸을 때 SQL server와 같은 제품에서 성능 향상이 많이 일어나지 않는 것이 확인 되었습니다. 코어 수가 늘어나면 디스패처 락 문제 때문에 성능 향상에 문제가 있는 것이 확인 되었습니다. 디스패처 락은 디스패치가 되는 오브젝트를 보호하는 락으로 스레드, 타이머, I/O 완료 포트, 동기화 객체 등을 보호 합니다. Windows 7 에서 디스패처 락을 제거하고 객체 별 락을 도입하였고 290%의 성능 향상을 보였습니다.
Windows 10 (Windows Server 2016)에서는 CPU Sets이 추가 되었습니다. CPU Set은 프로세스가 시스템을 나눠서 프로세스가 특정 프로세서 그룹을 사용할 수 있게하고 다른 프로세스나 시스템이 할당된 프로세서 그룹을 사용하지 못하게 하는 것 입니다. 프로세서가 CPU set으로 설정되면 디바이스의 코드도 실행되지 않습니다. Windows 10의 Game Mode가 이 기능을 사용한다고 합니다. (https://www.windowscentral.com/windows-10-game-mode)
그 외에도 ARM 관련 내용이 일부 있습니다.
Scheduler 라는 것이 일반 사용자나 개발자 모두에게 잘 보여지지 않는 내용인데 블로그를 통해서 간단하게나마 소개가 되었습니다. 하지만 깊이 있는 내용은 문서를 찾기 힘들고 이해 하기가 힘들기 때문에 이정도에서 마무리 하려고 합니다.