OS - IPC & fork()

윤형·2025년 4월 10일

Operation System

목록 보기
4/9
post-thumbnail

Process Creation

OS가 새로운 프로세서를 만들고 메모리에 로드하여 실행하는 과정을 의미한다. 부모 프로세서가 새로운 자식 프로세서를 생성하며 이는 운영체제의 멀티테스킹과 프로세스 관리의 핵심 메커니즘이다.

1. Process Identification (PID)

  • 각 프로세서는 고유한 PID로 식별되고, 이를 통해 프로세서스를 관리한다.
  • PID는 생성시 할당이 된다. - 일반적으로 정수 형태임

2. Resource Sharing (자원 공유 방식)

  • 프로세스 생성 시 부모 - 자식 간의 자원 공유 방식은 다음과 같이 구분한다.
  1. Parent and Children share all resources -> fork(), exec() 호출 X
  2. Children share subset of parent's resources -> 부모의 자원을 같이 할당 받는다.
  3. Parent and child share no resources -> fork() 혹은 exec()를 호출하는 경우.

3. Excution - 실행 방식

  1. Parent and children execut concurrently(동시 실행)
    -> 부모와 자식 프로세스가 병렬(concurrently) 로 실행된다.
    -> example) 웹 서버가 여러 클라이언트 요청을 처리하기 위해 자식 프로세스를 생성하는 경우.

  2. Parent waits until children terminate
    -> 부모 프로세스는 자식이 종료될 때까지 대기(Block) 한다.
    -> wait() 시스템 호출을 하는 경우.

fork() && exec()

1. fork()

현재 실행중인 프로세스(부모) 를 완전히 복사해 자식 프로세스를 생성한다.

  • 부모와 자식은 동일한 코드, 메모리, 파일 디스크립터, PC 를 공유한다.

  • Copy-on-Write(COW) : 호출이 되면 복사가 아니라 실제 쓰기 작업이 들어가면 복사함.

  • fork()의 반환 값

    • 부모 프로세스 : 자식의 PID를 반환 (실패 시 -1)
    • 자식 프로세스 : 0을 반환

2. exec()

현재 프로세스의 메모리 공간을 새로운 프로그램 으로 교체한다.

  • fork()로 생성된 자식 프로세스가 새로운 프로그램을 실행 할때 사용된다.

  • 원본 프로세스의 PID가 유지되지만, 코드와 데이터는 완전히 새로 로드된다.

  • 성공 시 리턴하지 않음, 실패 시 -1리턴

  • execlp(), execvp(), execle(), execv(), execve() 등이 존재
    :모두 동일하지만 $PATH 사용 여부, 인자 전달 방식, 환경 변수 변경에서 차이가 있음.

fork()와 exec()의 전형적 흐름

  1. fork()로 자식 프로세스 생성
    -> 부모와 자식이 동일한 코드를 실행.

  2. 자식 프로세스에서 exec() 호출
    -> 자식만 새로운 프로그램으로 덮어쓰고, 부모는 원래 작업을 계속.

  3. 부모는 wait()으로 자식 종료 대기
    -> 자식이 종료되면 부모가 다음 코드를 실행.

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

int main() {
    pid_t pid = fork();  // 1. 프로세스 복제

    if (pid < 0) {       // fork 실패
        fprintf(stderr, "Fork Failed");
        return 1;
    }
    else if (pid == 0) { // 2. 자식 프로세스
        execlp("/bin/ls", "ls", NULL);  // 3. "ls"로 교체
    }
    else {               // 4. 부모 프로세스
        wait(NULL);      // 5. 자식 종료 대기
        printf("Child Complete");
    }
    return 0;
}

주의 사항

  • 부모 프로세스에 wait()이 없으면 부모와 자식간에 실행 순서는 확정적이지 않음. -> 스케쥴러가 결정하며 Race Condition이 발생할 수 있음.

  • Race Condition : 두 개 이상의 프로세스(또는 스레드)가 공유 자원에 동시에 접근할 때, 실행 순서에 따라 결과가 달라지는 상황.

  • 자식이 종료되었지만, 부모가 wait()으로 회수하지 않으면 "좀비 프로세스"가 된다. (시스템 리소스를 잠식하지 않지만, 프로세스 테이블을 잠식함)

  • exec()를 하면 기존 데이터가 지워지기 때문에 이전으로 돌아갈 수 없음.

Wait() 설명

  • 블로킹(Blocking) 방식으로 아무 자식 프로세스가 하나가 종료될 때까지
    대기합니다.

  • 자식 프로세스의 종료 상태(exit status)를 status 포인터로 반환합니다. (status : 종료 상태를 저장할 변수 )

  • 자식 프로세스의 PCB(Process Control Block)를 정리합니다 (좀비 프로세스 방지).

#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 자식 프로세스
        printf("Child PID: %d\n", getpid());
        sleep(2);
        return 42;  // 종료 코드 42
    } else {
        // 부모 프로세스
        int status;
        pid_t child_pid = waitpid(pid, &status, 0);  // 특정 자식 대기
        if (WIFEXITED(status)) {
            printf("Child %d exited with code %d\n", child_pid, WEXITSTATUS(status));
        }
    }
    return 0;
}
  • pid_t waitpid(pid_t pid, int *status, int options);
    -> 특정 자식 프로세스(pid) 가 종료될 때까지 대기합니다.
    -> options : 0이면 블로킹, WNOHANG 이면 논 블로킹

  • WIFEXITED(status) : status분석 -> true(자식이 정상 종료), false(비정상 종료)

wait()를 호출하지 않으면?

  1. 좀비 프로세스 (Zombie):
    자식이 종료되었지만, 부모가 wait()으로 상태를 읽지 않아 프로세스 테이블에 잔류.
    해결: 반드시 wait() 호출 또는 SIGCHLD 시그널 처리.

  2. 고아 프로세스 (Orphan):
    부모가 먼저 종료되어 고아처럼 남겨진 프로세스.
    부모가 먼저 종료되면, 자식은 init 프로세스(PID=1)에 의해 자동으로 수거.


Process Termination (프로세스 종료)

1. Process 정상 종료

프로세스가 마지막 명령어를 실행한 후 운영체제에 종료를 요청(exit)하는 경우.

  • 자식 프로세스는 wait()을 통해 부모에게 종료 상태 데이터를 전달할 수 있음.
  • 운영체제는 프로세스의 할당된 자원(메모리, 파일, cpu)등을 해제한다.
#include <stdlib.h>
int main() {
    exit(0);  // 정상 종료 (종료 코드 0)
}

2.Parent terminate execution of childern process (abort)

부모 프로세스가 자식 프로세스를 강제로 종료시키는 경우.

  1. Child has exceeded allocated resources -> 자식 프로세스가 할당된 자원을 초과한다.

  2. Task assigned to child is no longer required -> 자식 프로세스에 할당된 작업이 더이상 필요하지 않는다.

  3. 부모의 종료: 일부 운영체제는 부모 종료 시 모든 자식을 함께 종료시킨다. (Cascading Termination(계단식 종료))

  • Cascading termination : 부모 프로세스가 종료되면 모든 자식 프로세스도 재귀적으로 종료된다.

IPC (Interprocess Communication)

프로세스 간 데이터를 주고받거나 동작을 조율하는 통신 매커니즘이다.

필요성

  • 프로세스는 기본적으로 독립적이기 때문에 직접 메모리나 자원을 공유할 수 없다.
  • 정보 공유
  • 계산 속도 향상
  • 모듈화, 편의성
  • 예시 : 웹 브라우저와 다운로드 관리자 간 진행 상태 공유, 채팅 프로그램에서 메시지 전달.

IPC의 주요 방식

  1. Shared Memoey (공유 메모리) (b)
    -> 프로세스가 공유된 메모리 영역에 접근한다.
    -> 빠르지만 동기화가 필수적이다.
    / Unix/Linux 공유 메모리 예제
    int shm_id = shmget(key, size, IPC_CREAT | 0666);
    char *data = (char*)shmat(shm_id, NULL, 0);
    sprintf(data, "Hello from PID %d", getpid());  // 데이터 쓰기
  2. Message Passing (a)
    -> 프로세스가 메시지 큐, 파이프, 소캣 등을 통해 데이터 교환
    -> 안전하지만 느림(커널 오버헤드 발생).
Message PassingShared Memory
ImplementationEasierDifficult
SpeedSlowerFaster
Kernal intervention(개입)A lot초기 메모리 할당만 관여, 이후에는 프로세스가 직접 접근
Data sizesmall amount - kblarge amount - GB

Shared Memory

공유 메모리 생성 및 설정 과정

  1. 프로세스 A가 공유 메모리 생성
  2. 프로세스 A가 주소 공간에 연결
  3. 프로세스 B의 접근 허용

<핵심 특징>

  • 주소 공간 공유 : 프로세스 A와 B가 동일한 물리 메모리를 가르키는 가상 주소를 가진다.
  • 데이터 형식 없음 : 구조화된 포맷이 없어 개발자가 직접 데이터 형식 관리(구조체, 배열)
  • 커널 개입 최소 : 초기 설정 후 커널 관여 없이 직접 메모리 접근 가능.

<추가 정보>

  • 프로세스 마다 물리적 메모리 공간을 일정량 할당받게 된다. -> 각 메모리 공간은 바로 붙어있지 않고 여기저기 떨어져 있다. 하지만 MMU를 통해 가상 메모리로 접근할 수 있다. CPU는 메모리의 물리적 주소를 갖고있지 않고 가상 메모리 주소를 가지고 있고, 이를 MMU를 통해 실제 물리적 주소로 연결하게 되는것이다.
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define BUFF_SIZE 1024

int main() {
    // 1. 익명 메모리 매핑 (공유 가능한 메모리 할당)
    char *buffer = mmap(NULL, BUFF_SIZE, 
                        PROT_READ | PROT_WRITE, 
                        MAP_SHARED | MAP_ANONYMOUS, 
                        -1, 0);
    if (buffer == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    // 2. 부모 프로세스가 데이터 작성
    strncpy(buffer, "I am a parent", BUFF_SIZE-1);

    // 3. 자식 프로세스 생성
    pid_t pid = fork();

    if (pid == 0) {
        // 자식 프로세스: 데이터 수정
        strncpy(buffer, "I am a child", BUFF_SIZE-1);
    } else {
        // 부모 프로세스: 자식 종료 대기 후 출력
        wait(NULL);
        printf("buffer: %s\n", buffer);  // 출력: "I am a child"

        // 4. 메모리 매핑 해제
        munmap(buffer, BUFF_SIZE);
    }
    return 0;
}
  • mmap : 파일 또는 메모리 영역을 프로세스의 가상 주소 공간에 매핑한다.
void *mmap(void *addr, size_t size, int prot, 
int flags, int fd, off_t offset);
  • addr : 매핑할 가상 주소(보통 NULL로 해야 커널이 자동 할당)
  • size : 매핑할 영역 크기
  • prot : 보호 모드
  • flags : 동작 옵션(공유, 사적, 익명 메모리)
  • fd : 파일 디스크립터 (익명 메모리 : -1)
  • offset : 파일 오프셋 (익명 메모리 : 0)

Producer - Consumer Model

하나의 시스템에서 데이터를 생성하는 역할 소비하는 역할이 동시에 작용하는 모델. 직접 통신하지 않고 버퍼를 통해 주고받는다.

  • Producer : 데이터를 만드는 역할을 하는 주체, 버퍼에 데이터를 write함.

  • Consumer : 버퍼에서 데이터를 읽어서 사용하는 주체, 버퍼에서 read함.

  • Buffer : 생산자와 소비자가 직접 데이터를 주고받지 않고, 임시로 데이터를 저장해 놓는 중간 공간. (일종의 큐 혹은 배열로 구현됨)

Shared Buffer Model

생산자와 소비자가 데이터를 주고받기 위해 사용하는 공유된 메모리 공간을 기반으로 한 모델

유형Unbounded BufferBounded Buffer
버퍼 크기무제한고정된 크기 (BUFF_SIZE)
Producer항상 데이터 생성 가능버퍼가 가득 차면 대기
Consumer버퍼가 비어있으면 소비 불가 (out==in 시 중단)동일
실제 사용거의 사용되지 않음 (메모리 고갈)대부분 채택(파이프, 메시지 큐)

Shared Buffer by Circular Array

  • 대표적인 Bounded Buffer임.

  • out == in : 버퍼가 비어있음.
  • (in+1) % BUFF_SIZE == out : 버퍼가 가득 참.
<Producer>
int in = 0;
int out = 0;

while(true){
	while((in+1) % BUFFER_SIZE == out){,,대기,,}
    
    buffer[in] = get_data();
    
    in = (in+1) % BUFFER_SIZE;
}

<Consumer>
int in = 0;
int out = 0;

while(true){
	while(in==out){,,대기,,}
    
    read_data(buffer[out]);
    
    out = (out + 1) % BUFFER_SIZE;
}

Message Passing System

  • P프로세스와 Q프로세스가 통신하기 위해서는 "통신 링크"를 설정한다.

Message 전달 방식 4가지

1. Direct Vs Indirect Communication

방식설명예시
Direct프로세스끼리 직접 이름을 지정해서
메시지를 주고받음
send(Q, msg) -> Q에게 직접 전송
receive(P, message) -> P에게 전달받기
Indeirect메일박스를 통해 메시지를 주고 받음send(mailbox1, msg)
-> Q는 receive(mainbox)

Direct

  • 링크는 자동적으로 확립(established)된다.
  • 하나의 링크는 오직 한 쌍의 프로세스와만 연결된다.
  • 프로세스 쌍 사이에는 반드시 하나의 링크만 존재한다.
  • 링크는 단방향, 양방향 둘다 가능하다.
  1. User Mode에서 send(B, message) 같은 system call 호출

  2. System Call 인터페이스를 통해 커널 모드로 전환됨

  3. 커널은 전달받은 수신자 이름 (B) 를 기반으로

    • B 프로세스의 메시지 큐를 찾아 메시지를 저장하거나
    • 즉시 수신자에게 전달함 (수신자가 기다리고 있는 경우)
  4. 처리 후 다시 User Mode로 복귀

Indirect

  • 생산자와 소비자가 서로의 이름을 몰라도, 같은 메일박스를 공유하고 있다면 통신이 가능하다.
  • 메일박스는 고유한 ID를 가지고 있다.
  • 프로세스들이 공통의 메일박스를 공유할 경우에만 링크가 설정된다.
  • 하나의 링크(=메일박스)는 여러 프로세스와 연결될 수 있음
  • 프로세스 쌍은 여러 개의 메일박스(링크)를 공유할 수 있음 (로그용, 데이터용 등등)
  • 링크는 단방향, 양방향 둘다 가능하다.

<보낼 때>

  1. A가 send(mailbox, msg)를 호출한다.
  2. 커널 모드로 전환하고, 커널은
    • 해당 메시지가 존재하는지 확인
    • 존재한다면 메시지를 메일박스 큐에 저장
  3. 메일 박스 큐가 꽉 차면 A는 Block이 될 수 있다.

<받을 때>

  1. B가 receive(mailbox)를 호출한다.
  2. 커널모드로 전환하고, 커널은
    • 메일박스에 메시지가 존재하는지 확인.
    • 존재하면 큐에서 메시지를 꺼내 B에게 전달.
    • 없다는 B는 block상태로 전환된다.
  3. 이때 먼저 가져가서 읽은 프로세스가 메시지를 가져가게 된다.

2. Synchronous Vs Asynchronus

방식설명예시
Synchronus보내는 쪽(send)과 받는 쪽(receive)이 동시에 준비되어 있어야 한다.실시간 채팅
Asynchronus보내는 쪽은 그냥 보내고, 받는 쪽은 언제든 나중에 받을 수 있음이메일

Blocking - 동기식

  • Blocking send : 메시지가 수신될 때까지 sender를 Block(대기)한다.
  • Blocking receive : 수신한 메시지가 도착할 때까지 receiver를 Block한다.

Non-Blocking - 비동기식

  • Non-Blocking send : 메시지를 보내고 즉시 다음 작업을 수행함.
  • Non-Blocking receive : 메시지가 있으면 즉시 수신, 없으면 null 또는 실패 상태 반환하고 다음 작업 진행.

Pipe

두 프로세스간의 데이터 통신을 위한 관 역할. 데이터를 한쪽에서는 쓰고, 한쪽에서는 읽는다.

  • 기본 파이프는 단방향이다.(half-duplex) -> 한쪽은 쓰고 한쪽은 읽음.
  • full-duplex를 구현하기 위해서는 두개의 파이프가 필요하다.
  • 전통적인 파이프는 부모-자식 관계에서만 사용 가능(fork사용), 하지만 이름이 있는 파이프(named pipe)는 서로 다른 독립적인 프로세스끼리도 통신 가능.

ordinary Pipe

  • 생산자-소비자 모델 방식으로 통신을 할 수 있게 해준다.
  • 단방향
  • 두개의 file descipter가 반환된다. fork를 통해 자식이 생성되면 자식도 동일한 파일 디스크립터 배열을 복사해 가진다.
  • 즉, ordinary pipe는 부모-자식 프로세스간 통신만 가능하다. (프로세스가 끝나면 사라진다.)
  • 파이프는 커널이 관리하는 버퍼를 통해 간접적으로 데이터를 주고 받는 방식이다. (shared memory 아님, file descripter은 사용자 프로세스에 생성이 되지만 이것이 가르키고 있는 데이터는 커널 메모리에 존재하게 됨.)

Named pipe

파일 시스템에 이름으로 등록되는 파이프

  • 일반 파이프와는 달리 프로세스간의 관계가 없어도 통신 가능.
  • 지속성 : 프로세스가 종료돼도 파일로 남아있음. (ordinary는 사라짐)
  • FIFO임.

<Unix에서 Named Pipe>

  • 생성 : mkfifo("pipeName")
  • 사용 : open(), read(), write(), close()
  • 삭제 전까지 파일 시스템에 유지
  • half-duplex : 양방향은 어렵고 번갈아 사용.

<window에서 Named Pipie>

  • 생성 : createNameedPipe()
  • 사용 : ConnectNamedPipe(), ReadFile(), WriteFile()
  • 같은 컴퓨터 또는 네트워크 상 프로세스도 통신 가능.
  • full-duplex 지원

Unix Domain Socket

동일한 시스템 내에서만 작동하는 소켓 기반 통신 방식, 파일 시스템 내의 파일을 경로로 통신한다.

  • 양방향 통신 가능.
  • 스트림 또는 데이터그램 방식 선택 가능.
  • 네트워크가 아닌 로컬 IPC 용도로 최적화됨.
  • 오버헤드가 가장 크다.

profile
제가 관심있고 공부하고 싶은걸 정리하는 저만의 노트입니다.

0개의 댓글