프로세스와 쓰레드
요약
프로세스?
프로세스는 CPU에 의해서 현재 실행되고 있는 프로그램이다.
- PCB(Process Control Block, 프로세스 제어 블록)의 존재로서 명시되는 것
- 프로세서가 할당되는 개체로서 디스패치(Dispatch)가 가능한 단위
- 비동기적 행위를 일으키는 주체
- CPU가 할당되는 실체
- 운영체제가 관리하는 최소 단위의 작업(프로그램)
- task란 용어와 함께 사용되며, 다양한 정의를 가짐
- 프로그램과 달리 메모리에 주소 공간을 갖는 능동적인 객체
프로세스 메모리 구조
프로세스는 스택, 힙, 데이터, 코드로 구성되어 있다.
스택 (Stack)
- 데이터를 일시적으로 저장하는 영역이다.
- 지역변수를 저장하는데 사용되고 해당 변수가 범위를 벗어나면 공간이 해제된다.
- 호출한 함수의 반환 주소, 반환 값, 매개 변수 등에 사용된다.
- 값들이 쌓일 때마다 힙이 있는 방향으로 점점 커지며 두 공간이 만나게 되면 메모리가 소진되었다는 뜻이다.
힙 (Heap)
- 동적으로 메모리를 할당하는데 사용되는 영역이다. (malloc, calloc)
- 스택과 마찬가지로 동적 메모리 할당이 발생하면 스택이 있는 위쪽 방향으로 커진다.
데이터 (Data)
- 프로그램이 실행될 때 생성되고 종료되면 시스템에 반환된다.
- 전역 변수나 정적 변수를 저장하거나 할당한다.
- DATA 영역과 BSS영역으로 구분되어 있다.
- DATA : 초기화 된 데이터
- BSS : 초기화되지 않은 데이터
코드 (Code)
- 프로그램의 코드 자체를 저장하는 영역이다.
- 기계어로 제어되는 메모리 영역이다.
- 읽기 전용이기 때문에 프로그램이 쓰기를 시도하면 오류가 발생한다.
PCB(Process Control Block, 프로세스 제어 블록)
프로세스에 할당된 자원에 대한 정보를 저장하는 영역이다.
PCB 내의 정보는 아래와 같다.
- 프로세스 고유 번호 (PID)
- 프로세스 우선순위
· 우선순위: 기반스케줄링시필요한정보
- 프로세스 현재 상태
- 프로세스가 할당 받은 자원들의 리스트 또는 이들에 대한 포인터
- 문맥 저장 영역
· 우선순위: 기반스케줄링시필요한정보
프로세스 상태 변화
프로세스를 아래와 같이 세분화할 수 있다. 운영체제의 종류에 따라 프로세스의 상태를 다르게 표현하기도 하는데 중요한 것은 한 순간에 실행 중인 프로그램은 하나이라는 점이다.
프로세스 상태 변화 (출처)
- 준비 -> 실행(dispatch)
(스케쥴러에 의해 선택된) 준비큐 맨 앞에 있는 프로세스에게 프로세서를 할당하는 것을 디스패치라고 한다.
- 실행 -> 준비(timeout)
운영체제는 한 프로세스가 독점하는 것을 막기 위해 timer를 두어 특정 프로세스가 할당된 시간 만큼만 프로세서를 점유하게 한다. 해당 시간이 끝나면 timer는 인터럽트를 발생시켜 운영체제에게 프로세서 제어권을 부여한다. 그러면 시간을 전부 소진한 프로세스는 준비상태로 가고 준비큐 맨 앞의 프로세스가 실행상태가 된다.
- 실행 -> 대기(block)
프로세스를 실행하다가 입출력 명령이나 시간이 많이 필요한 이벤트가 발생하면 프로세서를 스스로 반납하고 대기 상태가 된다.
- 대기 -> 준비(wakeup)
대기 상태에서 입출력 작업이 끝나면 대기에서 준비 상태가 된다
프로세스의 생성
프로세스 생성은 부모 프로세스가 연산을 통해 자식 프로세스를 만들어냅니다. 생성된 자식 프로세스 또한 새로운 자식 프로세스를 만들 수 있으며 이를 구별하기 위해 모든 프로세스는 각자 고유의 PID를 가지게 됩니다. 이렇게 생성된 프로세스 간의 관계는 하나의 큰 트리구조가 됩니다.
생성된 자식 프로세스는 각자 고유의 PID, 메모리, CPU 등 새 PCB가 할당되며 고유의 자원을 획득하게 됩니다. 이로 인하여 부모 프로세스의 자원 접근에 제한이 생기며 특수한 방법을 통해 공유할 수 있게 됩니다.
프로세스를 생성한 후 부모 프로세스는 다음과 같이 2가지 행동을 할 수 있습니다.
- 자식 프로세스가 끝날 때까지 기다립니다. ( -> waiting queue )
- 자식 프로세스와 함께 동작합니다. (멀티 프로세싱 환경)
자식 프로세스는 다음 중 하나의 프로세스가 됩니다.
- 부모 프로세스와 동일한 새로운 프로세스 : 이 경우 부모 프로세스의 프로그램, 데이터가 완전 복사됩니다.
- 새로운 프로그램 실행 : 새로운 프로그램을 메모리에 load 하고 이를 실행하게 됩니다.
fork()
Linux/UNIX 환경에서 새로운 프로세스를 만드는 시스템 콜 함수입니다.
- 생성된 자식 프로세스는 부모 프로세스의 데이터와 프로그램이 완전 복사가 되어 똑같은 프로그램을 수행하는 프로세스가 됩니다. 이를 통해 부모, 자식 프로세스가 서로 간편하게 의사소통을 할 수 있습니다.
- 멀티 프로세싱을 통해 부모, 자식 프로세스는 함께 동작합니다.
- fork() 함수는 부모 프로세스에서 자식의 PID를 반환하고, 자식 프로세스에서는 0을 반환하여 구분할 수 있도록 해 줍니다.
exec()
Linux/UNIX 환경에서 프로세스를 새로운 프로그램을 실행하는 프로세스로 대체하는 시스템 콜 함수입니다.
- fork()와 다르게 자식 자식 프로세스를 생성하는 것이 아닌 현재 프로세스의 프로그램 코드를 새로운 프로그램 코드로 바꿔줍니다. 이로 인하여 프로그램 코드, 메모리, 파일 등 프로세스 자원이 새로 바뀌게 됩니다.
- exec() 함수는 현재 프로세스가 완전히 새로운 프로그램을 실행하는 프로세스로 대체되므로 반환 값이 없습니다.
보통 동작하는 방식은 fork()를 통해 자식 프로세스를 생성하고 자식 프로세스에서 exec()를 통해 새로운 프로그램을 돌리게 됩니다. 이때 부로 프로세스가 자식 프로세스가 끝나기를 기다려야 한다면 wait() 시스템 콜 함수를 이용하여 기다릴 수 있습니다.
wait()
- pid_t wait(int *statloc) 형태이며 자식 프로세스가 종료될 때까지 현재 프로세스의 동작을 멈추는 시스템 콜 함수입니다.
- 자식 프로세스가 종료되면 자식 프로세스 종료 시그널(SIGSHLD)이 발생하여 waiting queue에 있는 부모 프로세스가 ready queue로 넘어가게 되어 다시 실행이 가능해집니다.
- 블럭 모드로 동작하기 대문에 비용이 비싸며 자식 프로세스 관리를 하지 않으면 무한정 대기, 혹은 함수가 OS에 의해 강제 종료당할 수 있습니다.
- 종료된 자식 프로세스의 PID를 반환하며 인자로 받은 statloc에 프로세스의 종료 상태를 알 수 있는 번호가 입력됩니다.
exit()
현재 프로세스가 종료되어 SIGCHLD 시그널이 부모 프로세스로 전달되고, wait() 된 부모 프로세스는 자식 프로세스의 exit() 함수에 의해 정상 종료가 됨을 알 수 있습니다.
- void exit(int status) 형태입니다.
- status 값이 부모 프로세스 wait(int *statloc)의 statloc에 지정되어 반환합니다. 이를 통해 부모 프로세스는 자식 프로세스가 정상 종료 혹은 문제가 있었는지 확인할 수 있습니다.
- 함수 호출 후 자식 프로세스에 할당된 모든 자원은 풀어집니다.
- exit 함수가 없더라도 프로그램 코드의 끝에 도달하거나 return 키워드를 만나게 되면 자동으로 exit() 함수가 호출됩니다.
kill()
리눅스 명령어 kill 은 해당 프로세스를 종료시킵니다. 명령어와 달리 kill() 함수는 프로세스에게 특정 시그널을 전송하며 SIGKILL 시그널을 보내게 되면 명령어 kill과 같은 동작을 하게 됩니다.
- int kill(pid_t pid, int sig) 형태이며 pid는 시그널을 보낼 프로세스의 PID, sig는 보낼 시그널입니다.
- pid에 따라 단일 프로세스, 여러 프로세스에게 강제 종료 명령을 보낼 수 있습니다.
PID 인수에 따른 동작
- 양의 정수 : 해당 PID를 가진 프로세스에 시그널을 보냅니다.
- 0 : 같은 프로세스 그룹에 있는 모든 프로세스에 시그널을 보냅니다.
- -1 : 프로세스가 전송 권한이 있는 모든 프로세스에 시그널을 보냅니다.
- -1 외의 음수 : 절댓값을 취한 수의 프로세스가 속한 그룹에 포함된 모든 프로세스에 시그널을 보냅니다.
이 kill() 함수를 통해 부모 프로세스는 자식 프로세스를 관리할 수 있으며 효율적인 관리를 위해 다음과 같은 경우 자식 프로세스를 중단시키는 것이 좋습니다.
- 자식 프로세스가 너무 많은 자원을 사용하는 경우
- 자식 프로세스의 동작 결과가 어떤 이유로 인하여 더 이상 필요 없을 경우
- 부모 프로세스가 동작을 마치고 종료하는 경우
참고
https://popcorntree.tistory.com/56
https://hoyeonkim795.github.io/posts/process_concept/
https://latter2005.tistory.com/105
댓글