[WEB] 프로세스와 스레드에 대한 이해 (1)프로세스




목표

본 포스팅에서는 프로세스에 대한 기초적인 이해를 목표로 하고 있습니다. 웹 서버에 필요한 CGI에 대해 언급하고 있으며, 한국 및 일본 정보처리 산업 기사 및 기사(Industrial Engineer & Engineer)에 포함되는 내용을 기준으로 작성하였습니다.




목차

  1. 프로세스 이해의 필요성
  2. 프로세스
  3. 상태에 대해서
  4. 구조에 대해서
    ①Code
    ②Data
    ③Stack
    ④Heap
  5. 프로세스 스케쥴링
    ①선점(preemptive)
    ②비선점(non-preemptive)
  6. Fork System Call
    ①fork()
    ②IPC(Inter-Process Communication, 프로세스 간 통신)
    ③CGI(Common Gateway Interface, 공용 게이트웨이 인터페이스)
  7. 정리




1. 프로세스 이해의 필요성



클라이언트의 요구 사항들이 꾸준히 늘어가면서, 웹 상에서 처리해야 하는 로직이 점점 복잡해지고 있습니다. 특정 규모의 서비스를 꾸준히 제공하기 위해서는, 아키텍처의 개선에 대한 요구를 동반하기도 합니다.

뿐만 아니라, 서비스가 안정적으로 자리를 잡으면 처리해야 할 클라이언트의 수도 증가하는데, 이를 위한 아키텍처 개선을 위해 기본적인 프로세스 및 스레드의 이해를 필요로 합니다.





과거의 운영체제(MS-DOS 등)는 윈도우, 리눅스, 유닉스와 같은 운영체제와 대조적으로 한 번에 하나의 작업만이 가능했습니다. 요즘의 운영체제에서 흔히 여러 개의 프로세스를 동시에 실행하여 작업하는 것을 멀티 태스킹multi-tasking이라고 하며, 특정 작업을 하나 이상의 프로세스에서 병렬로 처리하는 것을 멀티 프로세싱multi-processing이라고 합니다. 멀티 태스킹을 통해 컴퓨터는 I/O 장치 및 사용자 입력을 기다리는 데 소요되는 시간을 잘 활용할 수 있습니다.

여기서, 우리가 하드디스크에 설치하여 사용하는 여러 게임들 및 인터넷과 같은 응용 프로그램을, 메모리 위에 올려 실행 중인 작업task이 프로세스process가 됩니다.




2. 프로세스

프로세스는 활성 프로그램, 즉 실행되고 있는 프로그램의 인스턴스 입니다. 유저로부터 받은 이벤트 처리와 같이 사소한 백그라운드 이벤트부터 Internet Explorer와 같은 응용 프로그램의 실행 등을 담당합니다. 운영체제에는 많은 백그라운드 작업이 실행되고 있으므로, 시스템에서는 항상 실제 프로그램보다 더 많은 프로세스가 실행되고 있습니다.

프로그램 실행 시 OSoperating system는 프로세스에 아래와 같은 구조의 독립된 메모리 영역을 할당합니다.

  • Code
  • Data
  • Stack
  • Heap

메모리 영역은 프로그램의 소스 코드(텍스트), 데이터, 프로그램의 활동들을 포함합니다. 실행 파일을 클릭했을 때, 운영체제에서 메모리의 할당이 이루어집니다. 이 메모리 공간으로 명령어를 담은 이진binary 코드가 올라가는데, 이 순간부터 프로그램이 프로세스가 됩니다.



3. 상태에 대해서

프로세스는 메모리에 올라감과 동시에 아래와 같은 5가지 상태를 가지게 됩니다.

  • 생성(New): 새로운 프로세스가 생성 된 상태
  • 준비(Ready): 메모리 할당 허가를 기다리는 상태
  • 대기(Waiting): 프로세스가 스케쥴 이벤트를 기다리고 있는 상태
  • 실행(Running): 프로세스가 프로세서를 차지하고 명령어들이 실행되고 있는 상태
  • 종료(Terminated): 프로세스의 실행이 종료된 상태



4. 구조에 대해서


①Code

텍스트의 형태로 코드가 올라가는 영역으로, 프로그램의 명령어가 올라가는 곳 입니다. 컴파일 시, Read-Only의 영역으로 지정되기 때문에 중간 수정을 할 수 없습니다.


②Data

컴파일 시 할당되어 프로그램이 종료될 때까지 값이 반환 되지 않는 영역으로, 전역변수, 정적 변수(static), 배열 등이 할당되는 곳입니다. 초기화의 여부에 따라 Data와 Bss로 나뉘누는데, 초기화 된 데이터는 Data 영역, 초기화되지 않은 데이터는 BSS(Block Stated Symbol) 영역에 저장됩니다. 함수 내부에 선언된 Static 변수의 경우, 초기에 공간만 할당된 후 실제 함수가 실행될 때 초기화됩니다.


③Stack

프로세스 실행의 가장 기본 구조는 스택 구조로, 스택의 영역과 사이즈는 그림과 같이 각 프로세스마다 할당됩니다. 컴파일 시, 프로세스가 메모리에 로드 되면서 스택의 사이즈를 지정하기 때문에 런타임 시에는 사이즈를 바꿀 수 없습니다.

스택 구조는 가장 최근에 들어간 값이 가장 먼저 나오는 후입선출Last In First Out입니다. 스택 영역은 지역변수와 매개변수의 할당에 쓰이는데, 이와 같은 역할을 수행하기 위해서 함수의 호출과 리턴에 따라 push 명령어로 값을 추가하고 pop 명령어를 통해 값을 제거합니다. push, pop 명령 호출 시 스택 포인터가 각각 위 아래로 이동하며 위치를 표시합니다.

스택과 함수 호출에 대해서는 아래를 인용합니다.

… 그런데 현대 프로그램의 실행에서 이에 못지 않게 중요한 것이 함수여서 함수의 시작부로 이동했다가 리턴하면 호출한 다음 문장으로 돌아오는 제어의 이동이 많이 사용된다. 프로그램의 실행이란 이러한 제어의 흐름이나 함수 호출 관계가 어떻게 바뀌느냐에 따라 다양한 경로와 계산을 수행하고 다른 결과가 나오게 된다.

이러한 함수의 호출에 따라 지역변수와 매개변수의 메모리 영역이 할당되어야 한다. 한 함수에 대해 할당되는 영역을 스택프레임이라고 하고 이들은 그 함수가 동작하는 동안에만 유지되고 함수의 반환 시에 사라진다. 또한 함수 안에서 다른 함수를 호출할 수 있고 그 경우 제일 마지막에 호출된 함수가 가장 먼저 반환하므로 프레임들은 스택 형태로 쌓이게 된다.

출처 [프로그래밍노리터]



*콜스택(Call Stack)에 대해서




 void Procedure1(int Parameter1, int Parameter2) { 
 int Local Variable 1;
 int Local Variable 2; 
 Procedure2(Parameter1, Parameter2)
 return;
}


위와 같은 Procedure1~4와 같은 함수가 있고 순서대로 다음 함수를 콜 한다면, 가장 먼저 지역변수가 콜스택에 들어가게 됩니다. 이후 매개변수와 반환 주소 값(Return address) 콜스택에 쌓입니다. 반환 주소 값은, 콜 한 함수(Procedure2)가 끝난 후에 프로그램 카운터의 재반환 위치를 지정해주기 위한 값입니다.


④Heap

런타임 시 할당되는 영역이며, 프로그래머가 할당한 후 반환할 수 있기 때문에 직접 메모리를 활용 할 수 있는 영역입니다. new 생성자를 예로 들 수 있습니다. 배열의 사이즈가 고정이 아니며 변동이 있을 때 Heap 영역을 활용해서 메모리를 할당합니다. 별도의 *가비지 컬렉터(GC,Garbage Collector)가 존재하지 않는 경우, 힙 영역을 쓴 후에는 메모리 누수를 방지하기 위하여 메모리를 회수하는 작업이 반드시 필요합니다.

*GC(Garbage Collector)
힙 내에 선언 된 객체 중 Garbage 객체를 찾아내 반환하여 메모리를 회수합니다.
기본적으로, 해당 객체를 참조하는 전체 값들이 null일 경우 회수의 대상이 됩니다.
+ 메모리 할당, 회수하는 코드 추가

*Stack Overflow?
Stack은 메모리를 사용할 때, 로컬 변수를 사용하고 반환하는 식으로 주소 값, 즉 스택 포인터를 아래에서 윗 방향으로 움직입니다. 반대로 Heap은 위에서 아래 방향으로 채워나갑니다. 서로 주소 값을 채우다 Stack에서 Heap 방향으로 영역을 침범하는 경우를 Stack Overflow라고 합니다. Heap에서 Stack 방향으로 영역을 침범하는 경우에는 Heap Overflow입니다. 각각의 오버플로우는 시스템의 손상을 야기하여 취약점이 되며, 이를 악용하는 사례가 많습니다.




5. 프로세스 스케쥴링

컨테이너에 프로그램의 테스트 및 릴리즈 환경을 설정하는 작업은, 프로세스가 생성되고 실행될 때 필요한 시스템의 리소스를 할당하는 작업과 밀접한 관련이 있습니다. 여러 프로세스를 메모리에 적재 시킨 후, CPU의 가동 시간을 나누어 각각의 프로세서에게 넘겨 처리하도록 하는데, 이 때 프로세스의 대기 시간은 최소화 하기 위해 프로세스 스케쥴링이 필요합니다.

효율적인 스케쥴링 기법이 필요한 이유는, CPU를 사용하는 패턴은 프로그램 별로 모두 다른데 동일한 시스템 내부에서 함께 실행되어야 하기 때문입니다. 프로그램이 실행되어 메모리에 올라가면 프로세스가 되고, CPU는 명령어 포인터가 가리키는 주소의 기계어 명령을 하나 씩 수행합니다.

프로세서 스케줄링의 기법에는 아래와 같이 2가지가 있습니다.




・선점(preemptive) 스케쥴링 기법
 : 프로세스에 일정 시간 동안 사이클에 따라 CPU가 할당되며, 프로세스의 작업이 완료되기 전이라도 사이클에 따라 중단될 수 있습니다. 할당된 CPU 자원을 우선순위가 높은 프로세스에게 강제로 넘길 수도 있습니다. 운영 체제가 작업과 관계없이 제어 할 수 있으며, 빠른 응답을 요구하는 시스템에 많이 쓰입니다.


・비선점(non-preemptive) 스케쥴링 기법
 : 프로세스가 완전히 종료 될 때까지 CPU를 유지하며 작업이 완료 될 때까지 중단되지 않습니다. 즉, 이미 할당된 CPU에 있어서는 스스로 CPU를 반납할 때까지 다른 프로세스가 강제로 선점하여 사용할 수 없는 스케쥴링 기법입니다. 하지만, 우선순위가 높은 프로세스의 경우 의미 없는 대기 시간을 가져야 하는 경우가 있습니다.



No.선점(preemptive)비선점(non-preemptive)
1라운드 로빈(Round-Robin/RR)
FCFS를 기반으로 하여 CPU를 할당하지만, 일정 시간이 지나면 CPU를 넘기게 되는 방식
FCFS(First Come First Serve Scheduling)
CPU 를 먼저 요청한 프로세스가 먼저 CPU 를 할당 받는 방식
2SRT(Shortest Remaining Time)
대기열 큐에서 완료까지 남은 CPU 요구량을 처리하는 시간이 가장 짧은 프로세스를 먼저 실행시켜주는 방식 
SPN(Shortest Process Next)
대기열 큐에서 기다리고 있는 프로세스 중에서, 가장 적은 CPU 요구량의 프로세스를 먼저 실행시켜 주는 방식
3다단계 큐(Multi-level Queue)
(1)프로세스들의 우선순위 수 만큼 큐를 준비
(2)우선순위 큐에 각각 프로세스가 갖고 있는 우선순위 값에 따라 프로세스들을 배치
(3)우선순위가 낮은 큐의 작업은 실행 중이더라도 우선순위가 높은 큐에의 프로세스가 도착하면 CPU를 넘기는 다단계 방식
HRRN(Highest Response Ratio Next)
SPN과 SRT에서 발생하는 프로세스의 장시간 대기를 개선하기 위한 방식
준비 큐에 있는 프로세스들 중에서 *응답률Response Ratio이 가장 높은 프로세스에게 높은 우선순위를 주는 방식
*Response Ratio = (대기시간 + CPU 요구량) / CPU 요구량



6. Fork System Call

①fork()

 fork()는 프로세스가 스스로를 복제하여 메모리를 격리하는 동작으로, 부모 프로세스의 주소 데이터를 복제하여 자식 프로세스가 생성됩니다. 시스템 호출System call의 일종이며, 유닉스 환경을 바탕으로 커널 안에서 구현되기 때문에, 윈도우 OS에서는 제공하지 않습니다(배포 후에 개발 환경을 옮기는 작업은 상당히 큰 비용을 감수 해야 하기 때문에, 사전에 프로세스 복제를 필요로 하는 환경인지에 따라 운영체제에 대해 고민을 할 필요가 있습니다.)

  • fork() : 새로운 프로세스를 생성
  • wait() : 자식 프로세스가 종료될 때 까지 대기
  • getpid() : 현재 프로세스의 id를 반환
  • getppid() : 부모 프로세스의 id를 반환


*윈도우에서 fork()를 비슷하게나마 구현하기 위해서는, CreateProcessA()를 활용하여 가능합니다. CreateProcessA()를 이용하여 동작시키려 하는 코드 혹은 프로그램을 부르면, 해당 동작을 가진 프로세스가 생성됩니다. 이 때 새로 생성 된 프로세스를 자식 프로세스, 프로세스를 생성한 주체를 부모 프로세스라 할 수 있습니다. 자식 프로세스가 끝날 때 까지 WaitForSingleObject()를 걸어 부모 프로세스를 대기 상태로 만들면 됩니다.
참고 : Windows Developer API(CreateProcessA function ,WaitForSingleObject )


②IPC(Inter-Process Communication, 프로세스 간 통신)
 프로세스는 생성되면서 프로세스 간에 메모리 공간 등을 공유하는 것이 아닌, 복사하여 자원을 할당합니다. 즉, 프로세스를 생성하기 전 변수를 선언할 시, 각각의 프로세스는 동일한 변수를 각각 갖게 됩니다. 프로세스 복제는 서로를 그대로 복제하기 때문에, 통신을 위해서는 별도의 공간을 따로 만들어 두어야 합니다.

③CGI(Common Gateway Interface, 공용 게이트웨이 인터페이스)
CGI는 유저에게 동적 콘텐츠를 제공하기 위해 만들어진 인터페이스 입니다. 정적 콘텐츠를 제공하는 경우 단순히 html 페이지 파일만 제공하면 되지만, CGI의 경우에는 스크립트를 실행 및 해석 후 응답을 반환합니다.

웹 서버는 클라이언트 쪽에서 요청을 보냈을 때 응답을 반환하지만, 응답 내용에 별도의 처리나 데이터베이스로의 연결이 필요한 경우가 많습니다. 따라서, CGI는 다른 프로세스를 생성하여 이와 같은 처리들을 진행합니다. 하지만, 모든 요청에 대해서 새 프로세스를 생성하고, 이에 따른 시스템 부하를 개선하기 위해 FastCGI, WSGI 등이 등장했습니다.




7. 정리

프로세스는 실행되고 있는 프로그램의 인스턴스로, Code, Data, Stack, Heap과 같은 구조의 독립된 메모리 영역을 할당 받습니다. 프로세스가 메모리에 올라가면, 생성, 준비, 대기, 실행, 종료와 같은 5가지 상태를 가지게 됩니다.

시스템은 여러 프로세스를 메모리에 적재 시킨 후 CPU의 가동 시간을 나누어 각각의 프로세서에 넘겨 처리하게 됩니다. 메모리에 적재되어 있는 프로세스들은 프로세서에 의해 실행됩니다. 이 때 프로세스의 대기 시간을 최소화 하기 위해 프로세스 스케쥴링이 필요합니다. 실제 프로세스를 생성하고 스케쥴링하는 과정은 fork. CGI, IPC등을 활용하여 진행됩니다.