운영체제의 중심에는 실행 중인 프로그램의 인스턴스인 프로세스를 관리하는 정교한 메커니즘이 존재한다. Windows와 Linux는 근본적으로 다른 설계 철학을 바탕으로 프로세스를 표현하고 관리하며, 이러한 차이는 각 운영체제의 핵심 커널 데이터 구조에서 가장 명확하게 드러난다.
Linux 커널에서 프로세스와 스레드는 ‘태스크(task)’ 라는 단일 개념으로 추상화하며, 모든 태스크는 task_struct라는 거대한 데이터 구조를 통해 표현된다.
이 구조체는 커널이 특정 태스크에 대해 알아야 할 모든 정보를 담고 있는 중앙 저장소 역할을 한다. task_struct 는 효율적인 메모리 관리를 위해 슬랩 할당자(slab allocator)를 통해 할당된다.
Windows는 프로세스 정보를 여러 구조체에 걸쳐 계층적으로 관리한다. 핵심 구조체는 커널 공간의 EPROCESS(Executive Process Block)와 KPROCESS(Kernel Process Block), 그리고 사용자 공간의 PEB(Process Environment Block)이다. 이러한 분리는 정책(policy)을 담당하는 Executive 계층과 메커니즘(mechanism)을 담당하는 Kernel 계층을 구분하는 NT 아키텍처의 특징을 그대로 보여준다.
Peb 포인터는 사용자 모드에 존재하는 PEB를 가리킨다.ThreadListHead), 최상위 페이지 테이블의 물리 주소(DirectoryTableBase), 기본 우선순위(BasePriority) 등을 포함한다.ntdll.dll과 같은 사용자 모드 라이브러리가 커널 전환 없이 프로세스 정보에 접근하기 위한 성능 최적화 설계이다. 로드된 모듈 목록, 커맨드 라인 인수 등의 정보를 포함하지만, 공식적으로 문서화되지 않은 구조체이다.구조적 차이에도 불구하고, Windows와 Linux 프로세스는 모두 하드웨어 메모리 관리 장치(MMU)에 의해 강제되는 독립적인 가상 주소 공간 내에서 실행된다는 공통된 기반을 가진다. 이 가상 공간은 텍스트, 데이터, BSS, 힙, 스택 세그먼트로 구성되며, 다른 프로세스로부터 메모리 공간이 격리되어 안정성과 보안을 제공한다. 두 운영체제 모두 시스템 콜 처리를 최적화하기 위해 각 프로세스의 가상 주소 공간 상위 영역에 커널 공간을 매핑한다.
Linux의 task_struct 단일 구조는 UNIX의 전통적인 프로세스 중심 모델을 계승하며, 엄격한 프로세스 계층 구조에 중점을 둔다. 반면, Windows의 EPROCESS/KPROCESS 분리 구조는 자원 관리(정책)와 스케줄링(메커니즘)을 분리하는 NT 커널의 객체 지향적 아키텍처를 반영한다.
특히, PEB를 사용자 공간에 배치한 Windows의 결정은 성능과 보안 사이의 근본적인 트레이드오프를 보여준다. 커널 전환 없이 정보에 접근해 성능이 향상되지만, 동시에 중요한 내부 상태 정보가 악성 코드에 의한 정찰 및 조작 대상이 될 수 있다. 이는 Linux가 /proc 파일 시스템을 통해 커널이 중재하는 방식으로만 정보 접근을 허용하여 보안 경계를 더 명확히 하는 것과 대조된다.

프로세스의 생명주기는 생성, 실행(스케줄링), 종료의 세 단계로 구성된다. Linux의 fork()와 exec() 패러다임은 프로세스 복제와 변환으로 명확히 구분되는 반면, Windows의 CreateProcess 는 새로운 객체를 인스턴스화하는 단일 통합 모델을 채택한다. 이러한 생성 모델의 차이는 스케줄링 전략과 프로세스 관계 및 종료 처리 방식에까지 깊은 영향을 미친다.
Linux에서는 fork() 시스템 콜이 부모 프로세스를 거의 완벽하게 복제하여 자식 프로세스를 생성한다. 이후, 자식 프로세스는 보통 exec() 시스템 콜을 호출하여 자신의 메모리 공간을 새로운 프로그램 이미지로 완전히 덮어쓴다. 성능 최적화를 위해 Linux는 Copy-on-Write (CoW) 기법을 사용한다. fork() 시 물리적 메모리 페이지를 복사하는 대신, 부모와 자식이 페이지 테이블을 공유하고 페이지를 읽기 전용으로 표시한다. 어느 한쪽에서 쓰기 작업을 시도할 때만 해당 페이지의 물리적 복사본이 생성되어 불필요한 메모리 복사를 방지하고 fork()를 매우 효율적으로 만든다.
Windows에서는 CreateProcess API가 새로운 프로세스를 생성하고 지정된 실행 파일을 로드하는 작업을 한 번에 수행한다. 이는 개념적으로 fork()와 exec()가 결합된 것과 유사하지만, 내부적으로는 새로운 객체를 처음부터 구성하고 초기화하는 과정에 가깝다. Windows 네이티브 API에는 fork()와 직접적으로 대응하는 기능이 존재하지 않는다.
Linux의 Completely Fair Scheduler (CFS)는 ‘완전한 공정성(fairness)’을 최우선 목표로 삼는다. ‘가상 런타임(vruntime)’이라는 개념을 도입하여 모든 실행 가능한 태스크가 CPU 시간을 공평하게 나눠 갖도록 모델링한다. CFS의 실행 큐는 vruntime을 기준으로 정렬된 레드-블랙 트리로 구현되며, 스케줄러는 항상 트리의 가장 왼쪽에 있는 노드, 즉 가장 낮은 vruntime을 가진 태스크를 선택하여 실행한다.
Windows의 NT 스케줄러는 응답성(responsiveness)을 극대화하는 것을 목표로 하는 우선순위 기반의 선점형 스케줄러이다. 핵심 원칙은 “실행 가능한 스레드 중 가장 높은 우선순위를 가진 스레드가 항상 실행된다”는 것이다. 시스템은 0부터 31까지 총 32개의 우선순위 수준을 가지며, ReadySummary 라는 32비트 비트마스크를 사용하여 상수 시간(O(1)) 내에 다음 실행 스레드를 찾아낸다.
Linux에서 모든 프로세스는 init 프로세스(PID 1)를 루트로 하는 엄격한 트리 구조를 형성한다. 자식이 종료되면 부모가 wait() 시스템 콜로 종료 상태를 회수할 때까지 '좀비 프로세스(Zombie Process)' 상태로 남는다. 만약 부모가 먼저 종료되면 자식은 '고아 프로세스(Orphan Process)' 가 되며, 커널은 이러한 고아 프로세스들을 자동으로 init 프로세스의 자식으로 입양시켜 좀비가 되는 것을 방지한다.
Windows는 엄격한 부모-자식 관계를 유지하지 않는다. 부모가 종료되더라도 자식은 아무런 영향을 받지 않고 독립적으로 실행된다. Windows에서 ‘좀비 프로세스’ 는 다른 의미로, 프로세스가 종료되었지만 다른 프로세스가 해당 프로세스에 대한 핸들을 닫지 않아 커널 객체(EPROCESS)가 메모리에서 해제되지 못하는 자원 누수 현상을 의미한다. 이는 생명주기의 정상적인 일부가 아니라 프로그래밍 오류이다.
프로세스가 수행할 수 있는 작업의 범위는 운영체제의 권한 모델에 의해 결정된다. Linux는 UID/GID 기반의 전통적인 UNIX 접근 방식을, Windows는 Access Token이라는 객체에 보안 정보를 캡슐화하는 정교한 모델을 사용한다. 이 두 모델의 차이는 권한 부여의 세분성, 특권 관리 방식, 그리고 결과적으로 형성되는 공격 표면에 근본적인 영향을 미친다.
Linux의 접근 제어는 특정 사용자(UID) 및 그룹(GID)의 컨텍스트에서 실행되는 프로세스를 기반으로 한다. UID 0은 대부분의 권한 검사를 우회하는 슈퍼유저, 즉 root 사용자를 위해 예약되어 있다. 프로세스의 cred 구조체는 실제 ID(Real UID/GID), 권한 검사에 사용되는 유효 ID(Effective UID/GID), 그리고 권한 상승 후 복귀를 위한 저장된 ID(Saved Set-UID/GID) 등 여러 종류의 ID를 유지한다.
Windows에서 프로세스의 보안 컨텍스트는 접근 토큰(Access Token) 이라는 단일 커널 객체에 의해 정의된다. 스레드가 보안 가능한 객체에 접근을 시도할 때, 커널의 보안 참조 모니터(SRM)는 스레드의 접근 토큰 정보와 객체의 보안 서술자를 비교하여 접근 허용 여부를 결정한다. 접근 토큰은 사용자 SID, 그룹 SID 목록, 특권(Privileges), 무결성 수준(Integrity Level) 등 복합적인 정보를 포함하며, 객체의 DACL(임의 접근 제어 목록)과 비교된다.
Windows의 접근 토큰 모델은 본질적으로 더 세분화된 제어를 제공한다. 반면 Linux의 기본 DAC 모델은 더 정교한 제어를 위해 SELinux나 POSIX Capabilities와 같은 추가 보안 모듈을 필요로 한다.
이러한 특성은 고유한 공격 표면을 만들어낸다. Linux 환경 공격자는 종종 setuid 바이너리를 악용해 UID 0을 획득하려 하지만, Windows 환경에서는 더 높은 권한 프로세스의 접근 토큰을 훔치는 ‘토큰 훔치기(Token Stealing)’ 라는 고유한 공격 기법이 가능하다.
운영체제는 프로세스를 격리하고 침해 영향을 최소화하기 위해 고급 보안 프레임워크를 구축한다. Linux는 네임스페이스, Capabilities, LSM을 통해 강력한 샌드박싱 환경을 제공하는 데 중점을 둔다. 반면, Windows는 강제 무결성 제어(MIC) 와 애플리케이션 제어 정책을 통해 신뢰도 기반의 접근 제어와 화이트리스팅을 구현한다.
운영체제의 아키텍처는 필연적으로 고유한 취약점과 공격 벡터를 만들어낸다. 이에 대응하여, 운영체제는 메모리 보호, 제어 흐름 무결성 등 시스템 전반에 걸친 방어 메커니즘을 구축해왔다.
CAP_SYS_ADMIN과 같이 과도한 기능이 부여된 컨테이너는 호스트의 파일 시스템을 마운트하여 컨테이너를 탈출할 수 있다.SeDebugPrivilege 특권을 가진 프로세스는 SYSTEM과 같이 더 높은 권한을 가진 프로세스의 접근 토큰을 복제할 수 있다. 공격자는 이 복제된 토큰을 사용하여 SYSTEM 권한으로 실행되는 새로운 프로세스를 생성함으로써 권한을 효과적으로 상승시킬 수 있다.LD_PRELOAD 환경 변수는 특정 공유 라이브러리를 다른 모든 라이브러리보다 먼저 로드하도록 강제한다. 공격자는 표준 함수를 덮어쓰는 악성 라이브러리를 제작하고, 이 환경 변수를 통해 합법적인 프로그램이 악성 코드를 자신의 주소 공간 내에서 실행하게 만들 수 있다.CreateRemoteThread를 이용한 클래식 DLL 주입, 합법적인 프로세스의 메모리를 비우고 악성 코드를 채우는 프로세스 할로윙(Process Hollowing), 그리고 실행 중인 스레드의 명령어 포인터를 조작하는 스레드 실행 하이재킹 등 다양한 코드 주입 기법이 존재한다.컨테이너 탈출은 컨테이너 내부의 프로세스가 격리 경계를 넘어 호스트 시스템에 접근하는 공격이다. 컨테이너는 호스트 커널을 공유하기 때문에, 컨테이너 내부에서 실행된 커널 익스플로잇은 호스트 전체를 장악할 수 있다. 과도한 Capabilities 부여나 Docker 소켓과 같은 민감한 리소스를 컨테이너 내부에 마운트하는 것도 주요 탈출 경로가 된다.