Chapter 10 프로세스와 스레드 - goorm-6th-Als/for_study_Algorithm GitHub Wiki
프로그램은 실행되기 전에는 보조기억장치에 있는 데이터 일뿐이지만, 프로그램을 메모리에 적재하고 실행하면 프로세스가 된다. == '프로세스를 생성한다.'
실제 프로세스를 확인해보면 내가 직접 실행한 프로그램 말고도 여러 프로세스가 실행되고 있다.
= 사용자가 볼 수 있는 영역에서 실행되는 프로세스
= 사용자가 보지 못하는 뒷 영역에서 실행되는 프로세스
- 사용자와 직접 상호작용할 수 있는 백그라운드 프로세스
- 사용자와 직접 상호작용 하지 않고 정해진 일만 수행하는 백그라운드 프로세스 = 데몬(유닉스), 서비스(윈도우)
모든 프로세스는 실행을 위해 CPU가 필요. 하지만 CPU의 자원은 한정되어있다.
프로세스들은 돌아가며 한정된 시간 만큼 CPU사용. 차례대로 사용하고 타이머 인터럽트가 발생하면 양보
- 타이머 인터럽트 = 클럭 신호를 발생시키는 장치에 의해 주기적으로 발생하는 하드웨어 인터럽트의 한 종류, 타임아웃 인터럽트라고 하기도함
이렇게 빠르게 돌아가며 수행되는 프로세스를 관리하기 위한 자료구조가 프로세스 제어 블록(PCB)
- 프로세스 관련 정보를 저장하는 자료 구조
- 프로세스 관련 정보를 저장하는 태그와 같음.
- 프로세스 생성 시 커널 영역에 생성, 종료시 폐기
- 특정 프로세스를 식별하기 위해 부여하는 고유한 번호 (lke 학교의 학번)
- 프로세스는 자신의 실행 차례가 오면 이전까지 사용한 레지스터 중간 값을 모두 복원 -> 실행 재개
프로세스 제어 블록(PCB)에 왜 레지스터 값이 저장되는지 이유를 아는게 중요
각각의 프로세스는 CPU를 이용하며 그 안의 여러가지 레지스터를 이용하며 실행됨. 타이머 인터럽트가 발생하며 실행중인 프로세스가 중단되고 다시 대기하게 되는데 지금까지 사용한 CPU의 레지스터 값들을 알고 있어야 다시 차례가 왔을때 실행을 재개 할 수 있음
ex) 프로그램 카운터에는 다음번에 실행할 메모리 주소가 담기는데 프로세스가 그걸 기억하지 못하면 다음에 차례가 왔을때 다음에 실행할 메모리 주소를 모름.
연산 도중 타이머 인터럽트 발생 후 다시 차례가 와도 이전에 했던 연산의 중간 값을 모르면? 안됨.
- 다음 챕터에서 자세히 나옴 ex) 입출력 장치를 사용하기 위해 기다리는 상태, CPU를 사용하기 위해 기다리는 상태, CPU 이용중인 상태....
- 프로세스가 언제, 어떤 순서로 CPU를 할당받아 사용하는지에 대한 정보 ( 모든 프로세스가 일정한 시간만 CPU를 쓰지는 않음. )
- 프로세스가 어느 메모리 주소에 저장되어 있는지에 대한 정보 (프로세스마다 메모리에 저장된 위치가 다름.)
- 페이지 테이블 정보(나중에 나옴. 현재는 메모리 주소를 알 수 있는 정보가 담긴다. 정도로 이해)
- 할당된 입출력장치, 사용중인 파일 정보.
-> 중요
한 프로세스에서 다른 프로세스로 실행 순서가 넘어간다면 어떤 작업을 거쳐야 할까?
- 기존에 실행되던 프로세스는 지금까지의 중간정보(프로그램 카운터등 각종 레지스터 값, 메모리 정보등)를 백업해야한다.
이러한 중간 정보를 문맥(context)라고 함
-> 실행 문맥을 백업해두면 언제든 해당 프로세스의 실행을 재개 할 수 있다.
즉, 사용중이던 프로세스의 문맥은 백업하고 다음 프로세스의 문맥을 복구해서 자연스럽게 실행중인 프로세스가 바뀌는 것이 문맥 교환.
여러 프로세스가 끊임없이 빠르게 번갈아 가며 실행되는 원리. 마치 프로세스가 동시에 실행되는 것 처럼 보임.
- 실행할 수 있는 코드(기계어로 이루어진 명령어) 저장 (프로그램은 데이터와 명령어로 이루어져 있고, 실행됨)
- 데이터가 아닌 CPU가 실행할 명령어가 담기므로 쓰기 가 금지된 영역(read only)
- 잠깐 사용하고 삭제될 데이터가 아닌 프로그램 실행 동안 유지할 데이터 저장 (전역변수)
+) 코드 영역, 데이터 영역은 크기가 변하지 않고 고정됨 -> 정적 할당 영역
- 프로그램을 만드는 사용자(프로그래머)가 직접 할당할 수 있는 저장 공간
- 힙 영역에 메모리 공간을 할당하면 사용후 반환해야한다.(운영체제에 '더이상 해당 메모리 공간을 사용하지 않는다.' 라고 알려주는 것)
- 메모리 공간을 반환하지 않으면 메모리 낭비를 초래(메모리 누수)
- 데이터가 일시적으로 저장되는 공간
- (데이터 영역에 담기는 값과는 달리) 잠깐 쓰다가 삭제되는 값들이 저장 (매개변수, 지역변수)
- 일시적으로 저장할 데이터는 스택영역에 Push, 더 이상 필요하지 않으면 pop되어 사라짐.
+) 힙, 스택 영역은 프로그램이 실행되며 동적으로 크기가 변하는 영역 -> 동적 할당 영역
일반적으로 힙 영역은 낮은 주소 -> 높은 주소로 할당 일반적으로 스택 영역은 높은 주소 -> 낮은 주소로 할당
프로세스 상태와 계층 구조
운영체제는 프로세스의 상태를 PCB(Process Control Block)에 저장이 됩니다.
컴퓨터는 여러 프로세스를 번갈아가 면서 사용합니다.
- 생성상테
이제 막 메모리에 적재되어 PCB를 할당 받은 상태
- 준비상태
CPU의 할당을 받기위해 기다리는 상태
- 실행상태
CPU를 할당받아 실행 중인 상태, 실행 상태인 프로세스는 할당된 일정 시간 동안 CPU를 할당 받아 할당 된 시간을 모두 사용하면 준비 상태로 변화하고, 실행 도중 입출력의장치를 사용하면 입출력이 끝날때 까지 대기 상태가 됩니다
- 대기상태
입출력 장치나 다른 프로세스의 작업을 기다리는 상태로 작업이 완료되면 준비 상태로 변환된니다.
- 종료상태
프로세스가 종료된 상태, 프로세스가 종료되면 운영체제는 PCB와 프로세스가 사용한 메모리를 정리합니다.
프로세스는 실행 도중 시스템 호출을 통해 다른 프로세스를 생성할 수 있습니다. 이때 새로운 프로세스를 생성한 프로세스를 부모 프로세스, 새로운 프로세스를 자식 프로세스라고 합니다.
프로세스들은 각각의 고유한 PID를 가지고 있습니다. 자식 프로세스인 경우에는 부모 프로세스를 가지키는 PPID 를 가지고 있습니다.
이러한 형식의 프로세스들을 그림으로 나타내면 아래 같은 트리 구조의 그림이 나타납니다.
이를 프로세스 계층 구조
라고 합니다.
사용자가 컴퓨터를 키고, 로그인 창을 성공적으로 로그인해서 bash 셸로 Vim 을 실행시키는 과정을 살펴 봅시다.
- 프로세스 실행 순서
- 사용자가 컴퓨터를 켠 순간, 최초의 프로세스가 실행이 됨
- 최초의 프로세스가 로그인 프로세스를 자식 프로세스로 생성함
- 로그인 프로세스는 사용자 인터페이스 프로세스를 자식 프로세스로 생성함
- 사용자 인터페이스 프로세스는 Vim 프로세스를 생성함
fork 와 exec 를 이용하여 생성합니다.
-
fork와 exec의 기능
- fork: 부모의 프로세스를 복사하여 자식 프로세스를 생성
- exec: 같은 주소 공간을 가진 자신 복제 프로세스를 만듦
- 스레드란 프로세스를 구성하는 실행 흐름 단위이다.
- 하나의 프로세스는 여러 개의 스레드를 가질 수 있다.
-
하나의 프로세스가 한 번에 하나의 일을 처리하기 때문에 스레드와 헷갈릴 수 있지만 위에 설명한 것처럼 프로세스 안에서의 실행 단위이기 때문에 스레드는 프로세스의 하위 단위이다.
-
따라서 프로세스 안에서 스레드가 하나만 쓰이면 단일 스레드, 둘 이상이면 멀티 스레드라고 한다.
-
멀티 스레드의 예시를 들자면 같은 카페에 바리스타가 세 명 있고, 고객 A, B, C가 각각 아메리카노, 카푸치노, 라떼를 주문했다고 가정하면 세 명의 바리스타는 각각의 주문을 동시에 처리하기 시작하므로 전체 주문 처리 시간이 대폭 줄어들지만 작업 관리와 자원 분배에 있어서 복잡성이 증가할 수 있다.
-
반대로 바리스타가 한 명만 있다면 아메리카노를 처리하고 나서야 다음 카푸치노를 시작할 수 있기 때문에 여러 주문이 동시에 들어올 경우 처리 시간이 길어지겠지만 이런 방식은 간당하고 관리가 쉽다.
-
또한 바리스타들이 신체 스펙과 생김새가 다른 개인이지만 카페의 원두와 우유등 처럼 공유하는 부분이 있는 것 처럼 스레드 또한 아래 사진처럼 레지스터, 스택, 프로그램 카운터는 스레드 마다 가지고 있지만 코드, 데이터 영역은 공유한다.
- 각각의 스레드는 프로세스의 자원을 공유 하기때문에 서로 협력과 통신에 유리하다.
- 하지만 자원을 공유하는 것은 장점이기도 하지만 하나의 스레드가 공유 자원에 영향을 줄만큼 문제가 생긴다면 모든 스레드를 쓸 수 없게 돼버리기 때문에 조심해서 써야 할 것이다.