운영체제(3) : 프로세스와 스레드

김두현·2024년 11월 3일
1
post-thumbnail

📍목차


  1. 프로세스의 개요
  2. 프로세스 제어 블록과 문맥 교환
  3. 프로세스의 연산
  4. 스레드
  5. 동적 할당 영역과 시스템 호출

1️⃣ 프로세스의 개요


✔️ 프로세스 제어 블록(PCB)

본 포스팅에서는 프로세스가 생성되고 완료되기까지의 과정을 살펴보고, 스레드의 역할을 프로세스와 비교하며 살펴본다.
이에 앞서, 프로세스의 처리 과정에 대해 알아보자.

프로세스란, 프로그램이 메모리에 올라와 실행 중인 상태이다.
운영체제가 프로그램을 메모리에 가져올 때, 프로세스 제어 블록(PCB)이라는 일종의 작업 지시서를 만든다.

즉, 어떤 프로그램이 프로세스가 되었다는 것은 운영체제로부터 PCB를 받았다는 것이다.

PCB는 운영체제가 해당 프로세스를 실행하기 위해 관리하는 데이터 구조이므로, 운영체제 영역에 만들어져 프로세스의 종료와 함께 PCB도 사라진다.

✔️ 프로세스의 상태

현대의 컴퓨터는 CPU가 여러 프로세스를 번갈아가며 실행하는 멀티 프로세싱 방식으로 동작한다.
따라서 CPU를 얻어 실행 중인 프로세스가 다른 프로세스에게 CPU를 넘겨주는 일이 자주 발생한다.

이에 따라 프로세스의 상태 또한 자주 바뀌게 되는데, 생성, 준비, 실행, 대기, 완료 중에 하나를 지니게 된다.
각 상태의 특징에 대해 알아보자.

1. 생성 상태

프로그램이 메모리에 올라오고, 운영체제로부터 PCB를 할당받은 상태다.
생성된 프로세스는 준비 상태로 전환되어 CPU를 기다린다.

2. 준비 상태

프로세스가 자신의 순서를 기다리는 상태로, 준비 큐에서 기다리며 CPU 스케줄러에 의해 관리된다.
다수의 준비 큐가 운영되며, 스케줄러는 PCB에 기록된 우선순위를 기준으로 실행 상태로 옮길 프로세스를 선택한다.

3. 실행 상태

CPU를 할당받아 실행되는 상태로, 자신에게 주어진 시간 동안만 작업할 수 있다.
주어진 시간을 타임 슬라이스라고 한다.

  • 디스패치
    CPU 스케줄러가 프로세스를 실행 상태로 옮기는 과정을 디스패치라고 한다.

실행 상태에 있는 프로세스가 입출력을 요청하면, 입출력 관리자에게 입출력을 요청하고 해당 프로세스를 대기 상태로 옮긴다.

4. 대기 상태

실행 상태의 프로세스가 입출력을 요청하면, 입출력이 완료될 때까지 기다리는 상태이다.
이 상태의 프로세스는 입출력장치별로 마련된 큐에서 기다린다.

입출력이 완료되면 인터럽트가 발생하고, PCB를 준비 상태로 이동시킨다.

5. 완료 상태

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

  • 코어 덤프
    만약 프로세스가 비정상적으로 종료된 경우, 복구를 위해 종료 직전의 메모리 상태를 저장장치로 옮기는데
    이를 코어 덤프라고 한다.

6. 휴식 상태와 보류 상태

위 다섯 가지 상태를 활성 상태라 하고, 프로세스의 상태는 활성 상태 외에 휴식 상태와 보류 상태가 있다.

  • 휴식 상태 : 프로세스가 작업을 일시적으로 쉬고 있는 상태
  • 보류 상태 : 프로세스가 메모리에서 쫓겨나 스왑 영역에 보관되는 상태
    • 메모리 공간 부족, 프로그램 오류, 악성 프로그램로 의심되는 경우 등에 보류 상태가 된다.

2️⃣ 프로세스 제어 블록과 문맥 교환


PCB에 대해 자세히 알아보자.
앞서 언급한 바와 같이, PCB는 프로세스를 실행하는 데 필요한 주요 정보를 보관하는 자료구조이다.
모든 프로세스는 고유의 PCB를 가진다.

✔️ PCB 구성

명칭역할
포인터PCB를 연결하여 준비 상태나 대기 상태의 큐를 구현한다.
프로세스 상태앞서 살펴본 프로세스의 상태 중 현재 어떤 상태에 있는지를 나타낸다.
프로세스 구분자프로세스 구분을 위한 구분자를 저장한다.
프로그램 카운터다음에 실행될 명령어의 위치를 가리키는 프로그램 카운터의 값을 저장한다.
각종 레지스터 정보누산기, 색인 레지스터, 스택 포인터 등을 포함하여 각종 중간 연산값을 저장한다.
메모리 관리 정보메모리 내 프로세스의 위치, 메모리 보호를 위한 경계 레지스터와 한계 레지스터 등을 저장한다.
할당된 자원 정보입출력 자원, 오픈 파일 등의 정보를 저장한다.
계정 정보계정 번호, CPU 할당 시간, CPU 사용 시간 등이 저장된다.
부모 프로세스 구분자, 자식 프로세스 구분자PPID, CPID를 통해 프로세스의 부모-자식 관계를 저장한다.

✔️ 문맥 교환

문맥 교환이란, CPU를 차지하던 프로세스가 나가고 새로운 프로세스를 받아들이는 작업을 말한다.

실행 상태에서 나가는 PCB에는 지금까지의 작업 내용을 저장하고, 실행 상태로 들어오는 PCB의 정보로 CPU를 세팅해야 이전의 작업에 이어서 작업을 할 수 있게 된다.
이와 같이 두 프로세스의 PCB를 교환하는 작업이 문맥 교환(context switching)이다.

이러한 문맥 교환에 걸리는 시간을 고려하여 타임 슬라이스를 정하게 되는데,
타임 슬라이스를 되도록 작게 설정하되 문맥 교환의 시간보다 짧게 설정하여 성능이 저하되는 것에 유의해야 한다.

3️⃣ 프로세스의 연산


✔️ 프로세스의 구조

프로세스는 코드 영역, 데이터 영역, 스택 영역, 힙 영역으로 구성되어 있다.

코드 영역과 데이터 영역은 프로세스 실행 직전에 위치와 크기가 결정되어 정적 할당 영역이라 부르며,
힙 영역과 스택 영역은 프로세스가 실행되는 동안 만들어지는 동적 할당 영역이다.

각 영역의 역할은 아래와 같다.

영역역할
코드 영역프로그램의 본문이 기술된 곳으로, 프로그래머가 작성한 프로그램이 탑재된다.
데이터 영역코드가 실행되면서 사용하는 변수나 파일 등의 데이터를 모아놓은 곳이다.
스택 영역함수 호출 후 되돌아올 위치를 저장하는 등 운영체제가 프로세스를 실행하기 위해 부수적으로 필요한 데이터를 모아놓은 곳이다.
힙 영역동적으로 할당되는 변수 영역으로, malloc() 등으로 인해 프로그램 실행 도중에 할당되는 데이터가 모인다.

✔️ fork() : 프로세스 복사

사용자가 프로그램을 실행하면 운영체제는 프로그램을 메모리로 가져와 코드 영역에 넣고 PCB를 생성한다.
이후 메모리에 데이터 영역과 스택 영역을 확보한 후 프로세스를 실행한다.

그러나 프로그램을 매번 하드디스크에서 가져오면 시간이 오래 걸리기 때문에,

기존 메모리에서 복사하여 자식 프로세스를 생성하는 fork() 시스템 호출을 통해 프로세스를 생성한다.

예를 들어 크롬에서 새로운 탭을 실행하면 크롬이 하나 더 실행되는데, 이는 기존의 크롬 프로세스를 복사하여 생성한 것이다.
이때 중요한 것은,

프로세스를 복사하면 기존 프로세스는 부모 프로세스가 되고, 새로운 프로세스는 자식 프로세스가 된다.

fork()의 장점

  1. 프로세스의 생성 속도가 빠르다.
    하드디스크에서 새로 가져오지 않고 기존 메모리에서 복사하기 때문에,
    자식 프로세스의 생성 속도가 빠르다.

  2. 추가 작업 없이 자원을 상속할 수 있다.
    부모 프로세스가 사용하던 모든 자원을 자식 프로세스에 상속할 수 있다.
    부모 프로세스가 파일 A를 초기화했다면, 자식 프로세스도 파일 A를 바로 사용할 수 있다.

  3. 시스템 관리를 효율적으로 할 수 있다.
    부모 프로세스와 자식 프로세스가 PPID와 CPID로 연결되어 있기 때문에,
    자식 프로세스를 종료하면 부모 프로세스가 자식이 사용하던 자원을 정리할 수 있다.

프로세스를 종료하면 사용하던 메모리 영역, 파일, 하드웨어를 정리해야 하는데, 이를 부모 프로세스에 맡김으로써 시스템이 효율적으로 관리된다.

✔️ exec() : 프로세스 전환

컴퓨터에서는 크롬 뿐만 아니라 카카오톡, PPT 등 다양한 프로세스를 실행하는데,
이를 위해선 기존의 프로세스를 새로운 프로세스로 전환하는 과정이 필요하다.

fork()를 통해 새로운 프로세스를 복사했다면, exec()를 통해 완전히 다른 프로세스로 전환된다.

이는 프로세스의 구조를 재활용하기 위함으로, 새로운 프로세스를 만들려면 PCB를 만든 후 메모리의 자리를 확보해야 한다.
또한 프로세스를 종료한 후 사용한 메모리를 청소하기 위해 부모-자식 관계를 만들어야 한다.

하지만 exec()를 통해 기존의 PCB, 메모리 영역, 부모-자식 관계를 그대로 사용할 수 있어 운영체제의 효율성이 향상된다.

exec() 동작 과정


exec()를 실행하면 코드 영역과 데이터 영역이 새로운 값으로 채워지고, 스택 영역이 비워진다. 또한 각종 레지스터와 사용한 파일 정보가 리셋된다.

fork()를 통해 할당된 PPID와 CPID는 그대로 유지된다.

✔️ 고아 프로세스와 좀비 프로세스

부모 프로세스는 자원을 회수하기 위해 자식 프로세스가 끝날 때까지 기다린다.
그런데 부모 프로세스가 먼저 종료되거나, 자식 프로세스가 비정상적으로 종료되어 부모 프로세스에게 종료했음을 통지하지 못하는 경우도 있다.

  • 고아 프로세스
    • 부모 프로세스가 자식 프로세스보다 먼저 종료되는 경우
  • 좀비 프로세스
    • 자식 프로세스가 종료되었지만 부모 프로세스가 뒤처리를 하지 않는 경우

고아 프로세스와 좀비 프로세스가 많아지면 자원이 정리되지 않고 그대로 남게 되어 운영 효율성이 떨어진다.
따라서 운영체제는 주기적으로 반환되지 못한 자원을 회수해야한다.

exit()와 return() 시스템 호출

main() 함수 마지막에 exit() 혹은 return()을 사용하는 이유는 부모 프로세스에게 자식 프로세스가 끝났음을 알리기 위함이다.

이를 통해 부모 프로세스는 자식 프로세스가 사용하던 자원을 빠르게 회수할 수 있고, exit() 함수는 전달하는 인자를 통해 자식 프로세스가 정상 종료(0)인지, 비정상 종료(-1)인지 알 수 있다.

wait() 시스템 호출

exit()return()을 통해 자식 프로세스의 종료를 알린다고 해도,
여전히 부모 프로세스가 먼저 종료되어 고아 프로세스가 발생할 수 있다.

wait() 시스템 호출은 자식 프로세스가 끝나기를 기다렸다가, 자식 프로세스가 종료되면 다음 명령을 실행한다.

4️⃣ 스레드


✔️ 스레드의 개념

프로세스의 작업 과정을 살펴보면, 운영체제가 코드와 데이터를 메모리에 올린 후 PCB를 생성한다.
이어서 작업에 필요한 메모리 영역을 확보한 후, 프로세스를 준비 큐에 삽입한다.

이렇게 프로세스가 생성되면 CPU 스케줄러는 프로세스가 해야 할 일을 CPU에게 전달하는데,

CPU 스케줄러가 CPU에 전달하는 일 하나가 스레드다.

즉, 운영체제 입장에서 작업 단위는 프로세스이고, CPU 입장에서 작업 단위는 스레드가 된다.

✔️ 멀티스레드

멀티태스킹의 단점

위에서 살펴본 바와 같이, 여러 개의 작업을 동시에 처리하기 위해 fork()를 통해 똑같은 프로세스를 생성했다.

그러나 문서 편집기를 여러 개 띄워 각기 다른 작업을 동시에 진행하는 경우,
fork()를 통해 프로세스를 복사하면 코드 영역과 데이터 영역의 일부가 메모리에 중복되는 낭비 요소가 존재하게 된다.

멀티스레드는 이러한 멀티태스킹의 단점을 제거하기 위해 사용된다.

비슷한 일을 하는 2개의 프로세스 대신, 코드, 데이터 등을 공유하면서 여러 개의 일을 하나의 프로세스 내에서 진행한다.

멀티스레드 구조

프로세스는 정적 할당 영역과 동적 할당 영역으로 나뉜다고 언급한 바 있다.
멀티스레드는 작업을 하면서 값이 바뀌거나 새로 생기는 동적 할당 영역만 여러 개 생성하고, 정적 할당 영역을 공유하는 형태로 자원의 효율성을 향상시킨다.

이러한 멀티스레드 구조는 아래와 같은 장단점을 갖는다.

  • 장점
  1. 응답성 향상 : 한 스레드가 실행 상태가 아니더라도 다른 스레드가 작업을 계속해 사용자에게 빨리 응답할 수 있다.
  2. 자원 공유 : 한 프로세스 내의 공유 자원을 모든 스레드가 공유하게 되어 작업을 원할하게 진행할 수 있다.
    또한 여러 개의 프로세스를 생성할 필요가 없어 불필요한 자원의 중복을 방지한다.
  • 단점
    멀티태스킹의 경우, 각 프로세스가 독립적이기 때문에 한 프로세스의 문제가 다른 프로세스로 전달되지 않는다.
    그러나 멀티스레드의 경우, 모든 스레드가 자원을 공유하기 때문에 한 스레드에 문제가 생기면 전체 프로세스에 영향을 미친다.

    예를 들어, 인터넷 익스플로러에서 문제가 있는 화면을 강제로 종료하면 모든 화면이 종료되는 경우이다.

✔️ 멀티스레드 모델

프로세스가 커널 프로세스와 사용자 프로세스로 나뉘듯이,
스레드도 커널 스레드와 사용자 스레드로 나뉘며, 사용자 스레드는 시스템 호출을 통해 커널 스레드를 사용할 수 있다.

이때 커널 스레드와 사용자 스레드의 대응 방식은 1 to N1\ to\ N 모델, 1 to 11\ to\ 1 모델, M to NM\ to\ N 모델로 분류된다.

1 to N 모델( = 사용자 스레드)

커널이 멀티스레드를 지원하지 않을 때 사용했던 초기의 스레드 시스템으로,
사용자 스레드가 커널의 스케줄링 기능, 동기화 기능을 대신 구현한다.

사용자 스레드가 직접 스케줄링하고 작업에 필요한 정보를 처리하기 때문에 문맥 교환이 필요없어 속도가 빠르다.

그러나 여러 개의 사용자 스레드가 하나의 커널 스레드와 연결되기 때문에 커널 스레드가 대기 상태에 들어가면 모든 사용자 스레드는 대기해야 한다.
커널 입장에서 각 사용자 스레드는 하나의 프로세스이기 때문에 일부만 대기 상태로 보낼 수 없기 때문이다.

또한 커널 스레드에서 공유 변수를 보호하는 장치가 없어 사용자 스레드에서 구현해야한다는 보안적 약점이 존재한다.

1 to 1 모델( = 커널 스레드)


커널이 멀티스레드를 지원하는 방식으로, 하나의 사용자 스레드가 하나의 커널 스레드와 연결된다.

이는 1 to N 모델과 반대되는 장단점을 가진다.

커널 스레드가 독립적으로 스케줄링되어 특정 스레드가 대기 상태여도 다른 스레드가 작업을 이어갈 수 있고, 변수 보호 기능이 존재한다.

다만 문맥 교환으로 인해 오버헤드가 발생할 수 있다.

M to N 모델( = 멀티레벨 스레드)

사용자 스레드와 커널 스레드를 혼합한 방식으로, 두 모델의 장단점을 모두 가지고 있다.

사용자 스레드의 개수가 커널 스레드의 개수보다 많거나 같게되며,
커널이 멀티스레드를 지원하여 사용자 스레드보다 유연한 작업이 가능하나, 여전히 문맥 교환 오버헤드가 발생하여 사용자 스레드보다는 느리다.

따라서 속도가 우선시되는 작업은 사용자 스레드로 작동하고,
안정성이 중요한 작업은 커널 스레드로 작동
한다.

👏 마무리


프로세스가 관리되는 방식과 스레드 개념에 대해 알아보았다.

다음 포스팅에서는, 본 포스팅에서 여러 차례 언급된 CPU 스케줄러가 어떠한 알고리즘이 있고 어떻게 동작하는지 알아보자.


참고 자료

쉽게 배우는 운영체제


💕오류 지적 및 피드백은 언제든 환영입니다. 복제시 출처 남겨주세요!💕
💕좋아요와 댓글은 큰 힘이 됩니다.💕
profile
I AM WHO I AM

2개의 댓글

관련 채용 정보