프로그램이란 특정 작업을 수행하기 위한 명령어들의 집합체로써
코드 덩어리로써 아직 메모리에 로드되지 않고 저장장치에 저장되어있는 상태를 말합니다.
한마디로 실행파일을 의미한다고 말 할 수 있을 것 같습니다.
우선 프로그램을 실행되는 과정을 간단하게 봐 봅시다.
- 컴퓨터에게 프로그램 실행을 요청합니다. (실행파일을 실행합니다.)
- 컴퓨터는 프로그램의 정보를 읽고 프로그램이 메모리를 사용할 수 있게 할당해 줍니다.
- 프로그램의 정보들이 메모리에 로드되고, 프로그램이 실행됩니다.
이런 과정을 거치게 되는데
프로세스는 이런 과정에서 프로그램 실행요청을 한 이후의 상태를 말합니다.
프로세스가 할당받는 메모리의 구조는 [ 메모리 영역은 어떤 식으로 구성되어 있을까? ]에 정리해 두었으니 한번 보고오시면 좋을 것 같습니다.
일반적으로 CPU는 하나의 프로세스만 관리할 수 있습니다.
그런데 우리는 음악을 들으면서 게임을 할 수도 있고, 인터넷도 할 수 있죠.
어떻게 그렇게 할 수 있을까요??
바로 여러개의 프로세스를 아주 빠르게 번갈아 가면서 실행시켜서 동시에 실행하는 것 처럼 보이게 만드는 겁니다.
좀 더 자세히 알아보기 위해서 프로세스의 구성과 관리에 대해서 알아보도록 합시다.
프로세스에 대한 정보는 프로세스 제어블록(PCB, Process Control Block)또는 프로세스 기술자(process descriptor)
라고 불리는 자료구조에 저장됩니다.
PCB의 구성은 다음과 같습니다.
운영체제가 각 프로세스를 식별하기 위해 부여된 프로세스 식별번호(PID, Process IDentification)
입니다.
CPU는 프로세스를 빠르게 교체하면서 실행하기 때문에 실행중인 프로세스도 있고 대기 중인 프로세스도 있습니다.
그런 프로세스의 상태를 저장합니다.
프로세스 상태는 생성, 준비, 실행, 대기, 종료
로 나누어져 있습니다.
CPU가 다음으로 실행할 명령어를 가리키는 값
입니다. CPU는 기계어를 한 단위씩 읽어서 처리하는데 프로세스를 실행하기 위해 다음으로 실행할 기계어가 저장된 메모리 주소를 가리키는 값입니다.
운영체제는 여러개의 프로세스를 동시에 실행하는 환상을 제공합니다.
운영체제가 여러 개의 프로세스가 CPU에서 실행되는 순서를 결정하는 것
을 스케줄링이라고 합니다.
이 스케줄링에서 우선순위가 높으면 먼저 실행될 수 있는데 이를 스케줄링 우선순위라고 합니다.
프로세스가 접근할 수 있는 자원을 결정하는 정보
입니다.
안드로이드 앱을 예로 들면 아무 앱이나 휴대폰 통화내역을 볼 수 있는 권한을 가지면 이를 악의적으로 이용하는 앱이 등장하겠죠? 그래서 프로세스마다 어디까지 접근할 수 있는지에 대한 권한이 필요합니다.
최초로 생성되는 init 프로세스를 제외하고 모든 프로세스는 부모 프로세스를 복제해서 생성되고 이 계층관계는 트리를 형성합니다.
그래서 각 프로세스는 자식 프로세스와 부모프로세스에 대한 정보
를 가지고 있습니다.
프로세스는 실행중인 프로그램입니다.
따라서 프로그램에 대한 정보를 가지고 있어야하고, 프로그램에 대한 정보는 프로세스가 메모리에 가지는 자신만의 주소 공간에 저장됩니다.
이 공간에 대한 포인터 값
을 가집니다.
프로세스가 실행상태에서 마지막으로 실행한 프로세서의 레지스터 내용
을 담고 있습니다.
CPU에 의해 실행되는 프로세스는 운영체제에 의해 계속 교체되는데 교체되었다가 다시 자신의 차례가 되어서 실행될때 중단된적 없고 마치 연속적으로 실행된것처럼하기 위해 이 레지스터 정보를 가지고 있습니다.
프로세스의 상태는 위 내용처럼 생성, 준비, 실행, 대기, 종료가 있습니다. 프로세스는 이 상태들의 반복으로 실행되며 관리되고 있습니다.
각 상태의 정보는 아래와 같습니다.
아직 메모리에 적재되지 않은 상태
를 말합니다.메모리에 적재되고, CPU의 할당을 기다리는 상태
입니다.명령어를 실행하고 있는 상태
입니다.특정 이벤트에 의해 실행이 중지되어 멈춰있는 상태
입니다.종료된 상태
입니다.이렇게 각 상태의 내용을 알아봤는데 각 상태들이 특정 이벤트로 인해 다른 상태로 변화할때 우리는 이 것을 프로세스의 상태전이
라고 합니다.
상태전이의 정보는 아래와 같습니다.
해당 프로세스를 실행해도 된다고 승인
합니다.
보통 dispatch라고도 하며 준비상태에 있던 프로세스가 CPU를 할당받아 실행되는 것
을 말합니다.
이 작업은 디스패처(dispatcher)가 수행하게 됩니다.
실행 중이던 프로세스가 특정 이벤트로 인해서 다시 준비상태로 돌아가는 것
을 말합니다.
프로세스가 할당된 시간 내에 모든 명령어를 수행하지 못하면 넘어가는 Time out등이 있습니다.
CPU가 프로세스를 실행 중에 있을 때 급작스런 이벤트에 의해서 CPU가 다른 프로세스를 할당하게 될때 현재 실행중인 프로세스는 대기상태로 넘어가는 상태
를 말합니다.
급작스런 이벤트에 의한 CPU할당을 받는 프로세스의 실행이 끝나면 대기상태에 있던 프로세스는 다시 준비상태로 넘어가게 되는 상태
를 말합니다.
프로세스의 모든 명령이 실행되고 종료
되는 상황을 말합니다.
출처 : 운영체제 #1_ 스레드와 프로세스, 멀티프로그래밍,멀티태스킹
상태 전이들을 보기 편하고 이해하기 쉽게 마인드맵으로 정리해 봤습니다.
독립된 메모리
를 할당받습니다.프로세스는 다른 프로세스의 메모리 영역을 참조할 수 없습니다.
프로세스간의 통신(Inter-Process Communication, IPC)
를 통해서 가능하게 됩니다.하나 이상의 스레드
를 가지게 됩니다.스레드는 프로세스 내부에서 동작하는 흐름의 단위
입니다.
좀 더 설명해 보자면 스레드는 메인 함수처럼 모든 함수를 지정해서 사용할 수도 있고, 자식 스레드를 만들어서 특정 함수들만 스레드로 사용되게 할 수도 있는겁니다.
간단한 예시를 들어보자면 게임이라는 프로세스 내에서 캐릭터, 몬스터 등이 스레드가 됩니다.
여기서 이 스레드들은 각각 움직이거나 먹거나 공격하는 등 다양한 움직임을 하게 할 수도 있고, 움직이게만 할 수도 있는 등 범위를 정할수도 있죠.
이렇게 우리는 여러개의 스레드를 만들어서 여러가지 동작을 한번에 할 수 있는 것 처럼 만들 수 있습니다.
Stack 영역
만 따로 할당을 받고 나머지 Code, Data, Heap 영역은 공유자원
으로써 모든 스레드가 접근할 수 있습니다.C언어에서는 pthread.h안에 있는 pthread함수들을 통해서 스레드를 만들거나, 조작할 수 있습니다.
그럼 pthread에는 어떤 함수들이 있는지 자주 사용되는 것들 위주로 알아보겠습니다.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
스레드를 생성
합니다.
첫 번째 매개변수
해당 변수에 스레드의 식별자가 저장됩니다.
두 번째 매개변수
thread의 옵션
을 지정할 때 사용됩니다. 기본적인 스레드의 옵션을 사용할 거라면 NULL을 사용합니다.
세 번째 매개변수
pthread를 이용해서 분기 할 함수
입니다. 만들어진 스레드는 해당 함수만을 사용하게 됩니다.
네 번째 매개변수
분기된 함수의 매개변수
로 들어갑니다. void *의 형태이기 때문에 자료형의 구분이 없고, 분기될 함수 내에서 원하는 자료형으로 캐스팅이 필요합니다.
스레드 생성 성공시 0을 리턴하고, 실패시 에러코드를 리턴합니다.
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 상태일경우
int pthread_detach( pthread_t th_id );
이 함수를 사용한 스레드는 메인 스레드에서 분리되고, 스레드가 종료가 된다면 자원을 자동으로 해제
해 줍니다.
이 함수를 사용한 스레드는 pthread_join함수를 사용할 수 없고, 이 함수를 사용하지 않는다면 꼭 pthread_join을 사용해서 할당된 자원을 해제
해야합니다.
메인 스레드에서 분리할 스레드의 TID
성공 시 0을 리턴하고 실패시 에러코드를 리턴합니다.
ESRCH
th식별자를 가진 쓰레드가 존재하지 않는 경우
EINVAL
th식별자를 가진 쓰레드가 이미 detach상태에 있는 경우
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" 뮤텍스의 경우에만 발생)
int pthread_mutex_destroy(pthread_mutex_t *mutex);
인자로 주어진 뮤텍스 객체를 제거
하기 위해서 사용됩니다.
이 함수를 사용하기 위해서 뮤텍스는 반드시 잠금이 해제된 상태여야 합니다.
제거할 뮤텍스 객체
성공 시 0, 실패 시 에러코드를 반환합니다.
EINVAL
지정한 뮤텍스 객체 mutex은 유효하지 않는 경우
EBUSY
지정한 뮤텍스 객체는 다른 스레드에 의해 잠겨있는 경우
int pthread_mutex_lock(pthread_mutex_t *mutex);
뮤텍스 잠금을 요청
합니다.
만약 잠금 해제 상태라면 잠그고 만약 잠긴 상태라면 해당 뮤텍스의 잠금이 해제될 때까지 기다리게 됩니다.
잠글 뮤텍스 객체
성공 시 0, 실패 시 에러코드를 반환합니다.
EINVAL
뮤텍스가 잘못 초기화 되었다.
EDEADLK
이미 잠금을 얻은 쓰레드가 다시 잠금을 요청할 때 (error checking 뮤텍스일 경우 사용할 수 있습니다.)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
뮤텍스의 잠금을 해제
합니다.
잠금을 해제할 뮤텍스 객체
성공 시 0, 실패 시 에러코드를 반환합니다.
EINVAL
지정한 뮤텍스 객체 mutex가 NULL이거나 유효한 뮤텍스 객체가 아닐 경우
EPERM
지정한 뮤텍스 객체는 호출한 스레드에 의해 잠기지 않았을 경우