운영체제 사례
Windows 스레드 (Windows Threads)
Windows 운영체제는 다중 스레드 커널이며, 실시간 애플리케이션 및 다중 프로세서에 대한 지원을 제공합니다. Windows에서 애플리케이션은 별도의 프로세스로 실행되며, 각 프로세스는 하나 이상의 스레드를 포함할 수 있습니다. Windows는 사용자 수준 스레드와 커널 수준 스레드 간의
일대일 매핑을 사용합니다. 이는 각 사용자 수준 스레드가 하나의 커널 스레드에 대응된다는 의미입니다.
스레드의 일반적인 구성 요소는 다음과 같습니다:
스레드 ID (Thread ID): 스레드를 고유하게 식별하는 ID입니다.
레지스터 세트 (Register set): 프로세서의 상태를 나타내는 레지스터의 집합입니다.
프로그램 카운터 (Program counter): 다음에 실행될 명령어의 주소를 나타냅니다.
사용자 스택 (User stack): 스레드가 사용자 모드에서 실행될 때 사용되는 스택입니다.
커널 스택 (Kernel stack): 스레드가 커널 모드에서 실행될 때 사용되는 스택입니다.
전용 저장 영역 (Private storage area): 다양한 런타임 라이브러리 및 동적 링크 라이브러리(DLL)에서 사용되는 영역입니다.
이러한 레지스터 세트, 스택, 전용 저장 영역을 통틀어 **스레드의 컨텍스트(context)**라고 부릅니다.
Windows 스레드의 주요 데이터 구조는 다음과 같습니다:
ETHREAD (Executive Thread Block): 실행 스레드 블록은 스레드가 속한 프로세스에 대한 포인터와 스레드가 제어를 시작하는 루틴의 주소를 포함합니다. 또한 해당 KTHREAD에 대한 포인터도 포함합니다. ETHREAD는 커널 공간에 존재하며 커널만 접근할 수 있습니다.
KTHREAD (Kernel Thread Block): 커널 스레드 블록은 스레드에 대한 스케줄링 및 동기화 정보를 포함합니다. 커널 스택(스레드가 커널 모드에서 실행될 때 사용)과 TEB에 대한 포인터도 포함합니다. KTHREAD 또한 커널 공간에 존재합니다.
TEB (Thread Environment Block): 스레드 환경 블록은 스레드가 사용자 모드에서 실행될 때 접근되는 사용자 공간 데이터 구조입니다. TEB는 스레드 ID, 사용자 모드 스택 및 스레드 로컬 저장소(thread-local storage)를 위한 배열을 포함합니다.
이러한 구조들은 Windows가 스레드 상태를 효율적으로 관리하고, 필요한 경우 컨텍스트 전환을 수행하며, 스레드 간의 격리를 유지하는 데 기여합니다.
Linux 스레드 (Linux Threads)
Linux는 프로세스와 스레드를 구분하지 않는 독특한 접근 방식을 채택합니다. 대신, Linux는 프로그램 내의 제어 흐름 단위를 "태스크(task)"라고 부릅니다. 전통적인
fork()
시스템 호출은 프로세스를 복제하는 데 사용되지만, Linux는 clone()
시스템 호출을 통해 스레드를 생성하는 기능도 제공합니다.
clone()
시스템 호출은 부모와 자식 태스크 간에 어떤 리소스가 공유될지 결정하는 플래그(flags) 세트를 인수로 받습니다:
CLONE_FS
: 파일 시스템 정보를 공유합니다. (예: 현재 작업 디렉토리)CLONE_VM
: 동일한 메모리 공간을 공유합니다.CLONE_SIGHAND
: 시그널 핸들러를 공유합니다.CLONE_FILES
: 열린 파일들의 집합을 공유합니다.
만약
clone()
이 CLONE_FS
, CLONE_VM
, CLONE_SIGHAND
, CLONE_FILES
플래그를 모두 전달받으면, 부모와 자식 태스크는 동일한 파일 시스템 정보, 메모리 공간, 시그널 핸들러, 그리고 열린 파일 집합을 공유하게 됩니다. 이는 다른 시스템에서 스레드를 생성하는 것과 동일한 효과를 냅니다. 반대로,
clone()
이 이러한 플래그 중 어느 것도 설정하지 않고 호출되면, 어떤 공유도 발생하지 않으며, 이는 fork()
시스템 호출이 제공하는 기능과 유사합니다.
프로세스와 스레드 간의 이러한 구분 부족은 Linux가 태스크의 전체 컨텍스트를 주 프로세스 데이터 구조 내에 유지하지 않기 때문에 가능합니다. 대신, Linux는 컨텍스트를 독립적인
하위 컨텍스트(subcontexts) 내에 유지합니다. 예를 들어, 프로세스의 파일 시스템 컨텍스트, 파일 디스크립터 테이블, 시그널 핸들러 테이블 및 가상 메모리 컨텍스트는 별도의 데이터 구조에 보관됩니다. 프로세스 데이터 구조는 단순히 이러한 다른 구조에 대한 포인터를 포함하므로, 여러 프로세스가 동일한 하위 컨텍스트를 가리키고 참조 카운터를 증가시킴으로써 쉽게 하위 컨텍스트를 공유할 수 있습니다.
clone()
시스템 호출의 인수는 어떤 하위 컨텍스트를 복사하고 어떤 것을 공유할지 지시합니다. 새로운 태스크는 항상 새로운 식별자와 새로운 스케줄링 컨텍스트를 부여받습니다. 그러나 전달된 인수에 따라 커널은 부모의 사본인 새로운 하위 컨텍스트 데이터 구조를 생성하거나, 새로운 태스크가 부모가 사용하는 것과 동일한 하위 컨텍스트 데이터 구조를 사용하도록 설정할 수 있습니다.
결론적으로, Linux는
clone()
시스템 호출의 유연성을 통해 프로세스와 스레드의 개념을 통합하며, 이는 Linux 컨테이너 기술(LXC)의 기반이 되기도 합니다. 이러한 설계는 리소스 공유와 시스템의 확장성 측면에서 큰 이점을 제공합니다.
Last updated