JVM : 자바가상머신. 자바 바이트코드를 실행시키는 주체. 운영체제 종류와 무관하게 가능 즉, 운영체제 위에서 동작하는 프로세스로 자바를 컴파일해서 얻은 바이트코드를 기계어로 바꿔서 실행시키는 역할.
1. 소스코드 -> 바이트코드
소스코드는 .java로 저장한다.이걸 자바 컴파일러(javac)가 바이트코드로 바꿔주는데 그건 .class로 저장한다. 왜? 일단 1차적으로는 코드 숨기기, byte코드로 바꾸면 문법검사같은건 안하게 되면서 실행 시간이 단축됨. 근데 이러면 소스코드 변경할때마다 또 컴파일러가 .class로 byte코드로 변경하니까 번거로움.
2. 바이트코드 -> Runtime Data Area
이렇게 변경된 byte코드(.class)파일들은 class Loader가 Runtime Data Area로 로딩시킨다. Runtime Data Area는 5가지 영역으로 되어있는데, 1. 메소드영역 , 힙 영역 : 모든 스레드가 공유하는 영역 2. stack, PC Register, Native Method Stack : 스레드마다 하나씩 생성되는 공간 이렇게 나뉜다.
하나씩 보자.
1) method 영역
JVM이 시각될 때 생성되는 공간으로 byte코드(.class)가 여기에 저장된다. 그리고 모든 스레드가 공유하는 영역이니까 클래스의 정보, 변수 정보, static으로 선언한 공유변수가 저장되고 모든 스레드가 공유한다.
2) heap 영역
동적으로 생성된 객체가 저장되는 영역, 즉 new 연산으로 동적으로 생성된 인스턴스(클래스가 객체가 된 것)가 여기에 저장된다. 클래스의 객체, 배열 등 쉽게 소멸되지 않는 데이터가 있다.
heap 영역은 가비지 콜렉터(GC)의 영역이 된다. heap도 크게 3가지로 나뉘는데, young/old/permanent 이다.
2-1) GC(가비지 콜렉터)
가비지 콜렉터란 정리되지 않는 메모리, 유효하지 않는 메모리 주소로, 예를 들어 첫 초기화 이후에 값을 또 할당했을 때 값이 덮어씌워지는데, 그 전에 선언했던 값이라던지 선언은 해서 메모리는 가지고 있는데 사용이 되지 않은 값이라던지를 자바에서는 garbage라 부른다. 메모리가 부족할 때 가비지를 메모리에서 해제시키고 공간만들어주는 것이 GC의 역할. 크게 Mark와 Sweep 과정으로 나뉘는데, Mark는 변수나 객체를 스캔하면서 어떤 객체 참조하는지 찾는 과정(=도달성(reacheable))이고(이때 스탑함), Sweep는 Mark가 안된 객체를 힙에서 제거하는 과정이다. GC가 Mark and Sweep를 거치며 가비지를 구분할 때 도달성(reachable)이라는 개념이 있는데, 객체에 유효한 레퍼런스가 있는지(=객체를 참조하는지)를 말한다.
2-2) Stop The World
GC가 실행되려면 JVM이 애플리케이션 실행을 잠시 멈춰야한다. 그걸 Stop The World라고 한다. 멈추는 시간을 줄이는 것을 GC 튜닝이라고 한다.
2-3)heap 영역의 구조
Young 영역에서 발생한 GC를 Minor GC, old/permanent 영역에서 발생한 GC를 Major GC(Full GC)라고 한다.
쉽게 말해 young은 새롭게 생성된 객체가 위치해서 대부분 생성되었다가 사라지는 곳. old는 reachable 상태가 유지되서 살아남은 객체들 모음. GC가 적게 발생함. perm은 메소드랑 비슷하다. 클래스와 메소드 정보가 들어있는 곳.
3) Stack 영역
스택은 지역변수나 메서드의 매개변수, 임시 사용된 변수, 메서드의 정보가 저장되는 영역. 지역변수나 매개변수는 메소드가 호출이 종료되면 그 안에 있는 변수는 사라진다. 즉, 금방선언되고 금방 사라지는 애들이 여기 있다가 없어진다.
즉, class a(변수명) = new 생성자명(생성자 안에 들어갈 데이터);
여기서 a는 스택에 저장된다. 생성자명(데이터)는 heap 영역에 저장된다. 왜? 원래 자바의 레퍼런스 타입(클래스,인터페이스,배열,상수)들은 구조가 복잡하고 용량이 크다. 이것들까지 스택에 보관해서 그때그때 뽑아쓰면 비효율적이므로 heap에다가 저장 후, 얘네들 주소를 참조하는 변수(a)를 스택에다가 저장해서 불러오는 것이다.
4) PC Register 영역
스레드가 어떤부분을 어떤 명령어로 수행할지 저장하는 공간. 스레드가 시작될 때 생성되며, 현재 수행되는 JVM 명령어 주소를 저장한다.
5) Native Method Stack 영역
자바를 제외하고 다른 언어,C언어나 C++언어가 실행되는 공간. 자바 프로그램이 컴파일 된 byte코드(.class)가 아니라 실제로 실행 가능한 기계어(0101)를 실행시키는 영역.
3. Runtime Data Area -> Execution Engine 영역 : 해석할 차례
로드된 클래스 파일의 byte코드를 실행하는 곳. 여기서 컴퓨터가 이해할 수 있는 기계어로 바꾼다. 방법은 두가지.
1. 인터프리터 : 명령어 한줄한줄 해석하면서 실행.
2. JIT(just-in-time) 컴파일러 : 한줄한줄말고 런타임에 전부 한번에 실행. 여기서 해석한 것을 다시 Runtime Data Area로 가져가서 배치하고 스레드가 동기화되거나 가비지 컬렉션에 들어간다.
4. JNI(Java Native Interface) 영역
추가로 JNI는 JDK의 일부분인데, 다른 언어로 쓰여져있는 애플리케이션이나 라이브러리가 자바 가상머신과 상호작용을 할 수 있게 도와준다.
자바(JAVA)는 대표적인하이브리드 타입(Hybrid Type)언어로,소스코드 전체를 중간코드(바이트코드)로 번역한 뒤 가상머신(VM)에서 한줄씩 실행해준다.바이트코드(byte Code)로 바꾸는컴파일 타임 환경과 이를한 줄씩 번역하면서 실행하는런타임 환경으로 나뉜다.
하나씩 나눠서 살펴보자.
1. JAVA 컴파일 과정
자바가 컴파일 되는 과정을 살펴보자.
자바의 컴파일 과정
먼저 자바의 소스코드,원시코드(*.java)는 CPU가 인식하지 못하므로기계어로 컴파일해줘야 한다.이 때, 바로 CPU에서 인식할 수 있는 기계어로 컴파일하는 것이 아니라 JVM이라는 가상머신을 거쳐서 가기 때문에자바 컴파일러(Java compiler)는 중간언어인자바 바이트코드(java bytecode(*.class))로 변환해준다. JVM에서 이 자바 바이트코드(java bytecode)를 해석해주기 때문에OS와 관계없이 JVM만 설치되어 있다면 어느 디바이스든 Java 파일을 JVM 위에서 실행할 수 있다.
[ 실전 지식 ]
▶Java compiler는 JDK를 설치하면 bin에 존재하는javac.exe 파일이다.(JDK에 java compiler가 포함되어있다는 말임)javac명령어를 통해 .java를 .class로 컴파일할 수 있다. 또한JVM 역시 JDK 디렉토리의 bin 폴더 안에 존재하는 java.exe 파일로java명령어를 통해 JVM을 구동할 수 있다.
▶JDK는 Java로 소프트웨어를 개발할 수 있도록 여러 기능들을 제공하는 패키지(키트)이다.JDK는프로그램을생성, 실행, 컴파일할 수 있다.
JRE(Java Runtime Enviroment): 자바 런타임 환경으로JVM + 자바 클래스 라이브러리(Java Class Library)등 다양한 파일들을 포함한다. 컴파일 된 Java 프로그램을 실행하려면 JRE를 설치해야한다.
JVM(Java Virtual Machine): Java가 실제로 동작하는 가상 환경. 이 JVM덕분에 하나의 Java프로젝트를 개발해도 여러 환경에서 원활하게 실행시킬 수 있다.
기타 등등...
JDK 종류
Java SE : Java Platform , Standard Edition 표준 자바 플랫폼으로 표준적인 컴퓨팅 환경을 지원하기 위한 자바 가상머신 규격 및 API 집합을 포함한다. JavaEE, JavaME는 구체적인 목적에 따라 자바 SE를 기반으로 API를 추가하거나 자바 가상머신 규격 및 API의 일부를 택하여 정의된다.
Java EE : Java Platform , Enterprise Edition JavaSE에 웹 어플리케이션 서버에서 동작하는 기능을 추가한 플랫폼 이 스펙에 따라 제품을 구현한 것을 웹 어플리케이션 서버(WAS)라 한다. ex. tomcat
WAS가 무엇인지 잘 모르겠다면여기를 들어가보자! WAS 와 웹서버의 차이점을 이해하기 쉽게 알려준다! 감사합니다 작성자님 ☺️
Java ME : Java Platform , Micro Edition 제한된 자원을 가진 휴대전화, PDA 등에서 Java 프로그래밍 언어를 지원하기 위해 만든 플랫폼 중 하나이다.
결론
JavaSE는 Java가 어떠한 문법적인 구성을 가졌는지와 같은 것들을 나타내는 명세표이다. JavaSE를 기반으로 특정 기능을 구현하기 위한 JavaEE, JavaME 플랫폼도 있다.
JDK는 JavaSE와 같은 규격을 토대로 만들어진 소프트웨어 패키지이다. 이는 Java를개발 및 실행하는 데 필요한 툴들을 제공한다.
▶바이트코드와 바이너리 코드는 다르다.Byte Code는JVM이 이해할 수 있는 언어로 변환된 자바 소스코드를 의미한다. 자바 컴파일러에 의해 변환된 코드의 명령어 크기가 1바이트라서자바 바이트 코드라고 불리고 있다. 바이트 코드는 VM이 이해하는 코드이고, CPU가 이해할 수 있으려면 바이너리 코드(=0101 이진코드)로 변환해줘야한다.모든 기계어는 0과 1로 이루어진 이진코드(바이너리 코드)로 이루어졌을 뿐이다. 바이너리 코드라는 알파벳으로 되어있을 뿐 CPU가 알아들으려면 이해할 수 있게 또다시 번역을 해줘야한다. JVM이 이제 일한다.
2. JAVA 런타임 과정
컴파일 과정을 거쳐 .java 파일은 중간 코드인 자바 바이트 코드(.class) 파일로 변환해줬다. 이제 런타임을 통해 자바 바이트 코드(.class)를 컴퓨터가 이해할 수 있는 기계어로 번역해주자.
자바가 런타임 되는 과정을 살펴보기 위해 JVM을 좀 더 까보자.
JVM 구성 요소
JVM에서는 어떤 일이 벌어질까?
1)클래스 로더(Class Loader)
클래스 로더(Class Loader)는JVM 내로 클래스 파일(*.class)를 동적으로 읽어서 메모리에 로드되어 JVM에 링크되게 한다.컴파일된 .class 파일은 '로딩(Loading)', '링킹(Linking)', '초기화(Initializing)' 단계를 거쳐 JVM에서 사용할 수 있게 된다.
우선 클래스 로더는 .class 파일을 읽고, 그 내용에 따라 적절한 바이너리 데이터를 만들고 메소드 영역에 저장하는 동작을 수행한다. 이 과정에서 .class 파일이 JVM 스펙에 맞는지 확인하고, Java Version을 확인한다.
Linking
Verify 읽은 클래스의 바이너리데이터가 유효한 것인지 확인해야한다..class 파일 형식이 유효한지 여러가지 체크를 한 다음믿을 수 있는 .class 파일 데이터인 경우에 진행한다.
Prepare 클래스의 static 변수와 기본값에 필요한메모리 공간을 준비한다.
Resolution 선택적으로 진행되는 과정으로 사용하는 환경에 따라 동작 유무가 정해진다. 이 과정에서 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 힙 메모리 영역에 있는 인스턴스에 대한 레퍼런스로 교체해준다. Constant Pool의 심볼릭 레퍼런스를 다이렉트 레퍼런스, 즉실제 메모리 주소 값으로 변경해주는 작업을 한다.
Initializing
링크 단계의 Prepare 단계에서확보한 메모리 영역에 클래스의 static 값들을 할당한다. 그리고 SuperClass 초기화와 해당 클래스의 초기화를 진행한다.
JIT 컴파일러(just-in-time compliation)는 한번 바이트 코드 전체를 컴파일해서 기계어(바이너리코드)로 변환하고 나면 그 이후에는 다시 번역하지 않고 이미 번역해놓은 기계어를 직접 실행한다. 이 번역된(컴파일된) 기계어는 캐시에 보관한다. 그래서 한번 컴파일한 코드는 다시 수행하게 되면 빠르게 수행할 수 있게 되는 것이다. 당연히 한줄씩 번역하는것보다는 바이트 코드를 전부 컴파일하는게 느리기 때문에, JVM가 내부적으로 해당 메소드를 얼마나 자주 사용하는지 체크하고 있다가 적절한 시점에 바이트 코드 전체를 컴파일하는 JIT 컴파일러를 수행한다.
가비지 콜렉터(Garbage Collerctor)는더이상 사용되지 않는 인스턴스를 찾아 메모리에서 삭제한다.불필요한 걸 청소해주는 청소부로메모리를 관리해준다.
프로그램을 개발 하다 보면 유효하지 않은 메모리인 가바지(Garbage)가 발생하게 된다. 예를 들어, C언어를 이용하면 free()라는 함수를 통해 직접 메모리를 해제해주어야 한다. 하지만 Java나 Kotlin을 이용해 개발을 하다 보면 개발자가 메모리를 직접 해제해주는 일이 없다. 그 이유는 JVM의 가비지 컬렉터가 불필요한 메모리를 알아서 정리해주기 때문이다.
가비지 컬렉션은 영어로Garbeage Collection으로 줄여서GC라고도 부른다. 가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스를 말한다.
JVM에 탑재되어 있는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 개발자 입장에서 메모리 관리, 메모리 누수(Memory Leak) 문제에서 대해 완벽하게 관리하지 않아도 되어 오롯이 개발에만 집중할 수 있다는 장점이 있다.
단점은 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없고, 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생한다.
프로그램은 하드디스크 등의 보조기억장치에 저장된 실행코드를 뜻하고, 프로세스(process)는 프로그램을 구동하여 메모리 상에 실행되는 작업단위이다. 하나의 프로그램을 여러번 구동하면 여러개의 프로세스가 메모리(RAM) 상에서 실행되는 것. 이 프로세스(process)도 뜯어보면 데이터와 메모리 등의 자원, 그리고 스레드로 구성된다.
스레드(Thread)는 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미한다. 프로세스 내부에 있는 CPU 수행 단위로, 모든 프로세스는 한 개 이상의 스레드가 존재하여 작업을 수행한다. 프로세스 내부에 여러개의 스레드가 구성된다면 자기들끼리 할당된 메모리와 자원을 공유한다.
프로세스와 같이 실행, 준비, 대기 등의 실행상태를 가지며, 실행상태가 변할때마다 스레드 문맥교환(context switching)을 수행한다. 각각의 스레드별로 자신만의 스택과 레지스터를 가지고 있다.
각각의 스레드별로 자신만의 스택과 레지스터를 가지고 있다.
CPU는 한개의 프로세스만을 실행할 수 있다. 하지만 멀티스레딩(Multi Thread)을 거친다면 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것이 가능하다. 한 프로세스 내에서 프로세스 내의 주소공간이나 자원 공유가 가능하기 때문에 독립적인 작업 수행을 가능하게 하며, 아주 짧은 시간동안 여러 작업을 번갈아가면서 수행할 수 있게 해주니 동시에 여러 작업이 수행되는 것처럼 보인다. 이 손발을 스레드(Thread)가 가능하게 해준다.
JVM 단위에서의 런타임 데이터 영역
JVM단위는 JVM이 시작될 때 단 하나만 생성되며, 모든 스레드들이 공유한다.
Heap
힙 영역은 모든 클래스 인스턴스와 Array객체같이 긴 생명주기를 가진 데이터들이 저장되는 공간이다. Heap의 메모리 해제는 오직 Garbage Collector 의해 수행된다. 또한 모든 스레드들이 공유하는 영역이라 Race Condition을 유발할 수 있다.
Method Area
메서드 영역은클래스 로더에 의해 로드 된클래스 정보를 맨 처음 메모리 공간에 올릴 때, 초기화 되는 대상을 저장하는 공간이다. 런타임 상수 풀 그리고 그림에는 안나와 있지만 필드, 메서드, 생성자, 데이터 등의 코드 내용으로 이루어져 있다.
Runtime Constant Pool
런타임 상수풀은 메서드 영역에 클래스가 정보가 로드될 때 생성된다. 런타임 상수 풀에는 런타임에 해석되는 메서드와 필드의 참조 등 여러 종류의 상수가 저장되어있다.
Thread 단위에서의 런타임 데이터 영역
PC Register
PC Register는 스레드가 생성될 때마다 생기는 공간으로 스레드가 현재 실행중인 명령을 저장하는 역할을 한다.
JVM Stack
JVM Stack은JVM Frame을 저장하는 역할을 한다. Stack의 메모리 크기는 고정크기와 가변크기가 있다.
JVM Frame
JVM Frame은 메소드가 호출될 때마다 생성되며, 메소드의 상태정보를 저장한다. Frame읜 구성요소는 총 3가지로 다음과 같다.
Local Variables
Operand Stack
Constant Pool Reference
Local Variables
Local Variables의 크기는 컴파일 타임에 결정되며, 자바 바이트 코드에서 Code속성에 locals로 표현된다.
long, double은 2개의 슬롯을 차지하지만, boolean, byte, char, short, int float, reference, retrunAddress는 1개의 슬롯을 차지한다. 메서드가 호출될 때 해당 메서드의 파라미터 값은 Local Variables를 통해 넘겨진다.
만약 호출되는 메서드가클래스 메서드일 경우에는 메서드의 첫번째 파라미터는 Local Variables의 0번째 index에 저장되면 이후 차례대로 저장된다.
반대로 메서드가 인스턴스 메서드일 경우에는this가 Local Variables의 0번째 index에 저장되고 이후 메서드의 파라미터가 차례대로 저장된다.
Operand Stack
Operand Stack의 크기 또한 Local Variables처럼 컴파일 타임에 결정되며, 자바 바이트 코드에서 Code속성에 stack으로 표현된다.
Operand Stack은 프레임이 생성될 당시에는 비어있으며, 메소드 내 계산 과정 등 모든 과정에서 이용된다.
Constant Pool Reference
Constant Pool Reference 영역은 해당 프레임과 대응되는 메소드가 속한 클래스 단위의 런타임 상수풀에 대한 참조를 의미한다.
Native Method Stack
Native Method Stack은 자바가 아닌 다른 언어로 작성된 네이티브 메서드를 지원하기 위한 스택이다.
어떻게 만들지에 대한 논리적 설계를 짜면, 이걸 프로그래밍 언어로 각 언어에 맞게 프로그램을 작성한다. 프로그래밍 언어는 JAVA, Python, C++ 등이 있다. 이 때 프로그래머가 각 언어의 맞는 기능을 썼는지, 설계한대로 잘 만들고 있는지, 오타는 안냈는지 확인해야 한다. 오타 같은 구문에러(syntax error)는 프로그래밍 언어가 안돌아가면서(실행실패) 오류 메시지를 발생시키기도 하지만, 논리적인 에러(logical error)의 경우 구문 에러는 아니라서 돌아는 가는데 원하는 설계대로 동작이 안될 수 있다. (ex.아버지 가방에 들어가신다. -> 논리적 에러는 잘못된 동작을 하게 만들수도 있다.)
3. 프로그램 번역
여기서부터는 눈에 보이지 않는 동작이다. 에러 없이 원하는 설계대로 프로그램을 작성했다면, 프로그래밍 언어의 번역 프로그램이 자동으로 동작하기 때문이다. 프로그램 번역은 쉽게 말해서 우리가 쓴 말을 컴퓨터가 알아들을 수 있게 해석해주는 것이다. 컴퓨터는 무슨 말을 쓸까?
1) 기계어(Machine Language)
저급 기계어 (low-level machine language)라고 불리는기계어(Machine Language)는 컴퓨터가 알아들을 수 있는 언어로 0과 1로 구성되어 있다. 컴퓨터는 0과 1로 이루어진 언어체계로 말을 알아듣는다. 0과 1로 이루어진 언어체계를 이진코드 또는 바이너리코드(binary code)라고 부른다. 컴퓨터는(=CPU는) 바이너리 코드(binary code) 로 만들어진 기계어(Machine Language)로 쓰인 명령어를 수행한다. 사람이 아무리 컴퓨터에 명령하려고 해도 알아들을 수 있어야 시키는대로 할테니까 말이다. (ex.미국인(=컴퓨터)은 알파벳(=바이너리 코드)으로 만들어진 영어(=기계어)를 쓴다)
2) 어셈블리어(Assembly Language)
우리는 어떻게 바이너리코드(binary code) 를 쓰는 컴퓨터와 소통할 수 있을까? 우리가 외국인과 대화할 수 있는 이유는 [안녕] 이라는 한글이 [Hello]와 같은 의미라는 것을 알기 때문이다. 이와 같이 0과 1로 되어있는 기계어의 숫자를 사람이 이해할 수 있는 언어와 매치하여 만든 언어를 어셈블리어(Assembly Language)라고 한다.
//x86 계열 CPU의 기계어 명령
10110000 01100001
위는 x86 계열 CPU의 기계어 명령이고, 이것을 어셈블리어로 옮겨쓰면 다음과 같다.
//어셈블리어로 변환
mov al, 061h
기계어보다는 익숙한 언어로 변경된다. 어셈블리어(Assembly Language)는 사람이 알아들을 수 있게 만들었으니 반대로 CPU가 이해하기 위해서는 다시 기계어로 바꿔줘야 한다. 어셈블리어를 기계어로 바꿔주는 프로그램을 er을 붙여서 어셈블러(assambler)라고 한다.
// 예시) NASM x86 어셈블리어로 구현한 헬로 월드 프로그램
adosseg
.model small
.stack 100h
.data
hello_message db 'Hello, World!',0dh,0ah,'$'
.code
main proc
mov ax, @data
mov ds, ax
mov ah, 9
mov dx, offset hello_message
int 21h
mov ax, 4C00h
int 21h
main endp
end main
3)고급 프로그래밍 언어 (high-level programing language)
컴퓨터와의 소통은 이렇게 시작했다. 현재는 어셈블리어(Assembly Language)보다 더 발전된 언어 체계로 프로그래밍을 할 수 있다. 고급 프로그래밍 언어 (high-level programing language)는 사람이 이해하기 쉽게 작성된 언어 체계로, 저급 프로그래밍 언어보다 가독성이 높고 다루기 간단하다는 장점이 있다. C, C++, C#, JAVA 등을 고급 프로그래밍 언어라고 한다.
※ 여기서 저급과 고급은 좋고 나쁨이 아니라 저급일수록 기계어 문법과 유사하고 고급일수록 사람의 언어와 유사한 것이다.
//고급 프로그래밍 언어 예제(java)
int num1 = 8, num2 = 4;
result = num1 + num2;
System.out.println("+ 연산자에 의한 결과 : "+ result);
// + 연산자에 의한 결과 : 12
어셈블리어보다 이해하기 쉽다. 그 이유는 사람이 쓰는 수학적 논리 구성과 인간의 언어로 구성되어있기 때문이다.
4) 빌드(Build)
빌드 과정
고급 프로그래밍 언어와 같은 각각의 언어 문법에 맞게 명령들을 기술한 파일들을 원시파일(source File)이라고 하며, 간단하게는 소스코드라고 부른다. 소스로 작성한 명령들을 CPU가 알아들을 수 있게 기계어로 번역하는 작업을 컴파일(Compile)이라고 부르고, 고급 프로그래밍 언어로 작성된 명령어를 기계어로 변경하는 프로그램을컴파일러(Compiler)라고 부른다.
컴파일(Compile)은 어떻게 이루어질까? 원시파일(source File)로 작성된 내용은 기계어 코드로 바뀌면서 그 결과를 목적파일(Object File)에 저장된다. 컴파일러는 원시파일을 해석해서 목적파일로 바꾸는 프로그램이다. 하지만 목적파일만 있다고 바로 실행할 수 있는건 아니다. 운영체제가 요구하는 코드가 추가로 있어야 실행할 수 있는 것이다.
목적파일을 실행가능파일(Executable File)로 바꾸기 위해서는 운영체제 요건에 맞게 형태가 바뀌고 StartUp이라는 실행코드가 있어야 한다. 이러한 처리를 링크(Link)라고 하며, 목적파일을 링크 처리해서 실행파일로 바꿔주는 프로그램을 링커(Linker)라고 한다.
우리가 만든 소스코드를 컴퓨터가 실행할 수 있도록 번역해주는 과정, 이 전 과정을 빌드(Build)라고 부른다.
최근 개발툴들은 그 자체에 소스 편집기, 컴파일러, 링커를 모두 내장하고 있어서 한번에 소스를 실행파일로 바꿀 수 있다. (하지만 아직 일부 리눅스, 유닉스 환경에서는 이 과정을 프로그래머가 거쳐야 실행파일로 만들 수 있다.) 여기에 디버거, 프로파일러, 개발편의 기능까지 제공하기 때문에 우리는 이러한 개발 툴들을 통합 개발 환경(IDE)라고 부른다. 컴파일러는 소스파일을 목적파일로 변환하는 프로그램을 의미하지만 요즘엔 개발 환경 자체를 컴파일러라고 부르기도 한다.
빌드(Build)는 소스를 실행파일로 바꾸는 과정이다. 그렇다면 빌드는 어떤 방식으로 진행될까?
컴파일 타입(Compile Type)은 소스코드 전체를 기계어로 한번에 번역해서 실행파일로 만들어준다. 대표적으로 C, C++, Go 언어가 있다.
컴파일러를 이용한 빌드 과정
언어별로 약간의 과정이 추가 또는 생략될 수 있지만 대부분 위와 같은 과정을 거치며 실행파일로 만든다.
전처리(preprocessing)로 main 소스가 시작되기 전에 소스코드에 포함된 매크로나 지시자 같은 것을 포함시켜준다. 예를 들어 C언어나 C++ 언어에서 #으로 시작하는 구문들(#include, #define 등)과 같은 것들을 처리해준다. 책을 번역하기 전에 저자 이름과 인용 등을 먼저 처리해서 나중에 일일히 찾아보지 않게끔 먼저 정리해준다고 생각하면 좋다.
컴파일(Compilation)은 원시파일(source File)로 작성된 내용을 기계어 코드로 바뀌면서 그 결과를 목적파일(Object File)에 저장하는 것으로 위에서 말했는데, 사실 원시파일 -> 어셈블리어(저수준언어) -> 목적파일로 가기도 한다. 대표적으로 C, C++ 언어의 경우 컴파일 과정에서 소스코드(=원시파일)을 저수준 언어로 번역하니 상황마다 어셈블(assemble)과정을 거치기도, 안거치기도 한다. (gcc, g++, Visual C++ 과 같은 컴파일러들은 저수준언어로 번역해준다)
어셈블(assemble)은 저수준언어(어셈블리어)를 기계어로 번역하여 목적파일(Object File)로 번역해준다. 여기서는 목적이라고 하지만 '객체'로 생각하면 이해가 편하다. 컴퓨터도 내부에서 각 책의 챕터별로 번역하기 때문에 각각의 목적파일들이 생기는 것이라고 볼 수 있다.
링크(linking)는 컴파일과 어셈블을 통해 생긴 각각의 목적파일들을 하나로 연결해준다. 각 챕터를 엮어서 하나의 책으로 엮는 것.(위에서 말한 것처럼 운영체제 요건에 맞게 형태가 바뀌고 StartUp이라는 실행코드가 있어야 한다.)
이 과정을 한번에 단계를 밟아 처리하는게 컴파일 방식이다.
컴파일 타입(Compile Type) : 통번역
장점
빌드 완료한 실행가능한 파일은 실행속도가 빠르다.(다 끝냈으니까) 매번 번역할 필요없이 실행파일만 실행하면 된다.
단점
만약 프로그램을 수정하면 처음부터 다시 통번역해야한다. 플랫폼에 매우 의존적이다. (이 말은 Mac과 Window와 64비트, 32비트 별로 설치파일이 다른 이유이기도 한데, CPU하나에 1:1로 매칭되는 어셈블리어이기 때문에 해당 플랫폼 전용 번역본이라 수정되거나 OS를 변경할 경우 다시 조건에 맞게 형태를 바꾸고 처음부터 다시 빌드해줘야 한다.)
2. Interpreted Type : 통역
인터프리트 타입(Interpreted Type)은 소스코드를 한 줄씩 번역하면서 실행해준다. 대표적으로 자바스크립트, Ruby 언어가 있다.
목적파일을 만드는게 아니라 곧바로 번역해서 그때그때 실행한다
소스코드의 한 명령 세트마다 기계어로 번역하면서 바로바로 실행해주는 프로그램(또는 환경)을 인터프리터(Interpreter)라고 한다. 실행파일을 안만든다기보다는 소스코드 자체가 실행가능한 파일로 바뀌는 것. 얘는 각 OS에 맞는 번역가만 세우면 다 번역해주기 때문에 플랫폼에서 독립적이다.
인터프리트 타입(Interpreted Type) : 통역
장점
컴파일 과정 없이 바로 실행하므로 수정, 디버깅에 유리하다. 즉, 개발 속도가 빠르다. (바꿔도 또 그 부분만 번역해서 실행하면 되기 때문.) 각 플랫폼에 지원하는 인터프리터만 있으면 실행이 가능하기 떄문에 플랫폼에 독립적이다.
단점
빌드가 되어있는 컴파일 언어 프로그램보다는 실행시간이 느리다. 컴파일 언어 프로그램은 이미 다 되어있는거 실행만 하면 되지만 인터프리트 프로그램은 번역 후 실행을 반복하기 때문. (하지만 요즘이야 하드웨어 스펙이 높아져서 사람들이 체감할만큼 차이가 있지도 않다) 코드를 열면 내부가 다 보이기 때문에 보안에 좋지 않다.
3. Hybrid Type : 영어(Byte Code)로 1차 번역 후 통역
하이브리드 타입(Hybrid Type)은 소스코드 전체를 중간코드(바이트코드)로 번역한 뒤 가상머신(VM)에서 한줄씩 실행해준다. 대표적으로 JAVA 언어가 있다.
바이트코드로 컴파일 후 가상머신에서 한 명령여씩 번역해서 그때그때 실행한다.
프랑스어, 힌두어를 할 줄 아는 사람을 데려와서 그때그때 통역하는 것도 방법이지만 영어(=바이트코드)는 어디서든 대부분 쓰기 때문에 조금 번거롭더라도 중간 번역과정을 거쳐두면 그 다음부터 재사용하기 쉽고 다른 언어로도 번역하기 쉽다.
바이트 코드(Byte Code)는 일종의 중간 언어이다. VM(Vertual Machine : 가상머신)이 중요한데, VM이라는 프로그램이 바이트코드를 기계어로 바꿔준다. 이 VM만 각각의 OS에 맞게 깔려있다면 가상머신 안에 있는 인터프리터(해석기)가 바이트코드를 해석해서 실행한다.
가장 유명한 VM은 JVM(Java Virtual Machine)이다. VM이 해당 운영체제에 맞게 지원만 해준다면 플랫폼에 독립적으로 실행할 수 있다는 장점이 있다. 인터프리터 방식과 같은 원리인 것이다. (물론 VM이 인터프리트(=해석)만 하는 것이 아니라 최근에는 컴파일 방식과 혼용하여 구현되어 있다. 대표적으로 JIT(Just-in-Time) 이 있음.)
하이브리드 타입(Hybrid Type) : 1차 번역 후 통역
장점
각 플랫폼에 지원하는 가상머신(VM)이 있다면 실행가능하기 때문에 플랫폼에 독립적이다.
단점
컴파일 언어처럼 하드웨어를 직접 제어하는 작업은 불가능하다.
※뭐가 인터프리터 언어이고, 뭐가 하이브리드 언어인지는 중요하지 않다. 이런 방식들이 있다는 것이 중요하다. 각 언어는 서로의 것을 기반으로 섞이는 경우가 많기 때문에 이를 나눌 필요는 없다.
4. 프로그램 실행
번역을 마치고 실행가능한 파일을 컴퓨터가 실행시킨다. 처음 설계했던 도면대로, 그리고 그 내용으로 코딩한 내용대로, 컴퓨터가 차례로 실행한다.
컴퓨터는 여러 장치를 합쳐놓은 기계다. 본체 내부에도 여러 장치를 합쳐놓았고, 모니터, 키보드, 스피커, 마우스 등의 구성요소도 컴퓨터의 한 요소이다. 이들의 장치를 기능으로 분류하면 크게 다섯가지이다.
컴퓨터 구조(출처:네이버)
▶ 컴퓨터 구조
컴퓨터 구조 장치
장치 설명
입력장치
키보드 등과 같은 데이터를 컴퓨터에 입력할 수 있는 장치(ex. 키보드)
출력장치
컴퓨터에서 외부로 정보를 출력하는 장치(ex. 프린터)
중앙 처리 장치 (CPU)
제어장치(control unit)
기계 제어하는데 필요한 신호를 공급해주는 장치. CPU의 구성요소로 요구되는 마이크로 동작들을 연속적으로 수행하게 하는 신호를 보냄으로써 명령을 수행하게 한다.
연산장치(ALU: Arithmetic and Logic Unit)
컴퓨터의 작업을 수행하는 장치
기억장치 (저장장치)
컴퓨터에서 사용하는 프로그램과 처리할 데이터, 결과를 저장하는 장치. 주기억장치, 보조기억장치, 캐시기억장치, 레지스터로 구분된다.
주기억장치 (손)
CPU와 직접 자료를 교환할 수 있는 장치이며, 기본적인 명령어와 데이터를 기억한다. 보조 기억장치보다 접근속도가 빠르고 순간적인 내용을 찾고 저장할 수 있다. RAM( Random Access Memory): 임시 기억 장치로, 휘발성 메모리이다. 찾는 자료 위치를 순차적으로 찾아가는게 아니라 어느 특정 위치에 직접 자료를 검색하고 저장하는 방법이다. CPU를 도와 명령 처리를 도와주며, 컴퓨터의 활동을 기억하고 있다가 다시 필요할 때 빠른 속도로 기억을 불러온다. (의사(CPU) 옆에 있는 레지던트(RAM) 느낌이랄까... 메스 요청하면 쥐어준다.) RAM 성능이 좋을수록 손이 많은 느낌이라 더욱 빠르다. 그래서 배그 같은 게임도 잘돌아감. ROM( Read Only Memory) : 읽기용 기억 장치로, 비휘발성 메모리이다. 컴퓨터의 전원이 끊어져도 내용이 유지되며, 컴퓨터 기본 운영 체제 기능이나 언어해석장치(interpreter)를 내장하고 있다. 내용 변경이 불가능해서 고쳐쓰거나 삭제할 수 없다. ROM은 컴퓨터 부팅할 때 어떤 순서로 켜지는지 저장되어있는데 이걸 불러와서 표시하는데 시간이 걸려서 부팅이 느린거임. 대표적인 예로 비디오게임 콘솔의 카트리지나, 전자기기의 시스템 메모리가 이에 해당한다.
보조기억장치 (금고)
CPU가 실행할 프로그램이나 데이터를 영구적으로 저장할 수 있는 장치. 주기억장치보다 속도는 느리지만 용량이 크고 비용이 저렴하다. 하드디스크, SSD, CD_ROM,DVD 등이 이에 해당한다. RAM과 다르게 순차적으로 저장된 데이터에 접근하기 때문에 하나하나 훑느라 주기억장치보다 속도가 느리다.
캐시기억장치 (손 메모장)
주기억장치에 저장되어있는 명령어와 데이터의 일부를 임시적으로 복사해서 저장해두고 있다가 빠르게 제공해주는 장치. CPU가 주기억장치에서 데이터를 처리할 때보다 빠르다. 둘 사이의 속도차이를 줄여주는 메모장과 같은 역할. 주기억장치가 메모리에 기억할 때 그 주소값을 복사해서 가지고 있으면 처음부터 뒤지지 않아도 바로 그 위치로 찾아갈 수 있게 도와주는 것.(메스 1-3 선반에 있어요!)
레지스터 (선반)
CPU 내의 고속의 기억 장치로, CPU가 연산 제어, 정보 해석 등의 요청을 처리할 때 필요한 데이터를 일시적으로 저장한다.
데이터를 아주 잠깐 거치해두는 선반이라고 생각하면 쉽다.(수술하다가 메스 두는 곳) 대부분 SRAM(손)이 데이터(메스)를 이리로 옮겨와 데이터를 처리하고 결과를 다시 SRAM(손)에 저장한다.
▶ 메모리 계층 구조
메모리 계층 구조
컴퓨터의 설계 상 여러 종류의 저장장치들을 함께 사용해야 상황에 맞게 최적의 효율을 낸다. 하지만 빠른 저장장치는 용량에 비해 가격이 비싸고, 용량이 넉넉하면 속도가 느리기 때문에 싸고 성능 좋은 컴퓨터를 위해 이러한 메모리 계층 구조가 탄생하였다. 구조 설명은 위 내용을 참고하고, 한마디로 속도가 빠르고 비싼건 CPU와 가깝게 배치하고, 속도가 느리고 싼 저장장치는 CPU와 멀게 배치해서 적당한 가격으로 빠른 속도와 큰 용량을 얻을 수 있게 해준다. 대부분의 프로그램이 메모리의 작은 부분에만 자주 접근해서 사용되기 때문에 전체적인 성능이 향상된다.
▶ 그래서, 메모리란?
메모리는 보통 기억장치에 있는 주기억장치, 특히나 RAM을 의미한다. 프로그램을 구동하기 위해서 운영체제가 메모리(RAM)에 데이터 및 명령어를 저장할 공간을 할당하여 준다.
프로그램은 하드디스크 등의 보조기억장치에 저장된 실행코드를 뜻하고, 프로세스(process)는 프로그램을 구동하여 메모리 상에 실행되는 작업단위이다. 하나의 프로그램을 여러번 구동하면 여러개의 프로세스가 메모리(RAM) 상에서 실행되는 것. 프로세스 여러개가 있으면 각각의 메모리 공간이 있으며 원칙적으로 서로 다른 프로세스간의 메모리 공간 접근은 허용되지 않는다.(만약 접근하려면 프로세스간 통신이 필요)
프로그램을 로드하면 메모리(RAM) 상에 프로세스가 실행된다.
원래 CPU(=프로세서)는 한순간에 하나의 프로세스만 실행할 수 있다. 쉽게 말해 CPU는 한 사람이라서 한순간에 동작을 하나만 할 수 있는 것. 근데 어떻게 여러개의 프로세스가 메모리상에 실행될 수 있을까? 바로 CPU가 처리하는 시간을 잘게 쪼개서 여러 프로세스에 배분하는 방식으로 동작하기 때문이다. (이를 시분할 시스템(Time Sharing System)이라고 한다.) CPU가 첫번째 프로세스를 실행하다가 입/출력 등 때문에 대기상태가 되면 곧바로 다른 작업을 하다가, 대기상태가 끝나면 첫번째 프로세스로 다시 돌아와서 작업하는 방식으로 쪼개서 여러 일을 동시에 수행한다. (그리고 프로세스가 대기상태인 것을 다른 프로세스들에게 알려주고, 작업을 마저 도와줄 수 있도록 하는 프로세스 내부의 세포(?)의 이름이 스레드(Thread)이다.)
메모리는 컴퓨터에게 있어 가장 중요한 자산이고 사용할 수 있는 공간이 한정되어 있기 때문에 어떻게 관리하느냐에 따라서 프로그램의 성능(속도 등)이 좌우된다. 손(=프로세스)의 구조가 어떻게 구성되어있느냐에 따라 효율이 다를테니까. 따라서 메모리를 효율적으로 사용하기 위해서는 메모리의 구성과 특징에 대해서 이해할 필요가 있다.
프로그램의 실행되어 프로세스 메모리 영역에 자리를 잡고 움직이기 시작하면 어떤 일이 벌어질까?
▶ 프로그램의 실행(프로세스)과 메모리 영역
프로그램이 실행되기 위해서는 운영체제(OS)가 프로그램의 정보를 메모리에 로드해야 한다. 또한 프로그램이 실행되는 동안 CPU가 코드를 처리하기 위해서는, 메모리가 명령어와 데이터들을 저장해야 한다. 이와 같이 프로그램을 실행하기 위해 프로세스가 실행되면 운영체제(OS)는 프로그램 실행을 위해 각각의 독립된 메모리 공간인코드, 데이터, 스택, 힙을 할당한다. 이러한 주소 공간을 가상메모리(또는 논리적 메모리 : logical memory)라고 부른다.
아래는 프로세스가 프로그램 실행을 위해 점유한 메모리 영역을 나눈 것이다.
프로세스 메모리 공간 구조(가상메모리)
찬찬히 뜯어보면 다음과 같다.
프로세스 주소 공간
설명
code 영역 (Text 영역)
실행할프로그램의 코드가 저장되는 영역이다. 사용자가 작성한 프로그램 함수들의 코드가CPU가 읽을 수 있는 기계어 형태로 변환되어(=컴파일타임(Compiletime)을 거침) 저장되어있음. CPU는 code 영역에 저장된 명령을 하나씩 가져가서 처리한다. 중간에 바꿀 수 없게Read-only로 되어 있다.
data 영역
전역 변수 또는 static 변수 등 프로그램이 사용하는 데이터가 저장되는 영역이다. 전역 변수 또는 static 값을 참조한 코드는 컴파일이 완료되면 data 영역의 주소값을 가르키도록 바뀐다. 그래서 주소값으로 데이터를 찾아가는 것. 전역변수가 변경 될 수도 있어Read-Write로 되어있다.
heap 영역
프로그래머가 필요할 때마다 직접 공간을 할당, 해제하여 사용하는 메모리 영역이다. heap 영역은 컴파일 후 응용프로그램이 동작하는런타임(Runtime)에 결정된다.
heap 영역에서malloc()또는 new 연산자를 통해 메모리를 할당하고, free() 또는 delete 연산자를 통해 메모리를 해제한다.선입선출(FIFO, First-In First-Out)의 방식으로, 가장 먼저 들어온 데이터가 가장 먼저 인출 된다.메모리의 낮은 주소에서 높은 주소의 방향으로 할당되기 때문이다. (자바에서는 객체가 heap영역에 생성되고 GC에 의해 정리된다.)
stack 영역
프로그램이 자동으로 사용하는 임시 메모리 영역이다. 호출된 함수의 수행을 마치고 복귀할 주소 및 데이터(지역변수, 매개변수, 리턴값 등)를 임시로 저장하는 공간으로, 함수 호출이 완료되면 사라진다.
stack 영역에서 푸시(push) 로 데이터를 저장하고, 팝(pop) 으로 데이터를 인출한다.후입선출(LIFO, Last In First Out) 방식으로, 가장 나중에 들어온 데이터가 가장 먼저 인출 된다.메모리의 높은 주소에서 낮은 주소의 방향으로 할당 되기 때문이다.
컴파일 시 stack 영역의 크기가 결정되기 때문에 무한정 할당 할 수 없다. 따라서 재귀함수가 반복해서 호출되거나 함수가 지역변수를 메모리를 초과할 정도로 너무 많이 가지고 있다면stack overflow가 발생한다.
C 프로그램 언어 구조
이 그림이 잘 설명되어 있어서 가져와봤다.
코드(Text)는 code 영역에 저장되어 있고, 이 코드를 실행할 때 잘 뽑아쓸 수 있게 영역마다 나눠놓는것이다. data 영역에서는 전역변수(모든 지역에서 접근가능), static 참조 변수와 같이 다 가져다쓸 수 있는 데이터를 넣어놓는다. 얘네는 주소값만 가지고 있으면 모두 찾아갈 수 있고, 덮어쓸 수 있으니 수정도 가능하다. stack 영역은 함수 내에 할당된 지역변수(해당 지역에서만 접근가능)들을 넣어놓는다. 임시 메모리 영역이라는 이유는 함수가 끝나면 그 지역을 벗어났으므로 저장되었던 지역변수들은 없어지기 때문이다. 이 모든 과정은 컴파일러에 미리 정의되어 있는 루틴을 통해 수행해서 자동으로 되므로, 프로그래머가 신경쓰지 않아도 된다. Heap 영역은 프로그래머가 작성한 명령을 수행중에 메모리가 차곡차곡 쌓아올려(heap) 할당되는 곳이다. new 연산자를 쓰면 Heap 영역의 메모리 공간에 자리를 만들어주고, 만들어진 객체, 배열 등을 그곳에 저장한다. 이곳에 저장된 값을 곧바로 꺼내 쓰는게 아니라 그 공간의 주소값을 new 연산자를 통해 리턴받는다. 이렇게 되면 실제 데이터는 Heap 영역에 있는데, 그게 어디있는지 알려주는 주소값(참조값)을 stack 영역의 객체가 가지고 있는 것이다.
▶ 오버플로우(Overflow)
오버플로우 : 메모리가 넘쳐흐른다.
오버플로우는 말 그대로 넘쳐 흐른다는 말이다.
한정된 메모리 공간이 부족해서 데이터가 넘친다는 의미로, 힙의 경우에는 낮은 주소에서 높은 주소로 할당되고, 스택의 경우에는 높은 주소에서 낮은 주소로 할당되어 서로의 영역을 침범할 수 있다. 이때 힙이 스택 영역을 침범하는 경우를 힙 오버플로우, 스택이 힙 영역을 침범하는 경우를 스택 오버플로우라고 한다.
Underflow (언더플로우) : 메모리가 표현할 수 있는 수보다 적은 수의 값을 저장하는 경우
Python과 같은 동적언어는 런타임 시 자동으로 변수의 타입이 지정되기 때문에 함수가 매개변수를 받을 때에 자료형을 따로 지정하지 않는다. 반면 Java는 '정적언어'로 컴파일할 때 변수의 타입이 결정되기 때문에, 변수를 선언할 때에는 자료형을 별도로 지정해주어야 한다. (Java 외에도 C언어 또한 변수 타입을 지정해주어야 하는 정적언어)
약간 비유적으로 말하자면... 컴퓨터는 "이거 정수야!" 라고 말해주고 "이거 실수야!" 라고 말해주어야 그렇게 읽는다. int(32 비트) 기준으로 보면
사람 : "이거 정수야!"
컴퓨터 : "정수라는 것은 32 비트로 -2^31 ( -2147483648 )에서 2^31 -1 ( 2147483647 ) 사이의 값을 할당 할 수 있습니다. 이 사이의 값으로 인식합니다."
라고 하는 것이다.
근데 정수라고 말해놓고 정수보다 더 큰 값을 넣는다고 생각해보자. 그럼 32비트보다 넘기 때문에 남은 비트가 "넘쳐흐르게 된다."
▶프로세스의 상태(status)
CPU가 여러 작업을 동시에 하는 것 처럼 보이는 이유는 쪼개서 일하기 때문이라고 했다.첫번째 프로세스를 실행하다가 입/출력 등 때문에 대기상태가 되면 곧바로 다른 작업을 하다가, 대기상태가 끝나면 첫번째 프로세스로 다시 돌아와서 작업하는 방식으로 쪼개서 여러 일을 동시에 수행한다. 이 프로세스의 상태는 크게 실행(Running), 준비(Ready), 봉쇄(Blocked || Waiting || Sleep) 세 가지로 분류된다.
new : 프로세스 생성 상태 ready : 프로세스 할당 대기 상태 running : 프로세스의 명령어를 실행 중인 상태 waiting : 프로세스가 어떠한 이벤트가 일어나는 것을 기다리는 상태 terminated : 프로세스가 종료된 상태
- Admitted [생성 -> 준비] : 준비 큐가 비어있을 때 작업 스케줄러에 의해 실행 - Dispatch [준비 -> 실행] : 스케줄러에 의해 준비 큐 맨 앞에 있는 프로세스에게 CPU를 할당 - Blocked [실행 -> 대기] : CPU를 할당받은 프로세스가 입출력 작업등으로 인해 명령을 실행할 수 없는 상태 - Wake up [대기 -> 준비] :block상태의 프로세스가 입출력 작업이 끝나면 대기 상태에서 준비 상태가 됨 - Interrupt [실행 -> 준비] : Timer run out, CPU를 점유 중인 프로세스가 할당된 시간을 모두 사용하여 타임 아웃되거나, CPU 스케줄링 정책에 따라 우선순위가 높은 프로세스로 CPU 디스 패치된 상태 - Exit [실행 -> 종료] : 프로세스가 CPU를 할당받아 작업을 모두 수행한 상태
▶문맥 교환(Context Switch)
상태가 입력/출력 상태라면 이걸 다른 프로세스에게 전달해주어야 끊김 없이 CPU가 일하겠지? 문맥 교환(Context Switch)란 하나의 프로세스로부터 다른 프로세스로 CPU 제어권이 이양되는 과정을 뜻하는데, 이러한 과정을 CPU 디스패치(Dispatch)라고 한다. 어떠한 프로세스가 CPU를 할당받고 실행되는 도중 인터럽트가 발생하면 CPU의 제어권은 운영체제로 넘어가게 되고 타이머 인터럽트 처리 루틴으로 가서 수행 중이던 프로세스의 문맥을 저장하고 새롭게 실행시킬 프로세스에게 CPU를 이양한다. 하지만 프로세스 간 문맥 교환이 빈번하게 발생 시에는 오버헤드가 발생할 수 있다.
따라서 프로그램 실행 순서는 아래와 같다.
프로그램 실행 순서 (출처 : 이미지클릭)
약간씩 차이는 있겠지만 대부분 RAM이라는 주기억장치, 즉 메모리에서 프로그램을 수행하기 위해 프로세스 별로 데이터를 나누고 가공하고 기계어로 번역하는 등의 일을 진행한다.