TH3 6R3@T H@CK
프로세스와 스레드 본문
※ 운영체제 수업 내용 복습
프로그램
저장장치에 저장되어 있는 정적인 상태
∙ 애플리케이션
∙ 컴퓨터 디스크에 저장되어 있는 상태
프로세스
실행을 위해 메모리에 올라온 동적인 상태
∙ 폰노이만 구조에 따라 CPU가 프로그램에 적혀 있는 코드를 line by line으로 실행하기 위해, 디스크에 저장되어 있는 코드나 데이터 등이 메모리에 실질적으로 적재되어 있는 상태
프로세스(Process)란? "실행 중인 프로그램"
∙ 프로그램과 달리, 프로세스는 메모리에 주소 공간을 갖는 능동적 개체
∙ CPU 스케줄링을 할 때의 기본 처리 단위 → 프로세스 단위
∙ 프로세스 관리는 운영체제 입장에서 굉장히 중요한 일임
프로세스 제어 블록 (Process Control Block: PCB)
∙ 프로세스 관리를 위해 유지되는 레코드 데이터 블록으로, 메타데이터라고 볼 수 있음
→ 프로세스 관련 주요 정보들을 저장해 놓은 데이터 객체
∙ 프로세스는 고유의 PCB를 가짐
→ 프로세스별로 메타 정보들이 다르기 때
∙ CPU에서 수행 프로세스가 바뀔 때, 문맥 교환이 발생하는데, 이때 PCB 정보를 활용
∙ 프로세스 = 프로그램 + PCB
→ 애플리케이션 = 파일 시스템에 저장되어 있는 코드나 데이터 + 메타 정보
프로세스 제어 블록: 프로세스 구조체 형태로 정의
∙ 이 구조체들의 연결 리스트를 통해 프로세스 간 관계를 유지
pid t pid; /* 프로세스 ID */
long state; /* 프로세스 상태 */
unsigned int time slice /* 스케줄링 정보 */
struct task struct *parent; /* 부모프로세스 */
struct list head children; /* 자식 프로세스 리스트 */
struct files struct *files; /* 오픈 파일 리스트 */
struct mm struct *mm; /* 프로세스 주소 공간 */
→ mm: memory management
* cf. task_struct 같은 데이터 구조체들을 grab 명령어로 정보 확인할 수 있음
프로세스 제어 블록의 구성
∙ 포인터: 준비 상태나 대기 상태의 큐를 구현할 때 사용
→ 특정 메모리 주소를 가리키는 형태의 변수
∙ 프로세스 상태: 프로세스가 현재 어떤 상태에 있는지를 나타내는 정보
∙ 프로세스 구분자: 운영체제 내에 있는 여러 프로세스를 구현하기 위한 구분자
→ pid (프로세스 id)
∙ 프로그램 카운터: 다음에 실행될 명령어의 위치를 가리키는 프로그램 카운터의 값
→ 다양한 컴퓨터 레지스터 중 하나
∙ 프로세스 우선순위: 프로세스의 실행 순서를 결정하는 우선순위
∙ 각종 레지스터 정보: 프로세스가 실행되는 중에 사용하던 레지스터의 값
→ PCB에 저장되어 있음
∙ 메모리 관리 정보: 프로세스가 메모리의 어디에 있는지 나타내는 메모리 위치 정보, 메모리 보호를 위해 사용하는 경계 레지스터 값과 한계 레지스터 값 등
∙ 할당된 자원 정보: 프로세스를 실행하기 위해 사용하는 입출력 자원이나 오픈 파일 등에 대한 정보
→ ex. fd(file descripter) 정보, 소켓 id
∙ 계정 정보: 계정 번호, CPU 할당 시간, CPU 사용 시간 등
∙ 부모 프로세스 구분자와 자식 프로세스 구분자: 부모 프로세스를 가리키는 PPID와 자식 프로세스를 가리키는 CPID 정보
* 리눅스 환경에서 실행 중인 프로세스 정보를 확인하고 싶을 때 → ps command 이용
프로세스 실행 정보의 확인
∙ Process Explorer, Sysinternals 등을 활용
→ 심층적인 정보 분석 가능
→ 윈도우에서 사용
프로세스의 상태
생성 상태
∙ 프로그램이 메모리에 올라오고 운영체제로부터 프로세스 제어 블록(PCB)을 할당받은 상태
→ 사용자의 애플리케이션 실행 행위를 통해 프로세스가 운영체제에 의해 생성
∙ 생성된 프로세스는 바로 실행되는 것이 아니라 준비 상태에서 자기 순서를 기다리며, 프로세스 제어 블록도 같이 준비 상태로 옮겨짐
준비 상태
∙ 실행 대기 중인 모든 프로세스가 자기 순서를 기다리는 상태
∙ 프로세스 제어 블록은 준비 큐에서 기다리며 CPU 스케줄러에 의해 관리
∙ CPU 스케줄러는 준비 상태에서 큐를 몇 개 운영할지 큐에 있는 어떤 프로세스의 프로세스 제어 블록을 실행 상태로 보낼지 결정 → 프로세스 우선순위 값 고려 등
∙ CPU 스케줄러가 어떤 프로세스 제어 블록을 선택하는 작업은 dispatch(PID) 명령으로 처리
∙ CPU 스케줄러가 dispatch(PID)를 실행하면 해당 프로세스가 준비 상태에서 실행 상태로 바뀌어 작업이 이루어짐
실행 상태
∙ 프로세스가 CPU를 할당 받아 실행되는 상태
→ 프로그램에 해당되는 코드들이 CPU 사이클에 맞춰 실행되는 상태
∙ 실행 상태에 있는 프로세스는 자신에게 주어진 시간, 즉 타임 슬라이스 동안 작업할 수 있음
→ 타임 슬라이스는 CPU 스케줄러 동작 방식에 따라 달라짐
∙ 그 시간을 다 사용하면 timeout(PID)가 실행되어 or 실행 과정 중 인터럽트가 발생하면 실행 상태에서 준비 상태로 옮김
∙ 실행 상태 동안 작업이 완료되면 exit(PID)가 실행되어 프로세스가 정상 종료
∙ 실행 상태에 있는 프로세스가 입출력을 요청하면 CPU는 입출력 관리자에게 입출력을 요청하고 block(PID)를 실행
∙ block(PID)는 입출력이 완료될 때까지 작업을 진행할 수 없기 때문에 해당 프로세스를 대기 상태로 옮기고 CPU 스케줄러는 새로운 프로세스를 실행 상태로 가져옴
디스패치
∙ 준비 상태의 프로세스 중 하나를 골라 실행 상태로 바꾸는 CPU 스케줄러의 작업
타임아웃
∙ 프로세스가 자신에게 주어진 하나의 타임 슬라이스 동안 작업을 끝내지 못하면 다시 준비 상태로 돌아가는 것
대기 상태
∙ 실행 상태에 있는 프로세스가 입출력을 요청하면 입출력이 완료될 때까지 기다리는 상태
∙ 대기 상태의 프로세스는 입출력장치별로 마련된 큐에서 기다리다가 완료되면 인터럽트가 발생하고, 대기 상태에 있는 여러 프로세스 중 해당 인터럽트로 깨어날 프로세스를 찾는데 이것이 wakeup(PID)
∙ wakeup(PID)로 해당 프로세스의 프로세스 제어 블록이 준비 상태로 이동
포인터
∙ 대기 상태에는 같은 입출력을 요구한 프로세스끼리 연결할 때 포인터 사용
완료 상태
∙ 프로세스가 종료되는 상태
∙ 코드와 사용했던 데이터를 메모리에서 삭제하고 프로세스 제어 블록(PCB)을 폐기
→ 운영체제 입장에서 실행이 끝난 프로세스를 위한 메타 정보를 관리하는 것은 비효율적임
∙ 정상적인 종료는 간단히 exit()로 처리
∙ 오류나 다른 프로세스에 의해 비정상적으로 종료되는 강제 종료를 만나면 디버깅하기 위해 종료 직전의 메모리 상태를 저장장치로 옮기는데 이를 코어 덤프(core dump)라고 함
→ ex. segmantation fault 발생 시
보류 상태
∙ 프로세스가 메모리에서 잠시 쫓겨난 상태
∙ 프로세스는 다음과 같은 경우에 보류 상태가 됨
∙ 메모리가 꽉 차서 일부 프로세스를 메모리 밖으로 내보낼 때 → 디스크로 잠깐 내보냄 (스왑 상태)
∙ 프로그램에 오류가 있어서 실행을 미루어야 할 때
∙ 바이러스와 같이 악의적인 공격을 하는 프로세스라고 판단될 때
∙ 매우 긴 주기로 반복되는 프로세스라 메모리 밖으로 쫓아내도 큰 문제가 없을 때
∙ 입출력을 기다리는 프로세스의 입출력이 계속 지연될 때
문맥 교환
∙ context switch (≒ 상태 교환)
∙ CPU를 차지하던 프로세스가 나가고 새로운 프로세스를 받아들이는 작업
∙ 실행 상태에서 나가는 프로세스 제어 블록에는 지금까지의 작업 내용을 저장하고, 반대로 실행 상태로 들어오는 프로세스 제어 블록의 내용으로 CPU가 다시 세팅 (준비 상태 ↔ 실행 상태)
∙ 문맥 교환이 너무 빠르게 이루어지면 비효율적이고, 너무 느리게 이루어지면 아사 현상 발
Q. 문맥 교환이 너무 빠르게 이루어지면 비효율적인 이유? 시간 단축 효과가 있는 게 아닌지
A. ??
프로세스의 구조
∙ 코드 영역: 프로그램 코드가 저장되는 영역
∙ 데이터 영역: 전역 변수가 저장되는 영역
∙ 스택 영역: 함수 호출 과정에서 사용되는 지역 변수와 매개 변수가 저장되는 영역, 위에서 아래로 자람, last in first out 구조를 따름
∙ 힙 영역: 동적으로 사용자가 필요할 때 할당했다가 회수하는 용도로 쓰이는 영역, 아래에서 위로 자람
Last-In-First-Out 원리를 따르는 데이터 구조
∙ 스택에 데이터를 넣는 것을 Push, 빼는 것을 Pop
데이터, 코드 영역 외에 스택과 힙 영역이 있음
∙ 스택 영역: 함수 호출 시 생성되는 지역 변수 및 매개 변수가 저장
∙ 힙 영역: 동적으로 할당된 메모리를 관리
∙ 힙은 위로 (낮은 메모리주소에서 높은 주소), 스택은 아래로 (높은 주소에서 낮은 주소) 자란다
fork() 시스템 호출의 개념
∙ 실행 중인 프로세스로부터 새로운 프로세스를 복사하는 함수
∙ 실행 중인 프로세스와 똑같은 프로세스가 하나 더 만들어짐
→ 부모 프로세스를 fork()하면 똑같이 생긴 자식 프로세스 하나 생성
→ 똑같이 생겼지만 다른 PID 값을 가짐
fork() 시스템 호출의 동작 과정
∙ fork() 시스템 호출을 하면 프로세스 제어 블록을 포함한 부모 프로세스 영역의 대부분이 자식 프로세스에 복사되어 똑같은 프로세스가 만들어짐
∙ 단, 프로세스 제어 블록의 내용 중 다음이 변경됨
∙ 부모 프로세스 구분자와 자식 프로세스 구분자
∙ 프로세스 구분자
∙ 메모리 관련 정보
fork() 시스템 호출의 장점
∙ 프로세스의 생성 속도가 빠름
∙ 추가 작업 없이 자원을 상속할 수 있음
∙ 시스템 관리를 효율적으로 할 수 있음
fork() 시스템 호출의 예시
∙ 부모 프로세스의 코드가 실행되어 fork()를 만나면 똑같은 내용의 자식 프로세스를 하나 생성
∙ 이때 fork() 문은 부모 프로세스에 0보다 큰 값을 반환하고 자식 프로세스에 0을 반환
∙ 만약 0보다 작은 값을 반환하면 자식 프로세스가 생성되지 않은 것으로 여겨 'Error'를 출력
exec() 시스템 호출의 개념
∙ 기존의 프로세스를 새로운 프로세스로 전환(재사용)하는 함수
∙ fork(): 새로운 프로세스를 복사하는 시스템 호출
∙ exec(): 프로세스는 그대로 둔 채 내용만 바꾸는 시스템 호출
→ fork()를 해서 복사한 다음에 자식 프로세스에서 실행할 내용을 알려주는 형태의 시스템 호출
exec() 시스템 호출의 동작 과정
∙ exec() 시스템 호출을 하면 코드 영역에 있는 기존의 내용을 지우고 새로운 코드로 바꿔버림
∙ 데이터 영역이 새로운 변수로 채워지고 스택 영역이 리셋
∙ 프로세스 제어 블록의 내용 중 프로세스 구분자, 부모 프로세스 구분자, 자식 프로세스 구분자, 메모리 관련 사항 등은 변하지 않지만 프로그램 카운터 레지스터 값을 비롯한 각종 레지스터와 사용한 파일 정보가 모두 리셋
exit() 시스템 호출
∙ 작업의 종료를 알려주는 시스템 호출
∙ exit() 함수를 선언함으로써 부모 프로세스는 자식 프로세스가 사용하던 자원을 빨리 거둬 갈 수 있음
∙ exit() 함수는 전달하는 인자를 확인하여 자식 프로세스가 어떤 상태로 종료 되었는지를 알려 주는데, 인자가 0이면 정상 종료이고 -1이면 비정상 종료
wait() 시스템 호출
∙ 자식 프로세스가 끝나기를 기다렸다가 자식 프로세스가 종료되면 다음 문장을 실행하는 시스템 호출
∙ 부모 프로세스와 자식 프로세스 간 동기화에도 사용
유닉스의 프로세스 계층 구조
∙ 유닉스의 모든 프로세스는 init 프로세스의 자식이 되어 트리 구조를 이룸
프로세스 계층 구조의 장점
∙ 여러 작업을 동시에 처리할 수 있음
∙ 프로세스의 재사용이 용이
∙ 자원 회수가 쉬움
∙ 프로세스를 계층 구조로 만들면 프로세스 간의 책임 관계가 분명해져서 시스템을 관리하기가 수월
스레드의 정의
∙ CPU 스케줄러가 CPU에 전달하는 일 하나
∙ CPU가 처리하는 작업의 단위는 프로세스로부터 전달받은 스레드
→ 스레드: 하나의 프로세스 내에서 프로세스를 여러 개의 하위 작업으로 쪼갠 하나하나를 부르는 명칭
∙ 운영체제 입장에서의 작업 단위는 프로세스
∙ CPU 입장에서의 작업 단위는 스레드
프로세스와 스레드의 차이
∙ 프로세스끼리는 약하게 연결되어 있는 반면 스레드끼리는 강하게 연결되어 있음
→ 스레드는 하나의 프로세스 안에서 나눠져 있기 때문에 코드 영역, 소켓 디스크립터, 파일 디스크립터, 전역 변수 등을 서로 공유함
멀티태스크와 멀티스레드의 차이
멀티태스크: 여러 개의 프로세스로 구성된 것
멀티스레드: 하나의 프로세스에 여러 개의 스레드로 구성된 것
멀티스레드
∙ 프로세스 내 작업을 여러 개의 스레드로 분할함으로써 작업의 부담을 줄이는 프로세스 운영 기법
→ 애플리케이션을 구현하는 방법
멀티태스킹
∙ 운영체제가 CPU에 작업을 줄 때 시간을 잘게 나누어 배분하는 기법
멀티프로세싱
∙ CPU를 여러 개 사용하여 여러 개의 스레드를 동시에 처리하는 작업 환경
멀티태스킹의 낭비 요소
∙ fork() 시스템 호출로 프로세스를 복사하면 코드 영역과 데이터 영역의 일부가 메모리에 중복되어 존재하며, 부모-자식 관계이지만 서로 독립적인 프로세스이므로 이러한 낭비 요소를 제거할 수 없음
멀티태스킹과 멀티스레드의 차이
∙ fork() 시스템 호출로 여러 개의 프로세스를 만들면 필요 없는 정적 영역이 여러 개가 됨
∙ 멀티스레드는 코드, 파일 등의 자원을 공유함으로써 자원의 낭비를 막고 효율성 향상
멀티스레드의 장점
∙ 응답성 향상
∙ 자원 공유
∙ 효율성 향상
∙ 다중 CPU 지원
멀티스레드의 단점
∙ 모든 스레드가 자원을 공유하기 때문에 한 스레드에 문제가 생기면 전체 프로세스에 영향을 미침
∙ 인터넷 익스플로러에서 여러 개의 화면을 동시에 띄웠는데 그중 하나에 문제가 생기면 인터넷 익스플?
커널 스레드와 사용자 스레드
커널 스레드: 커널이 직접 생성하고 관리하는 스레드
사용자 스레드: 라이브러리에 의해 구현된 일반적인 스레드
사용자 스레드
∙ 사용자 프로세스 내에 여러 개의 스레드가 커널의 스레드 하나와 연결 (1 to N 모델)
∙ 라이브러리가 직접 스케줄링을 하고 작업에 필요한 정보를 처리하기 때문에 문맥 교환이 필요 없음
∙ 커널 스레드가 입출력 작업을 위해 대기 상태에 들어가면 모든 사용자 스레드가 같이 대기하게 됨
∙ 한 프로세스의 타임 슬라이스를 여러 스레드가 공유하기 때문에 여러 개의 CPU를 동시에 사용할 수 없음
커널 스레드
∙ 하나의 사용자 스레드가 하나의 커널 스레드와 연결 (1 to 1 모델)
∙ 독립적으로 스케줄링이 되므로 특정 스레드가 대기 상태에 들어가도 다른 스레드는 작업을 계속할 수 있음
∙ 커널 레벨에서 모든 작업을 지원하기 때문에 멀티 CPU를 사용할 수 있음
∙ 하나의 스레드가 대기 상태에 있어도 다른 스레드는 작업을 계속할 수 있음
∙ 커널의 기능을 사용하므로 보안에 강하고 안정적으로 작동
∙ 문맥 교환할 때 오버헤드 때문에 느리게 작동
멀티레벨 스레드
∙ 사용자 스레드와 커널 스레드를 혼합한 방식 (M to N 모델)
∙ 커널 스레드가 대기 상태에 들어가면 다른 커널 스레드가 대신 작업을 하여 사용자 스레드보다 유연하게 작업을 처리할 수 있음
∙ 커널 스레드를 같이 사용하기 때문에 여전히 문맥 교환 시 오버헤드가 있어 사용자 스레드만큼 빠르지 않음
∙ 빠르게 움직여야 하는 스레드는 사용자 스레드로 작동하고, 안정적으로 움직여야 하는 스레드는 커널 스레드로 작동