프로세스와 스레드

@Wonyu· December 28, 2024 · 19 min read

프로세스란?

프로그램과 프로세스의 차이

프로그램은 어떤 데이터를 사용하여 어떤 작업을 할지 그 절차를 정의한 것이다. 따라서 저장장치에 저장되는 정적인 상태다.

반면 프로세스는 정의된 절차를 실행에 옮기는 것으로 메모리에 올라온 동적인 상태다. 그렇다면 프로그램이 프로세스로 전환되려면 어떤 과정을 거쳐야할까?

프로그램에서 프로세스로의 전환

가장 먼저 운영체제는 프로그램을 메모리의 적당한 위치로 가져온다. 그와 동시에 프로세스 제어 블록(PCB)을 만든다. PCB에는 프로세스를 처리하는 데 필요한 다양한 정보가 들어있어 프로그램이 프로세스로 전환되는 데 꼭 필요하다.

프로세스 제어 블록

  • 개념: 운영체제가 각 프로세스를 관리하기 위해 사용하는 데이터 구조
  • 주요 정보

    • PID: 프로세스를 구분하는 ID
    • 메모리 관련 정보: 프로세스의 메모리 위치 정보
    • 각종 중간값: 시분할 시스템에서는 여러 프로세스가 번갈아 가며 실행되기 때문에 다음에 작업해야할 코드의 위치가 담긴 프로그램 카운터가 저장된다.
    • 우선순위 정보: 프로세스의 스케줄링 순서를 결정한다.
  • 주요 역할

    • 프로세스 상태 관리
    • 스케줄링 지원
    • Context Switching

프로세스의 상태

이전 기록에서 다룬 일괄 작업 시스템의 경우 하나의 작업이 모두 끝난 후 다른 작업이 수행된다. 따라서 프로세스 상태는 생성, 실행, 완료 중 하나가 된다.

하지만 시분할 시스템은 좀 더 복잡하다. 프로세스의 작업이 끝나지 않아도 CPU를 넘겨주는 일이 빈번하기 때문이다. process status 생성 상태 생성 상태는 프로그램이 올라오고 PCB를 할당받은 상태다. 생성된 프로세스는 바로 실행되는 것이 아닌 준비 상태에서 본인 순서를 기다린다.

준비 상태 준비 상태는 실행 순서를 기다리는 상태다. PCB는 Ready Queue에서 기다리며 CPU 스케줄러에 의해 관리된다.

실행 상태 실행 상태는 프로세스가 CPU를 할당받아 실행되는 상태다. 자신에게 주어진 시간, 즉 타임 슬라이스 동안만 작업이 가능하다. 타임 슬라이스 동안 작업이 완료되면 정상 종료되고, 작업을 끝내지 못하면 클록이 보낸 인터럽트에 의해 timeout이 발생해 다시 준비 상태로 돌아간다.

대기 상태 대기 상태는 실행 상태에서 입출력을 요청한 프로세스가 입출력 완료를 기다리는 상태다. 입출력이 완료되면 인터럽트가 발생해 준비 상태로 전환된다 .

완료 상태 완료 상태는 프로세스가 종료되는 상태다. 코드와 사용한 데이터를 메모리에서 삭제하고 PCB는 폐기한다.


Context Switching

Context Switcing은 CPU가 현재 실행 중인 프로세스의 상태(Context)를 저장하고, 다음에 실행할 프로세스의 상태를 복원하여 작업을 전환하는 과정이다.

절차

  1. 실행 중인 프로세스 상태 저장
  2. 실행 중인 프로세스의 상태(Context)를 PCB(Process Control Block)에 저장한다.
  3. 저장되는 정보:

    • 프로그램 카운터(PC): 다음에 실행할 명령어의 주소.
    • CPU 레지스터 상태: 계산 중이던 데이터, 연산 상태 등.
    • 메모리 관리 정보: 프로세스가 사용하는 메모리 주소 정보.
  4. 새 프로세스의 상태 복원
  5. 실행할 프로세스의 PCB에서 상태(Context)를 복원한다.
  6. 복원되는 정보:

    • 프로그램 카운터(PC).
    • CPU 레지스터 값.
    • 메모리 정보.
  7. 프로세스 전환
  8. CPU가 새 프로세스의 명령어 실행을 시작한다.

Context Switching은 위처럼 일반적으로 타임 슬라이스가 만료되었을 때 발생한다. 하지만 다음과 같은 상황에서도 발생한다.

  • 입출력 요청
  • 우선순위 스케줄링
  • 인터럽트 발생

타임 슬라이스의 크기

위 Context Switching이 너무 빈번하게 일어나면, 시스템 성능이 떨어질 것이다. 따라서 타임 슬라이스의 크기를 정하는 것도 중요하다.

만약 타임 슬라이스 크기가 너무 크다면, 작업 전환까지 시간이 너무 오래 걸려 끊겨 보일 것이다. 비디오 플레이어와 워드프로세서를 동시에 실행한다고 해보자. 타임 슬라이스가 너무 크다면, 비디오가 끊겨 보이고, 워드프로세서의 반응 속도도 매우 느릴 것이다.

타임 슬라이스를 너무 작게 설정한다면, 위에서 말한 대로 시스템의 성능이 떨어진다. Context Switching에 5ms가 걸린다고 가정해보자. 만약 타임 슬라이스를 1ms로 설정한다면, 실제 작업을 못 하는 문제가 발생할 것이다.

결론적으로 타임 슬라이스는 되도록 작게 설정하되 Context Switching에 걸리는 시간을 고려해 적당한 크기로 설정하는 것이 중요하다.


프로세스의 연산

이번엔 프로세스가 어떻게 생성되고 분리되는지 알아보자.

프로세스의 구조

코드 영역: 프로그램의 실행 코드(명령어)가 저장되는 영역으로 CPU가 실행할 기계어로 번역된 명령어들이 포함

  • 읽기 전용으로 처리된다. 데이터 영역: 코드가 실행되면서 사용하는 변수나 파일 등 각종 데이터가 저장되는 영역
  • 읽기/쓰기로 처리된다. 스택 영역: 함수 호출 시 지역 변수, 매개변수, 반환 주소 등이 저장되는 영역
  • 사용자에게는 보이지 않는다.

fork() 시스템 호출

개념: fork()는 실행중인 프로세스(부모 프로세스)로부터 새로운 프로세스(자식 프로세스)를 생성한다.

동작 방식:

  1. 프로세스 복사:

    • 부모 프로세스의 메모리 공간(텍스트, 데이터, 힙, 스택)을 그대로 복사하여 자식 프로세스를 생성.
  2. 프로세스 ID(PID):

    • fork()는 자식 프로세스에서는 0을 반환, 부모 프로세스에서는 자식의 PID(Process ID)를 반환.
  3. 독립적인 실행:

    • 부모와 자식 프로세스는 서로 독립적으로 실행되며, 서로 영향을 미치지 않음.
    • 각 프로세스는 자신만의 PCB(Process Control Block)를 가짐.

장점

  • 프로세스의 생성 속도: 새로 생성하는 것보다 생성 속도가 빠르다.
  • 추가 작업 없이 자원 상속: 부모 프로세스가 사용하던 모든 자원을 추가 작업 없이 상속할 수 있다.
  • 시스템 관리 효율: 부모 프로세스와 자식 프로세스가 구분자로 연결되어 있기 때문에 자식 프로세스를 종료하면 부모 프로세스가 정리할 수 있다.

exec() 시스템 호출

개념: exec()는 기존 프로세스를 새로운 프로세스로 전환하는 함수다.

동작 방식:

  1. 현재 프로세스 대체:

    • exec()는 호출된 프로세스의 기존 코드, 데이터, 힙, 스택을 모두 제거하고, 새로 지정된 프로그램으로 메모리 내용을 교체합니다.
  2. 프로세스 ID 유지:

    • exec()는 새로운 프로그램을 실행하지만, 프로세스 ID(PID)는 변경되지 않습니다.
    • 즉, 프로세스의 껍데기는 그대로 유지된 상태에서 실행 코드만 변경됩니다.
  3. 새로운 프로그램 실행:

    • 새로운 프로그램의 실행이 시작되며, 기존 프로그램의 흐름은 완전히 종료됩니다.

exec()를 호출하면, fork()와 같이 새로운 프로세스가 생성되는 것이 아닌 기존 프로세스가 새로운 프로세스로 대체된다. 따라서 기존 프로세스의 PID를 유지한다.

fork()와 exec()

위 내용을 요약하면, fork()는 부모 프로세스를 복사해 효율적으로 새로운 프로세스를 생성하고, exec()는 기존 프로세스와 다른 새로운 프로세스로 전환할 수 있게 한다.

이러한 프로세스의 복사와 전환은 프로세스의 생성 과정과 계층 구조를 이해하는 데 큰 도움이 된다.

프로세스의 계층 구조

유닉스의 예를 통해 프로세스의 계층 구조를 살펴보자. process hierarchy 유닉스에서 커널이 처음 부팅되면, 커널 관련 프로세스를 여러 개 만드는데 그 중 init 프로세스는 전체 프로세스의 출발점이 된다. fork()와 exec() 시스템 호출을 이용해 자식 프로세스를 만드는 것이다.

장점

  1. 관리 용이성:

    • 트리 구조를 통해 프로세스의 부모-자식 관계를 명확히 하고, 고아 프로세스나 좀비 프로세스를 효율적으로 관리.
  2. 계층적 접근:

    • 부모-자식 관계를 기반으로 프로세스 상태를 추적하거나 자원을 할당.
  3. 자원 분리:

    • 부모와 자식 프로세스는 독립적인 메모리와 실행 흐름을 가지므로, 안전하고 안정적인 운영 가능.
  4. 유연한 작업 분배:

    • 부모 프로세스가 주요 작업을 관리하고, 자식 프로세스가 세부 작업을 분담.

고아 프로세스: 자식 프로세스가 종료되기 전 부모 프로세스가 먼저 종료될 경우 자식 프로세스는 고아 프로세스가 된다. 좀비 프로세스: 자식 프로세스가 종료되었는데도 부모 프로세스가 뒤처리를 하지 않을 때 발생한다.

위 고아 프로세스와 좀비 프로세스가 많아지면, 자원이 낭비되기 때문에 효율적인 운영에 방해가 된다. 따라서 운영체제는 주기적으로 반환되지 못한 자원을 회수해야 한다.


스레드

오늘날의 운영체제는 프로세스의 낭비 요소를 제거하고 프로세스 작업의 유연성을 얻기 위해 멀티스레드를 사용한다.

개념

스레드는 프로세스의 코드에 정의된 절차에 따라 CPU에 작업 요청을 하는 실행 단위다. 운영체제 입장에서 작업 단위는 프로세스이고, 프로세스가 생성되고 CPU 스케줄러가 CPU에게 전달하는 작업하나가 스레드다. 즉, CPU 입장에서 작업 단위는 스레드인 것이다.

프로세스와 스레드의 차이

구분 프로세스 스레드
실행 단위 독립적인 실행 단위. 프로세스 내의 실행 단위.
자원 관리 프로세스는 독립된 메모리 공간(코드, 데이터, 힙, 스택)을 가짐. 같은 프로세스 내의 스레드끼리 메모리와 자원 공유.
통신 방식 프로세스 간 통신(IPC) 필요. 프로세스 내 스레드는 직접 메모리 접근 가능.
오버헤드 생성 및 전환 시 오버헤드 큼. 생성 및 전환 시 오버헤드 작음.
독립성 프로세스는 서로 독립적. 같은 프로세스 내 스레드는 독립적으로 실행되지만, 자원 공유로 인해 영향을 받음.

스레드는 프로세스라는 개념이 만들어진 한참 후에 만들어진 개념이다. 초기의 운영체제에서는 작업의 단위가 프로세스 하나뿐이었다. 당시엔 1개의 스레드만 가진 프로세스만 있는 셈이었기 때문에 구분할 필요가 없던 것이다.

CPU와 프로그래밍 기술이 발전하면서 여러 개의 코어를 가진 CPU가 생겨나 멀티스레드를 지원하기 시작했다. 따라서 프로세스를 다양한 스레드로 나눠 여러 코어에 배분함으로써 시스템의 효율을 높이게 된 것이다.

멀티스레드

멀티스레드는 하나의 프로세스에서 여러 스레드가 병렬로 실행되도록 하는 기술이다.

과거에는 프로세스 하나에 스레드가 하나였기 때문에 여러 작업을 하려면 fork() 시스템 호출로 여러 개의 프로세스를 만들어야 했다. 하지만 오늘날에는 하나의 프로세스에 여러 스레드를 만들어 사용한다.

프로세스의 데이터는 정적인 영역과 동적인 영역으로 나뉜다. 동적인 영역의 대표적인 예는 레지스터 값, 스택 등이 있다. 멀티스레드 환경에선 여러 스레드들이 정적인 영역을 공유하고, 독립적으로 동적 영역을 가진다.

장점

  • 응답성 향상: 여러 스레드를 활용하므로 사용자의 작업 요구에 빨리 응답할 수 있다.
  • 자원 공유: 프로세스가 가진 자원을 모든 스레드가 공유하게 되어 원활하게 작업을 진행할 수 있다.
  • 효율성 향상: 여러 개의 프로세스를 생성할 필요가 없어 불필요한 자원의 중복을 막는다.
  • 다중 CPU 지원: 2개 이상의 CPU를 가진 컴퓨터에서 효율적으로 CPU를 이용할 수 있다.

단점

  • 동기화 문제: 여러 스레드가 동일한 자원(변수, 데이터 구조)에 접근할 때, 데이터 충돌이나 비정상 동작이 발생할 수 있다.
  • 디버깅의 어려움: 스레드는 병렬로 실행되므로 실행 순서를 예측하기 어렵고, 디버깅 및 테스트가 복잡하다.
  • 데드락: 여러 스레드가 서로가 보유한 자원을 기다리며 영원히 정지 상태에 빠지는 문제가 발생할 수 있다.
  • 하나의 스레드 오류가 전체 프로세스에 영향: 같은 프로세스 내의 스레드들은 메모리를 공유하므로, 하나의 스레드가 비정상 종료되면 다른 스레드와 프로세스 전체에 영향을 줄 수 있다.
@Wonyu
Hello :) I'm Wonyu