[ 프로세스 : Proecess ]

프로세스는 독립된 (가상) 메모리 구조를 가지는 하나의 실행단위입니다. 운영체제는 프로그램이 실행되면 프로세스를 실행하고 RAM, 케시 등의 물리메모리의 주소를 가상적으로 다시 만들어 프로세스에 제공해줍니다. 프로세스는 값의 사용범위(Scope)와 값변경 유무에 따라 값을 Heap, Stack, Bss, Data 로 불리는  공간 모두 제공된 하나의 메모리공간을 사용합니다. (즉 같은 주소공간을 사용합니다.) 각 공간의 주소가 어떻게 되는가는 운영체제의 방침에 따라 다르지만, 일반적으로 아래와 같이 크기가 고정된 BSS, Data, Text, Kernel Space를 양쪽에 두고 크기가 가변적인 Stack, Heap 의 한쪽을 비워둡니다. 일단 언어적으로 내가 코딩할 때 선언한 값들이 어느 공간에 할당되는지 알아봅시다.


(32bit 컴퓨터 주소공간 : 0x00000000 - 0xFFFFFFFF)



C에서 {} 안에서 선언된 값들이 Stack에 할당됩니다. Stack은 LIFO 구조로, 마지막에 할당해준 값에 접근하는데 최적화되어 있습니다.  할당한 값이 많아질수록 처음 할당한 값에 접근하기 힘들어지기 때문에, 잠깐 임시로 사용하는 값에 특화되어 있고. C에서 코드가 중괄호를 벗어나면 {} 안에서 할당된 공간을 다시 비웁니다. {} 안에 선언된 값들을 {} 안쪽에서만 사용되기 때문에 Stack에 어울리는 언어 디자인이죠.


C에서 malloc()를 통해 할당된 값은 Heap에 할당됩니다. 정확하게 malloc()의 return값인 pointer가 가리키는 값이 Heap에 할당됩니다. Heap에 할당된 값은 실행코드가 괄호 밖으로 나갈때 통해 할당이 취소되지 않기 때문에, free()를 통해 직접 해제해주어야 합니다. 아래 예시를 보면 number, numberPtr은 Stack에 할당되었고, *numberPtr은 Heap에 할당되었습니다. 할당된 값의 주소값을 알고 있으면 Heap은 어디서든 사용 가능하지만, Stack에 비해 메모리 접근 속도가 느리다는 단점이 있습니다. 또한 free()를 적절히 해주지 않으면, 접근할 수 는 없지만, 사용되고 있는 공간이 생기는 메모리 누수가 일어납니다.


Data, BSS는 전역변수를 저장하는 공간입니다. 전역변수는 프로그램 실행도 중 추가로 할당되지 않기 때문에, 컴파일 시 크기가 정해집니다. 초기화가 되어있느냐 아니냐에 따라 Data, BSS로 나누어 집니다. 추가로 static 키워드를 사용한 변수들은 지역변수와 같은 사용범위(Scope)을 가지지만, 전역변수처럼 행동하고, Data, BSS에 할당됩니다.




[ 스레드 : Thread, Light Weight Process ]
그런데 프로그램을 실행하려면, 하나의 프로세스만으로는 부족합니다. 동시에 여러 작업을 실행해야 합니다. UI 를 Update 하면서 Save 파일도 불러오고, 계산도 해야죠. 그렇다면 프로세스 여럿을 만들어야 합니다. 그렇지만, 프로세스는 서로 다른 주소공간을 사용하고 서로의 주소공간에 접근할 수 없습니다. 유일하게 공유하는 공간은 Kernel Space 지만, 이 공간은 운영체제가 관리하는 공간이며, 프로그래머가 값을 할당할 수 있는 공간이 아닙니다. 그렇기 때문에 운영체제는

- 'KernelSpace을 이용한 라이브러리(Android Binder/SharedMemory)'
- 'File 등의 시스템자원(Linux NameLocalSocket)'

등을 이용해 프로세스 간의 통신(IPC) 를 지원해 줍니다. 하지만 성능 면에서 부족하고, 병렬작업 간의 모든 데이터전송을 IPC로 하면 프로그램이 많아질수록 운영체제의 부담이 커집니다. 따라서 운영체재는 Data, BSS, Heap 공간을 공유하는 실행단위를 제공해주는데 이를 스레드 혹은 경량프로세스(Light Weight Process)라고 합니다.

[덤]
+ OOP 언어에서 일반적으로 Object 생성은 Heap, 로컬변수는 Stack에 생성됩니다. (예 : Java)
+ Thread간의 작업전환, Process간의 작업전환은 CPU의 문맥교환(Context-Switching)을 통해 이루어지며,
Process의 문맥교환보다 Thread 간의 문맥교환이 더 빠릅니다.