컴퓨터에서 프로그램을 실행하면 벌어지는 일
세줄 요약
- 프로그램 > 프로세스 > 스레드
- 메모리에 저장된 값을 읽고, 해석하고, 실행 == CPU
- 메모리 관리를 수행, 추상화 계층 == OS
컴퓨터를 켜서 뭔가 프로그램을 실행하고 사용하는 과정은 익숙하다. 그렇담 그 안에서, 내부적으로 일어나는 과정을 알아보자.
컴퓨터 전원을 작동시키고 모니터 화면이 켜지면 우리는 입출력 장치인 마우스를 활용해 특정 프로그램 아이콘 위에 커서를 갖다대서 클릭한다. 그럼 뭔가가 창이 화면에 뜨면서 프로그램이 로딩될거다.
이렇게 우리가 게임을 하고, 웹서핑을 하고, 문서작업을 하는등 컴퓨터에서 벌어지는 모든 일들은 CPU와 OS가 함께 구성하고 있기때문에, 컴퓨터와 사용자가 편리하게 상호작용을 할 수 있는거다.
하드웨어적 관점
CPU는 명령어를 해석, 실행, 처리하는 역할을 한다. CPU의 내부 구성은 어떻게 이루어져있길래 CPU가 명령어를 읽고 실행할 수 있는걸까?
CPU는 크게 연산장치와 제어장치, 레지스터로 이뤄져있다.
1. 레지스터
CPU 안의 작은 고속 임시 저장 장치로, 내부(제어장치, ALU)구성 요소간에 통신을하거나 CPU 전체에 연결되어있는 시스템 버스를 활용해 각종 부품(RAM, 입출력 장치…)과도 통신을 할 수 있다.
만약 레지스터 없이 메모리만으로 데이터 및 명령어를 보관했다면 CPU가 해당 데이터에 연결을 해야될 때마다 메모리로 이동을해야될 것이며 이 과정은 프로그램의 실행 속도를 낮춘다. 이는 레지스터가 필요한 이유와 귀결된다.
이러한 레지스터의 종류에는 대표적으로
이미지를 누르면 원본 사이트로 이동된다.
가 있다.
2. 제어장치
제어장치는 명령어를 해석하고 그에 따라 내부(ALU, 레지스터)와 외부 (메모리, 입출력장치)의 동작을 제어할 수 있는 제어신호를 생성하고 다시 그 신호를 내보낸다.
입력으로는 클럭 신호, 명령어 레지스터의 명령어, 플래그 레지스터의 상태, 메모리 제어신호 등을 받고,
출력으로는 내부 구성 요소(레지스터, ALU 등)와 외부 구성 요소(메모리, I/O 장치)에 전달되는 제어신호를 생성한다.
3. 연산장치(ALU)
연산장치는 제어장치의 명령에 따라 레지스터에서 전달받은 피연산자들로 산술 또는 논리 연산을 수행한다.
결과값은 다시 레지스터로 보내지고, 비교 연산 등의 결과로 설정된 플래그는 플래그 레지스터에 저장된다.
즉 사용자가 입출력 장치 중 하나인 마우스로 프로그램 아이콘을 클릭했다고 가정했을 때 일어나는 일은 다음과 같다.
- 사용자가 마우스로 프로그램 아이콘을 클릭한다.
- 마우스 컨트롤러는 해당 클릭을 감지하고 인터럽트 신호를 생성하여 인터럽트 컨트롤러에 해당 신호를 전송한다.
- 인터럽트 컨트롤러는 CPU의 제어장치에 인터럽트 요청을 전송한다.
- 제어장치는 현재 실행 중인 명령어가 있다면 해당 명령어 사이클을 완료 후, 인터럽트 플래그를 확인하여 0인지, 1인지 확인한다. 즉 인터럽트가 허용된 상태인지 아닌지를 체크한다.
- 제어장치는 인터럽트 플래그가 1일시 인터럽트가 허용됨을 인식하고, 현재 실행 중인 프로세스의 상태(프로그램 카운터, 레지스터 값 등)를 메모리 스택에 저장한다.
- CPU는 인터럽트 벡터 테이블을 참조하여 해당 인터럽트에 대한 핸들러의 주소를 찾는다.
- CPU 레지스터 중 프로그램 카운터는 인터럽트가 발생한 주소로 점프하여 운영체제의 인터럽트 핸들러를 실행한다.
- 인터럽트 핸들러는 마우스 드라이버를 호출하여 클릭 이벤트를 처리한다.
- 그 후 GUI 시스템이 어떤 아이콘이 클릭되었는지 확인하고, 해당 프로그램을 실행하기 위한 프로세스를 시작한다.
7번부터는 운영체제가 개입해 각 프로세스에 PCB를 생성하고, 스케줄링을 관리한다. 그럼 이제부턴 OS에 대해 알아보자.
OS적 관점
만약 운영체제가 존재하지 않는다면, 메모리를 효율적으로 관리할 수 없어 각 프로그램이 메모리 공간을 서로 침범하거나, 메모리를 해제하지 않아 누수(leak)가 발생하는 등의 문제가 생겼을 것이다. 이는 시스템 전체의 불안정을 야기하고, 웹서핑같은 간단한 작업만해도 컴퓨터가 다운되거나 멈추는 일이 반복됐을거다.
즉 OS는 컴퓨터 사용자와 컴퓨터 하드웨어 간의 인터페이스로서
- 프로세스와 기억장치,
- 입출력장치,
- 파일 및 정보등의 자원을 관리하는 역할을 한다.
따라서 OS의 목적에는
- 처리 능력 (Throughput)
- 반환 시간 (Turn Around Time)
- 사용가능도 (Availability)
- 신뢰도 (Realiability) 가 있다. 해당 목적들은 곧이어 OS의 성능을 평가하는 기준이 된다.
이 때 프로세스(process)란, 실행 중인 프로그램의 최소 단위 작업을 의미한다.
보통 본체 부품이나 노트북을 구매할 때 RAM 용량을 많이보지 않는가. 그 이유가 뭘까 생각해보면, 여러 프로그램을 동시에 실행시켰을 때 얼마나 안정적인지 보기 위해서다.
그래서 우리는 OS가 메모리를 관리하기에 작업 관리자를 활용해 현재 실행중인 프로세스의 상태도 확인하고 이를 강제로 종료시킬 수도 있다.
왜 RAM 용량이 여러 프로그램을 동시에 띄웠을 때의 안전성과 연관이 있을까? 도 생각해보면, 앞에서도 말했지만 프로그램을 실행했을 때 발생되는 프로세스가 RAM상에 올라가기 때문이다.
따라서 각 프로세스는 메모리상에서 서로 완전히 독립된 메모리 공간을 갖는다.
한 프로그램 안에서도 여러개의 프로세스가 존재할 수 있는거고, 각 프로세스간에는 영향을 주지 않는다. 이 말은 비동기적 행위를 한다는 말과도 같다. 따라서 비동기적이라는 말은 다시 A프로세스가 B라는 프로세스에 전혀 영향을 주지 않는다는걸 말한다.
다시 돌아와서, 결론적으로 OS는 하나의 프로그램 안에 1개 이상의 프로세스가 실행될 때 각 프로세스마다 PCB(Process Control Block)와 메모리공간을 할당하고 초기화하는 과정을 거친다.
하나의 프로세스 안에 있는 요소는
- 프로시저(procedure),
- 스레드(thread) 로 나눌 수 있다.
프로시저란 특정 작업을 수행하는 명령어의 집합, 즉 함수 단위의 코드 블록을 말한다. 절차지향 프로그래밍 언어(C, Pascal 등)에서 많이 사용되며, 객체지향 프로그래밍에서는 클래스 내부의 함수인 메서드(Method)와 유사한 개념이라 할 수 있다.
스레드(Thread)
다음으로 스레드(Thread)란, 원래 과거엔 프로그램 안에 프로세스 밖에 없었는데 소프트웨어가 발전하면서 스레드라는 개념이 생긴거다.
왜 이런 개념이 생긴걸까? 스레드는 하나의 프로세스 안에서 동기적 행위를 하는 주체다. 아까 비동기적 행위는 메모리를 공유하지 않고 독립적으로 존재한다고 하지 않았는가? 근데 스레드는 아니라는거다. 스레드는 동기적으로 메모리 공간을 공유한다. 하나의 프로세스 안에 여러개의 스레드가 있어 해당 스레드는 각각의 스택과 레지스터를 가지며 독립된 제어흐름을 갖는다. 다만 메모리 공간만 공유할 뿐이다.
왜 메모리 공간을 공유해야 될까? 우리가 크롬을 사용할 때 사이트 화면을 봐보자.
보다시피 DOM 요소가 완전히 불러와지지 않았음에도 (페이지가 로딩 중임에도) 불구하고 자유롭게 위 아래로 스크롤을 할 수 있다.
이는 렌더러 프로세스 안에 DOM을 조작하는 메인 스레드와 스크롤을 관리하는 컴포지터 스레드가 있을 때 두 스레드 간에 메모리 공유가 일어나기 때문이다.
만약 메모리를 공유하지 않았더라면 페이지가 렌더링 될 때는 렌더링 되는 과정만 지켜봐야 했을것이다.
따라서 페이지 로딩이 완벽하게 되지 않았을 때도 사용자는 부드럽게 스크롤을 할 수 있는거다.
근데, 프로세스는 왜 한 프로그램 안에 여러개가 필요할까?
크롬 화면을 보면 맨 상단엔 현재 사용중인 브라우저 탭들이있고, 그 밑에는 주소 입력창이 있고, 그 밑에는 북마크창도 있고, 그 밑엔 브라우저를 띄워주면 렌더링 화면이 있다.
하나의 프로세스만 사용했다면 특정 사이트를 로드함과 동시에 북마크 탭에서 특정 사이트를 클릭해서 이동하지 못할거다.
근데 지금은 보다시피 페이지가 로딩이 되고 있는 상태임에도 불구하고 북마크 탭으로 계속 다른 사이트로 끊기지 않고 이동할 수 있다.
이런 과정을 관리하는 것이 스케줄링이다.
스케줄링은 왜 필요할까? 하나의 CPU 코어는 한 순간에 정확히 하나의 스레드만 실행한다. 현대엔 코어수가 많은 CPU를 사용하고 있기에 사용자 관점에선 멀티 태스킹 작업이 가능한 것 처럼 보인다. 하지만 이는 컴퓨터 관점에서 엄밀히 말하면 하나의 작업만 하고 각 프로세스간의 전환(스위칭)을 엄청나게 빠르게 하는거다.
이러한 스케줄링 방법에는
- 선점: 이미 할당되어있는 cpu를 다른 프로세스가 뺏어서 사용할 수 있는 것
- RR, SRT, …
- 비선점: “ 사용할 수 없는 것
- FCFS(FIFO), SJF, 우선순위, HRN, …
이 있다.
결론 (컴퓨터에서 프로그램 실행 과정)
- 사용자가 I/O 장치(마우스)로 프로그램 아이콘을 클릭
- 마우스 클릭이 인터럽트를 발생시키고 마더보드의 제어버스를 통해 인터럽트 신호를 CPU에 전달
- CPU는 해당 신호를 받아 운영체제의 인터럽트 핸들러를 실행
- 운영체제는 클릭된 프로그램을 식별하고 보조기억장치에서 프로그램 파일을 찾음
- 운영체제는 해당 프로그램 파일을 보조기억장치에서 메모리(RAM)으로 로드
- 운영체제의 프로세스 관리자가 프로세스를 생성하고 필요한 자원을 할당
- 운영체제의 스케줄러가 앞서 생성한 프로세스를 스케줄링하여 어디 CPU 코어에 할당할지 결정 후
- CPU는 해당 프로세스의 명령어를 실행
- GUI 관련 명령어가 실행될 때 OS의 그래픽 서브시스템을 호출하여 창을 생성하고 그래픽 요소들을 렌더링
- 렌더링 된 데이터는 RAM의 프레임 버퍼에 저장
- GPU가 해당 신호를 디지털 신호로 변환하여 디스플레이 포트를 활용해 모니터로 전송