Home Windows 구조
Post
Cancel

Windows 구조

한주성, 『구조를 알아야 개발이 보인다: 윈도우 편 C++로 풀어보는 윈도우 구조』, 비제이퍼블릭를 요약한 글입니다. Wikipedia, MSDN 등을 참조하여 내용을 보충하였습니다.

윈도우 기본 개념 이해하기

OS 기본 지식에 해당하는 부분이 많음

⭐ 표시된 항목은 이 책에서 꼭 알아야 한다고 강조한 요소에 해당 함

  1. 유저 모드와 커널 모드
    • 자용자 지원에 대한 편리성과 안정성을 위해 분리
  2. 프로세스
    • 하나의 프로그램을 실행하기 위한 자원의 집합을 뜻 함
    • 프로그램을 실행하면 프로세스(여럿이 될 수 있음)
  3. 스레드
    • 프로그램의 실행 코드를 커널에서 처리하는 역할
    • 다음 정보를 가지고 있음
      • CPU에서 수행되기 때문에 CPU 상태 정보
      • CPU를 나누어 쓰기 위한 스케줄링과 관련된 정보
  4. 가상 메모리
    • 물리 메모리와 디스크를 이용한 메모리를 모두 통합하여 관리해서, 프로세스들이 메모리를 의식하지 않고 사용할 수 있도록 도와주는 역할
  5. 객체와 핸들
    • 하나의 기능을 위해 사용되는 구조체들을 객체라고 부름
      • 윈도우에서 객체들은 커널 메모리 공간에 존재함
    • 커널 메모리에 있기 때문에 직접 접근하지 못하고 윈도우의 관리하에 접근해야 함
      • 윈도우 객체에 접근하기 위한 조정자를 핸들이라고 부름
  6. 멀티프로세싱
    • 윈도우는 다수의 프로세서가 하나의 메모리 영역과 하드웨어 자원들을 공유해서 사용하는 대칭형 다중 처리(Symmetric multiprocessing, 이하 SMP)를 지원함
    • SMP는 CPU 입장에서 데이터가 어느 위치에 있던 동일하게 접근할 수 있고, 작업 스케줄링을 통해서 효율적으로 여러 프로세서를 이용할 수 있도록 함(Windows 10에서 최대 256개의 logical processor 지원)
  7. 유니코드
    • 유니코드란
    • VS 2005부터 개발 시 기본적으로 유니코드를 사용하도록 구성됨
  8. 서브시스템
    • 윈도우에서는 실행 중인 응용 프로그램들의 환경을 지원하는 기능을 제공함(환경 서브시스템, Environment subsystem)
    • 환경 서브시스템은 응용 프로그램의 샐행을 위한 환경 부분과 프로세스 관리만 담당
      • 프로세스를 실행하는 경우: 프로세스의 자원들을 받아서 메모리에 로드하고 실행할 준비를 한 다음 초기 스레드(메인 함수)를 실행해주는 역할을 함
  9. 세션
    • 윈도우에서 다중 사용자 혹은 화면을 제어하는 프로그램을 개발할 때 먼저 이해해야 하는 영역
    • 프로그램을 실행시킨 사용자만이 해당 프로그램의 입력과 출력을 보장받고 제어할 수 있도록 구분해주는 역할
    • 구성 요소
      • Window Station
      • Desktop

프로세스(Process)

프로세스가 담고 있는 정보들

정보설명
개인 가상 메모리 영역해당 프로세스에서만 사용 가능한 메모리 영역이 할당됨
개인 물리 메모리 영역프로세스에서 사용하는 물리 메모리 영역을 할당함
보안 토큰프로세스가 가진 권한으로 공유 자원을 엑세스할 때 사용됨
핸들 테이블이용할 수 있는 커널 개체를 나타내며 핸들을 통해 커널 개체를 운용함
스레드프로세스는 하나 이상의 스레드를 가지고 있으며 프로세스 객체 생성 후 메인 스레드를 초기화하여 실행함
우선 순위프로세스의 기본 우선 순위를 지정할 수 있음. 스레드는 프로세스의 기본 우선 순위를 기준으로 동작(Priority class)
메모리 주소현재 프로세스가 사용하는 메모리 주소를 가지고 있음

KPROCESS라는 구조체가 프로세스의 실행 시간부터 프로세스가 포함하는 핸들 테이블 위치 및 가상 메모리 주소와 권한 정보를 포함함

프로세스의 구분

분류설명
시스템 프로세스(System support process)OS에서 생성하고 반드시 존재해야 하는 프로세스들. Native API를 사용
서비스 프로세스(Service process)Win32 Services에 의지하여 서비스 형태로 실행되는 프로세스들을 말함
응용 프로그램(User mode application)유저 모드에서 실행되는 프로그램들이 여기에 속함
환경 서브시스템(Environment subsystem, Win32 Subsystem)응용 프로그램과 OS의 커널 함수 간의 인터페이스 역할

프로세스 관련 API

역할함수
프로세스 생성CreateProcess
다른 유저 권한으로 프로세스 생성CreateProcessAsUser, CreateProcessWithLogonW
프로세스 종료ExitProcess, TerminateProcess

프로세스 생성 순서

  1. 파일 열기(CreateProcess): 파일의 PE Header를 읽어서 프로세스 생성 작업을 시작
  2. 프로세스 객체 생성 - 커널 모드: 프로세스 객체가 커널에 생성됨. 커널에서 관리하기 위해 필요한 정보가 있으며 메모리에 프로세스 공간을 만들고 저장함
  3. 스레드 초기화 - 커널 모드: 스레드 생성을 위한 준비 작업으로 스택과 컨텍스트를 만듦
  4. 메인 스레드 객체 생성 - 커널 모드: 메인 스레드에는 스레드 실행을 위한 스케줄링과 처리 중인 내용(스택, 컨텍스트)의 위치에 대한 정보들이 포함됨
  5. 환경 서브시스템에 생성된 프로세스와 스레드 알림/복제: CSRSS에게 프로세스, 스레드 핸들과 ID를 알림과 함께 전달함
  6. 환경 서브세스템 프로세스와 스레드 관리 시작: 알림을 받은 CSRSS는 프로세스와 스레드의 핸들을 복제하고 초기화하여 여러 응용 프로그램이 동작할 수 있도록 관리함
  7. 첫번째 스레드 메인 함수 실행

스택(Stack): 스레드가 처리(processing)를 위해 이용함

컨텍스트(Context): 멀티 프로세싱에서 실행과 대기 상태를 반복할 수 있도록 자신이 실행했던 지점을 기억하는 공간


스레드(Thread)

프로세서는 처리에 필요한 자원들이 모여 있는 작업 공간임. 실제 작업은 프로세스에 속해 있는 스레드가 진행함.

스레드가 담고 있는 정보들

정보내용
CPU 레지스터 정보컨텍스트라고 부름. 이를 이용하여 CPU를 나누어 사용할 때 기존 처리 정보를 유지함
현재 모드현재 사용되는 API에 따라서 유저 모드인지 커널 모드인지 확인함
스택유저 모드와 커널 모드 공간에 각각 하나씩 2개의 스택을 이용
개인 저장 영역스레드에서 개별적으로 이용할 수 있는 변수 저장 공간으로 사용됨
임의의 보안 토큰권한을 부여하여 리소스 접근에 이용함
메시지 큐메시지 큐를 이용하여 윈도우나 스레드를 생성하고 관리할 수 있음
스레드 우선순위스레드 스케줄링에 사용됨
스레드 상태 정보준비, 실행, 대기 인지 등 현재 상태 정보를 표시함

스레드 관련 API

역할함수
프로세스 생성CreateThread
스레드 일시 중지와 재개SuspendThread, ResumeThread
프로세스 종료ExitThread, TerminateThread

스레드 스택

스레드가 사용하는 변수와 처리 결과를 저장하는 용도로 쓰인다. CPU의 레지스터들이 접근하는 경로에 해당한다. 커널 모드에서 사용되는 스택은 크기가 정해져 있다(일반적으로 64비트 OS에서 24Kb). 유저 모드의 스택의 크기는 자유롭게 지정할 수 있다.

유저 모드에서 스택의 크기 및 조절 방법

  • 기본적으로 1Mb로 예약(Reserve)
  • 4Kb씩 증가하여 커밋(Commit) 되며, 기본 크기는 64Kb로 생성
  • Linker를 이용하여 기본 크기를 지정
  • CreateThread함수나 CreateRemoteThread를 이용하여 조절

스레드 우선순위

CPU의 한정된 자원을 나누어 사용해야 하는데, 무조건 동일한 조건으로 스레드를 나누어 실행할 수 없다. 우선 순위 레벨은 0~31까지 존재한다.(낮을 수록 빠르게 처리됨) 가장 낮은 레벨의 스레드는 Zeropage Thread라는 프로세스가 사용하여, 프로세스에서 더 이상 사용하지 않는 메모리를 해제하는 작업에 이용된다.

스레드 우선순위 API

역할함수명
프로세스의 우선 순위 변경SetPriorityClass
스레드가 상속 받은 우선 순위를 기준으로 우선 순위 변경SetThreadPriority

클럭과 퀀텀

프로세서를 사용할 때에는 클럭(Clocks)을 기준으로 삼음. 스레드를 변경하게 된다면 준비 작업인 컨텍스트 전환(Context switch)을 진행한다.

컨텍스트는 스레드가 이전에 사용하던 프로세서에서 처리를 완료하지 못하고 다른 스레드로 변경할 때, 처리를 재개하기 위해 정보를 저장 해놓은 데이터라고 생각하면 된다.

퀀텀 1은 클럭 1과 동일하며, 스레드의 퀀텀 값이 0이 되면 컨텍스트 전환이 일어난다.

스레드 스케줄링

커널모드에서 스케줄러를 이용하여 실행해야 할 스레드들을 큐(Queue)를 이용해 관리한다. 스레드 스케줄링은 기본적으로 스레드 우선 순위에 따라 관리되고, 상황에 따라 다른 스레드보다 먼저 처리해야 하는 경우 Dispatcher를 통해 점유(Preemptive)하여 프로세서를 선점해 사용하게 된다. 또한 Dispatcher는 스레드가 실행되었는지, 계속 대기하고 있는지의 상태 정보를 통해 보다 세밀하게 프로세서를 분배하고 스레드 실행 순서를 관리하는 기능을 수행한다.

Thread_Init_to_Term 스레드의 활동 초기화에서 종료. 준비(1,7), 실행(2), 실행 대기(3), 스레드 종료(4), 대기(5), 전이(6)

  1. 새로운 스레드 초기화
  2. 준비(Ready): 실행할 준비가 된 상태. 준비 상태에 들어간 스레드에 큐를 이용해서 다음에 실행할 스레드의 순서를 스레드 스케줄러가 관리한다. CreateThread 호출시 바로 준비 상태가 된다.
  3. 실행(Running): 스레드가 CPU를 할당받아서 실행 중인 상태. 아래와 같은 경우 프로세서가 실행을 멈추고 다른 상태로 전환됨
    1. 퀀텀 소진
    2. 스레드가 처리할 작업을 모두 완료
    3. 자발적으로 전환을 시도
  4. 실행 대기(Standby): 프로세서를 할당 받았지만 아직 대기 중인 상태. CPU 내 프로세서당 하나만 존재하며, 컨텍스트 전환을 통해 실행 상태로 바뀝니다.
  5. 종료(Terminated): 스레드의 모든 작업을 완료 혹은 ExitThread를 호출했을 때 종료 상태가 됨. 해당 스레드가 참조하는 핸들을 모두 닫고 스레드 역시 소멸됨
  6. 대기(Waiting): 실행 중인 스레드가 Sleep과 같은 대기 함수를 호출한 경우, 자발적으로 대기 상태로 전환됨. 설정한 조건을 만족하거나 시간이 만료되면 준비 상태로 전환됨
  7. 전이(Transition): 대기 상태에서 간혹 물리적 메모리를 사용하지 않아 물리 메모리를 할당해야 하는 스레드는 전이 상태가 됨. 가상 메모리로 페이징된 메모리가 물리 메모리에 올라오게 되면 대기 상태가 됨
  8. 지연 실행 대기(Deferred Ready): 프로세서 할당은 받았지만 아직 스케줄링이 되지 않은 상태.

스레드 스케줄링을 다시 조정하게 되는 경우

  1. 스레드가 퀀텀을 다 사용했을 때
  2. 입/출력이 완료되었을 때
  3. 스레드 우선 순위가 변경되었을 때
  4. 대기 상태로 객체나 스레드가 변경되었을 때
  5. Sleep()을 호출했을 때

스레드 동기화

처리되는 상태를 확인하거나 처리 신호를 보내는 방식에 사용되는 기술. 스레드들이 서로 사용할 수 있는지를 확인하기 위해 상태 정보를 주고 받는 것을 동기화라 하고, 이러한 이벤트 및 스레드 동기화하는 객체들을 디스패처에서 관리하기 때문에 디스패처 객체라고도 한다.

디스패처(Dispatcher)는 커널에 존재하는 객체들 중 상태 정보를 통해 관리되는 객체들을 제어한다.

디스패처 객체의 상태 값

명칭설명
프로세스(Process)프로세스 종료
스레드(Thread)스레드 종료
파일(File)I/O 완료되었을 때
뮤텍스(Mutex)뮤텍스 할당이 해제되었을 때
세마포어(Semaphore)세마포어 카운트가 0보다 크게 증가했을 때
이벤트(Event)이벤트가 발생했음
타이머(Timer)지정한 시간 값이 만료함

여기서는 뮤텍스, 세마포어, 이벤트 그리고 커널에서 관리되는 디스패처 객체는 아니지만 유저 모드에서 동기화에 사용할 수 있는 임계 영역에 대해 설명한다.

뮤텍스(Mutex) 동기화

상호 배제(Mutual exclusion, 줄여서 Mutex)라고도 한다. 뮤텍스가 접근 가능한 상태에서만 코드 안에서 접근하는 객체를 사용할 수 있다. 임계 영역 동기화과 유사하지만, 뮤텍스는 커널 모드에서 관리된다는 차이점이 있다.

객체를 동시에 사용할 수 없도록 상호 배제하는 것

  1. 뮤텍스는 하나의 스레드를 임계 구역이라는 공유 불가능한 영역을 사용하여 다른 객체가 해당 객체에 접근하지 못하도록 보호한다.
  2. 자신이 처리해야할 작업을 완료하면 임계 구역을 나오게 되고, 뮤텍스가 사용 가능한 상태로 할당을 해제한다. 그리고 대기하던 객체가 임계 구역으로 진입하며 뮤텍스를 할당 받게 된다.
  3. Auto-Reset 형태로 자원의 사용이 가능하면 자동으로 Non-Signaled 상태가 된다.

세마포어(Sepmaphore) 동기화

뮤텍스 동기화처럼 하나의 객체에게만 자원을 할당하지 않고, 최대값를 설정하여 여러 객체에게 자원을 할당할 수 있다.(최대값이 1이면 뮤텍스처럼 사용 가능)

  1. 리소스의 상태를 나타내는 카운터를 통해서 관리한다.
  2. 자원을 할당받게 되면, 세마포어 카운터를 감소시킨다.
  3. 세마포어 값이 0인 상태가 되면, 자원을 할당받을 수 없게 되고 사용할 수 있을 때까지 대기 상태가 된다.
  4. 자원 사용이 끝나면, 세마포어 카운터를 증가시켜 다른 객체가 사용할 수 있도록 한다.
  5. Auto-Reset 형태로 자원의 사용이 가능하면 자동으로 Non-Signaled 상태가 된다.

이벤트(Event) 동기화

여러 개의 스레드를 동시에 사용해야 할 때 유용하다. 여러 스레드의 상태를 동시에 변경 할 수 있다. 동기화를 자동 혹은 수동으로 하도록 설정할 수 있다.

  1. BOOL 타입 변수를 통해서 관리된다.
  2. 이벤트 생성은 Manual-Reset(커널에게 알림) 모드와 Auto-Reset(동기화) 모드로 생성할 수 있다.
  3. Manual-Reset의 경우 여러 스레드의 상태를 동시에 변경할 수 있고, Auto-Reset의 경우 하나의 스레드만 가능하다.

임계 영역(Critical section) 동기화

뮤텍스 동기화와 비슷하지만 유저 모드에서 동작하기 때문에 프로세스 간의 동기화에 사용할 수 없다.

  1. 하나의 프로세스 내에서 스레드 간 동기화하는 데 사용한다.
  2. 뮤텍스 동기화보다 약간 더 빠르고 효율적인 메커니즘을 제공한다.

병렬 처리(Parallel processing)

책의 설명이 부실하여 다음 링크로 대체

마이크로소프트에서 생산성과 안정성을 확보하기 위해 Parallel Patterns Library (PPL)을 제공한다.


객체와 핸들(Object and Handle)

윈도우에서 객체 관리자들 통해 객체를 관리함.(프로그래밍에서의 객체와 다름) 커널 메모리 공간에 존재하며, 커널 코드만이 객체에 직접 접근할 수 있다. 유저 모드에서는 핸들(Handle)을 부여받아서 사용할 수 있다.

객체

프로세스가 직접 객체를 생성하지 않는다. 프로세스는 윈도우 서브 시스템 내의 유저 모드 API를 호출한다. 그러면 내부에서 커널 API를 사용하여 커널 모드 내의 객체 관리자에게 전달되고 해당 객체를 생성하게 된다. 이후 객체 관리자는 프로세스에게 해당 객체의 핸들을 전달한다.

객체 관리자는 프로세스 관리자, 메모리 관리자와 같은 커널의 주요 관리자 중 하나이다. 객체의 생성, 삭제 그리고 객체의 핸들을 관리한다.

핸들

핸들은 각 프로세스마다 핸들 테이블이라는 핸들의 목록을 가지고 있다. 핸들 테이블의 각 핸들은 객체 헤더 주소, 특성(감시, 상속, 보호), 권한 정보(Access mask)를 가지고 있다.

핸들의 접근 권한

Access mask는 핸들이 객체에 접근할 때 가지는 권한으로 비트 플래그 형식으로 사용할 수 있다.

링크: 핸들의 Access Mask 별 권한

접근 권한을 최소화하는 것이 객체의 피해를 실수나 오류로부터 최소화 할 수 있다.


메모리(Memory)

가상 메모리와 페이징

메모리 관리자: 커널에서 가상 메모리 할당/해제, 프로세스 간 메모리 공유, 파일과 메모리 매핑(Mapping) 등 메모리 관련 전반적인 작업을 수행하는 관리자이다.

프로세스 별로 실제 메모리보다 ㅁ낳은 공간을 사용할 수 있도록 윈도우에서는 가상 메모리 공간을 제공한다. 따라서 프로세스는 실제 물리 메모리에 접근하지 않고, 윈도우에서 제공하는 가상 메모리만을 사용하게 되어 있다. 반드시 물리 메모리에 있어야 되는 영역을 제외한 나머지를 하드 디스크에 저장하고 실제 메모리처럼 사용해 부족한 공간을 활용한다. 이때 사용되는 기법을 페이징(Paging)이라고 한다.

Virtual Memory Structure 단순화한 가상 메모리 구조. 출처: 위키백과

페이징 파일

페이징 파일은 메모리의 백업 공간으로 생각하면 쉽게 이해할 수 있다. 윈도우에서 페이징 파일은 메모리 공간과 동일하게 인식되며, 보통 공유되지 않는 쓰기 가능한 메모리가 저장된다.

  1. 윈도우에서 최대 16개의 페이징 파일을 지원(윈도우 버전별로 달라질 수 있음)
  2. 다른 디스크 파티션에 할당할 수 있음
  3. 페이징 파일의 최대 크기는 64비트 기준으로 16Tb(PAE 포함)
  4. 초기 크기와 최대 크기 설정 가능하다. 따로 설정하지 않은 경우에 초기 크기는 물리 메모리 크기와 같고 최대 크기는 물리 메모리 크기의 3배이다.

PAE(Physical Address Extension): 4Gb 이상의 물리 메모리를 32비트 시스템에서 사용할 수 있도록 만들어 주는 x86, x86-64 프로세서의 기능이다.

페이지 공유(데이터 공유)

메모리 관리자는 프로세스 페이지 공유를 제공해 효율적으로 메모리를 관리한다. 페이지 공유는 윈도우(시스템)에서 제공하는 DLL을 동일한 주소에서 로드하는 것이다. 여러 프로세스에서 동일한 DLL을 사용하는 것으로 상당한 메모리를 아낄 수 있다.

힙(Heap)

유저가 프로그램에서 직접 메모리를 할당하거나 해제할 수 있는 프로그램의 메인 메모리 공간이 힙이다. 프로그램 실행시 윈도우는 프로세스를 생성하면서 기본적으로 1개의 힙을 생성하고 이를 기본 프로세스 힙이라고 한다.

큰 메모리 페이지 조각을 찾는 것보다 필요한 데이터 페이지에 맞게 힙을 할당해준다면 데이터 검색 속도가 보다 빨라질 수 있다.

힙 관리자의 주요 특징

  1. 관리할 수 있는 최소 힙 크기는 8바이트
  2. 힙으로 시작하는 Windows API는 Ntdll.dll을 거치기 때문에 최소 크기보다 더 많은 메모리를 할당하거나 예약한다.
  3. GetProcessHeap을 통해 현재 프로세스의 기본 힙을 확인할 수 있으며, 핸들을 얻을 수 있다.
  4. 힙을 만들 때 동적이나 고정으로 크기를 만들 수 있다.

Heap management API 힙을 관리할 수 있는 Low/High 레벨 API.

프론트엔드 할당자(Front-End Allocator)

프론트엔드 할당자의 가장 큰 특징은 단방향 연결 리스트를 이용한다는 것이다. 프론트엔드 할당자는 같은 크기의 메모리를 반복적으로 할당/해제할 경우 이용하는데, 물리 메모리 영역에 위치하여 매우 빠르게 동작한다.

메모리 맵 파일(Memory Maped File)

메모리에 DLL이나 파일을 연결하는 기능으로, 윈도우에서 파일을 메모리에 로드할 때 많이 사용하는 유용한 기능이다. 메모리에 연결된 파일을 섹션 객체(Section object)라고 하며 Winobj를 통해 확인할 수 있다.

일반적으로 메모리에 파일을 연결하는 목적은 프로세스 간에 파일을 공유하거나 메모리에서 빠르게 I/O 처리가 가능하기 때문이다.

https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%A7%B5_%ED%8C%8C%EC%9D%BC


추가로 공부할 것들

  • 예외 처리 방식
  • 어셈블리

덧. 아주 전문적이거나 정확한 설명이 아니므로 키워드를 바탕으로 더 연구하는 것을 추천합니다.

This post is licensed under CC BY 4.0 by the author.