CS/CA, OS

컴퓨터의 프로세스

Basaeng 2025. 4. 5. 16:18

개요

보통 실행되고 있는 프로그램을 프로세스라고 부른다.

프로세스는 메모리 영역으로 스택, 데이터 영역, 힙 영역 + 코드 영역을 가진다.

 

 

Process(Task)의 State 

  • new(created): 생성 중인 프로세스
  • running: 실행 중인 프로세스
  • waiting(blocked): io, event로 인해 대기 중인 프로세스
  • ready: 자원 할당을 기다리는 프로세스
  • terminated: 실행 종료된 프로세스

 

PCB (Process Control Block)

PCB의 프로세스 정보를 통해서 OS가 프로세스를 관리한다.

아래와 같은 정보들을 포함한다.

  • PID
  • Process State
  • PC(Process Counter)
  • CPU Registers
  • 스케줄링 정보: Priority 등
  • 메모리 관리 정보: Page Table, Segment Table, Base Limit Registers 등
  • IO 정보: 해당 Process가 사용중인 파일 목록, 할당된 입출력 장치 정보
  • 계정, 보안 정보: 사용자 ID, CPU 사용 시간 등

 

context switching 시에 이러한 정보를 임시적으로 저장 후 해당 프로세스가 다시 실행될 때 PCB정보를 복원한다.

 

Process Scheduling

멀티 태스킹(스레딩)을 위해 빠른 병행 실행이 필요하다.

스케줄러를 통해서 다음으로 어떤 프로세스를 실행할지 결정한다.

하나의 코어에서 한 시점에는 정확히 한 개만의 프로세스가 실행되므로(running state), 이를 결정하는 것은 실행속도에 큰 영향을 준다.

 

위에서 봤던 state 중 ready와 wait에는 여러 프로세스들이 대기할 수 있다. 이를 위해 큐를 사용한다.

ready, wait 큐에서는 각 스케줄러를 통해서 ready->running, wait->ready로 갈 프로세스를 정한다.

 

어떤 프로세스를 이동할지는 각 스케줄러의 정책에 따라 다르다. 대표적인 예로는 프로세스마다 우선순위를 정해서 우선순위가 높은 프로세스를 먼저 이동시키는 것이다.

 

Scheduler

스케줄러는 스케줄러의 특성에 따라 역할이 다르다.

보통 3가지로 분류를 나눈다.

  • 장기 스케줄러 (Long-Term Scheduler): 어떤 프로세스를 메모리에 올릴지 결정
  • 단기 스케줄러 (Short-Term Scheduler): Ready → Running 전환 결정
  • 중기 스케줄러 (Medium-Term Scheduler): I/O 대기 프로세스의 메모리 관리 및 Wait → Ready 전환 결정

Context Switch

인터럽트가 발생하면 OS는 CPU를 현재 작업에서 빼앗아서 커널 루틴을 실행할 수 있다.

이 때 실행중이던 프로세스의 문맥(pid, process상태, 메모리 상태 등)을 복구할 수 있도록 저장이 필요하다.

커널 루틴이 실행된 뒤에는 저장했던 문맥의 복구 작업이 필요하다.

 

이러한 저장, 복구 작업을 문맥교환(Context Switch)라고 한다.

 

프로세스 생성

대부분의 OS는 pid를 통해서 프로세스를 구분한다.

 

프로세스는 부모 프로세스로 부터 파생된다.

이 때

1. 부모는 자식과 병행해서 실행되거나

2. 부모는 자식의 실행이 끝날 때 까지 기다리는

방식으로 실행된다.

또한

1. 자식 프로세스는 부모 프로세스의 복사본일 수 있고,

2. 자식 프로세스는 부모 프로세스와는 다른 별개의 프로그램이 될 수 있다.

 

나는 Windows 환경에서 개발중이므로 Windows API의 예시를 보겠다.

#include <stdio.h>
#include <Windows.h>

int main(VOID)
{
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));
    WCHAR cmd[] = L"C:\\Windows\\System32\\notepad.exe";
    BOOL result = CreateProcess(
        NULL,       // 앱 이름 (NULL이면 명령줄 첫 토큰을 실행)
        cmd,        // 명령줄 (수정 가능해야 함)
        NULL,       // 프로세스 보안 속성
        NULL,       // 스레드 보안 속성
        FALSE,      // 핸들 상속 여부
        0,          // 생성 플래그 (기본 0)
        NULL,       // 환경 변수 (부모 프로세스 환경 상속)
        NULL,       // 작업 디렉터리 (기본 NULL: 부모와 동일)
        &si,        // STARTUPINFO 구조체
        &pi         // PROCESS_INFORMATION 구조체
    );

    if (!result) {
        DWORD errorCode = GetLastError();
        fprintf(stderr, "CreateProcess failed. Error code: %lu\n", errorCode);

        return -1;
    }

    WaitForSingleObject(pi.hProcess, INFINITE);
    printf("Child Complete");

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

 

STARTUPINFO, PROCESS_INFORMATION은 프로세스의 특성, 식별자를 포함하는 struct이다.

 

ZeroMemory를 통해 구조체에 값을 0으로 초기화 해준다.

CreateProcess 인자에 맞게 문자열을 WCHAR로 바꿔준다.

 

결과적으로는, cmd의 경로에 따라 메모장이 실행된다.

 

프로세스 종료

프로세스는 부모 프로세스의 시스템 호출을 통해서 종료될 수 있다.

아래와 같은 이유 등으로 인해 종료될 수 있다.

1. 자식 프로세스가 할당된 자원을 초과해 사용하는 경우

2. 자식 프로세스에게 할당된 태스크가 필요없어진 경우

3. 부모가 먼저 종료되었을 때 자식의 실행을 허용하지 않는 경우

 

3의 경우 부모->자식-> 으로 연쇄적으로 종료되는데 이를 cascading termination(연쇄식 종료)라고 한다.

 

좀비 프로세스: 자식 프로세스가 종료되었지만, 부모가 wait호출을 하지 않아, pid와 프로세스 테이블 엔트리를 차지 하는 상황

고아(orphan) 프로세스: 부모 프로세스가 종료되었는데 자식 프로세스가 남아 있는 경우 / linux, unix의 경우에는 init 프로세스를 새로운 부모로 지정한다. windows의 경우 그냥 독립적으로 실행한다. 

 

IPC (Interprocess Communication)

프로세스는 협력할 수 있다.

대개의 운영체제는 

1. 공유 메모리

2. 메시지 전달

을 통해서 프로세스 간 통신을 구현한다.

 

메시지 전달은 구현이 간단하지만 공유 메모리에 비해 대개 느리다.

왜냐하면 메시지 전달은 시스템 콜을 사용해 구현되어, 커널 레벨 전환등의 시간이 필요하지만, 공유 메모리는 처음 공유할 메모리를 확보한 뒤에는 각 프로세스들이 일반 메모리 접근처럼 공유 메모리를 접근할 수 있기 때문이다.

 

공유 메모리 시스템

기본적으로 각 프로세스는 서로 메모리를 접근할 수 없다.

따라서 공유 메모리를 갖추기 위해서는, 각 프로세스들이 서공유 메모리에 접근할 수 있도록 제약조건을 제거하는 것에 서로 동의하고 진행한다.

 

보통 공유 메모리 세그먼트를 생성하는 프로세스의 메모리 주소에 위치하고, 다른 프로세스들은 이 세그먼트를 자신의 공간에 추가해 통신한다.

 

프로세스 간의 약속이지, OS가 데이터의 형식과 위치를 정하는 것이 아니다.

 

메시지 전달 시스템

기본적으로 send(message), receive(message) 연산을 통해서 구현된다.

send, receive는 여러 방법을 통해 이루어질 수 있다.

직접, 간접 통신

동기, 비동기 통신

자동, 명시적 버퍼링

 

직접 통신의 경우 어떤 프로세스를 지정해 직접 보내지만, 

간접 통신은 보내는 쪽은 특정한 매개채(메일박스, 포트)로 송신하고, 받는 쪽은 그 매개체에서 메시지를 수신한다. 

 

Windows의 IPC 예시

Windows는 ALPC(Advanced Local Procedure Call Facility)를 통해 동일 기계 상의 두 프로세스 간 통신한다.

 

위는 현재 내 PC의 ALPC Port들이다.

연결 포트와 통신 포트를 통해서 통신한다.

 

클라이언트 서버 환경 통신

책에서는 세 가지 통신 전략 1.소켓 2. RPC 3.파이프 에 대해서 설명한다.

소켓 통신은 Transport Layer 나머지는 Application Layer 통신이라고 볼 수 있다.

 

1. 소켓 

소켓 통신은 전송 계층 통신이므로, IP + Port Number가 필요하다.

1024 미만은 well-known port로 표준 서비스 구현에 사용된다.

그보다 큰 port 번호는 사용자 프로그램에 사용할 수 있다.

 

보통 stateful한 소켓으로 TCP, stateless한 소켓으로 UDP 소켓을 사용한다.

자세한 내용은 나중에 소켓공부 포스팅에 작성해야겠다.

 

2. RPC

RPC 통신에서 전달되는 메시지는 구조화 되어있고, 데이터 패킷 수준을 넘어선다. (단순한 바이트 스트림이 아님)

RPC는 클라이언트가 원격 호스트의 프로시저 호출을 마치 자신의 것을 호출하는 것 처럼 해준다.

이는 stub을 통해서 구현된다.

stub은 원격지에 있는 함수를 로컬 함수처럼 호출할 수 있게 해주는 중간 코드이다.

[클라이언트 코드]
   ↓ 호출
[클라이언트 스텁]  ← 우리가 직접 호출하는 함수처럼 보임
   ↓ 네트워크 전송
[서버 스텁]
   ↓ 실제 서버 함수 호출
[서버 함수]

RPC를 사용할 때 클라이언트 서버간의 데이터 표기 방식이 다를 수 있다.

9빅엔디안, 리틀엔디안)

이를 해결하기 위해 htonl(), ntohl()같은 함수를 사용해 호스트 방식과 네트워크 방식을 맞춰줄 수 있다.

 

RPC는 정확히 단 한번 처리되도록 보장해야 한다.

다만 네트워크는

1. 요청이 도착하지 않음

2. 응답이 도착하지 않음

3. 서버가 중복 처리함

등의 문제가 생길 수 있기 때문에 완벽하게 보장하기는 어렵다.

 

1. 같은 요청이 여러번 오더라도 결과가 같도록 처리

2. 요청 ID를 통해 중복 감지

3. 트랜잭션 + 로깅 + Ack 

등의 행동을 통해 보장을 노력한다.

 

다만 실제로는 

At-most-once(최대 한번): 중복 실행은 막음, 실패할 수도

At-least-once(최소 한번): 반드시 실행은 함, 중복은 생길 수도

를 보장하는 경우가 많다.

 

RPC를 보낼 때 서버 RPC의 주소를 어떻게 알 수 있나

RPC를 보내기 위해 클라이언트는 서버의 RPC 주소를 알아야 한다.

이를 위해 

1. 각 RPC의 포트번호를 고정하고 컴파일 시에 주어지도록 함

2. 랑데부 방식을 통한 동적 바인딩

방법을 사용할 수 있다.

2의 경우 랑데부 데몬(matchmaker)을 사용한다.

랑데부 데몬은 RPC테이블을 가지고 있어 클라이언트가 요청하면 해당하는 포트 번호를 반환한다.

 

3. 파이프  

파이프 구현 시에는

1. 단방향 or 양방향 방식 

2. 반이중 or 전이중 방식

3. 부모-자식 등 특정 관계 존재 여부

4. 네트워크 사용 or 동일한 머신 내의 통신

위의 네가지 방식을 고려하고 구현하게 된다.

 

일반 파이프

윈도우의 일반 파이프는 익명(anonymous) 파이프 라고도 불린다.

단방향이어야 하며, 통신하는 프로세스 간에는 부모-자식 관계가 성립해야 한다.

 

지명 파이프(Named Pipes)

프로세스 통신 시에만 존재하고 통신 종료 시에는 사라지는 일반 파이프와는 다르게

지명 파이프는 통신 프로세스 종료 시에도 존재할 수 있다.

또한, 지명 파이프는 양방향 방식이고, 부모-자식 관계도 필요로 하지 않는다.

 

윈도우의 지명 파이프는 다른 기계와도 통신할 수 있다.


출처: operating system concecpts assential 번역판을 참고해 작성했습니다.