본문 바로가기

2025 - 1학기/플랫폼OS

OS 메세지

728x90
728x90
Reporting Date: April. 04, 2025

OS 메세지에 대해 다루고자 한다.


목차

01 스레드
02 프로세스 스케줄
03 스케줄러
04 문맥교환
05 모바일 시스템의 멀티태스킹
06 프로세스 주요 작업
07 프로세스 생성의 4단계
08 예제
09 프로세스 종료
10 Android 프로세스
11 Chrome 브라우저


 

 

 

01 스레드

Threads

지금까지는 프로세스가 하나의
실행 스레드만을 가지고 있다고 가정해왔다.

하지만 이제는 하나의 프로세스가 여러 개의
프로그램 카운터(PC)를 갖는 경우를 생각해볼 수 있다.

이는 하나의 프로세스가 여러 위치에서
동시에 실행될 수 있음을 의미하며,

스레드는 이러한 여러 제어 흐름을 의미한다.

따라서 이를 위해서는 각 스레드의
세부 정보를 저장할 수 있는 별도의 저장소가 필요하며,

프로세스 제어 블록(PCB)에는
여러 개의 프로그램 카운터가 포함되어야 한다.

 

 

리눅스 OS에서 프로세스 표현법

리눅스 커널 내부에서는 각 프로세스를
task_struct라는 구조체(struct)로 표현한다.

이 구조체는 프로세스와 관련된 모든 정보를 담고 있다.

task_struct 구조체의 주요 구성 요소 설명

struct task_struct {
    pid_t pid;                          // 프로세스 ID (고유 식별자)
    long state;                         // 현재 프로세스 상태 (실행 중, 대기 중, 종료 등)
    unsigned int time_slice;           // CPU 시간을 얼마나 할당받았는지 (스케줄링 정보)
    struct task_struct *parent;        // 부모 프로세스를 가리키는 포인터
    struct list_head children;         // 자식 프로세스들을 연결한 리스트
    struct files_struct *files;        // 열려 있는 파일 목록
    struct mm_struct *mm;              // 메모리 주소 공간 정보
};

 

만약 어떤 프로세스 A가 실행 중이라면,
커널은 이 프로세스를 task_struct 구조체로 관리하고 있으며,

거기엔 APID, 상태, 부모와 자식 정보,
열려 있는 파일, 메모리 정보 등이 다 들어 있다.

그래서 커널은 task_struct를 통해 이 프로세스가
누구고, 지금 뭐하고 있고, 어떤 자원을 쓰고 있는지를 다 알 수 있다.

 


 

 

개념 Windows Linux (또는 Unix)
프로세스 제어 정보 저장 테스크(Task) 구조체 PCB (Process Control Block)
주요 역할 프로세스의 상태, 레지스터 값, 메모리 정보 등을 저장 동일
관리 단위 Windows에서는 "Task"라는 용어를 사용 Linux에서는 "PCB"라는 용어 사용

 

02 프로세스 스케줄링

Process Scheduling

다중 프로그래밍의 주요 목적은
CPU의 이용률을 최대화하는 것이다.

시스템이 가능한 많은 작업을 동시에 메모리에 적재하고,
CPU가 한 작업을 기다리는 동안 다른 작업을
수행할 수 있도록 하여 전체 성능을 높인다.

이러한 다중 프로그래밍 환경에서는 여러 개의 실행 가능한 프로세스들 중에서
어떤 프로세스를 CPU에 할당할지를 결정해야 하는데,
이 역할을 수행하는 것이 바로 프로세스 스케줄러이다.

스케줄러는 다양한 스케줄링 큐(Scheduling Queue)를 관리합니다.

대표적으로는 다음과 같은 큐가 있다:

 

준비 큐(Ready Queue):

실행 준비가 완료된 프로세스들의 집합으로,
메인 메모리에 존재하며 일반적으로 연결 리스트 형태로 저장된다.
CPU가 사용할 수 있게 되면 여기서 하나의 프로세스가 선택되어 실행된다.

 

대기 큐(Wait Queues):

입출력 작업과 같은 특정 이벤트를
기다리는 프로세스들의 집합이다.

각 장치나 이벤트 종류마다 별도의 대기 큐가 존재하며,
해당 이벤트가 발생하면 프로세스는 다시 준비 큐로 이동한다.

 

실제로 하나의 프로세스는
전체 수명 주기 동안 여러 스케줄링 큐를 오가며 상태가 바뀐다.

예를 들어, 준비 큐에서 CPU를 배정받아 실행되다가

입출력 요청이 발생하면 대기 큐로 이동하고,
입출력이 완료되면 다시 준비 큐로 돌아오는 식이다.


OS에서 프로세스는 다양한 상태를 가지며,
이 상태에 따라 여러 종류의 큐에서 대기하게 된다.

예를 들어, 레디 큐(Ready Queue)는
실행 가능한 상태의 프로세스들이 들어가는 큐이다.

반면, 입출력 장치를 기다리는 프로세스들은
장치별 대기 큐(I/O Device Queues)에 들어간다.

하나의 시스템에는 여러 개의 레디 큐가 존재할 수 있으며,
각 장치마다 별도의 대기 큐가 구성된다.

예를 들어, 자기 테이프, 디스크, 터미널 등 총 4가지 장치가 있다면,
각 장치마다 하나씩의 큐가 할당되어 운영된다.

이러한 프로세스의 흐름은 프로세스 스케줄링 과정으로 더 잘 이해할 수 있다.

하드디스크에 저장된 프로그램이 메인 메모리로
적재 시, 그중 일부는 레디 큐에 올라가게 된다.

레디 큐에 있는 프로세스들 중 하나가
CPU 스케줄러에 의해 선택되면 실행 상태(Running)가 된다.

실행 중에는 다양한 사건이 발생할 수 있다.

예를 들어, 첫째로 입출력 요청이 발생하면
해당 프로세스는 해당 장치의 대기 큐로 이동하게 되고,
디스크 스케줄링 등의 과정을 통해 장치 자원을 할당받게 된다.

둘째로 타임 슬라이스(time slice)가 만료되면,
프로세스는 다시 레디 큐로 돌아가고 다른 프로세스에게 CPU가 할당된다.

셋째로 자식 프로세스가 생성되면,
해당 자식 프로세스가 부모보다 먼저 실행될 수도 있으며,
이는 프로세스 생성(create) 및 자식 종료(child termination) 등의 이벤트로 이어진다.

넷째로 인터럽트(interrupt)가 발생하면,
현재 실행 중이던 프로세스는 인터럽트 대기 큐로
이동하며, 다른 프로세스가 CPU를 사용하게 된다.

결과적으로 하나의 프로세스는
레디 상태, 실행 상태, 대기 상태 등을 반복하면서
CPU와 입출력 장치를 오가며 작업을 처리하게 된다.

이 모든 흐름은 큐 기반의 스케줄링 체계를 통해 체계적으로 관리된다.


 

03 스캐줄러

Schedulers

OS는 프로세스를 효과적으로 관리하고 자원을
효율적으로 분배하기 위해 세 가지 종류의 스케줄러를 사용한다.

 

 1 .  장기 스케줄러

Long-term Scheduler, Job Scheduler

디스크의 풀(Pool)에 대기 중인 작업들 중 일부를 선택하여
메인 메모리의 레디 큐(Ready Queue)에 적재하는 역할을 한다.

즉, 실행 준비가 된 프로세스를 메모리에 올리는 작업으로,
이는 메모리 자원을 할당해주는 과정(Giving Memory)이다.

실행 빈도가 낮아 상대적으로 수 초 또는 수 분에 한 번 정도 작동하며,
이는 프로세스 선택에 있어 더 많은 시간을 들일 수 있는 이유가 된다.

하드디스크에 대기 중인 작업들 중 어떤 것을 메인 메모리에
올릴지 결정
함으로써 동시에 실행 가능한 프로세스의 수,

즉 다중 프로그래밍의 정도를 조절한다.

 

시스템 내 두 가지 프로세스의 적절한 혼합을 유지하는 것이 중요하다.

입출력 중심 프로세스(I/O-bound Process) 대부분의 시간을 파일 읽기/쓰기, 네트워크 통신 등 
입출력 작업에 소비, 상대적으로 적은 연산량.
CPU 중심 프로세스(CPU-bound Process) 복잡한 계산 수행, 대부분의 시간을 
CPU 연산에 사용하며, 입출력 요청은 드물게 발생한다.

예를 들어 CPU 중심 프로세스만 지나치게 많을 경우, 
CPU 과부하로 인해 시스템 성능이 급격히 저하될 수 있다.

이 균형을 유지함으로써 시스템 자원의
효율적인 사용을 돕는 중요한 역할을 수행한다.

 

 

 2 .  단기 스케줄러

Short-term Scheduler, CPU Scheduler

다음으로, 메인 메모리에 올라온 프로세스 중 
레디 큐에 있는 프로세스들을 선택해 CPU를 할당한다.

매우 짧은 간격(수 밀리초 단위)으로 실행되며,
실행 준비가 완료된 프로세스들 사이에서 빠르게
결정을 내려야 하므로 시스템 성능에 중요한 영향을 미친다.

프로세스 실행 중 발생하는 다양한 사건들은
입출력 요청, 타임 슬라이스 만료 등이 있다.

이 과정은 CPU 자원을 분배하는 것
(Giving CPU)이라고 할 수 있다.

 

 

 3 .  중기 스케줄러

Medium-term Scheduler

일부 OS, 특히 시분할 시스템에서는
중간 수준의 스케줄링이 추가로 사용된다.

시스템에서 실행 중인 프로세스가 너무 많아 메모리가 부족하면,
일부 프로세스를 하드디스크로 내보내는 작업을 수행한다.

이를 스왑 아웃(swap out)이라고 하며, 실행을 일시적으로
중단한 프로세스를 메모리에서 제거해 여유 공간을 확보한다.

이후 다시 실행할 필요가 있을 때는 그 프로세스를 
다시 메모리로 불러들이는 스왑 인(swap in)이 발생한다.

이러한 과정을 스와핑(swapping)이라 한다.

메모리에서 경쟁 중인 프로세스의 수를 일시적으로 조절하여
다중 프로그래밍의 정도를 줄이고, 자원을 보다 효율적으로 관리한다.

이는 CPU에 배정되지 않고 대기만 하는
프로세스가 과도하게 많아지는 문제
를 완화해준다.

단기 스케줄러가 자주 실행되는 반면, 중기 스케줄러는
시스템의 메모리 상황을 고려하여 필요할 때만 작동한다.

따라서 From Memory (여유공간 만들기) To Disk
중기 스케줄링을 통한 스왑 아웃을 의미하며,

이는 실제 OS 설계에서 중요한 메모리 관리 기법 중 하나이다.


 

04 문맥 교환

Context Switch

CPU가 현재 실행 중인 프로세스에서
다른 프로세스로 전환할 때 발생하는 작업이다.

이 과정에서 OS는 먼저
기존 프로세스(Old Process)의 상태를
PCB
(Process Control Block)에 저장하고,

새로운 프로세스(New Process)
PCB에 저장된 상태를 다시 CPU로 적재
한다.

여기서 말하는 '문맥(Context)'이란,
CPU 레지스터 값, 프로세스 상태, 메모리 관리 정보 등을
포함하는 프로세스가 실행되기 위해 필요한 모든 정보를 뜻하며,

이는 PCB 안에 저장된다.

따라서 CPU는 문맥 교환을 통해 이전 프로세스를
정확히 중단된 지점부터 다시 실행할 수 있게 된다.

 

문맥 교환에는 시간이 소요되며, 이 시간 동안 시스템은 실제로
유용한 작업을 수행하지 않으므로
 순수한 오버헤드로 간주된다.

문맥 교환 속도는 여러 요소에 영향을 받는다.

예를 들어, 메모리 접근 속도, 저장 및 복원해야 할 레지스터 수,
레지스터 전체를 한 번에 저장하거나 불러올 수 있는 특수 명령어의 유무
등이 이에 해당한다.

실제 시스템에서 문맥 교환 속도는
1마이크로초(us)에서 1,000마이크로초(us)까지 다양하게 나타난다.
또한, 문맥 교환의 효율은 하드웨어 지원 여부에 따라 크게 좌우된다.

예를 들어, 어떤 CPU 아키텍처는 문맥 교환을 빠르게
수행할 수 있도록 특수한 명령어나 하드웨어 구조를 제공하기도 한다.

예를 들어, 프로세스 0번이 실행되다가 인터럽트가 발생하면,
OS는 해당 지점까지의 상태를 PCB에 저장하고 다른 프로세스(예: 1번)를 실행시킨다.

이후 다시 프로세스 0번을 실행할 경우, PCB에서 상태를 불러와
101번째 명령어부터 재실행할 수 있도록 도와주는 것이 문맥 교환의 핵심 역할이다.


 

05 모바일 시스템의 멀티태스킹

Multitasking in Mobile Systems

모바일 기기는 자원(메모리, 배터리 등)이 제한적이므로,
초기 iOS 버전에서는 사용자 애플리케이션의 멀티태스킹을 지원하지 않았다.

사용자는 한 번에 오직 하나의 응용 프로그램만 Foreground에서
실행
할 수 있었으며,
다른 응용은 모두 실행이 일시 정지(보류) 되었다.

하지만 iOS 4부터는 제한적이지만 멀티태스킹을 지원하게 되었다.
이때부터 일부 응용 프로그램은 백그라운드에서도 실행될 수 있게 되었으며,

이를 통해 사용자 경험이 향상되었다.

Foreground 응용 현재 화면에 보이며 사용 중인 앱.
Background 응용 화면에는 보이지 않지만 메모리에 상주 및 제한된 기능만 수행하는 앱.

다만, iOS에서는 모든 앱이 백그라운드에서 자유롭게 실행될 수 있는 것은 아니며,
제한된 형태의 백그라운드 API를 통해 특정 상황에서만 백그라운드 실행이 허용된다.

예를 들어, 음악 재생, 위치 추적, 다운로드, 푸시 알림 수신
같은 작업은 제한된 시간 동안 백그라운드에서 실행될 수 있다.

반면, Android는 멀티태스킹에 더 유연한 구조를 가진다.

백그라운드에서 실행되는 앱의 유형에 큰 제한이 없으며,
필요 시 서비스(Service)라는 특별한 구성 요소를 통해
지속적으로 백그라운드 작업을 수행할 수 있다.

서비스는 사용자 인터페이스 없이 동작하며,
음악 재생, 네트워크 통신, 데이터 동기화 등 다양한 작업에 활용된다.


 

06 프로세스의 주요 작업

Operations on Processes

OS에서 실행되는 대부분의 프로세스는 병렬적으로 실행될 수 있으며,
이들은 필요에 따라 동적으로 생성되거나 제거되어야 합니다.

따라서 OS는 다음과 같은 기본 기능을 제공해야 다:

프로세스 생성(Process Creation) 새 작업 요청 처리를 위해 새 프로세스를 메모리에 할당 및 실행 가능한 상태로 만드는 작업.
프로세스 종료(Process Termination): 실행 완료, 강제 중지된 프로세스를 시스템에서 제거 및 자원 회수하는 작업.

 

프로세스 생성

OS 내부에서는 다양한 방식으로 프로세스가 생성된다.

시스템이 부팅되면, 가장 먼저 PID 1
가진 데몬 프로세스(systemd 또는 init)가 생성된다.

이 프로세스는 시스템에서 가장 먼저 실행되는 사용자 공간 프로세스로,
이후 모든 다른 프로세스의 조상이 된다.

일반적으로 프로세스는 fork() 시스템 호출을 통해 생성된다.

부모 프로세스(Parent Process) 프로세스를 생성하는 주체
자식 프로세스(Child Process) 새로 생성된 프로세스

각 자식 프로세스는 또다시 자신만의 자식 프로세스를 생성할 수 있으므로,
전체 시스템은 트리 구조의 프로세스 계층, 즉 프로세스 트리를 형성하게 된다.

프로세스가 생성되어 실행되기 위해서는 다음과 같은 OS 자원이 필요하다:

  • CPU 시간
  • 메모리 공간
  • 파일 디스크립터
  • 입출력 장치 등

이러한 자원은 두 가지 방식으로 자식 프로세스에게 전달될 수 있다:

  1. 부모 프로세스의 자원을 일부 공유 혹은 분할해서 사용
  2. OS로부터 자식 프로세스가 독립적으로 자원을 요청하여 할당받는 방식

이러한 프로세스 생성 방식은
다중 프로세싱 환경에서 시스템 자원을 효율적으로 관리하고,
부모-자식 간 관계를 유지하며 프로세스를 제어하는 데 중요한 역할을 한다.

 


 

리눅스 시스템에서 프로세스 트리
각 프로세스가 어떤 부모 프로세스로부터 생성되었는지를 계층 구조로 나타낸 것입니다.

예를 들어, systemd는 부팅 시
가장 먼저 실행되는 최초의 사용자 공간 프로세스로서,
이후에 실행되는 모든 프로세스의 조상 역할을 한다.

sshd, bash, python, vim, ps 등 다양한 프로세스들이
이러한 계층 구조 속에서 부모-자식 관계를 형성하며 실행된다.

각 프로세스는 정수형 고유 번호인 PID(Process ID)를 가지며,
이 번호를 통해 시스템은 각 프로세스를 구분하고 관리할 수 있다.

이러한 트리는 마치 시스템 안의 프로세스들이
자신들이 사는 모습을 그려놓은 구조도와도 같으며,

실제 OS 내부에서 프로세스가 어떻게 생성되고
연결되어 있는지를 시각적으로 잘 보여준다.

또한, 이 구조는 시스템이 부팅될 때부터 시작된다.

전원이 켜지면 BIOS 또는 UEFI가 하드웨어 초기화를 수행하고,
하드디스크를 검사한 뒤 부트로더를 통해 OS를 메모리에 적재한다.

이후 커널이 실행되며 systemd가 초기화되고,
그 하위에서 다양한 사용자 프로세스들이 실행되면서 전체 시스템 환경이 구축된다.

 


 

실행 방식과 주소 공간

프로세스가 새로 생성될 경우,
OS는 해당 프로세스에 물리적·논리적 자원을 할당한다.

이와 함께, 부모 프로세스의 변수 값, 파일 핸들 등의
초기화 정보가 자식 프로세스에 전달될 수 있다.

이 정보는 프로세스의 문맥(Context) 또는 환경 정보를
통해 전달되며, OS에 따라 공유 방식 또는 복사 방식으로 구현된다.

 

부모와 자식의 실행 흐름

새로운 프로세스를 생성한 후,
부모 프로세스와 자식 프로세스는 다음 두 가지 방식으로 실행될 수 있다:

병기 실행 부모와 자식이 동시에 실행되며, 서로 독립적으로 동작한다.
동기 실행 부모 프로세스는 자식 프로세스의 실행 완료를 기다리는(wait) 방식으로 동작한다.

예: 웹 브라우저(부모)가 인쇄 작업(자식 프로세스)을
시작하고, 인쇄가 끝날 때까지 기다리는 경우.

 

자식 프로세스의 주소 공간

자식 프로세스는 일반적으로
부모 프로세스의 주소 공간을 복사하여 생성된다.

이 복사본은 자식에게 동일한 프로그램 코드, 데이터, 열린 파일 등을 제공한다.
다만, 그 이후 자식 프로세스가 다른 프로그램을 실행하도록 구성될 수도 있다.

이 경우 자식 프로세스는 기존 복사된 메모리 공간을 새 프로그램으로
덮어쓰게 되며
, 이 작업은 보통 exec() 시스템 호출을 통해 수행된다.

이 과정을 "오버라이트(overwrite)" 또는 "적재(load)"라고 하며,
결과적으로 자식은 부모와는 전혀 다른 프로그램을 수행하게 된다.


 

07 프로세스 생성의 4단계

단계 설명
1. PCB 생성 커널은 새 프로세스에 대한 정보를 저장하기 위해 PCB 자료구조 생성
2. 메인 메모리 공간 할당 실행에 필요한 코드, 데이터, 스택 등을 저장할 수 있도록 메모리 공간 할당
3. 이진 프로그램 적재 실행할 프로그램의 이진 파일이 메모리에 적재
4. 프로그램 초기화 첫 번째 명령어를 가리키는 명령 포인터(PC)가 설정되어, 프로그램이 실행 준비 상태로 진입

 

Unix 계열에서의 프로세스 생성 절차

fork() 시스템 호출을 통해 새로운 프로세스를 생성한다.

이 과정은 다음과 같이 진행된다.

fork()가 호출되면, OS는 부모 프로세스의 PCB
주소 공간을 복사하여 자식 프로세스를 생성한다.

이 단계에서 1단계 PCB 생성과
2단계 메모리 공간 할당이 수행된다.

단, fork() 직후 자식 프로세스는 부모의 복사본일 뿐,
새로운 프로그램은 아직 실행되지 않는다.

 

exec() 호출을 통한 프로그램 적재 및 실행

fork() 이후, 부모와 자식은 동시에 실행될 수 있다.

이때 일반적으로 자식 프로세스는 exec() 시스템 호출을 사용하여
자신에게 적합한 새로운 프로그램을 메모리에 적재
하고 실행한다.

exec()는 기존 프로세스의 메모리 공간을 지우고,
새 이진 프로그램을 적재한 후 3단계 프로그램 적재와
4단계 프로그램 초기화를 수행한다.

이렇게 하면 자식 프로세스는
부모와는 전혀 다른 프로그램을 실행하게 된다.

 

프로세스 생성의 반환값 및 흐름

fork() 반환값에 따른 프로세스 흐름

반환값 (fork()의 결과) 프로세스 종류 설명
0 자식 프로세스 자신이 자식 프로세스임을 인식함. 이후 exec()를 통해 다른 프로그램 실행 가능.
양수 (자식의 PID) 부모 프로세스 자식 프로세스가 성공적으로 생성됨을 인식하고, wait()로 자식 종료를 기다릴 수 있음.
음수 (-1) 오류 (실패) 메모리 부족, 프로세스 수 제한 등의 이유로 프로세스 생성 실패.

 

전형적인 프로세스 생성 흐름

단계 부모 프로세스 자식 프로세스
1 fork() 호출 fork() 호출
2 자식 PID를 반환받음 0을 반환받아 자식임을 인식
3 wait() 호출하여 자식 대기 exec() 호출하여 새 프로그램 실행
4 자식 종료 시 exit()로 종료 부모는 자식 종료 후 깨어나 다음 작업 수행

 

08 예제

C 프로그램은 fork()와 execlp()
시스템 콜을 이용해 자식 프로세스를 생성하고,

그 자식 프로세스에게 새 프로그램인
ls 명령어를 실행시키는 대표적인 예제이다.

 

pid = fork();
  • fork()는 현재 실행 중인 프로세스를 복사하여 새 자식 프로세스를 만든다.
  • 이 호출은 두 번 반환된다.
    • 부모 프로세스에게는 자식의 pid(양수)를 반환.
    • 자식 프로세스에게는 0을 반환.
    • 실패하면 -1을 반환.

 

자식 프로세스 (pid == 0)

execlp("/bin/ls", "ls", NULL);
  • execlp()는 자식 프로세스의 메모리를 완전히 바꾸고 ls 명령어를 실행한다.
  • 즉, 자식은 더 이상 기존 코드가 아닌 터미널에서 파일 목록을 보여주는 프로그램으로 동작하게 된다.
  • 이 줄 이후의 코드는 실행되지 않는다. (execlp()는 성공 시 돌아오지 않음)

 

부모 프로세스 (pid > 0)

wait(NULL);
printf("Child Complete");
  • 부모는 wait(NULL);을 통해 자식 프로세스가 끝날 때까지 기다린다.
  • 자식이 종료되면 "Child Complete" 메시지를 출력하고 부모도 종료한다.

 

부모 프로세스가 wait()을 호출하여
자식이 정상적으로 종료되는지 지켜본다.

OS에서는 이렇게 부모가 자식 프로세스의 종료 상태를 수집하는 것이 일반적이다.
그렇지 않으면 좀비 프로세스(Zombie Process)가 생길 수 있다.

 


 

 

자식 프로세스 생성 및 자식에게 ls 명령어를 실행시키는 예제

#include <sys/types.h>
#include <sys/wait.h>    // wait() 함수에 필요
#include <stdio.h>
#include <stdlib.h>      // exit() 함수에 필요
#include <unistd.h>

int main() {
    pid_t pid;

    /* 부모 프로세스가 자식 프로세스 생성 */
    pid = fork(); 

    if (pid < 0) {  // 오류 발생
        fprintf(stderr, "Fork Failed");
        exit(-1);
    } else if (pid == 0) {  // 자식 프로세스만 실행
        execlp("/bin/ls", "ls", NULL); // ls 명령어 실행
    } else {  // 부모 프로세스
        /* 부모는 자식이 종료할 때까지 기다림 */
        wait(NULL);
        printf("Child Complete\n"); // 자식이 끝난 후 부모가 출력
        exit(0);
    }
}

 


 

 

Windows OS에서 새 프로세스 생성하고
부모 프로세스가 자식 프로세스가 끝날 때까지 기다리는 예제이다.

#include <stdio.h>
#include <windows.h>

int main(VOID) {
    STARTUPINFO si; // 자식 프로세스의 초기 화면 정보
    PROCESS_INFORMATION pi; // 자식 프로세스의 핸들과 ID 정보

    // 구조체 내용 0으로 초기화
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // 자식 프로세스 생성 (예: mspaint.exe 실행)
    if (!CreateProcess(
        NULL,                            // 애플리케이션 이름 (명시하지 않음)
        "C:\\WINDOWS\\system32\\mspaint.exe", // 명령줄
        NULL,                            // 프로세스 보안 속성
        NULL,                            // 스레드 보안 속성
        FALSE,                           // 핸들 상속 여부
        0,                               // 생성 플래그 없음
        NULL,                            // 부모 환경 사용
        NULL,                            // 부모 디렉토리 사용
        &si,                             // STARTUPINFO 구조체
        &pi)) {                          // PROCESS_INFORMATION 구조체

        fprintf(stderr, "CreateProcess failed.\n");
        return -1;
    }

    // 자식 프로세스가 끝날 때까지 기다림
    WaitForSingleObject(pi.hProcess, INFINITE);

    printf("Child Complete\n");

    // 열린 핸들 닫기: 자원 정리
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}

 

Windows에서 프로세스를 동적으로 실행하고 싶을 때 사용한다.

여기서는 mspaint.exe를 실행하는 예제이지만,
다른 실행 파일도 경로만 바꾸면 실행 가능하다.

 


 

 

fork() vs CreateProcess()

항목  fork() (Linux/Unix) CreateProcess() (Windows)
역할 현재 프로세스를 복제 새 프로세스 생성
반환값 부모는 자식 PID, 자식은 0 BOOL (성공 여부 반환)
자식 프로세스 부모의 복사본 새롭게 실행할 프로그램 지정
실행 프로그램 기본적으로 부모 코드와 같음 (단, exec()로 다른 프로그램 실행 가능) 보통 다른 실행 파일을 명시적으로 지정
복잡도 상대적으로 간단 인자, 구조체 초기화 등 복잡함
예시 pid = fork(); CreateProcess("notepad.exe", ...)

 

fork()는 현재 프로세스 자체를 복제해서 "자식"을 만든다.
이후 자식은 exec()를 써서 다른 프로그램으로 전환할 수 있다.

CreateProcess()는 처음부터
어떤 프로그램을 실행할지 명시해서 자식 프로세스를 만든다.
즉, 자식은 부모를 복제하지 않고, 새로운 실행 파일을 실행한다.

fork()는 부모를 복사해서 쌍둥이를 만든 뒤,
그 쌍둥이에게 다른 옷(exec())을 입히는 방식

CreateProcess()는 새로 태어난 사람이
처음부터 원하는 옷을 입고 등장하는 방식


 

09 프로세스 종료

Process Termination

프로세스는 마지막 문장의 실행을 마치고 exit()
시스템 호출을 통해 OS에게 자신의 종료를 요청함으로써 종료된다.

이 호출은 프로세스가 사용하던 자원을 반환하고,
OS는 해당 프로세스를 메모리에서 제거한다.

또한, 한 프로세스가 abort()와 같은 시스템 호출을 이용해
다른 프로세스의 종료를 강제로 유발하는 경우도 있다.

이는 예기치 않은 오류나 제약 조건 위반 등의 상황에서 사용된다.

자식 프로세스가 다음과 같은 경우 종료될 수 있다:

  • 자신에게 할당된 자원을 초과해서 사용하는 경우 (예: 메모리 초과)
  • 자식에게 부여된 태스크가 더 이상 필요 없는 경우
  • 부모 프로세스가 종료되었는데, OS가 자식 프로세스의 독립 실행을 허용하지 않는 경우

많은 OS에서는 부모 프로세스가 종료될 경우,
그에 종속된 자식 프로세스들도 함께 종료되도록 설계되어 있다.

이러한 현상을 연쇄 종료(Cascading Termination)라고 하며,
OS가 자동으로 수행한다.

이와 같은 이유로, OS는 자식 프로세스의 상태를 주기적으로 확인하고,
필요 시 강제 종료하는 메커니즘을 갖추고 있어야 한다.

 


프로세스의 종료 관리

부모 프로세스는 wait() 시스템 호출을 통해
자식 프로세스가 종료될 때까지 기다릴 수 있다.

이 호출은 다음과 같이 사용되며:

pid = wait(&status);

이처럼 wait()는 자식 프로세스의
종료 상태를 얻기 위해 하나의 인자를 전달받는다.

자식 프로세스가 정상적으로 종료되면,
OS는 그 프로세스가 사용하던 자원을 회수한다.

그러나 자식 프로세스가 종료되었음에도
부모 프로세스가 아직 wait()를 호출하지 않은 경우,

해당 자식 프로세스는 메모리상에
종료 상태 정보만 남긴 채 실제로 제거되지 않으며,
이런 상태의 프로세스를 좀비(Zombie) 프로세스라고 한다.

이는 자원 회수가 이루어지지 않은 상태이므로,
       시스템에 과부하를 줄 수 있다.

 

부모 프로세스가 wait() 미 호출 및 먼저 종료할 경우,
아직 실행 중이던 자식 프로세스는 부모와의 연결이 끊긴 상태가 되며,
부모 없이 남겨진 자식 프로세스를 고아(Orphan) 프로세스라고 한다.

OS는 이를 방치하지 않고, 일반적으로 init 프로세스(프로세스 ID 1번)가
대신 부모 역할을 맡아 자식의 종료 상태를 수습하고 자원을 회수하게 한다.

즉, 부모 없이 방치된 자식 프로세스를 시스템이 대신 관리하여 안정성을 유지한다.


 

10 Android 프로세스

메모리와 같은 시스템 자원이 제한되어 있으므로,
필요 시 프로세스를 종료하여 자원을 회수해야 하는 상황이 자주 발생한다.

이를 효율적으로 관리하기 위해 Android
프로세스의 중요도를 기준으로 우선순위를 부여하고,
중요도가 낮은 순서대로 프로세스를 종료한다.

 

 1 .  포그라운드 프로세스

Foreground Process

현재 사용자와 직접 상호작용하고 있는 프로세스로,
화면에 표시된 Activity나 활성화된 서비스 등이 해당된다.

가장 높은 우선순위를 가지며, 가능한 한 종료되지 않는다.

 

 2 .  가시적 프로세스

Visible Process

직접 상호작용하진 않지만
사용자에게 여전히 화면상으로 표시된다.

예를 들어, 포그라운드 Activity의 일부를
구성하는 다른 Activity가 여기에 해당된다.

 

 3 .  서비스 프로세스

Service Process

백그라운드에서 사용자와 직접 상호작용하지는 않지만,
사용자가 요청한 작업을 수행 중인 서비스이다.

예: 음악 재생, 데이터 동기화 등

 

 4 .  백그라운드 프로세스

Background Process

사용자에게 직접 보이지 않으며,
일시적으로 중단된 Activity 등이 이에 포함된다.

메모리가 부족할 경우 우선적으로 종료된다.

 

 5 .  빈 프로세스

Empty Process

시스템에 캐시된 상태로,
빠른 재실행을 위해 보존된다.

가장 낮은 우선순위를 가지며,
자원이 부족할 때 가장 먼저 종료된다.

 

Android는 메모리가 부족해지면, 위 계층에서 아래로 내려가며
중요도가 가장 낮은 프로세스부터 순차적으로 종료하여 자원을 절약한다.

이러한 구조는 사용자 경험을 유지하면서도
시스템 효율성을 극대화하기 위한 전략이다.


 

11 Chrome 브라우저

현대 웹 브라우저는 탭 기반 브라우징을 지원하여,
하나의 브라우저 인스턴스 내에서 여러 개의 탭을
동시에 열어 다양한 웹사이트를 탐색할 수 있도록 한다.

그러나 기존의 단일 프로세스 구조에서는 한 탭에서
실행 중인 웹 애플리케이션이 오류를 일으키거나 멈출 경우,
브라우저 전체가 다운되거나 멈추는 문제가 발생할 수 있었다.

예전의 인터넷 익스플로러(IE)에서는 이런 현상이 자주 발생하며,
검색 중 하나의 탭이 먹통이 되면 브라우저 전체가 꺼지기도 했다.

이러한 문제를 해결하기 위해
Google Chrome 웹 브라우저는 ‘다중 프로세스 구조’를 도입했다.

Chrome은 브라우저를 다음과 같이
세 가지 주요 프로세스 유형으로 분리하여 동작한다.

 

 1 .  Browser 프로세스

브라우저의 사용자 인터페이스(UI)를 관리하고,
디스크 접근이나 네트워크 통신 등의 입출력 작업을 담당한다.

사용자가 탭을 열거나 닫는 작업,
주소창, 북마크, 설정 등도 이 프로세스가 담당한다.

 

 2 .  Renderer 프로세스

실제 웹 페이지를 그리는 역할을 한다.
HTML, CSS, JavaScript, 이미지 등 웹 콘텐츠를 해석하고 렌더링한다.

각 탭마다 별도의 렌더러 프로세스가 실행되므로,
한 탭에서 문제가 생겨도 다른 탭에 영향을 주지 않는다.

또한 보안을 위해 각 렌더러 프로세스는
샌드박스(Sandbox) 환경에서 실행되어
외부 시스템 자원에 대한 접근이 제한된다.

 

 3 .  Plug-in 프로세스

사용자가 활성화한 Flash, QuickTime 등의
플러그인이 별도의 프로세스로 실행된다.

플러그인 하나가 충돌하더라도
전체 브라우저가 중단되지 않도록 설계되어 있다.

 

이와 같은 Chrome의 다중 프로세스 아키텍처는
안정성과 보안성을 크게 향상시키며,

한 프로세스에 문제가 생겨도 전체 브라우저가
영향을 받지 않도록 설계
된 대표적인 사례이다.


[교제] 운영체제 제 10


728x90
반응형

'2025 - 1학기 > 플랫폼OS' 카테고리의 다른 글

분산 시스템 간의 통신  (2) 2025.04.19
OS 협력  (2) 2025.04.18
OS 구현  (0) 2025.04.14
OS 설계 구조  (8) 2025.04.14
OS 명령어 처리 과정  (4) 2025.04.13