운영체제(Operating System) - 3. 프로세스

@inup· April 07, 2025 · 34 min read

본 포스팅은 Abraham Silberschatz 저 『Operating System Concepts』(9th Edition)의 내용을 개인 학습 목적으로 요약·정리한 글입니다.

3.1 프로세스의 개념

  • 프로세스 = 실행 중인 프로그램 (메모리 위에 적재된 프로그램)

    • 프로그램 : 디스크에 저장된 파일과 같이 수동적인(Passive) 존재
    • 프로세스 : 다음에 실행할 명령어와 자원을 가지고 있는 능동적(Active) 존재
  • 프로세스는 운영체제에서 프로그램을 실행하는 작업 단위를 의미함
  • 프로세스는 프로세스를 실행하기 위한 작업의 최소 단위인 태스크(Task)를 완료하기 위해 다음의 자원을 필요로 한다 :

    • CPU
    • 메모리
    • 파일
    • I/O (입출력)

프로세스의 구조

image

image1

  • 프로세스의 구성요소

    구성 요소 설명
    스택(stack) 함수 호출 시 지역 변수, 리턴 주소, 매개변수 등이 저장되는 영역이다. LIFO 구조이다.
    [유휴 메모리] ↕️ 런타임 중 heap과 stack이 서로를 향해 자라도록 비워둔 유동적인 공간
    힙(heap) 동적으로 할당된 메모리가 저장되는 영역이다. 예: malloc, new 등. 크기가 런타임 중 변화한다.
    데이터(data) 전역 변수, 정적 변수(static variable)들이 저장된다. 초기화된 값들과 함께 시작된다.
    텍스트(text) 프로그램의 실행 코드가 저장되는 영역. 즉, 컴파일된 기계어 명령어들이 들어갑니다. 읽기 전용이다. (=소스코드)
  • 프로세스 예시

    #include <stdio.h>
    #include <stdlib.h>
    
    int x;
    int y = 15;
    
    int main(int argc, char *argv[]) {
        int *values;
        int i;
    
        values = (int *) malloc(sizeof(int)*5);
    
        for(i=0; i<5; i++) {
            values[i] = i;
        }
    
        for(i=0; i<5; i++) {
            printf("%d ", values[i]);
        }
        
        printf("\n");
        return 0;
    }
    • Stack에 들어가는 요소 : main, argc, argv (매개변수), values (main 내부에서 선언된 지역변수), i
    • Heap에 들어가는 요소 : (int) malloc(sizeof(int) 5);

      • values는 포인터이므로 stack에 있지만, 포인터가 가리키는 대상(말록)은 heap에 있다
    • Data에 들어가는 요소: 변수 x (초기화되지 않았으므로 BSS 영역(uninitialized data), 변수 y (초기화되어 있으므로 initialized data 섹션)
    • Text에 들어가는 요소: 실행할 수 있는 기계어 코드 (=컴파일된 명령어)
  • 구성요소 이외의 필요 자원

    • PC (프로그램 카운터) 레지스터

      • CPU가 현재 어떤 명령어를 실행 중인지를 추적하기 위해 사용
  • 프로세스는 두 가지 종류로 분류할 수 있다.

    • CPU Bound Process - CPU 연산에 많은 시간을 할애하는 프로세스
    • I/O Bound Process - 입출력 작업에 많은 시간을 소비하는 프로세스

프로세스 상태(State)

  • 프로세스는 실행되면서 상태(State)가 변화하며, 프로세스의 상태는 현재의 활동에 따라 정의
  • 프로세스의 5가지 상태

    img1 daumcdn

    1. New : 프로세스가 생성됨
    2. Running : 프로세스가 수행중임
    3. Waiting : 프로세스 이벤트가 발생되어 입출력(I/O)을 기다리는 중
    4. Ready : 프로세스가 CPU에 의해 실행되기를 기다리는 상태(언제든 실행 가능)
    5. Terminated : 프로세스가 실행 종료됨
  • 어느 한순간에 한 프로세서에서는 오직 하나의 프로세스만이 실행된다.
  • 그렇기 때문에 동시에 많은 프로세스가 Ready, Waiting 상태에 있을 수 있다.

프로세스 제어 블록 (PCB)

  • Process Control Block, PCB는 각각의 프로세스와 연관된 여러 정보를 가지고 있다.
  • PCB는 각 프로세스의 상태와 실행 정보를 저장하며, 운영체제가 해당 프로세스를 추적하고 제어하는 데 필요한 핵심 데이터 구조이다.

    image 1

  • 프로세스 상태 (State)

    • 현재 해당 프로세스가 Running인가? Ready, Waiting인가? 등
  • 프로세스 번호 (Number)

    • (OS 입장에서) 각 프로세스를 식별하기 위해 부여하는 고유번호(PID)
  • 프로그램 카운터 (PC)

    • 문맥 교환을 위해 PC 레지스터의 값을 임시로 저장해두는 (소프트웨어적) 저장공간
    • Memory의 다음 실행될 명령어의 주소를 저장해둠
  • 레지스터 (Registers)

    • 별도 저장된 (핵심적인) PC 값을 제외한 나머지 일반 레지스터 값이 저장되는 영역
  • CPU 스케줄링 정보

    • process priority, 큐에 대한 포인터 등과 같은 scheduling parameter들을 포함
  • 메모리 관련 정보

    • base register와 limit register 값, page table과 segment table의 정보를 포함
  • 통계(Accounting) 정보

    • CPU 사용시간, real time used, time limit, process number 등을 포함
  • 입출력 상태 정보

    • 해당 프로세스에게 할당된 I/O device 들과 open file 목록 등

스레드 (Threads)

  • 프로세스 안에 또다른 작은 프로세스를 의미하며 CPU가 프로세스를 실행시키기 위한 최소 작업 단위

    image 2

    • 현재, 대부분의 운영체제에서는 한 프로세스가 다수의 Thread를 가질 수 있도록 허용한다.
  • 이에 한 프로세스가 한 번에 하나 이상의 task를 수행할 수 있도록 할 수 있다.
  • 이러한 점은 여러 Thread가 병렬로 수행되는 multi-core system에서 이익을 얻을 수 있다.
  • Thread를 지원하는 System에서, PCB는 각 Thread에 대한 정보를 포함하도록 한다.
  • 한 프로세스 안에 포함된 쓰레드들은 프로세스의 자원을 공유한다.

3.2 프로세스 스케줄링

  • Multi-Programming(멀티프로그래밍) 의 목적

    • CPU의 이용률 최대화. 즉, 항상 어떤 프로세스가 실행되도록 하는 것
  • Time-Sharing(시분할) 시스템의 목적

    • 각 프로그램이 실행되는 동안 user가 상호작용할 수 있도록 프로세스들 사이에서 CPU를 자주 switching 해주는 것이다. (= 마치 동시에 수행되는 것처럼)
  • 프로세스 스케줄러(Process Scheduler)는 CPU에서 실행 가능한 여러 프로세스들 중 한 프로세스를 선택한다.

    • Single-Processor 환경에서, 실행 중인 프로세스는 한 개 이상 있을 수 없다.
    • 만일, 프로세스들이 여러 개가 있다면, 나머지 프로세스들은 (다른 프로세서가 점유하지 않아) CPU가 자유로워질 때까지 대기해야 한다.

스케줄링 큐 (Scheduling Queues)

  • 스케줄링 큐는 목적에 따라 여러가지 큐를 관리하는 것으로 유지한다.

    • Queue들은 일반적으로 PCB들의 연결리스트로 구현
  • Job queue – 시스템 내의 모든 프로세스 집합

    • Disk 에 존재하는 Queue
    • Job Queue에 존재하는 프로세스는 Memory에 로드되기 이전임 (=서류지원)
    • 큐에서 Long-Term Scheduler가 어떤 프로세스를 메인메모리로 Load할지를 결정
  • Ready queue – Job Queue에서 선택받은, Memory에 올라온 프로세스들 (=면접대기)

    • 큐에서 Short-Term Scheduler가 어떤 프로세스를 CPU로 Load할지를 결정

    image 3

image 4

image 5

  • Wait Queue : 특정한 이벤트가 발생되기를 기다리는 프로세스들
  • Device Queue(s) : 특정 입출력 장치를 대기하는 프로세스들의 리스트

스케줄러 (Schedulers)

             +--------------------+
입력 → → → → |     Job Queue      |   ← 디스크에 존재
             +--------------------+
                     |
            long-term scheduler
                     ↓
             +--------------------+
             |    Ready Queue     |   ← 메인 메모리에 존재
             +--------------------+
                     |
            short-term scheduler
                     ↓
                   CPU
  • Long-term scheduler (또는 Job scheduler) – Memory(=레디 큐)에 들어갈 프로세스를 선택

    • 드물게 호출됨 (초, 분 단위) → 느리게 처리할 수 있음
    • Long-term scheduler는 멀티프로그래밍(degree of multiprogramming)의 정도를 제어
    • 롱텀 스케줄러는 좋은 프로세스 혼합(process mix)을 목표로 한다. (= I/O바운드와 CPU바운드 프로세스를 잘 섞어서 메모리에 올림)
  • Short-term scheduler (또는 CPU scheduler) – CPU에 올라갈 프로세스를 선택

    • 시스템에서 유일한 스케줄러일 수 있음
    • 자주 호출됨 (밀리초 단위) → 빠르게 처리해야 함
  • Medium-Term Scheduler (또는 Swapper) image2

    • 멀티프로그래밍(degree of multiprogramming)의 정도를 줄여야 할 경우 추가될 수 있음
    • 프로세스를 Memory에서 제거하고 디스크에 저장한 후, 다시 가져와 실행을 계속하는 방식 : 스와핑 (Swapping)

      image 6

    • 메모리 공간 효율을 올리면 멀티프로그래밍의 정도가 늘어나는 거 아닌가요? → (X)

      • 멀티프로그래밍의 정도의 정의(definition) : Memory에 올라온 프로세스의 개수
      • 개수 자체가 줄어드므로 멀티프로그래밍 정도가 줄어든다고 표현해야 함

문맥 교환 (Context Switch)

  • CPU가 다른 프로세스로 전환될 때, 시스템은 기존 프로세스의 상태를 저장(save)하고 새로운 프로세스의 저장된 상태를 로드(reload)해야 함 image3

    • 개별 프로세스의 문맥(Context)은 개별 PCB에 저장
  • 문맥 전환에 드는 시간은 전부 오버헤드이다.

    • 시스템(컴퓨터)은 문맥 전환 중에 어떠한 다른 유용한 작업도 수행할 수 없다.
    • 운영체제와 PCB가 복잡할수록 문맥전환에 필요한 시간도 증가한다.
  • 문맥 전환에 드는 시간은 하드웨어 지원에 따라 다르다

    • 일부 하드웨어는 CPU당 여러 세트의 레지스터를 제공하여 여러 문맥을 동시 로드 가능

Ready → Running

  • Ready 상태에서 Running이 된다는 의미는?

    • 기존에 Running되고 있던 프로세스는 Wating/Terminated/Ready가 되었다는 뜻
  • 이 과정에서 문맥 전환이 일어난다. img1 daumcdn10

    • CPU를 뺏긴 프로세스는 뺏기기 직전까지의 수행 과정(=문맥)들을 전부 (kernel의 data 영역에 존재하는) 본인의 PCB 자료구조에 저장(Save)해두어야 한다.
    • CPU를 얻은 프로세스는 본인의 PCB 자료구조에서 문맥을 다시 가져와 실행(Reload)시켜야 한다.
  • Context Switch는 Dispatcher가 수행해준다.
  • Context Switch를 수행하는 데에도 일정 시간이 필요하다.

    • Context Switch 되는 동안에 시스템이 어떠한 유용한 일도 하지 못하기 때문에, 문맥 전환은 순수한 Overhead이다.
    • Context Switch 시간은 (메모리 속도, 명령어, 레지스터 수) 제반사항에 따라 다르므로, 기계마다 다르다.

Running → Ready

  • 타이머(Timer) 인터럽트에 의해 발생한다.

    • 만약 필요한 모든 과정을 수행하고 마쳤다면, Ready가 아닌 Termindated 되었을 것이다.
    • 타이머 인터럽트는 할당된 CPU 시간을 전부 써서 발생한 HW 인터럽트이다.

3.3 프로세스의 동작 과정

프로세스 생성 (Creation)

  • 부모 프로세스는 자식 프로세스를 생성할 수 있음

    • 예시: 터미널에서 ./a.out 실행 → 쉘이 새로운 프로세스 생성함
    [bash (터미널)]     ← 부모
          │
       fork()[my_program]        ← 자식 (exec()을 통해 변신 가능)
  • 자식 프로세스는 또한 다른 프로세스를 생성하여 프로세스 트리를 형성

    image 8

  • 일반적으로 프로세스는 PID(Process ID)를 통해 식별되고 관리됨
  • 프로세스 생성 시 두 가지 관점에서 분류할 수 있다 image4

    1. 실행 흐름 관점 (부모-자식의 실행 방식)

      • 부모와 자식이 병행(Concurrent) 실행
      • 부모가 자식의 종료를 기다림(wait)

        • UNIX에서는 (기본적으로) fork() 이후에 부모는 그대로 실행을 이어가고, 필요 시 wait() 등을 호출해 자식의 종료를 기다릴 수 있음.
    2. 주소 공간/프로그램 내용 관점

      • 자식이 부모의 복사본인 경우

        • fork() 후 부모와 똑같은 메모리 공간(=주소 공간) 복사본으로 시작됨
        • COW(copy-on-write) : 부모와 자식이 같은 물리 메모리를 읽기 전용으로 공유한 후, 수정하려는 시점에야 독립된 메모리 공간을 가짐
      • 자식이 새로운 프로그램을 실행하는 경우

        • fork() 이후 exec() 을 호출해서, 메모리를 완전히 다른 프로그램으로 덮어씀
        • 자식과 부모가 서로 다른 메모리 공간(=주소 공간)을 가지게 됨
        #include <stdio.h>
        #include <unistd.h>
        
        int main() {
            int pid;
            pid = fork();
        
            if (pid < 0) {
                fprintf(stderr, "Fork Failed");
                return 1;
            }
            else if (pid == 0) {  // 자식 프로세스
                printf("Hello, I'm Child\n");
                execlp("echo", "echo", "Hello, World!", (char *)0); // exec()
                printf("This will not be printed!\n");  // 실행되지 않음
            }
            else {  // 부모 프로세스
                printf("Hello, I'm Parent\n");
            }
            return 0;
        }
        • exec()의 echo 명령어로 자식 프로세스는 새로운 프로그램이 됨.
        • 이후 코드(print문)는 실행되지 않음.
  • 부모와 부모의 복제 프로세스인 자식이 어떻게 구분되는가?

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        pid_t pid = fork();
    
        if (pid > 0) {
            printf("부모 프로세스이다. 자식 PID: %d\n", pid);
        } else if (pid == 0) {
            printf("자식 프로세스이다. PID: %d\n", getpid());
        } else {
            printf("fork 실패\n");
        }
    
        return 0;
    }
    
    /* 출력값 */
    부모 프로세스이다. 자식 PID: 12345  
    자식 프로세스이다. PID: 12345
    • 부모 프로세스의 입장에서 :

      • fork() 가 자식의 pid 값을 반환하여 변수 pid의 값이 자식의 pid가 됨
      • pid값이 0이 아니면 자신이 부모 프로세스라는 것을 알 수 있음
    • 자식 프로세스의 입장에서 :

      • fork()가 어떤 값도 반환하지 않아 변수 pid의 값이 0임
      • pid 값이 0이면 자신이 자식이라는 것을 알 수 있음

프로세스 종료 (Termination)

  • 프로세스는 프로그램의 마지막 명령어를 실행하고, 운영체제에 자신을 삭제해달라고 요청

    • 삭제 요청 시에 → exit() 시스템 콜 사용 (자신을 Zombie 상태로 만들고 종료)
    • 자식 프로세스의 출력 데이터를 부모 프로세스로 전달 → wait() 시스템 콜 사용

      • 그러나 wait() 만으로 전달되지는 않고, 아래의 IPC 전달 방식을 사용해야 전달됨
      • wait()과 Pipe로 전달 : [자식] - write(fd[1]) --> [PIPE] --> read(fd[0]) - [부모]

좀비(Zombie)와 고아(Orphan) 프로세스

  • 좀비 프로세스

    • 실행은 종료되었지만, 부모가 아직 종료 상태(exit status)를 회수하지 않아 프로세스 테이블에 정보가 남아있는 상태
  • 고아 프로세스

    • 자식보다 부모 프로세스가 먼저 종료된 경우 → 자식 프로세스가 고아가 됨
    • 이 경우, init 프로세스가 대신 부모 역할을 맡아 관리함

3.4 프로세스 간 통신

  • 운영체제에서 동시에 실행되는 프로세스들은 독립적이거나 협력적일 수 있다.

    • 다른 프로세스에 영향을 주거나 받지 않고, 데이터도 공유하지 않는 프로세스 = 독립적
    • 다른 프로세스와 데이터/자원을 공유하며 상호작용하는 프로세스 = 협력적
  • 협력적 프로세스를 허용하는 이유

    • 정보 공유
    • 계산 속도 향상
    • 모듈화
    • 편의성
  • 데이터와 정보를 공유하기 위해 IPC 기법이 필요하다.
  • IPC에는 대표적으로 공유 메모리(Shared Memory) 방식과 메시지 전달(Message Passing) 방식이 있다.

    • 대부분의 운영체제는 이 두 가지 방식을 모두 지원한다.
  • 공유 메모리 방식은 초기 메모리 영역을 설정할 때에만 시스템 콜을 필요로 하므로 일반적으로 더 빠르다.
  • 메시지 전달 방식은 매번 시스템 콜이 필요하여 부가적인 오버헤드가 발생할 수 있다.

3.5 IPC 시스템

image5

Shared Memory 시스템

  • 공유 메모리를 사용하는 IPC에서는 통신하는 프로세스들이 Shared Memory 영역을 구축해야 한다. (=같은 메모리에서 그 데이터를 읽고 쓸 수 있기 때문에 빠르다)
  • Producer-Consumer Problem(생산자-소비자 문제)를 제한적으로 해결 가능하다.

    • 생산자 : 버퍼에 데이터를 생성해서 넣음
    • 소비자 : 버퍼에서 데이터를 꺼내 처리함
    • 이 때, 버퍼가 다 찼는데도 계속 데이터를 생성하거나, 꺼낼 게 없는데 꺼내려고 하거나, 생산과 소비가 동시에 일어나는 등(=데이터 손상, Race Condition)의 문제가 발생할 수 있음
    [공유 메모리] ← Shared Memory  
     ↑             ↑  
    Producer    Consumer  
      ↖︎  동기화   ↗︎  
       (semaphore, mutex 등)
    • 단, Shared Memory 만으로는 문제를 완전히 해결할 수 없으며 동기화 도구(세마포어, MuTeX 등)가 함께 있어야 한다.
    • Shared Memory는 "공간을 공유"할 수 있게 도와주는 도구고, "접근 순서"는 semaphore, mutex 같은 동기화 도구로 제어해야 한다.

Message Passing 시스템

  • 메시지 전달 방식은 동일한 주소 공간을 공유하지 않고도 프로세스들이 통신하고, 동기화할 수 있도록 한다.
  • 메모리를 직접 공유하지 않기 때문에 (공유 메모리 방식에 비해 상대적으로) 느릴 수 있지만, 생산자-소비자 문제 해결이 용이하다.
  • 메시지 전달 방식의 분류

    개념 무엇을 기준으로 나누는가?
    🅰️ Direct / Indirect Communication “누구에게” 메시지를 보내는가?대상 지정 방식
    🅱️ Blocking / Non-Blocking “언제까지 기다릴 것인가?”통신의 타이밍과 대기 방식
    • 서로 다른 분류 개념이므로, 함께 조합해서 사용 가능하다
    • send(P2, "hello") → P2 프로세스에 Direct 통신한다. 그러나 동기/비동기 여부는 알 수 없음

🅰️ 직접 통신 / 간접 통신

방식 설명 예시
Direct 메시지를 특정 프로세스 이름으로 직접 보냄 send(**P1**, msg)receive(**P2**, msg)
Indirect 메시지를 공통 메일박스(버퍼)를 통해 간접 전달 send(**mailboxA**, msg)receive(**mailboxA**, msg)
  • Indirect는 1:N 통신도 가능 (메일박스 이름만 알면 여러 프로세스가 이용 가능)
  • 링크 설정 방법

    • 링크는 자동으로 설정됨
    • 링크는 양방향일수도, 단방향일수도 있음
    • Direct는 명시적인 두 프로세스 간 직접 연결, Indirect는 메일박스 공유

❓ 링크 하나를 여러 프로세스가 공유할 수 있는가?

  • (Direct 기준) 하나의 링크는 정확히 하나의 프로세스 쌍과 연결된다.
  • (Indirect 기준) 하나의 링크는 여러 프로세스와 연결될 수 있다.

❓ 두 프로세스 사이에 몇 개의 링크가 있을 수 있는가?

  • (Direct 기준) 일반적으로 하나의 링크만 존재. 설계에 따라 여러 링크를 허용할 수 있음

❓ 링크는 얼마만큼의 메시지를 담을 수 있는가? → 버퍼링에 다라 다름

  • Zero capacity (0개): 메시지는 즉시 전달되어야 하며, 양쪽이 동시에 준비되어야 함 (완전 동기)
  • Bounded capacity (유한 크기): 특정 개수만큼 메시지를 담을 수 있고, 꽉 차면 send()대기
  • Unbounded capacity (무한 큐처럼 가정): send()는 절대 대기하지 않음 (비현실적이지만 이론적으로 사용됨)

만일 Indirect(=메일박스 공유)에서 두 프로세스가 받길 원하면 누가 메시지를 받는가?

  • 운영체제의 정책에 따라 달라진다
정책 설명
First-come, first-served 먼저 receive()를 호출한 프로세스가 메시지를 받음
Random selection 수신자들 중 무작위로 한 프로세스를 선택
Priority-based 우선순위가 높은 프로세스가 먼저 받음 (실시간 시스템 등)
Round-Robin (RR) 순서대로 하나씩 돌아가면서 받음
  • 메일박스를 여러 프로세스가 공유할 때 메시지를 동시에 받는 게 아니며 메시지 하나는 단 하나의 수신자만 받는다.

🅱️ Blocking / Non-Blocking

방식 송신자/수신자의 동작 설명
Blocking Send 송신자 대기 수신자가 받을 때까지 기다림
Non-Blocking Send 송신자는 바로 다음 코드 실행 메시지만 큐에 넣고 바로 복귀
Blocking Receive 수신자 대기 메시지가 도착할 때까지 기다림
Non-Blocking Receive 수신자는 바로 복귀 메시지가 없으면 그냥 null 반환 등 처리
  • Blocking 방식은 동기화(Sync)의 한 형태로 볼 수 있다.

    • 차단 전송은 발신자가 메시지 수신까지 차단됨
    • 차단 수신은 수신자가 메시지 도착할 때까지 기다린다.
  • Non-Blocking 방식은 비동기(Async) 의 한 형태로 볼 수 있다.

    • 비차단 전송은 수신과 상관없이 계속 실행된다
    • 비차단 수신은 수신자가 유효한 msg를 받거나 또는 null을 받을 수 있다.

Shared Memory 방식 vs. Direct Message Passing 방식의 차이

구분 기준 Message Passing (메시지 전달) Shared Memory (공유 메모리)
데이터 전달 방식 OS를 통해 메시지를 주고받음 메모리 공간 자체를 공유
예시 send(P, msg) / receive(Q, msg) shmget(), shmat()
Naming 방식 Direct / Indirect로 구분됨 해당 없음 (메모리 자체 공유)
동기화 필요 여부 동기화 포함되기도 함 (특히 blocking) 별도 동기화 필요 (세마포어 등)
주요 이슈 메시지 전달 경로, 메시지 크기 메모리 접근 충돌, 동기화 문제

3.6 클라이언트-서버 환경에서의 통신

  • 공유 메모리와 메시지 전달 기법을 사용하여 프로세스들이 통신하는 방법에 대해 학습함
  • 이 기법들은 클라이언트-서버 시스템의 통신에도 사용할 수 있다.
  • 이 경우에 사용할 수 있는 통신 전략은 3가지로, '소켓(Socket)', 'RPCs', '파이프(Pipe)'이다.

    방식 사용 범위 방향성 특이점
    Socket 로컬/원격 모두 가능 양방향 네트워크 기반, 유연성 높음
    RPC 원격 함수 호출용 양방향 호출 추상화, 개발 간편
    Pipe 동일 시스템 내 단방향(기본) 간단, 빠름, 익명/명명 형태

소켓 (Socket)

  • 소켓은 통신의 end-point이며, 두 프로세스가 네트워크 상에서 통신을 하기 위해 두 개의 소켓이 필요하다.
  • 서버는 지정된 port에 클라이언트 요청 메시지가 도착하기를 기다리게 된다.

image 9

항목 설명
개념 네트워크 통신을 위한 인터페이스. IP 주소 + 포트 번호로 통신 상대를 식별
용도 로컬/원격 간의 통신 모두 가능 (같은 기기 내부 또는 인터넷 등)
종류 - TCP 소켓 (신뢰성 보장) / UDP 소켓 (빠름, 비신뢰성)
- 도메인 소켓 (로컬 통신 전용)
특징 - 메시지 전달 기반
- 클라이언트-서버 모델에 가장 많이 사용
- 양방향 통신 가능
예시 웹 브라우저(클라이언트) ↔ 웹 서버(서버) 간 통신

원격 프로시저 호출 (RPC)

  • RPC는 네트워크에 연결되어 있는 두 시스템 사이의 통신을 연결하기 위한 프로시저 호출을 추상화하기 위한 방안으로 설계되었다.
  • RPC는 분산 파일 시스템(Distributed File System)을 구현하는데 유용하다. image7
항목 설명
개념 마치 로컬 함수를 호출하듯, 원격의 함수(서버 쪽)를 호출할 수 있도록 함
용도 분산 시스템에서 서로 떨어진 시스템 간 함수 호출이 필요할 때
특징 - 함수 호출처럼 보이지만 내부적으로는 메시지 전달 방식
- 개발자 입장에서 통신을 추상화해서 사용
장점 개발자가 네트워크 프로그래밍을 몰라도 쉽게 통신 가능
단점 성능 오버헤드 있음, 장애 처리 고려 필요
예시 gRPC, XML-RPC, Java RMI, Python xmlrpc

파이프 (Pipes)

  • 파이프(Pipe)는 두 프로세스가 통신할 수 있게 하는 전달자(conduits)로서 동작한다.

    • 일반 파이프: 생산자-소비자 형태로 두 프로세스 간의 통신을 허용 (=단방향)
    • Named 파이프: 일반 파이프는 오직 프로세스들이 통신하는 동안에만 존재하지만, 지명 파이프는 통신이 종료돼도 계속 존재한다. 지명 파이프가 구축되면, 여러 프로세스들이 이를 사용하여 통신할 수 있다. 양방향으로도 통신이 가능하며 부모-자식 관계도 필요하지 않다. image8

image 10

항목 설명
개념 같은 시스템 내의 프로세스 간 통신을 위한 단방향 데이터 통로
종류 - 익명 파이프: 부모-자식 간 통신

(일반적으로 부모 프로세스가 파이프를 생성하고, 이를 사용하여 fork()로 생성된 자식 프로세스와 통신함)

  • 명명된 파이프 (FIFO): 관련 없는 프로세스 간도 사용 가능 | | 특징 | - 기본적으로 단방향 (양방향은 두 개 필요)
  • 동기식 메시지 전달 구조 | | 제한점 | - 원격 통신은 불가능
  • 크로스 플랫폼 사용 어려움 | | 예시 | 리눅스 쉘에서 `ls |
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd[2];        // fd[0]: 읽기, fd[1]: 쓰기
    pipe(fd);

    pid_t pid = fork();

    if (pid == 0) {
        // 자식 프로세스
        close(fd[0]); // 읽기 닫기
        char msg[] = "hello from child!";
        write(fd[1], msg, sizeof(msg));
        exit(0);
    } else {
        // 부모 프로세스
        close(fd[1]); // 쓰기 닫기
        char buffer[100];
        read(fd[0], buffer, sizeof(buffer));
        printf("부모가 받은 메시지: %s\n", buffer);

        wait(NULL); // 자식 종료 기다리기
    }

    return 0;
}
  • wait()을 통해 자식의 종료를 기다리고,
  • 데이터 전달을 pipe를 통해 실행

    • 자식이 데이터를 쓰는 부분

      if (pid == 0) {
          close(fd[0]); // 읽기용 닫음 (나는 쓰기만 할 거니까)
          char msg[] = "hello from child!";
          write(fd[1], msg, sizeof(msg));  // 파이프에 메시지 씀
          exit(0);
      }
    • 부모가 데이터를 읽는 부분

      else {
          close(fd[1]); // 쓰기용 닫음 (나는 읽기만 할 거니까)
          char buffer[100];
          read(fd[0], buffer, sizeof(buffer));  // 파이프에서 읽음
          printf("부모가 받은 메시지: %s\n", buffer);
          wait(NULL);
      }
@inup
언제나 감사합니다 👨‍💻