Computer Science 기본 지식/운영체제

[운영체제] 프로세스와 스레드

로파이 2021. 1. 13. 12:51

독학사- 운영체제 편을 공부하고 정리한 내용입니다.

프로세스 개념

 

커널에 등록되어 실행중인 프로그램을 의미

  • 디스크에 있던 프로그램을 메모리에 적재하여 운영체제의 제어를 받는 상태이다.
  • 프로세서 점유 시간, 메모리, 파일 같은 자원을 할당받는다.
  • 프로세스는 활동상태를 나타내는 프로그램 카운터와 프로세서 레지스터를 포함한다.
  • 별도의 주소공간을 가지며 코드, 데이터, 힙, 스택으로 분리된 메모리 공간을 가진다.

-  프로세스 메모리 구조

https://www.geeksforgeeks.org/memory-layout-of-c-program/

  • 코드(text segment): 실행 가능한 명령어들이 포함된 코드가 정의되어있다. 수정(write)이 불가능하다.
  • 초기화된 전역 변수(data segment): 초기값이 있는 전역 변수가 정의되어있다.
  • 초기화되지 않은 전역변수(BSS): 초기값이 없는 전역 변수가 정의되어있다.
  • 힙(heap): 실행 시간에 동적으로 할당되는 메모리 공간으로 낮은 주소부터 차례대로 할당받는다.
  • 스택(stack): 함수 내 사용되는 지역변수가 정의되는 공간으로 높은 주소부터 차례대로 할당받는다.

메모리 초과로 스택 영역이 힙 영역을 침범하게되면 스택 오버 플러우,

힙 영역이 스택 영역을 침범하게 되면 힙 오버플로우 오류로 인한 인터럽트가 발생한다.

 

- 윈도우 운영체제

운영체제는 실제 물리 메모리 주소가 아니라 가상 주소를 사용하고 운영체제 마다 메모리 할당 방식이 다르다. "알려진" 메모리 구조는 높은 주소에서 부터 스택, 낮은 주소부터 힙 영역이 할당된다고 생각되는데, 실제 Visual Studio IDE에서 주소를 출력하면 조금 다른 양상이 나타난다.

힙 메모리는 스택보다 높은 가상 주소를 가지며 위로 증가한다. 또한 스택은 낮은 주소부터 할당되어 아래로 증가하게 된다. 이로 써 윈도우 운영체제에서는 힙과 스택이 충돌하는 일을 막고 있는 듯 하다.

초기화되지 않은 전역 변수 a, 초기화된 b 전역 변수가 낮은 주소부터 할당되어 있다.

"지역변수" c는 100개짜리 int 형 배열의 첫 번째 원소 주소를 가리키는 포인터 지역 변수이다.

따라서 실제 배열은 heap 영역에 동적으로 할당되어 있기 때문에 c [0]는 heap영역에 c는 지역변수이므로 stack영역에 정의되어 있다.

d는 100개짜리 int 형 배열이다. 그 의미는 d [0] 주소와 d주소는 같고 배열은 stack영역에 할당되어 있다.

 

--- 관련 글

stackoverflow.com/questions/12687274/size-of-stack-and-heap-memory

또다른 예시

#include <stdlib.h>
#include <stdio.h>

void check(int depth) {
    char c;
    char* ptr = (char*) malloc(1);
    printf("stack at %p, heap at %p\n", &c, ptr);
    if (depth <= 0) return;
    check(depth - 1);
}

int main() {
    check(10);
    return 0;
}

실행 결과

프로세스 상태

 

프로세스 상태 전이도

- 프로세스 상태와 전이

상태 설명 상태변화
생성(New) 프로세스 생성에 대한 허락을 받고 메모리와 프로세스 제어 블록을 할당 받는 중인 상태
준비 상태가 되기 위한 임시 상태이다.
New -> Ready: 운영체제로 부터 실행 허락을 받아 프로세스를 생성한다.
준비(Ready) 메모리 할당이 완료되고 CPU 스케줄로부터 선택되어 실행 차례를 기다리는 상태 Ready -> Running / dispatch(PID): CPU 스케줄러가 해당 프로세스를 실행한다. 
실행(Running) CPU를 할당받아 커널에 등록되어 실행되는 상태 Running -> Ready / timeout interrupt: 프로세스 마다 할당 받은 실행 시간을 초과하여 timeout 인터럽트로 인한 준비 상태로 전이된다.
Running -> Wait / block(PID): 입출력 요청으로 해당 프로세스를 잠시 대기한다.
대기(Wait) 입출력 인터럽트로 잠시 대기하는 상태
입출력 작업이 완료되면 다시 준비 상태가 된다.
Wait -> Ready / wakeup(PID): 입출력 완료로 인터럽트가 발생하고 대기 상태에 있던 프로세스를 준비 상태로 전이한다. 
완료(Terminated) 프로세스가 종료된 상태
할당받은 메모리를 해제하고 프로세스 제어 블록을 삭제한다.
Running -> Terminated
exit(status): 실행을 완료하고 정상적으로 종료한다.
abort(status): 오류나 다른 프로세스에 의해 비정상적으로 종료한다.

- Wait -> Suspended Wait / Ready -> Suspended Wait

Suspended는 중지되었음을 의미하고 프로세스가 할당받은 메모리가 해제된 상태이다.

이와 같은 상태 전이는 다른 우선순위가 높은 프로세스가 스케줄링되어 현재 실행 중인 프로세스를 중단함으로써 발생한다.

다시 메모리를 할당받아 실행될 수 있으므로 종료 상태와 구별된다.

 

프로세스 제어 블록 (Process Control Block)

프로세스를 실행하는데 필요한 중요한 정보를 보관한다.

PCB

  • 포인터: 프로세스를 가리키는 포인터, 부모/자식 프로세스 포인터, 할당받은 메모리 위치에 대한 포인터, 기타 할당받은 자원에 대한 포인터
  • 프로세스 상태: 생성, 준비, 실행, 대기 등의 상태
  • 프로세스 구분자: 프로세스 고유 식별 번호
  • 프로세스 카운터: 다음 실행될 명령어의 위치
  • 프로세스 우선순위: 스케줄링 정보와 해당 프로세스 우선순위
  • 레지스터 정보: 실행 중 사용되는 누산기, 색인 레지스터, 스택 포인터
  • 메모리 관리 정보: 메모리 위치 정보, 보호를 위한 경계 레지스터, 한계 레지스터 값, 세그먼테이션 테이블 및 페이지 테이블
  • 할당된 자원 정보: 입출력 자원이나 열려있는 파일 등
  • 계정 정보: 계정 정보, CPU 할당 시간, 사용 시간 등
  • 부모/자식 프로세스 구분자: 부모 프로세스의 구분자 PPID와 자식 프로세스의 구분자 CPID

문맥 교환 (Context Switching)

프로세서(CPU)가 현재 프로세스에서 사용되지 않을 때 다른 프로세스로 교체하여 프로세서를 쉬지 않게한다. 

  • 실행 중이던 프로세스를 교체하는 것을 의미한다.
  • 대기 상태에 있는 프로세스를 교체함으로써 프로세서 유휴 시간을 줄여 작업 처리량과 CPU 사용률을 높인다.
  • 지금까지의 작업 내용(PCB)을 저장하고 들어오는 프로세스 제어 블록 내용으로 CPU가 세팅된다.

Context Switching

  • 실행 중인 프로세스 P0가 타임아웃/입출력 인터럽트나 다른 시스템 호출로 대기 상태가 되는데, 이때 작업 내용을 PCB0에 저장한다.
  • 프로세스 P1를 실행하기 위해 PCB1 내용을 CPU 레지스터로 불러오고 P1을 실행한다.
  • 프로세스 P1이 똑같이 대기 상태로 돌입하고 작업내용을 PCB1에 저장한다.
  • PCB0에 있던 내용을 CPU 레지스터에 불러오고 P0를 실행한다. 

스레드

  • 프로세스의 실행 단위
  • CPU에 작업을 요청하는 실행 단위
  • 스레드 ID, 프로그램 카운터, 레지스터 정보, 스택으로 구성된다.
  • 프로세스 내에서 코드, 데이터, 힙 공간을 공유하기 때문에 불필요하게 자원을 생성하거나 복제하는 것을 방지한다.
  • 스레드마다 독립적인 실행 흐름을 가지기 때문에 함수 호출과 지역변수를 위한 별도의 스택 공간이 필요하고 실행 흐름을 기억하기 위해 PC와 레지스터 또한 필요하다. 

 

멀티 스레드

멀티 스레드

- 멀티 스레드의 특징

  • 멀티 스레드: 프로세스의 모든 작업을 순서대로 실행하지 않고 작은 단위의 스레드로 작업을 분할하여 부담을 줄이는 운영 기법이다. 멀티스레드 간 프로세스에 할당된 공유 자원은 공유하고 독립적인 실행 흐름으로 별도로 할당된 자원을 사용할 수 있다.
  • 멀티 태스킹: 운영체제가 CPU에 작업을 할당할 때 시간을 잘게 나누어 배분하는 기법이다. 여러 스레드에 시간을 잘게 나누어 주는 시스템을 시분할 시스템이라고하고 운영체제는 CPU에게 프로세스 대신 스레드를 할당한다.
  • 멀티 프로세싱: 스레드 간 공유 자원 문제 가능성이 적은 경우, 즉 병렬로 처리해도 되는 작업일 경우 다중 CPU를 사용하여 여러 스레드를 병렬적으로 처리할 수 있다.
  • CPU 멀티 스레드: 하드웨어적인 방법으로 하나의 CPU에서 여러 스레드를 동시에 처리하는 병렬 처리 기법이다. 

※ 멀티 프로그래밍(다중 프로그래밍): 초기 CPU에는 하나의 프로그램만 실행할 수 있는데, 그 프로그램이 입출력 처리로 CPU를 사용하지 않는 유휴 시간이 발생한다. 따라서 여러 프로그램을 메모리에 적재해 하나의 프로그램을 실행하다 CPU 유휴 시간 발생 시 다른 프로세스를 실행하고 프로세서의 사용률을 높일 수 있다.

 

- 용어

  • Single point of failure: 하나의 스레드에서 실패로 오류 종료 시 프로세스에 포함된 전체 스레드가 강제 종료된다.
  • Shared data: 스레드의 공유 자원에 대한 병렬적 접근에 따라 공유 자원에 대한 동기화 문제가 있다.

- 멀티 프로세스를 사용하지 않는 이유

  • 시분할 시스템, 프로세스나 스레드에 사용 시간이 할당되는 만큼 실행하고 교체되는 시스템에서 프로세스 교체 비용이 스레드 교체 비용보다 비싸기 때문이다.
  • 스레드는 한 프로세스 내에서 코드, 데이터, 힙 공간을 공유하고 운영체제의 제어 신호 및 열린 파일 자원 등을 공유할 수 있으므로 자원을 효과적으로 사용할 수 있다.
  • 프로세스 간 통신(IPC)을 위해 운영체제 자원(PIPE)을 이용해야 하고 과정이 복잡하다.

예를 들면, 웹 서버-클라이언트의 관계에서 클라이언트가 웹 페이지를 요구할 때마다 서버 프로그램에서 새로운 프로세스를 할당하거나 복제한다면 열려있는 소켓이나 웹 페이지 자원 등이 새로운 프로세스에 생성된다.

이 객체들에 대한 자원은 클라이언트마다 공유 가능하므로 클라이언트 요구마다 새로운 스레드를 생성하여 자원을 공유하고 멀티 태스킹을 통한 빠른 응답 시간, 즉 실시간성을 제공할 수 있다.

 

스레드 분류

- 사용자 레벨 스레드

  • 초기 운영체제가 멀티 스레드를 지원하지 않을 때 사용되었다.
  • 사용자가 직접 멀티 스레드 관련 스케줄링, 동기화 함수를 구현해서 멀티 스레드를 사용한다.
  • 스레드 문맥 교환만 일어나므로 비용이 싸고 빠르다.
  • 스레드 하나가 대기되면 프로세스 내 모든 스레드가 대기한다. 
  • 다른 CPU의 스케줄러를 사용할 수 없으므로 여러 CPU를 사용할 수 없다.

- 커널 레벨 스레드

  • 스레드마다 커널이 제공하는 독립적인 운영체제 기능을 사용할 수 있다.
  • 다중 CPU를 이용한 멀티 프로세싱이 가능하다.
  • 프로세스 문맥 교환이 필요하다면 비용이 비싸고 느리다.