1. 커널 영역과 사용자 영역
커널 영역에는 프로세스 제어 블록(PCB)이 저장되고, 사용자 영역에는 실행 중인 프로세스를 코드 영역, 데이터 영역, 힙 영역, 스택 영역으로 나뉘어 저장된다.
1-1. 코드 영역
코드 영역은 CPU가 실행할 명령어가 담겨 있기 때문에 읽기 전용(read-only) 모드로 사용한다. CPU는 코드 영역에 담긴 명령어를 읽고 실행만 하기 때문에, 수정되어서는 안 될 공간이다.
1-2. 데이터 영역
데이터 영역은 프로그램이 실행되는 동안 사용할 데이터가 저장되는 공간이다. 주로 정적 변수와 전역 변수가 저장된다.
- 전역 변수 : 프로그램 전체에서 사용 가능한 변수이다.
- 자바(Java)는 전역 변수를 지원하지 않는다.
- 정적 변수 : 클래스에 속한 변수로, 클래스의 모든 인스턴스가 공유하는 변수이다.
- 자바(Java)에서는 static 키워드를 사용하여 선언한다.
코드 영역과 데이터 영역은 프로그램 실행 도중에 크기에 변하지 않기 때문에 정적 할당 영역이라고 부른다. 정적 할당 영역이란 프로그램이 실행되기 전에 미리 메모리 크키가 결정되고, 프로그램이 종료될 때까지 할당된 메모리 공간이 변하지 않는다는 의미이다.
1-3. 힙 영역
힙 영역은 프로그램 실행 도중에 동적으로 할당되는 메모리 공간이다. 주로 객체와 배열을 저장하는 데 사용되며, 필요한 만큼 메모리를 할당한다. 메모리는 한정적이므로 할당한 공간을 더 이상 사용하지 않는다면 해당 공간을 반납해야 한다. 반납하지 않는 메모리가 쌓이면 메모리를 낭비하는 메모리 누수 문제가 발생할 수 있다. 자바에서는 GC(Garbage Collector)를 사용하여 더 이상 사용되지 않는 객체를 힙 영역에서 제거한다. 개발자가 메모리 해제를 명시적으로 호출하는 것이 아닌, 특정 시점에 자동으로 GC가 메모리를 관리한다.
힙 영역은 프로그램 실행 중에 메모리 공간을 할당 및 해제할 수 있기 때문에 동적 할당 영역이라고 한다.
public class Main {
public static void main(String[] args) {
// 힙 영역에 새로운 객체 할당
// obj1과 obj2는 힙 영역에 각각의 객체를 참조함
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
obj1 = null; // 더이상 사용되지 않으므로, GC에 의해 자동으로 힙 영역에서 삭제된다.
}
}
class MyClass {
int value;
MyClass() {
value = 10;
}
}
1-4. 스택 영역
데이터 영역이 프로그램을 실행하는 동안 사용할 데이터가 저장되는 영역이라면, 함수를 호출할 때 일시적으로 사용되는 메모리 영역이다. 함수를 호출할 때 전달된 매개변수와 함수 내부에서 사용되는 지역변수, 그리고 함수가 끝나면 이전 함수로 돌아가기 위한 복귀 주소를 스택 프레임(Stack Frame)에 담아 저장한다.
public class Main {
public static void main(String[] args) {
int result = add(5, 10); // add 함수 호출 시 스택에 매개변수와 복귀 주소 저장
System.out.println(result);
}
public static int add(int a, int b) {
int sum = a + b; // 지역 변수 sum이 스택에 저장됨
return sum; // 함수가 끝나면 스택에서 프레임이 제거됨
}
}
자바에서 예외(Exception)가 발생하였을 때, 출력되는 스택 트레이스(Stack Trace)는 스택 영역과 관련이 있다. 스택 트레이스는 프로그램 실행 중에 함수 호출 순서를 기록한 것으로, 함수가 호출될 때마다 해당 함수의 호출 정보(매개변수, 지역변수, 복귀주소)가 스택 프레임 형태로 스택 영역에 쌓인다. 따라서 예외가 발생하면 스택 영역에 쌓인 스택 프레임을 기반으로 예외가 어디서부터 발생했는지 추적할 수 있다.
아래는 main() 메서드가 가장 먼저 호출되어, 그다음으로 getThat() 메서드가 호출되었고, 마지막으로 getThis()를 호출하는 과정에서 예외가 발생하였다. 세 개의 메서드가 호출되면서 함수의 정보를 스택 프레임에 담아서 스택 영역에서 저장한다. 따라서 getThis() 메서드에서 발생한 NullPointerException 예외 메시지를 보여줄 때, 어떠한 메서드들이 호출되었는지를 추적할 수 있다.
2. PCB와 ContextSwitching (문맥 교환)
2-1. PCB(Process Control Block, 프로세스 제어 블록)란?
운영체제가 메모리에 적재된 여러 개의 프로세스를 관리하기 위해서는 각 프로세스를 식별할 수 있는 정보가 커널 영역에 저장되어야 한다. 프로세스를 식별할 수 있는 정보를 PCB(Process Control Block)이라고 한다. 프로세스가 생성될 때마다 PCB가 생성되어 커널 영역에 저장되고, 프로세스가 종료된다면 PCB가 삭제된다.
PCB에는 PID(Process ID, 프로세스 식별), 프로세스가 실행되면서 사용한 레지스터 값, 현재 프로세스의 상태, CPU 스케줄링 우선순위, 적재된 메모리 위치, 프로세스가 사용한 파일 및 입출력 장치 등의 정보가 저장된다. 모든 PCB는 커널 영역 내의 프로세스 테이블에 저장되어 관리되고, 프로세스가 종료되면 프로세스 테이블에서 PCB가 삭제된다.
2-2. Context Switching (문맥 교환)
메모리에 적재된 프로세스는 할당받은 시간 동안 CPU 자원을 사용한다. 메모리에 적재된 모든 프로세스들이 CPU 자원을 할당받아 번갈아가며 사용한다. CPU 자원은 한정적이기 때문에 모든 프로세스가 동시에 사용할 수 없다. 따라서 프로세스마다 CPU 자원을 사용할 수 있는 시간을 설정하는데, 이를 타이머 인터럽트라고 한다. 타이머 인터럽트란 시간이 완료되었다는 의미로, 프로세스가 CPU 자원을 사용하다가 타이머 인터럽트가 발생하면 사용하던 CPU 자원을 반납하고 다른 프로세스가 사용할 수 있도록 한다. 이때, 반납된 CPU 자원을 사용하는 프로세스는 CPU 스케줄링 우선순위에 의해 결정된다.
프로세스 A가 CPU 자원을 사용하다가 타이머 인터럽트가 발생하면 지금까지 작업하던 정보를 어디엔가 백업해야 한다. 작업하던 정보란 프로세스를 실행하며 사용한 레지스터 값, 파일 그리고 입출력장치 등을 의미한다. 백업을 하는 이유는 프로세스 A가 CPU 자원을 반납한다고 해서 종료되는 것이 아니라 추후에 본인이 다시 CPU 자원을 할당받아 사용하기 위한 대기 상태가 되기 때문이다. 따라서 프로세스의 백업 정보를 저장해야 하는데 이 백업 정보를 문맥(Context)라고 한다. 프로세스의 문맥은 커널 영역에 저장된 해당 프로세스의 PCB에 저장된다.
프로세스 A가 CPU 자원을 반납하였을 때, 프로세스 B가 CPU 스케줄링에 의해 CPU 자원을 할당받게 된다. 이때, 프로세스 A의 문맥을 PCB에 저장하고 프로세스 B는 이전에 했던 작업을 이어서 하기 위해 문맥을 가져와야 한다. CPU 자원을 사용하는 프로세스가 변경되면서 문맥을 저장하고 가져오는 행동을 문맥 교환(Context Switching)이라고 한다. 문맥 교환이 자주 일어나면 다양한 프로세스가 한정된 CPU 자원을 번갈아가면서 사용하기 때문에 좋을 것이라 생각할 수 있다. 그러나 너무 잦은 문맥 교환은 캐시 미스가 발생할 가능성이 높기 때문에 큰 오버헤드로 이어질 수 있다.
캐시 미스란? ⇒ CPU 캐시는 CPU의 성능을 높이기 위해 사용하는 고속 메모리이다. CPU가 데이터를 처리할 때 메인 메모리(RAM)에서 직접 데이터를 가져오는 대신, 자주 사용되는 데이터는 CPU 캐시 메모리에 저장하여 더 빠르게 접근할 수 있도록 한다. 캐시는 L1, L2, L3 캐시로 계층적으로 존재하며 가장 빠른 캐시는 L1 캐시이다. 캐시 미스는 CPU 캐시에서 원하는 데이터를 찾지 못하여 더 느린 메인 메모리에서 데이터를 가져와야 한다. 반면에 캐시 히트는 CPU 캐시에 원하는 데이터가 있어 데이터를 가져다가 바로 사용할 수 있다. 캐시 미스가 발생하면 CPU는 메인 메모리에서 데이터를 불러오는 시간이 필요하므로 이는 성능 저하로 이어진다.
캐시 미스에 의한 오버헤드가 발생하는 과정은 다음과 같다.
- 프로세스 A가 CPU 자원을 사용하다가 타이머 인터럽트가 발생하여 사용하던 CPU 자원을 반환한다.
- 프로세스 A의 실행 정보인 문맥(Context)을 커널 영역에 저장되어 있는 PCB에 저장한다.
- CPU 스케줄링에 의해 프로세스 B가 CPU 자원을 할당받는다.
- CPU 캐시에는 프로세스 A의 데이터가 주로 담겨있기 때문에, 프로세스 B가 필요한 데이터를 사용하기 위해서는 메인 메모리에서 데이터를 조회해야 한다.
- 메인 메모리에서 조회한 데이터는 CPU 캐시에 채운다.
위 과정이 CPU 자원을 사용하는 프로세스가 바뀔 때마다 컨텍스트 스위칭이 발생하면서 오버헤드를 발생시킬 수 있다.
3. 프로세스의 상태
프로세스가 CPU 자원을 사용하기 위해서는 여러 단계를 거쳐야 한다. 여러 단계란 생성, 준비, 실행, 대기, 종료 상태를 의미한다.
- 생성 상태(NEW)
- 하드 디스크에 존재하던 프로그램이 메모리에 적재된 상태이다. 단, CPU 스케줄링을 받을 수 있는 상태는 아니다.
- 준비 상태(READY)
- 생성 상태에 있던 프로세스가 CPU 스케줄링을 받을 수 있는 상태이다. 준비 상태에 있는 프로세스들은 운영체제에 의해 CPU 스케줄링을 기다리며 대기한다.
- 실행 상태(RUNNING)
- CPU 자원을 할당받아 실행 중인 상태로, 타이머 인터럽트가 발생하기 전까지 CPU 자원을 사용할 수 있다. 타이머 인터럽트가 발생하면 사용하던 CPU 자원을 반환하게 되고 현재 프로세스는 실행 상태에서 준비 상태가 된다. 반대로 입출력 장치를 사용하게 된다면 입출력 장치의 사용이 끝날 때까지 대기 상태가 된다.
- 대기 상태(BLOCKED)
- 프로세스의 입출력 작업으로 인하여 곧장 실행이 불가능한 경우 대기 상태가 된다. 대기 상태의 프로세스가 입출력 작업을 완료한 경우 다시 준비 상태가 되어 CPU 스케줄링을 기다린다.
- 종료 상태(TERMINATED)
- 프로세스가 종료된 상태를 의미하며, 운영체제는 프로세스가 사용한 메모리와 커널 영역에 존재하는 PCB를 정리한다.
3-1. 블로킹 입출력(blocking I/O)과 논블로킹 입출력(non-blocking I/O)
블로킹 입출력이란 프로세스가 CPU 자원을 사용하는 도중에 입출력 작업을 진행할 경우, 그 작업이 완료될 때까지 기다리는 상황이다. 즉, 입출력 작업이 발생하는 동안 아무 작업도 하지 않는 것이다. 프로세스는 입출력 작업 동안 대기 상태에 있다가 입출력 작업이 완료되면 준비 상태로 이동되어 CPU 스케줄링을 기다린다.
반면에 논블로킹 입출력이란 입출력 작업을 하는 동안, 그 작업이 끝나지 않더라도 CPU 자원을 계속 사용하는 방법이다. 입출력 작업이 끝날 때까지 기다리지 않고 그동안 다른 작업을 처리할 수 있다. 예를 들자면, 블로킹 I/O는 인터넷에서 파일을 다운로드하는 동안 다운로드가 완료될 때까지 아무 작업도 하지 않는 상태이다. 반면에 논블로킹 I/O는 파일을 다운로드하는 동안 작업을 멈추지 않고 웹 서핑, 문서 작성, 음악 재생과 같은 다른 작업을 계속 수행할 수 있다.
참고
https://m.yes24.com/Goods/Detail/130179291