핵심 요약
- 산업 현장에서 아직도 현역으로 쓰이는 1996년작 윈도우 NT 4.0용 프로그램을 만들기 위한 호환성 작업이 시작되었습니다.
- 단순한 std::mutex 락 하나 걸었을 뿐인데 비주얼 스튜디오 내부 런타임이 거대한 타이머 큐와 스케줄러를 줄줄이 호출하는 황당한 의존성이 드러났습니다.
- 구형 OS 호환을 가로막는 미지원 API들을 직접 구현하고 디버거로 원인을 파헤치며 새로운 빌드 접근법을 모색하게 됩니다.
요약 산업용 제어 시스템 개발 현장에서 1996년에 출시된 윈도우 NT 4.0 환경을 지원해야 하는 상황이 발생하면서 시작된 한 개발자의 삽질기입니다. 작성자는 이전 프로젝트에서 Visual Studio 2017을 활용해 윈도우 2000 호환 빌드를 성공시켰지만, 공장 자동화 현장에서는 아직도 25년 전 운영체제인 윈도우 NT 4.0 장비가 현역으로 돌아가고 있어 이에 맞는 소프트웨어가 필요해졌습니다.
처음에는 Visual Studio 2017의 v141_xp 툴셋을 NT 4.0에 맞추는 방식으로 접근했습니다. 하지만 테스트 앱(std::mutex, std::thread 사용)을 의존성 검사 프로그램(Dependency Walker)으로 확인해 보니 KERNEL32.DLL에서 CreateTimerQueueTimer 등 9개의 API가 누락되어 있었습니다. InitializeCriticalSectionAndSpinCount나 64비트 파일 오프셋을 분할 처리하는 SetFilePointerEx 같은 함수는 간단한 대체 구현으로 해결할 수 있었지만, 윈도우 2000에서 도입된 타이머 큐(Timer Queue) 관련 API는 운영체제 깊숙이 연계되어 있어 문제였습니다.
단순 뮤텍스 잠금과 스레드 생성만 하는 프로그램에서 왜 타이머 큐가 호출되는지 스텁 함수와 디버거로 역추적하자 황당한 사실이 드러났습니다. std::mutex::lock()을 호출했을 뿐인데 Visual Studio 2017의 C++ 런타임(Concurrency Runtime)이 작동하며 주기적 타이머 프레임워크와 스케줄러, 심지어 이벤트 트레이싱(ETW) 등록까지 거대한 호출 스택을 연쇄적으로 실행하고 있었던 것입니다. 이상적으로는 단순한 프로세서 명령어(LOCK CMPXCHG) 수준으로 끝나야 할 락 작업에 과도한 내부 프레임워크가 얽혀 있다는 사실이 확인되면서 개발자는 근본적으로 다른 빌드 접근 방식을 고민하게 됩니다.
Sponsored · 광고