프로그램과 프로세스

 

 

 

프로그램과 프로세스의 차이를 아시나요? 프로그램은 하드디스크에 저장된 명령문의 집합체라고 볼 수 있습니다. 카카오톡이라는 프로그램은 컴퓨터에 설치한 순간 하드디스크로 들어가며, 더블클릭을 누르기 전까지는  아이콘으로만 보여지죠. 더블클릭을 누른 순간 카카오톡 프로그램은 실행됩니다. 프로그램이 실행이 되기 위한 필수조건은 무엇일까요? 바로 '메모리에 올라가는 것'입니다. 메모리에 올라갔을 때 실행중인 프로그램을 프로세스라고 합니다. 즉, 프로그램이 실행되어 메모리에 올라가면 '프로세스'가 되는 거지요. 프로그램은 저장장치만 사용하는 수동적 존재이며 그 반대로 프로세스는 메모리, cpu 등의 사용을 하는 운영체제 속에서 능동적인 하나의 작업 단위입니다. 즉, 카카오톡 자체는 프로그램 누르면서 실행되는 순간 프로세스가 됩니다.

 

프로그램에서 프로세스로 전환될 때 생성되는 프로세스 제어 블록pcb는 다양한 정보값을 지니며 메모리와 cpu사이를 왔다갔다하고, 프로세스의 이용이 끝나면 폐기 됩니다. 

 

위 그림은 프로그램에서 프로세스로 전환시 저장장치와 메모리의 관계를 보여줍니다. 저장장치(하드디스크)에 있는 프로그램 a,b,c를 실행시킬 때 메모리에 프로그램이 올라가며 프로세스로 전환됩니다. 위에 언급한 프로세스 제어 블록pcb는 이때 생성되며 운영체제 영역에서 활동하게 됩니다. 

 

프로세스를 이해해야 스레드, cpu스케줄링 이해가 더 쉽기 때문에 프로세스의 구조, 상태, pcb와 그외에 많은 개념들을 알아보겠습니다.

 

 

 


 

 

프로세스의 구조

프로세스의 구조

 

프로세스의 구조는 코드영역, 데이터영역, 스택영역, 힙영역으로 나뉩니다. 

 

코드는 자신을 실행하는 코드입니다. 프로그램은 코드로 작성되었으니, 당연하게도 프로그램이 실행될 때 코드가 실행되어야 겠죠. 

데이터는 코드가 실행되면서 사용하는 전역 변수& 상수 변수나 파일, 각종 데이터를 모아놓은 곳입니다. 데이터는 변하는 깞이기에 기본적으로 데이터 내용은 상수는 읽기 전용만, 나머니 변수는 읽기 쓰기가 가능합니다.

스택 영역은 운영체제가 프로세스를 실행키 위해 부수적으로 필요한 데이터를 모아놓은 곳입니다. 지역변수나 함수가 호출되었을 때 필요한 정보나 위치를 이 영역에 저장하며, 스택 영역은 운영체제가 사용자를 위해 유지하는 영역이므로 사용자에게는 보이지 않습니다.

힙 영역은 프로그래머가 동적으로 메모리를 할당하는데 쓰입니다. malloc, free heap등의 영역에 자원을 할당/ 해제할 수 있습니다.

 

 


 

코드가 메모리에 올라가서 프로세스가 되는 과정

 

index.c라는 프로그램이 있다고 칩시다. 이 프로그램을 더블 클릭하면, 메모리에 올라가서 프로세스로 변환되겠죠? 이 과정을 알아봅시다.

 

test.c의 정의한 숫자를 치환하고 필요한 파일을 불러온다 -> 전처리기를 거치면 파일 확장자는 index.i가 된다 -> 고수준인 c언어를 저수준인 어셈블리어로 바꿔준다. -> 어셈블리어 기계어와 1대1 매칭을 한다 -> 어세믈러가 어셈블리어를 기계어로 바꿔준다. 이때, 기계어는 0과 1로만 이루어져있다. -> index.o -> 링커가 링킹을 한다(라이브러리, 다른 소스 코드 연결) -> index.exe가 된다 -> 파일 더블클릭한다 -> exe가 메모리에 올라가게 되면서 index는 프로세스가 되며 운영체제로부터 관리받는다. 

 

 


 

PCB 프로세스 제어 블록

 

위에서 언급했던대로, pcb는 프로세스가 만들어지면 생기는 것입니다. 이를 이해하기 위해선 먼저 pcb의 구조가 어떻게 생겼는 지 아는 것이 중요합니다.

pcb의 구조

 

pcb의 첫 번째 블록엔 포인터가 저장됩니다. 부모와 자식 프로세스에 대한 포인터, 할당된 자원에 대한 포인터 등이 있습니다. 프로세스의 한 상태에서 다른 상태로 전환될 때 저장하는 포인터를 가지고 있습니다* 프로세스의 상태(예: 준비 -> 실행 단계로 갈 때)

 

프로세스의 상태는 생성, 준비, 실행, 대기, 완료로 현재 프로세스의 상태를 나타냅니다. 

프로세스 구분자id는 프로세스를 식별하기 위한 숫자id가 저장됩니다.

프로그램 카운터는 다음에 실행될 명령어의 주소를 포함하는 프로그램 카운터 값을 저장합니다. 후에 알아보겠지만, 오늘날의 os는 시분할 처리로 짧은 시간동안 프로세스를 cpu가 번갈아 실행하기 때문에 cpu를 뺏겼다가 다시 실행될 때, 원래 실행하던 명령어를 내보내야 하기 때문에 프로그램 카운터가 필수입니다.

프로세스는 우선순위가 다릅니다. 예를들어 창을 두개 키고 하나는 참고 용도, 하나는 글쓰기를 하고 있다면 글쓰기 하는 창이 더 중요도가 높겠죠. 다양한 우선순위의 프로세스가 '대기 상태'에 있을 때 중요도 별로 따로 운영되거나 더 자주 실행되기 때문에 우선순위의 정보도 가지고 있어야 합니다.

레지스터 정보는 프로세스가 실행될 때 사용했던 레지스터 값이 저장됩니다. 이전에 사용하던 값을 보관해야 또 실행할 수 있기 때문입니다.

메모리 관리 정보는 프로세스가 메모리 어디에  있는 지에 대한 위치정보, 메모리 보호를 위한 경계 레지스터 값, 한계 레지스터 값이 저장됩니다.

할당된 자원 정보는 프로세스를 실행키 위한 입출력 자원, 파일에 대한 정보를 말합니다.

게정 정보엔 cpu스테줄링에 필요한 우선순위, 최종실행시간, cpu점유 시간 등이 저장됩니다.

PPID는 parent process, CPID는 child process를 지칭합니다. 이 부모프로세스와 자식프로세스의 정보도 pcb구조 안에 저장됩니다.

 

 


 

 

프로세스의 상태 

프로세스의 상태

 

Pcb안에는 프로세스 상태에 대한 정보가 들어가 있다고 했습니다. 프로세스의 상태에는 생성, 준비, 대기, 실행, 완료 상태가 있습니다.

요즘날의 컴퓨터는 cpu를 통해 시분할 처리를 합니다. cpu는 한 번에 하나의 프로세스 밖에 처리를 못하기 때문입니다. 하지만 처리 속도가 매우 빠르기 때문에 우리는 프로세스가 동시에 실행된다고 느낍니다. 프로세스들은 실행되면서 cpu를 기다리며 다섯가지의 상태를 지니게 됩니다.  

 

생성상태는 프로세스가 실행되며 메모리에 올라갔을 때를 의미합니다.

준비상태는 cpu를 사용키위해 기다리고 있는 상태입니다. cpu에는 우선순위가 있다고 하였죠. 실행 시킨 순번이나 중요도 순에 따라 cpu  스케줄러에 의해 cpu가 할당됩니다.

실행상태는 준비상태에 있는 프로세스가 cpu스케줄러에 의해 cpu를 할당받아 실행되는 상태입니다. cpu 부여시간이 끝나면 할당된 프로세스를 강제로 빼앗기게 되고 다시 준비단계로 돌아갑니다.

대기상태는 프로세스가 입출력 요청이 있을 때 입출력 되기 까지 기다리는 상태를 의미합니다. 입출력 시에는 다른 프로세스에게 cpu를 할당하고 입출력이 완료되면 대기 상태에 있는 프로세스에게 cpu할당을 합니다.

완료상태는 프로세스가 종료된 상태입니다. 이 때 사용한 데이터를 메모리에서 제거하며, pcb도 폐기 됩니다. 

 

과정이 많다고 느껴질 수 있지만, 정말 간단합니다. 카카오톡이라는 프로그램을 열었을 때, 카카오톡은 컴퓨터 메모리에 적재되며 프로세스가 되고, cpu의 할당을 기다리다가 실행이 되며, 대화를 보낼 땐 키보드를 쳐야 하기 때문에 입출력을 기다리는 대기 상태가 되었다가 그 사이에는 다른 프로세스에게 cpu를 할당했다가, 입출력이 끝나면 다시 실행상태로 돌아갔다가 대화가 종료되고 카카오톡이라는 프로세스를 끄면 pcb도 제거되는 것을 상상하면 됩니다. 데이터도 메모리에서 제거된다고 하는데, 카카오톡의 대화는 저장되어있지 않나요? 라는 질문엔 카카오톡 대화는 카카오톡의 클라우드에서 저장되고 있는 것이고, 이 데이터는 카카오톡에서 채팅을 치다가 깜빡하고 카카오톡을 제거했을 때, 채팅이 사라지는 것을 생각하면 될 것입니다. 

 

 


Context Switching 문맥교환

 

문맥 교환은 Cpu를 차지하던 프로세스가 나가면서 실행중인 프로세스의 상태를 저장하고 새로운 프로세스의 상태값으로 교체하는 작업을 말합니다. 이전 작업 상태가 되어야 다음 작업을 할 수 있기 때문입니다. 

 

문맥 교환이 일어나는 이유는 cpu점유 시간이 다 되었거나, 입출력i/o요청이 있거나 인터럽트가 있을 때 발생합니다.

문맥 교환이 일어나는 가장 많은 이유는 프로세스 끼리의 교환이 있을 때일 것입니다. a라는 프로세스가 실행 단계에서 cpu를 점유하다가, 준비 상태로 변환하게 되면서 b프로세스가 실행 단계로 오게 됩니다. 이를 문맥교환 context switching이라고 부릅니다. 문맥교환이 일어날 때는 프로세스가 변경된다는 의미니 각 프로세스가 지니던 pcb의 내용도 변경이 됩니다. pcb는 이 과정에서 실행중인 프로세스 작업내용을 저장합니다. 프로그램  카운터, 레지스터 값들도 다 같이 변경됩니다. 

 

메모리에 있는 모든 프로세스들은 이 문맥교환을 끝날 때 까지 모두 반복합니다. 

 


 

프로세스 생성과 종료 

 

[cmd(ctrl) + N] 을 눌러보세요. 지금 보고 있는 창을 크롬으로 보고 있다면 크롬 페이지 하나가 또  복사될 것입니다. 이를 프로세스 복사 함수 --fork()함수--라고 부르는데, 원래 실행하던 프로세스를 부모 프로세스, 새로 생긴 프로세스는 자식 프로세스로서 부모-자식 관계를 이룹니다. 

 

fork() 시스템 호출의 동작 과정

 

fork()시스템 호출을 하게 되면 pcb프로세스 제어 블록을 포함한 부모 프로세스의 대부분의 영역이 자식 프로세스에 복사됩니다. 단, 몇가지가 바뀝니다. 먼저 프로세스 구분자PID가 바뀝니다. 복사된 프로세스라도 독자적인 프로세스이기에 당연히 ID가 바뀌겠지요. 마찬가지로 독자적인 프로세스기에 기존 부모 프로세스와 다른 메모리 위치를 가집니다. 그러므로 메모리 관련 정보도 바뀝니다. PPID와 CPID가 바뀝니다. 기존 부모프로세스가 가지던 PID가 자식프로세스의 PPID로 생성되며 부모 프로세스의 CPID도 자식 프로세스의 PID로 바뀝니다. 서로를 알아보기 위한 장치라고 할 수 있습니다. 

 

fork()시스템 호출의 몇 가지 장점을 알아보겠습니다. 먼저 프로세스의 생성 속도가 빨라집니다. 더블클릭해서 프로그램을 가져오는 것보다 기존 메모리에서의 복사가 생성 속도에 더 이득을 가져다 줍니다. 또, 추가 작업 없이 자원을 상속할 수 있습니다. 이미 부모 프로세스가 초기화한 작업을 자식 프로세스는 바로 사용할 수 있습니다. 마지막으로 시스템 관리를 효율적으로 할 수 있습니다. 부모 프로세스와 자식 프로세스 구분자가 연결되어 있기 때문에 자식이 사용하던 자원을 부모가 정리할 수 있어 시스템이 효율적으로 관리됩니다.

 

exex()시스템 호출

 

이렇게 fork()함수로 코드, 데이터 등을 모두 복사해서 쓰는 방법은 매우 간편합니다. 똑같이 간편하고 싶은데, 코드는 바꾸고 싶을 경우엔 어떻게 해야할까요? exec()함수를 쓰면됩니다! 기존의 코드영역을 새 코드로, 데이터 영역도 새로운 변수로 채워지면서 새 데이터로, 스택 영역은 리셋됩니다. 하지만 프로세스 구분자나 부모 프로세스 구분자, 자식 프로세스 구분자, 메모리 등은 그대로입니다. 이는 프로세스의 구조를 재활용하는 것인데요, 자원을 재활용하면 사용이나 회수가 간편하고 빠르게 됩니다.

 

운영체제의 구조는 이처럼 부모-자식 관계를 잘 이용합니다. exit()함수를 사용한다면, 자식프로세스는 부모 프로세스에게 종료를 알립니다. 부모는 자식을 먼저 단속해서 끄게하는데요, 자식이 너무 많은 나머지 부모가 자식을 보지 못하고 먼저 종료된다면, 자식은 그대로 프로세스에 남게 됩니다. 프로세스는 꺼졌는데 말이죠. 그러면 프로세스는 제대로 꺼진 게 아니겠죠? 이때 남아있는 프로세스를 좀비 프로세스 혹은 고아 프로세스라고 칭합니다. 좀비 프로세스가 많아지면 자원이 낭비되고, 메모리에 침투하여 쾌적한 메모리 공간을 휘젓기 때문에 컴퓨터가 느려집니다. 프로세스를 여러 개 종료했는데 관리자 화면엔 프로세스가 남아있거나 컴퓨터가 버벅인다는 느낌이 든다면, 좀비 프로세스가 컴퓨터 안에 남아있는 것입니다. 엄청 느려진 컴퓨터를 복구하기 위해 우리는 컴퓨터 전원을 껐다 키죠. 그러면 정말 말끔하게 빨라집니다. 부팅을 하면 메모리에 있는 좀비프로세스가 다 사라지기 때문입니다. 

 

우리가 여기서 알면 되는 것은 그저 '운영체제는 프로세스의 복사, 상속, 계층 구조를 가진다, 이런 상속 관꼐는 자원의 사용을 용이하게 하고 회수도 빠르게 할 수 있어서 효율적이다' 입니다.

 

 

 


쓰레드

 

운영체제가 작업을 처리하는 단위는 '프로세스'입니다. 운영체제에게 작업을 요구할 때마다 프로세스가 생성되고, pcb도 함께 생성됩니다. 그러면 메모리에 코드, 데이터, 스택, 힙 영역이 적재되며 프로세스가 생성될 때마다 메모리에 가중됩니다. 독립적인 프로세스는 데이터를 주고 받을 때 프로세스 간 통신IPC을 이용하는데 이 통신 비용도 많이 들기도 합니다. 이런 문제가 있기 때문에 쓰레드가 고안됩니다.

 

쓰레드의 정의는 '프로세스의 코드에 정의된 절차에 따라 CPU에 작업 요청을 하는 실행단위이다.'인데, 그냥 쓰레드는 프로세스  내에 존재하며 그 갯수는 여러 개이다. 라고만 생각하셔도 됩니다. 쓰레드는 프로세스 내에서 pcb, 코드, 데이터, 힙 영역을 공유합니다. 하지만 스택을 공유하진 않습니다. 예를 들어 마이크로소프트의 웹을 상상해보겠습니다. 마이크로소프트의 웹브라우저를 누르면 메모리에 적재되며 프로세스가 됩니다. 이 때, 프로세스가 생성되면서 쓰레드도 생성됩니다. 탭을 추가합니다. 그러면 쓰레드가 또 생성됩니다. n개 까지 탭을 늘리면 쓰레드도 n개 까지 늘어납니다. 웹 브라우저의 pcb, 코드, 데이터, 힙 영역은 공유하기 때문에 탭을 추가하게 된다면 우리가 아는 그 화면이 나옵니다. 하지만 스택은 저장되지 않기 때문에 내가 보던 화면은 그대로 나타나진 않습니다. 이렇게 탭 추가하여 쓰레드를 이용한다면 프로세스의 코드, 데이터, 힙이 공유가 되니 메모리 절약이 정말 많이 됩니다! 

 

프로세스와 쓰레드의 장단점

 

이렇게 보면 쓰레드가 간편하고 좋은 것 같지만 또 각자 사용 시 장단점을 가집니다. 

안정성 문제에서는 프로세스는 서로 독립적이기에 하나의 프로세스가 문제가 있더라도 다른 프로세스는 거의 영향을 받지 않습니다. 반면 쓰레드는 하나의 프로세스 내에 여러개가 존재하기 떼문에 하나의 쓰레드 문제가 생기면 감염되는 것 처럼 다 문제가 일어나게 됩니다. 웹 브라우저 탭을 여러 개 켜놨다가 하나의 탭이 에러가 생겨도 그 탭이 모두 영향을 받는 것 처럼요. 안정성에서는 프로세스가 쓰레드보다 낫습니다.

각각의 프로세스는 데이터 공유가 어렵지만 쓰레드는 스택 제외하고는 모두 공유할 수 있기에 오버헤드도 적습니다. 자원 공유, 그로 인한 효율성 향상이 쓰레드의 최대 장점입니다.  쓰레드가 빠르고 용이하게 메모리에 접근해도 데이터가 공유되면서 문제가 생길 수 있는 점은 첫번 째 단점과 이어지는 부분입니다.

+ Recent posts