프로세스 메모리 구조와 멀티쓰레드

프로세스의 메모리 구조

프로세스에 대한 이해는 프로그래밍의 핵심이라고 할 수 있다. 프로세스는 간단하게 ‘수행중인 프로그램’이라고 정의할 수 있다(참고로 프로그램은 수행 가능한 디스크상의 이미지라고 정의할 수 있다). 다시 말해, 프로세스는 디스크에 저장돼 있던 실행 가능한 프로그램이 메모리에 적재돼 운영체제의 제어를 받는 상태를 말한다.

프로그램을 작성해 컴파일하고 링크하면 실행 가능한 파일이 생성된다(윈도우라면 *.exe라는 파일이, 유닉스라면 기본적으로 a.out이라는 파일이 생성된다). 쉘을 통해 사용자가 프로그램을 수행시키면, 커널은 이 프로그램을 제어에 적합한 자료구조로 만들어 메모리로 읽어낸 후, 커널의 프로세스 테이블에 등록하고, 메모리, 파일, 입출력 장치 같은 자원을 할당하는데, 이때부터 프로그램은 커널의 한 프로세스로서 실행 상태가 된다.


프로세스는 Win32의 경우에 CreateProcess(), 유닉스의 경우에는 fork() 시스템 콜을 사용해 새로운 프로세스를 생성하고, 프로세스간에는 다양한 IPC(Inter-Process Communication) 방법을 통해 데이터를 교환하거나 프로세스간의 동기화를 수행한다.

<그림 1>은 프로그램이 메모리에 적재된 모습을 나타낸다. 이 구조는 유닉스와 윈도우가 크게 다르지 않다 (JVM에서 돌아가는 자바 프로그램도 비슷한 구조를 갖는다). 그림에서 위쪽이 낮은 주소번지가 된다. 그림에서 보이는 네 개의 범위(텍스트, 데이터, 힙, 스택)를 각각 세그먼트라고 한다.

각 세그먼트에 대해 좀더 자세하게 알아보자(여기서는 유닉스와 C를 기준으로 서술하지만, 윈도우에서도 공통적인 특성을 갖는다).

◆ 텍스트 : 프로그램의 실행 코드인 기계어 코드와 읽기 전용 데이터 등을 가진다(CPU가 읽어들여 수행한다고 해서 텍스트라고 부르며, 코드 영역이라고도 한다).

◆ 데이터 : C언어에서 전역 변수, 정적 변수 등으로 선언된 변수 영역(읽기/쓰기 가능)

◆ 힙 : 프로그램 수행 중 malloc(), free() 등의 시스템 콜로 할당되고, 해지되는 메모리 영역

◆ 스택 : C언어의 함수 호출시 지역 변수와 인수, 함수의 수행이 끝났을 때 리턴할 주소(return address)를 푸시한다(함수가 끝나면 이 값을 팝하고 리턴하게 된다).

운영체제는 프로그램의 텍스트 부분에 메모리를 읽기 전용으로만 사용하고, 프로세스간에는 메모리 영역을 침범하지 못하도록 한다. 따라서, 프로그램의 텍스트로 되어 있는 메모리 영역을 침범해 기록하면 버스 에러나 세그먼트 결함이 일어나서 프로그램이 종료된다. 여러분은 윈도우에서 ‘잘못된 연산을 수행해 프로그램을 종료합니다’란 메시지를 본적이 있을 것이다. 이 에러(잘못된 연산)의 95% 이상이 바로 앞서 이런 연유에서 발생한다.

C/C++와 같이 포인터(주소를 갖는 변수)를 사용해 메모리를 직접 액세스하는 언어로 개발할 때도 흔히 이와 같은 에러가 발생한다. 따라서 프로그램을 개발할 때 가장 중요한 점이 바로 메모리 관리다. 프로그램 오류의 80% 이상을 차지하는 이유를 살펴보면 다음과 같다.

◆ 초기화
◆ 해지

자바는 가비지 컬렉션을 통해 사용하지 않는 메모리를 자동으로 해지하지만, 필요에 따라 명시해 주는 것이 좋다. C/C++에서는 쓰지 않는 메모리에 대해 반드시 명시적으로 delete를 써줘야 한다.

C/C++는 할당된 메모리에 대한 포인터를 잃어버리는 경우가 생기는데, 이를 메모리 누수라고 한다.

메모리 누수는 프로그래머를 괴롭히는 가장 큰 골칫거리 중 하나다. 꼼꼼히 확인하는 습관을 들이는 것이 좋다.


 


멀티로 시작하는 단어의 사용


멀티 프로그래밍, 멀티 프로세싱, 멀티 태스킹, 멀티 쓰레드, 멀티 유저는 비슷비슷한 용어이기 때문에 혼돈해 사용하지만 의미가 모두 다르다.

◆ 멀티 프로그래밍 : 여러 프로그램이 메모리에 적재돼 수행되는 것. 하나의 프로그램이 수행되다가 입출력을 하면 CPU는 다른 프로그램으로 제어권을 넘긴다. 즉, 프로그램의 입출력을 기준으로 프로그램 수행의 스위칭이 일어난다(요즘은 좀처럼 쓰이지 않는 말이다).

◆ 멀티 프로세싱 : 다수의 프로세서가 작업을 처리하는 것. 운영체제가 멀티 프로세싱을 지원한다는 것은 다수의 CPU를 지원할 수 있다는 뜻이다. 여러 CPU가 운영체제와 메모리를 공유해 프로그램을 수행하는 방식을 SMP(대칭형 멀티 프로세싱)라고 한다(윈도우 NT가 이런 방식이다).

◆ 멀티 태스킹(시분할 시스템) : 태스크(Task, 운영체제가 수행하는 기본 단위)가 여럿인 것. 운영체제가 강제로 CPU 시간을 프로세스에 할당한다.

◆ 멀티 유저 : 멀티 유저는 기본적으로 멀티 태스킹이 되는 시스템에 다중 사용자 파일 시스템 등 다중 사용자를 지원하기 위한 기능을 추가한 것이다.

◆ 멀티 쓰레드 : 하나의 프로세스 내에 여러 개의 수행경로가 존재하고, 이를 동시에 수행하는 것. 하나의 프로세스가 동시에 두 가지 이상의 작업을 수행하는데 활용된다(쉽게 말해 프로세스 내에서 멀티 태스킹을 하는 것이라고 볼 수 있다).

여기서 멀티 쓰레드에 대해 좀더 구체적으로 알아보자. 쓰레드는 ‘수행 경로’라고 정의할 수 있다.

CPU가 텍스트(코드)를 읽어 수행할 때 수행되는 절차를 하나의 수행 경로라고 한다. 멀티 쓰레드란 결국 이런 수행 경로가 여러 개 있다는 뜻이다. 멀티 쓰레드도 멀티 태스킹과 마찬가지로 기본적으로 여러 쓰레드를 번갈아 수행한다. 텍스트, 데이터, 힙, 스택의 각 세그먼트에 대한 주소를 CPU 레지스터에 담고 있는데, 이 레지스터의 내용을 바꿔(컨텍스트 스위칭) 다른 쓰레드를 수행하도록 한다. 쓰레드 간에 프로세스가 갖고 있는 메모리와 코드 영역을 공유하므로, 컨텍스트 스위칭할 때 레지스터의 내용을 조금만 바꿔도 멀티 태스킹에 비해 컨텍스트 스위칭 하는 속도가 빠르다.

멀티 태스킹이나 멀티 쓰레드는 두 개 이상의 작업이 동시에 수행되므로, 동시에 수행되는 작업에 대해 동기화가 필연적으로 필요하다.

자바는 언어 차원에서 동기화를 지원한다. synchronized 키워드를 이용해 손쉽게 동기화를 구현할 수 있지만, 꼭 필요할 때만 사용하는 것이 좋다. synchronized를 사용한 것이 사용하지 않은 코드에 비해 약 3∼4배정도 느려지기 때문이다. 대부분의 유닉스와 윈도우는 운영체제 차원에서 Event, Critical Section, Mutex, Semaphore 등의 동기화 메커니즘을 제공한다.

Leave a Reply