|
실행 서브 시스템 |
기능 |
객체 관리자 (Object Manager) |
리소스를 관리하고 전역 네임 스페이스를 구현한다. |
보안 참조 모니터 (Security Reference Monitor) |
NT의 보안 ID(SIDs) 보안 모델과 임의 접근 제어 리스트(Discretionary Access Control Lists(DACLs)를 구현 |
가상 메모리 관리자 (Virtual Memory Manager) |
프로세스 어드레스 영역을 정의(define)하고 물리적 메모리를 할당(assign)한다. |
I/O 관리자 (I/O Manager) |
애플리케이션과 디바이스 드라이버간의 인터페이스를 제공한다. |
캐시 관리자 (Cache Manager) |
파일 기반 전역 파일 캐시를 구현한다. |
로컬 프로시서 콜 설비 (Local Procedure Call Facility) |
효율적인 프로세스 간 통신을 제공한다. |
설정 관리자 (Configuration Manager) |
레지스트리를 관리한다. |
프로세스 관리자 (Process Manager) |
프로세스와 쓰레드에 관련된 API들을 익스포트 한다. |
Win32 |
Win32 메시징과 그랙픽 관련 함수들을 구현한다. (NT 4.0부터) |
플러그 앤 플레이 관리자 (Plug-and-Play Manager) |
디바이스 드라이버들에게 어떤 디바이스의 온/오프를 알려준다.(NT 5.0부터) |
전력 관리자 (Power Manager) |
기계의 전력 상태를 관리한다.(NT 5.0부터) |
실행부는 NT 운영체제의 가장 거대한 부분이라 할 수 있으며 I/O 관리자, 객체 관리자, 프로세스 관리자, 가상 메모리 관리자 등의 여러 컴포넌트로 구성되어 있고 각각의 컴포넌트 운영체제의 특정 역할을 책임지는 구조로 이루어져 있습니다.
<표1>은 NT 실행부의 서브시스템과 각각의 기능을 정리한 것입니다.
커널과 그 밖의 부분들
NT 실행부의 컴포넌트의 마이크로 커널에 구현되어 있는 여러 기본적인 기능들을 이용해 구현됐습니다. 이 마이크로 커널을 NT에서는 일반적으로 커널이라 부릅니다. 커널은 스케쥴러를 포함하고 있으며 동기화 프리미티브(Synchronization primitive)들을 제공합니다.
디바이스 드라이버와 하드웨어 추상 계층 (HAL : Hard ware Abstration Layer)은 컴퓨터 하드웨어와 상호작용을 합니다. HAL은 자체적인 API를 제공하고 있으며 이 API들을 하드웨어에 특화된 기계어 코드로 변환시키는 역할을 합니다. NT에서는 이식성을 높이기 위해 하드웨어 특화적인 코드들은 커널과 HAL에만 존재시키고 나머지 코드들은 모두 C와 C++로 구현했습니다.
운영체제 환경
NT가 개발되던 당시에는 PC운영체제는 DOS가 장악하고 있었고 윈도우 3.1과 OS/2등이 인지도를 얻어가던 중이었으며 연구 영역이나 대규모 시스템의 운영체제는 유닉스가 절대적이었습니다. 따라서 NT는 이들 모두를 지원하고자 했으며 각각의 운영체제를 별도의 모듈로 구현했습니다. 이들을 일컬어 운영체제 한경(Operation System Environments)이라 칭합니다. 이것은 매우 이상적인 구조였지만 각각의 운영체제 환경들은 본래 운영체제와 비교하면 필연적으로 어느 정도의 제약이 있었기 때문에 결국 Win32만을 공식적인 운영체제 환경으로 간주하고 있습니다.
운영체제 환경의 실행과정
NT의 운영체제 환경은 클라이언트-서버 모델로 구현되어 있습니다. 예를 들어 어떤 애플리케이션을 빌드한다고 하죠. 이때 어플리케이션은 운영체제 환경이 익스포트는 운영체제 API를 이용했을 것이고 이 API를 통해 각 운영체제 환경의 클라이언트 측 DLL에 링크 됩니다. 만일 Win32 API를 써서 프로그램을 만들었으면 Win32 운영체제 환경의 클라이언트 측 DLL인 kernel32.dll, gdi32.dll, user32.dll에 애플리케이션이 링크 됩니다. 또 POSIX 애플리케이션을 빌드했다면 POSIX 운영체제 환경의 클라이언트 측 DLL인 posix.dll에 링크 됩니다.
운영체제 환경의 클라이언트 측 DLL은 각각 서버 측을 대신해 특정 작업을 수행하며 이 때 해당 애플리케이션의 클라이언트 프로세스로서 작업을 수행합니다.
클라이언트 측 DLL은 어떤 경우에는 전혀 서버측의 도움 없이 스스로 작업을 완료할 수 있으나 반드시 서버 측의 도움을 받아야 할 필요도 있습니다.
보통 서버 측의 도움이 필요한 때는 오직 운영체제 환경과 관련된 어떤 전역 정보가 변경돼야 할 경우가 대부분입니다. 만일 클라이언트 측 DLL이 서버 측의 도움이 필요하다면 클라이언트 측 DLL은 로컬 프로시저 콜(LPC : Local Procedure Call)이라 불리는 메시지를 서버 측에 던집니다. 서버 측이 작업을 마치면 다시 응답을 보내고 클라이언트 측 DLL도 작업을 마칩니다. 이 경우 클라이언트나 서버 측 모두 네이티브 API를 사용할 수 있으며 많은 운영체제 환경 API들은 이 네이티브 API를 이용해 구현되어 있습니다. 다시 말해 운영체제 환경 API들 자체는 유저 모드에서 실행되나 커널 모드의 작업이 필요할 경우 네이티브 API를 사용하는 것이죠.
운영체제 환경의 성능 문제
예를 들어 CreateProcess를 호출해 프로세스를 만드는 과정을 살펴보죠. CreateProcess는 새로운 프로세스를 만드는 Win32 API 중 하나입니다. 새로운 프로세스를 만들기 위해서는 운영체제 환경의 서버 측은 새 프로세스를 만들려고 하는 클라이언트 측의 프로세스와 새로 생성되는 프로세스의 관계를 설정해야 합니다. 이 때 서버 측은 네이티브 API의 NtCreateProcess 함수를 호출합니다.
이와는 반대로 ReadFile Win32 API를 이용해 파일에서 정보를 읽을 경우 ReadFile은 운영체제의 환경 전역 정보를 수정할 필요가 없기 때문에 모든 작업이 클라이언트 측 DLL에서 수행되도록 구현되어 있습니다. 이 때 클라이언트 측 DLL은 네이티브 API,의 NtReadFile 함수를 이용해 디스크 등의 하드웨어와 통신을 하겠지만 서버 측의 도움은 필요 없는 것입니다.
여기에서 언제나 민감한 독자라면 운영체제 환경은 작업은 클라이언트와 서버 통신이 필요하기 때문에 성능 저하가 발생할 수 있다고 생각할 수 있을 것입니다. MS의 주장으로는 NT의 LPC 자체는 최적화되어 있기 때문에 매우 휴율적이라고는 하나 그래도 성능은 떨어지게 마련입니다. 이런 문제를 의식해서인지 MS는 NT 4.0에서 LPC가 많이 쓰여서 성능이 떨어진다고 생각되던 그래픽 디바이스 인터페이스(GDI : Graphic Device Interface)와 사용자 인터페이스를 담당하는 유저 컴포넌트 Win32 운영체제 환경에서 분리해 아예 실행부의 서브 시스템으로 만들었습니다.
시스템 서비스
시스템 서비스는 커널 모드에서 동작하는 네이티브 API들을 유저 모드에서 사용할 수 있도록 제공해 주는 역할을 합니다. 원래 네이티브 API들은 운영체제 환경에서 사용하려고 만들어진 것이나 일반 애플리케이션에서 이를 사용하는 것을 막는 것은 아무것도 없습니다. 오직 하나, 부실한 도큐먼트를 제외하고 말입니다.
보통 네이티브 API들은 앞에 'Nt'가 붙습니다. 앞서 보았듯이 Win32 API인 CreateProcess가 호출하는 네이티브 API는 NtCreateProcess입니다. 두 함수는 인자도 서로 유사하지만 보통 운영체제 환경의 함수는 운영체제가 수행해야 할 더 많은 작업들을 수행하게 됩니다. CreateProcess의 예를 들면 운영체제 환경의 함수는 해당 프로세스의 환경변수도 설정해야 하고, 어드레스 맵도 채워야 하는 것입니다.
애플리케이션이나 운영체제 환경이 네이티브 API를 이용하려면 NTDLL.DLL이라는 DLL을 통해 호출하게 됩니다. 이 DLL은 운영체제가 운영되고 있는 NT 시스템의 모든 프로세스와 링크되어 있으며 모든 시스템 서비스들의 진입점(entry point)으로 구성되어 있습니다. 이 진입점은 필요한 변수들은 준비하고 시스템 서비스 소프트웨어 예외를 발생시키는 역할을 합니다. 예외가 발생하면 커널 보드에서 동작하는 시스템 서비스 예외 핸들러가 이에 반응해 시스템 서비스 테이블에서 해당 서비스를 구현한 함수를 찾아 실행시키게 됩니다.
첫 회를 마무리하며
지금까지 윈도우NT 아키텍처를 가볍게 살펴봤습니다. 다음 회 부터는 본격적으로 윈도우 프로그래밍, 정확히 말하면 Win32 프로그래밍에 대한 이야기가 진행될 것입니다. 사실 윈도우 프로그래밍을 위해 미리 다루고 싶은 내용은 이보다 훨씬 많지만 이것으로 마무리하고자 합니다. 평소 운영체제에 별 관심이 없던 사람이라면 도대체 이런 것들을 왜 알아야 하는가에 대해 생각할 수도 있었을 것이고, 관심이 많던 사람이라면 실제 윈도우 구성에 대해 조금이나마 이해의 폭을 넓힐 수 있었으리라 생각 됩니다.
필자는 현재 온라인 게임 서버를 개발하고 있는데 최근에 많이 느끼는 것은 점점 시스템의 하부, 그리고 기본을 충실히 공부하는 프로그래머가 줄어들고 있다는 것입니다. 사실 점점 하드웨어는 좋아지고 네트워크는 빨라지는 것이 현실입니다. 이전에는 프로그래머들 개인이 재산으로 생각했던 팁들이 압도적인 기술의 발전에 초라하게 느껴지는 시대이기도 합니다. 하지만 프로세서가 생기고 메모리가 붙어서 컴퓨터가 탄생한 이후로 프로그래머가 알아야 하는 기본은 크게 변하지 않았다고 생각합니다. 그 기본을 탄탄히 하는 것은 새로운 것을 더욱 빨리 받아들이고 더욱 혁신적인 기술을 창발해내는 첫 단계일 것입니다. 게다가 시스템에 대한 탐구와 집착은 프로그래머들의 본성이 아닐까 싶습니다. 이 글이 첫 단계에 조금이나마 독자들에게 도움이 되었기를 바랍니다.
첫댓글 먼말인지 모르겠다.^^
윈도우 프로그래밍에서 프로세서에 관한 이야기 입니다.. 윈도우에 대한 시스템적 내용이에요