프로세스(Process)는 실행 중인 프로그램의 인스턴스이다.
운영 체제가 프로그램을 실행할 때, 그 프로그램의 코드와 데이터를 메모리에 로드하고 이를 프로세스라고 합니다. 각 프로세스에는 독립된 메모리 공간(주로 코드, 데이터, 스택, 힙 구조를 포함)이 있으며, 이를 '프로세스의 주소 공간'이라고 합니다. 프로세스는 운영 체제에 의해 스케줄링되어 CPU 시간을 할당받아 실행됩니다.
프로세스는 다른 프로세스의 메모리 영역에 접근할 수 없다.
프로세스의 주소 공간은 해당 프로세스가 사용하는 메모리 영역의 논리적인 뷰
운영 체제는 각 프로세스에게 독립된 주소 공간을 제공함으로써, 프로세스가 시스템의 다른 부분과 독립적으로 실행될 수 있도록 합니다.
주요 영역은 다음과 같습니다
1. 코드(code) 영역
2. 데이터(Data) 영역
3. 힙(Heap) 영역
코틀린 코드에서 이 메모리 구조가 어떻게 사용되는지 알아보자
사용자로부터 두 수를 입력받아 합을 계산하고 출력하는 간단한 콘솔 애플리케이션을 생각해보자
fun add(a: Int, b: Int): Int {
return a + b
}
fun main() {
print("Enter first number: ")
val num1 = readLine()?.toIntOrNull() ?: 0
print("Enter second number: ")
val num2 = readLine()?.toIntOrNull() ?: 0
val sum = add(num1, num2)
println("The sum is: $sum")
}
코드(Code) 영역
add 함수와 main 함수의 코드가 이 영역에 위치합니다. 코틀린에서도 이 함수들의 바이트코드(코틀린 코드가 컴파일된 형태)가 JVM(Java Virtual Machine) 상에서 실행 이 코드들이 CPU에 의해 실행됩니다.
데이터(Data) 영역
코틀린 프로그램에서 전역 변수나 정적 변수(static variables)가 이 영역에 저장됩니다. 이 예시에서는 직접적인 전역 변수나 정적 변수를 사용하지 않았지만, 만약 있었다면 이 영역에 위치하게 됩니다.
힙(Heap) 영역
코틀린에서 객체를 생성할 때(예: String, List, 사용자 정의 객체 등), 이 객체들은 힙 영역에 저장됩니다. 동적 메모리 할당이 이루어지는 곳이며, JVM이 가비지 컬렉션을 통해 관리합니다. 이 예시에서는 readLine()으로 입력받은 문자열이 힙 영역에 할당될 수 있습니다.
스택(Stack) 영역
main 함수의 num1, num2, sum과 같은 지역 변수들과 add 함수의 매개변수 a, b가 스택 영역에 저장됩니다. 코틀린에서 함수 호출 시 매개변수 전달, 지역 변수 저장 등을 위해 스택이 사용됩니다. 함수 호출이 종료되면 해당 함수의 스택 프레임(함수의 매개변수, 지역 변수, 반환 주소 등을 포함)이 스택에서 제거됩니다.
[ 메모리 구조 ]
+-----------------------+ <- 높은 메모리 주소
| 스택 영역 |
+-----------------------+
| sum (main의 지역 변수) |
| num2 (입력받은 값) |
| num1 (입력받은 값) |
| main의 매개변수 및 반환주소 |
+-----------------------+
| add의 매개변수 a, b |
| add의 반환 주소 |
+-----------------------+
| 힙 영역 |
+-----------------------+
| readLine()으로 생성된 String 객체 |
+-----------------------+
| 데이터 영역 |
+-----------------------+
| (전역 변수나 정적 변수 없음) |
+-----------------------+
| 코드 영역 |
+-----------------------+
| add 함수 코드 |
| main 함수 코드 |
+-----------------------+ <- 낮은 메모리 주소
메모리 영역을 공유하기 때문에 서로의 영역을 침범하는 문제가 생길 수 있다.
스택 영역이 힙 영역을 침범하는 경우를 스택 오버플로, 힙 영여깅 스택 영역을 침범하는 경우를 힙 오버플로우라고 한다.
스레드(Thread)는 프로세스 내에서 실행되는 실행 단위입니다. 하나의 프로세스는 여러 스레드를 가질 수 있으며, 이 스레드들은 프로세스의 자원(메모리)을 공유합니다. 스레드는 프로세스 내에서 코드 실행의 흐름을 나타내며, 각 스레드는 독립적인 실행 경로와 스택을 가지지만, 메모리(힙 영역 등)는 다른 스레드와 공유합니다. 스레드를 사용하면 병렬 실행과 효율적인 자원 사용이 가능해집니다.
스레드는 각각 독립된 실행 컨텍스트를 가지므로, 하나의 스레드가 중단되거나 대기 상태에 들어가도 다른 스레드의 실행에 영향을 주지 않습니다.
1. 경량성(Lightweight): 스레드는 프로세스에 비해 생성, 종료, 컨텍스트 스위칭 비용이 적습니다.
2. 자원 공유: 스레드는 같은 프로세스 내에서 메모리와 자원을 공유하므로, 통신 비용이 낮고 데이터 공유가 용이합니다.
3. 독립적인 실행 흐름: 각 스레드는 별도의 실행 흐름을 가지며, 멀티 코어 프로세서에서는 실제로 동시에 실행될수 있습니다.
장점:
1. 성능 향상: I/O 작업이 많은 프로그램에서 스레드를 사용하면 CPU의 유휴 시간을 줄이고, 전반적인 프로그램의 응답성을 향상시킬 수 있습니다.
2. 자원 효율성: 프로세스 간 자원 공유에 비해 스레드 간의 자원 공유는 훨씬 경제적이며, 메모리 사용량을 줄일 수 있습니다.
3. 응답성 향상: 사용자 인터페이스가 있는 애플리케이션에서 백그라운드 작업을 별도의 스레드로 처리하면, UI의 응답성을 유지할 수 있습니다.
단점:
1. 동기화 문제: 스레드 간 자원 공유로 인해 데이터 일관성을 유지하기 위한 복잡한 동기화가 필요합니다.
2. 데드락 위험: 잘못 설계된 멀티 스레드 프로그램은 데드락에 빠질 위험이 있습니다.
3. 디버깅 어려움: 스레드의 동시 실행은 프로그램의 디버깅을 더 어렵게 만듭니다.
1. 동기화: 공유 자원에 대한 접근을 제어하기 위해 뮤텍스, 세마포어, 모니터 등의 동기화 기법을 사용해야 합니다.
2. 데드락 방지: 데드락을 방지하기 위해 자원 할당 순서, 자원 요청 시 주의, 자원을 한 번에 요청하는 등의 전략을 고려해야 합니다.
3. 스레드 풀: 스레드의 생성과 소멸에는 비용이 들기 때문에, 스레드 풀을 사용하여 스레드를 재사용하는 방법도 고려할 수 있습니다.
스레드는 프로그램의 병렬 처리를 가능하게 하여 성능을 향상시키지만, 동기화와 데드락 같은 복잡한 문제를 동반합니다. 따라서 멀티 스레딩을 설계할 때는 세심한 주의가 필요
스레드는 운영 체제의 스케줄링과 관리 방식에 따라 크게 사용자 레벨 스레드(User-Level Threads, ULT)와 커널 레벨 스레드(Kernel-Level Threads, KLT)로 구분
시스템 내에서 어떻게 생성되고, 관리되며, 스케줄링되는지에 대한 근본적인 차이를 가진다.
사용자 레벨 스레드 (User-Level Threads, ULT)
정의: 사용자 레벨 스레드는 사용자 영역의 스레드 라이브러리에 의해 관리되는 스레드입니다. 이 모델에서는 커널이 스레드의 존재를 인식하지 못합니다. 스레드 관련 작업(생성, 스케줄링, 동기화)은 모두 사용자 영역에서 수행됩니다.
장점:
효율성: 스레드 간의 컨텍스트 스위치가 사용자 영역 내에서 이루어지기 때문에 빠르고 효율적입니다.
운영 체제 독립성: 커널의 지원 없이도 스레드를 구현할 수 있어, 다양한 운영 체제에서 사용할 수 있습니다.
단점:
시스템 호출의 블로킹: 하나의 스레드가 시스템 호출(예: I/O 작업)을 수행하여 블로킹되면, 동일 프로세스의 모든 스레드가 블로킹됩니다.
커널 지원 부족: 커널이 스레드의 존재를 인식하지 못하므로, 멀티 프로세서 환경에서의 효율적인 병렬 실행이 제한됩니다.
커널 레벨 스레드 (Kernel-Level Threads, KLT)
정의: 커널 레벨 스레드는 운영 체제의 커널에 의해 직접 관리되는 스레드입니다. 이 모델에서는 커널이 모든 스레드의 생성, 스케줄링, 동기화를 책임집니다.
장점:
멀티 프로세서 활용: 커널이 스레드를 관리하므로, 멀티 프로세서 시스템에서 여러 스레드를 동시에 실행할 수 있어 병렬 처리 성능이 향상됩니다.
블로킹 시스템 호출 관리: 하나의 스레드가 블로킹되어도, 커널이 다른 스레드를 계속 실행시킬 수 있습니다.
단점:
오버헤드: 스레드 간 컨텍스트 스위치가 커널 모드에서 수행되므로, 사용자 레벨 스레드에 비해 더 많은 오버헤드가 발생합니다.
구현 복잡성: 커널 내부에서 스레드를 관리해야 하므로, 운영 체제의 구현이 복잡해집니다.
혼합 모델 (Hybrid Models)
현대의 많은 운영 체제는 사용자 레벨 스레드와 커널 레벨 스레드의 장점을 결합한 혼합 모델을 사용합니다. 이 모델에서는 커널 레벨 스레드에 대응하는 사용자 레벨 스레드가 있어, 개발자는 사용자 레벨에서 스레드를 효율적으로 관리할 수 있으며, 커널은 멀티 프로세싱과 병렬 처리의 이점을 제공합니다. 혼합 모델은 유연성과 성능 사이의 균형을 제공하여, 오늘날 복잡한 애플리케이션의 요구를 충족시키기 위한 일반적인 접근 방식입니다.
아래의 one-to-one model이 요즘 os가 사용하는 혼합모델
하드웨어 성능이 올라가니깐 one-to-one model로 크게 부담이 없어서 사용하는듯?
OS는 프로세스를 제어하기 위해 프로세스 정보를 저장하는데, 이를 PCB(Process Control Block, 프로세스 제어 블록)라고 한다.
PCB는 프로세스의 '신분증'과 같습니다. 컴퓨터가 프로세스를 구별하고 관리할 수 있도록 해줍니다.
이 정보에는 프로세스의 상태, 프로세스 번호, 프로그램 카운터, 레지스터 값, 메모리 관리 정보, 계정 정보, I/O 상태 정보 등이 포함됩니다. 각 프로세스가 실행될 때, 운영 체제는 이 PCB를 사용하여 프로세스의 현재 상태를 추적하고 관리합니다.
PCB는 운영 체제가 프로세스를 효율적으로 관리하는 데 필수적인 요소로, 프로세스의 실행에 관련된 모든 중요한 정보를 포함