프로그램, 프로세스, 스레드에 대해서 알아보자

개발하는 곰댕이·2021년 6월 24일
1

OS

목록 보기
3/5
post-custom-banner

1. 프로그램이란?

프로그램이란 특정 작업을 수행하기 위한 명령어들의 집합체로써
코드 덩어리로써 아직 메모리에 로드되지 않고 저장장치에 저장되어있는 상태를 말합니다.
한마디로 실행파일을 의미한다고 말 할 수 있을 것 같습니다.

2. 프로세스란?

우선 프로그램을 실행되는 과정을 간단하게 봐 봅시다.

  1. 컴퓨터에게 프로그램 실행을 요청합니다. (실행파일을 실행합니다.)
  2. 컴퓨터는 프로그램의 정보를 읽고 프로그램이 메모리를 사용할 수 있게 할당해 줍니다.
  3. 프로그램의 정보들이 메모리에 로드되고, 프로그램이 실행됩니다.

이런 과정을 거치게 되는데
프로세스는 이런 과정에서 프로그램 실행요청을 한 이후의 상태를 말합니다.

프로세스가 할당받는 메모리의 구조는 [ 메모리 영역은 어떤 식으로 구성되어 있을까? ]에 정리해 두었으니 한번 보고오시면 좋을 것 같습니다.

일반적으로 CPU는 하나의 프로세스만 관리할 수 있습니다.
그런데 우리는 음악을 들으면서 게임을 할 수도 있고, 인터넷도 할 수 있죠.

어떻게 그렇게 할 수 있을까요??

바로 여러개의 프로세스를 아주 빠르게 번갈아 가면서 실행시켜서 동시에 실행하는 것 처럼 보이게 만드는 겁니다.
좀 더 자세히 알아보기 위해서 프로세스의 구성과 관리에 대해서 알아보도록 합시다.

프로세스의 구성

프로세스에 대한 정보는 프로세스 제어블록(PCB, Process Control Block)또는 프로세스 기술자(process descriptor)라고 불리는 자료구조에 저장됩니다.
PCB의 구성은 다음과 같습니다.

  • PID

    운영체제가 각 프로세스를 식별하기 위해 부여된 프로세스 식별번호(PID, Process IDentification)입니다.

  • 프로세스 상태

    CPU는 프로세스를 빠르게 교체하면서 실행하기 때문에 실행중인 프로세스도 있고 대기 중인 프로세스도 있습니다.
    그런 프로세스의 상태를 저장합니다.
    프로세스 상태는 생성, 준비, 실행, 대기, 종료로 나누어져 있습니다.

  • 프로그램 카운터

    CPU가 다음으로 실행할 명령어를 가리키는 값입니다. CPU는 기계어를 한 단위씩 읽어서 처리하는데 프로세스를 실행하기 위해 다음으로 실행할 기계어가 저장된 메모리 주소를 가리키는 값입니다.

  • 스케쥴링 우선순위

    운영체제는 여러개의 프로세스를 동시에 실행하는 환상을 제공합니다.
    운영체제가 여러 개의 프로세스가 CPU에서 실행되는 순서를 결정하는 것을 스케줄링이라고 합니다.
    이 스케줄링에서 우선순위가 높으면 먼저 실행될 수 있는데 이를 스케줄링 우선순위라고 합니다.

  • 권한

    프로세스가 접근할 수 있는 자원을 결정하는 정보입니다.
    안드로이드 앱을 예로 들면 아무 앱이나 휴대폰 통화내역을 볼 수 있는 권한을 가지면 이를 악의적으로 이용하는 앱이 등장하겠죠? 그래서 프로세스마다 어디까지 접근할 수 있는지에 대한 권한이 필요합니다.

  • 부모, 자식 프로세스의 정보

    최초로 생성되는 init 프로세스를 제외하고 모든 프로세스는 부모 프로세스를 복제해서 생성되고 이 계층관계는 트리를 형성합니다.
    그래서 각 프로세스는 자식 프로세스와 부모프로세스에 대한 정보를 가지고 있습니다.

  • 프로세스의 데이터와 명령어가 있는 메모리 위치를 가리키는 포인터

    프로세스는 실행중인 프로그램입니다.
    따라서 프로그램에 대한 정보를 가지고 있어야하고, 프로그램에 대한 정보는 프로세스가 메모리에 가지는 자신만의 주소 공간에 저장됩니다.
    이 공간에 대한 포인터 값을 가집니다.

  • 프로세스에 할당된 자원들을 가리키는 포인터

  • 실행문맥

    프로세스가 실행상태에서 마지막으로 실행한 프로세서의 레지스터 내용을 담고 있습니다.
    CPU에 의해 실행되는 프로세스는 운영체제에 의해 계속 교체되는데 교체되었다가 다시 자신의 차례가 되어서 실행될때 중단된적 없고 마치 연속적으로 실행된것처럼하기 위해 이 레지스터 정보를 가지고 있습니다.

출처 : [운영체제] 프로세스가 뭐지?

프로세스의 관리

프로세스의 상태는 위 내용처럼 생성, 준비, 실행, 대기, 종료가 있습니다. 프로세스는 이 상태들의 반복으로 실행되며 관리되고 있습니다.

각 상태의 정보는 아래와 같습니다.

  • 생성(create or new)

    프로세스 자체는 생성이 되었지만 아직 메모리에 적재되지 않은 상태를 말합니다.
  • 준비(ready)

    프로세스가 메모리에 적재되고, CPU의 할당을 기다리는 상태입니다.
  • 실행(running)

    프로세스가 CPU를 할당받고 명령어를 실행하고 있는 상태입니다.
  • 대기(waiting or Block)

    실행 상태에 있던 프로세스가 특정 이벤트에 의해 실행이 중지되어 멈춰있는 상태입니다.
  • 종료(terminated)

    프로세스의 작업이 모두 끝나 종료된 상태입니다.

이렇게 각 상태의 내용을 알아봤는데 각 상태들이 특정 이벤트로 인해 다른 상태로 변화할때 우리는 이 것을 프로세스의 상태전이라고 합니다.
상태전이의 정보는 아래와 같습니다.

  • admitted

    해당 프로세스를 실행해도 된다고 승인합니다.

  • scheduler dispatch

    보통 dispatch라고도 하며 준비상태에 있던 프로세스가 CPU를 할당받아 실행되는 것을 말합니다.
    이 작업은 디스패처(dispatcher)가 수행하게 됩니다.

  • interrupt

    실행 중이던 프로세스가 특정 이벤트로 인해서 다시 준비상태로 돌아가는 것을 말합니다.
    프로세스가 할당된 시간 내에 모든 명령어를 수행하지 못하면 넘어가는 Time out등이 있습니다.

  • I/O or event wait

    CPU가 프로세스를 실행 중에 있을 때 급작스런 이벤트에 의해서 CPU가 다른 프로세스를 할당하게 될때 현재 실행중인 프로세스는 대기상태로 넘어가는 상태를 말합니다.

  • I/O or event completion

    급작스런 이벤트에 의한 CPU할당을 받는 프로세스의 실행이 끝나면 대기상태에 있던 프로세스는 다시 준비상태로 넘어가게 되는 상태를 말합니다.

  • exit

    프로세스의 모든 명령이 실행되고 종료되는 상황을 말합니다.

출처 : 운영체제 #1_ 스레드와 프로세스, 멀티프로그래밍,멀티태스킹

상태 전이들을 보기 편하고 이해하기 쉽게 마인드맵으로 정리해 봤습니다.

프로세스의 특징

  • 각 프로세스는 독립된 메모리를 할당받습니다.
    • 독립되어 있기 때문에 일반적으로 프로세스는 다른 프로세스의 메모리 영역을 참조할 수 없습니다.
    • 다른 프로세스의 메모리를 참조하고 싶다면 프로세스간의 통신(Inter-Process Communication, IPC)를 통해서 가능하게 됩니다.
      ex ) 파이프, 유닉스 시그널 등
  • 기본적으로 프로세스는 하나 이상의 스레드를 가지게 됩니다.

3. 스레드

스레드는 프로세스 내부에서 동작하는 흐름의 단위입니다.
좀 더 설명해 보자면 스레드는 메인 함수처럼 모든 함수를 지정해서 사용할 수도 있고, 자식 스레드를 만들어서 특정 함수들만 스레드로 사용되게 할 수도 있는겁니다.
간단한 예시를 들어보자면 게임이라는 프로세스 내에서 캐릭터, 몬스터 등이 스레드가 됩니다.
여기서 이 스레드들은 각각 움직이거나 먹거나 공격하는 등 다양한 움직임을 하게 할 수도 있고, 움직이게만 할 수도 있는 등 범위를 정할수도 있죠.
이렇게 우리는 여러개의 스레드를 만들어서 여러가지 동작을 한번에 할 수 있는 것 처럼 만들 수 있습니다.

스레드의 특징

  • 스레드는 프로세스가 할당받은 자원 중 Stack 영역만 따로 할당을 받고 나머지 Code, Data, Heap 영역은 공유자원으로써 모든 스레드가 접근할 수 있습니다.

4. C언어 : pthread함수를 통해 스레드 조작하기

C언어에서는 pthread.h안에 있는 pthread함수들을 통해서 스레드를 만들거나, 조작할 수 있습니다.
그럼 pthread에는 어떤 함수들이 있는지 자주 사용되는 것들 위주로 알아보겠습니다.

pthread_create

  • 함수 원형

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
void *(*start_routine)(void *), void *arg);
  • 용도

    스레드를 생성합니다.

  • 매개 변수

    • 첫 번째 매개변수
      해당 변수에 스레드의 식별자가 저장됩니다.

    • 두 번째 매개변수
      thread의 옵션을 지정할 때 사용됩니다. 기본적인 스레드의 옵션을 사용할 거라면 NULL을 사용합니다.

    • 세 번째 매개변수
      pthread를 이용해서 분기 할 함수입니다. 만들어진 스레드는 해당 함수만을 사용하게 됩니다.

    • 네 번째 매개변수
      분기된 함수의 매개변수로 들어갑니다. void *의 형태이기 때문에 자료형의 구분이 없고, 분기될 함수 내에서 원하는 자료형으로 캐스팅이 필요합니다.

  • 리턴 값

    스레드 생성 성공시 0을 리턴하고, 실패시 에러코드를 리턴합니다.

  • 에러코드

    • EAGAIN
      쓰레드 생성을 위한 자원이 부족하거나, PTHREAD_THREADS_MAX를 초과해서 쓰레드 생성을 요청할경우

pthread_join

  • 함수 원형

int pthread_join(pthread_t th, void **thread_return);
  • 용도

    지정한 스레드가 종료될 때까지 기다리는 함수입니다.
    만약 스레드가 종료되면 자원을 해제합니다. 스레드는 종료된다고 모든 자원이 해제되지 않기 때문에 pthread_join을 통해서 자원을 해제해 주어야 메모리 누수가 발생하지 않습니다.

    그리고 pthread_join은 pthread_create에서 두 번째 매개변수인 attr이 JOINABLE(default)로 되어있고 pthread_detach함수를 통해 스레드의 상태가 detached상태가 아니어야 사용이 가능합니다.

  • 매개변수

    • 첫 번째 매개변수
      스레드의 TID

    • 두 번째 매개변수
      만약 스레드 함수에서 리턴값이 있다면 thread_return에 전달됩니다. 없다면 NULL을 넣으시면 됩니다.

  • 리턴 값

    성공시 0을 리턴하고 에러 발생시 에러코드를 리턴합니다.

  • 에러코드

    • ESRCH
      식별번호 th가 잘못된 식별번호 일 경우

    • EINVAL
      식별번호 th 쓰레드가 detached 상태일경우

pthread_detach

  • 함수 원형

int pthread_detach( pthread_t th_id );
  • 용도

    이 함수를 사용한 스레드는 메인 스레드에서 분리되고, 스레드가 종료가 된다면 자원을 자동으로 해제해 줍니다.
    이 함수를 사용한 스레드는 pthread_join함수를 사용할 수 없고, 이 함수를 사용하지 않는다면 꼭 pthread_join을 사용해서 할당된 자원을 해제해야합니다.

  • 매개변수

    메인 스레드에서 분리할 스레드의 TID

  • 리턴 값

    성공 시 0을 리턴하고 실패시 에러코드를 리턴합니다.

  • 에러코드

    • ESRCH
      th식별자를 가진 쓰레드가 존재하지 않는 경우

    • EINVAL
      th식별자를 가진 쓰레드가 이미 detach상태에 있는 경우

pthread_mutex_init

  • 함수 원형

int pthread_mutex_init(pthread_mutex_t * mutex, 
         const pthread_mutex_attr *attr);
  • 용도

    pthread_mutex_init은 뮤텍스 객체를 초기화하기 위해서 사용합니다.
    초기화에 성공하면 잠금이 해제됩니다.
    뮤텍스는 "fast", "recurisev", "error checking"의 3가지 종류중 하나를 선택할 수 있으며, 기본적으로 "fast"가 사용됩니다.

  • 매개변수

    • 첫 번째 매개변수
      초기화 시킬 뮤텍스의 객체입니다.
    • 두 번째 매개변수
      초기화 할 때 사용할 종류입니다.
  • 리턴 값

    성공하면 0, 실패 시 에러코드를 반환합니다.

  • 에러코드

    • EINVAL
      뮤텍스가 제대로 초기화 되지 않을 경우

    • EDEADLK
      뮤텍스가 이미 잠겨있는 경우("error checking" 뮤텍스의 경우에만 발생)

pthread_mutex_destroy

  • 함수 원형

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 용도

    인자로 주어진 뮤텍스 객체를 제거하기 위해서 사용됩니다.
    이 함수를 사용하기 위해서 뮤텍스는 반드시 잠금이 해제된 상태여야 합니다.

  • 매개변수

    제거할 뮤텍스 객체

  • 리턴 값

    성공 시 0, 실패 시 에러코드를 반환합니다.

  • 에러코드

    • EINVAL
      지정한 뮤텍스 객체 mutex은 유효하지 않는 경우

    • EBUSY
      지정한 뮤텍스 객체는 다른 스레드에 의해 잠겨있는 경우

pthread_mutex_lock

  • 함수 원형

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 용도

    뮤텍스 잠금을 요청합니다.
    만약 잠금 해제 상태라면 잠그고 만약 잠긴 상태라면 해당 뮤텍스의 잠금이 해제될 때까지 기다리게 됩니다.

  • 매개변수

    잠글 뮤텍스 객체

  • 리턴 값

    성공 시 0, 실패 시 에러코드를 반환합니다.

  • 에러코드

    • EINVAL
      뮤텍스가 잘못 초기화 되었다.

    • EDEADLK
      이미 잠금을 얻은 쓰레드가 다시 잠금을 요청할 때 (error checking 뮤텍스일 경우 사용할 수 있습니다.)

pthread_mutex_unlock

  • 함수 원형

int pthread_mutex_unlock(pthread_mutex_t *mutex); 
  • 용도

    뮤텍스의 잠금을 해제합니다.

  • 매개변수

    잠금을 해제할 뮤텍스 객체

  • 리턴 값

    성공 시 0, 실패 시 에러코드를 반환합니다.

  • 에러코드

    • EINVAL
      지정한 뮤텍스 객체 mutex가 NULL이거나 유효한 뮤텍스 객체가 아닐 경우

    • EPERM
      지정한 뮤텍스 객체는 호출한 스레드에 의해 잠기지 않았을 경우

참고
프로세스 스레드 차이
C언어 : pthread 기본 함수들

post-custom-banner

0개의 댓글