[Operating System] Program Structure & Execution


📝Program Structure & Execution

📌 프로그램의 구조와 인터럽트

  • 우리가 사용하는 컴퓨터 프로그램의 내부 구조는 함수들로 구성된다.
  • 하나의 함수가 수행되는 중에 다른 함수를 호출하고, 호출된 함수의 수행이 끝나면 다시 원래 호출했던 함수의 위치로 돌아가 프로그램을 계속 실행하게 된다.
  • 한편 프로그램이 CPU에서 명령을 수행하려면 해당 명령을 담은 프로그램의 주소 영역이 메모리에 올라가 있어야 한다.
  • 이때 프로그램의 주소 영역은 크게 코드(code), 데이터(data), 스택(stack) 영역으로 구분된다.
  • 코드 영역은 우리가 작성한 프로그램 함수들의 코드가 CPU에서 수행할 수 있는 기계 명령어 형태로 변환되어 저장되는 부분이고
  • 데이터 영역은 전역 변수(global variable) 등 프로그램이 사용하는 데이터를 저장하는 부분이다.
  • 스택 영역은 함수가 호출될 때 호출된 함수의 수행을 마치고 복귀할 주소 및 데이터를 임시로 저장하는 데에 사용되는 공간이다.

📌 컴퓨터 시스템의 작동 개요

  • CPU는 매 시점 메모리의 특정 주소에 존재하는 명령을 하나씩 읽어와 그대로 실행한다.
  • 이때 CPU가 수행해야 할 메모리 주소를 담고 있는 레지스터를 프로그램 카운터(Program Counter, PC)라고 부른다.
  • 즉, CPU는 매번 프로그램 카운터가 가리키는 메모리 위치의 명령을 처리한다.

📌 프로그램의 실행

  • ‘프로그램이 실행되고 있다’는 것은 컴퓨터 시스템 차원에서 볼 때 크게 두 가지 중요한 의미를 가진다.
  • 첫 번째는 디스크에 존재하던 실행파일이 메모리에 적재된다는 의미히고, 두 번째는 프로그램이 CPU를 할당받고 명령을 수행하고 있는 상태라는 의미다.
  • 일반적인 컴퓨터 시스템의 경우 CPU는 하나밖에 없으므로 매 시점 CPU에서 명령을 수행하는 프로그램은 기껏해야 하나뿐이다. 하지만 여러 프로그램이 짧은 시간 단위로 CPU를 나누어 쓰고 , 이들 프로그램이 메모리에 동시 적재되어 있을 수 있으므로 여러 프로그램이 동시에 실행된다는 말을 보편적으로 사용한다.
  • 실행파일이 메모리에 적재될 때, 일부분만 메모리에 올라가고 나머지는 디스크의 특정 영역에 내려가 있는 것이 일반적이다. 이는 메모리 공간을 효율적으로 사용하기 위함이다.
  • 각 프로그램마다 별도의 주소공간(코드, 데이터, 스택)을 가지며, 프로그램마다 독자적으로 존재하는 주소공간을 가상 메모리 또는 논리적 메모리라고 부른다.
  • 운영체제도 하나의 프로그램이므로 운영체제 커널 역시 코드, 데이터, 스택의 주소 공간을 가지고 있다.
  • 운영체제의 기능이 그 아랫단의 하드웨어 자원을 효율적으로 관리하는 것과 윗단의 응용프로그램 및 사용자에게 편리한 서비스를 제공하는 것이므로 커널의 코드는 CPU, 메모리 등의 자원을 관리하기 위한 부분과 사용자에게 편리한 인터페이스를 제공하기 위한 부분이 주를 이루고 있다. 이 밖에도 커널의 코드는 시스템 콜 및 인터럽트를 처리하기 위한 부분을 포함한다.
  • 커널의 데이터 영역에는 각 프로세스의 상태, CPU 사용정보, 메모리 사용정보 등을 유지하기 위한 자료구조인 PCB를 두고 있다. 커널의 데이터 영역에는 이와 같이 하드웨어와 소프트웨어를 포함하는 시스템 내의 모든 자원을 관리하기 위한 자료구조를 유지하고 있다.
  • 커널의 스택 영역은 일반 프로그램의 스택 영역과 마찬가지로 함수호출시의 복귀 주소를 저장하기 위한 용도로 사용된다. 하지만 커널의 스택은 일반 사용자 프로그램의 스택과 달리 현재 수행 중인 프로세스마다 별도의 스택을 두어 관리한다.
  • 프로그램이 만약 자기 자신의 코드 내에서 함수호출 및 복귀 주소를 유지하기 위해서는 자기 주소 공간 내의 스택을 사용하고, 시스템 콜이나 인터럽트 등으로 운영체제의 코드가 실행되는 중에 함수호출이 발생할 경우 커널 스택을 사용한다.

📌 사용자 프로그램이 사용하는 함수

  • 프로그램이 사용하는 함수는 크게 사용자 정의함수, 라이브러리 함수, 커널함수 이렇게 3가지로 구분할 수 있다.
  • 사용자 정의 함수란 프로그래머가 직접 작성한 함수를 뜻한다.
  • 라이브러리 함수란 프로그래머 본인이 직접 작성하지는 않았지만 이미 누군가 작성해놓은 함수를 호출만 하여 사용하는 것을 뜻한다.
  • 사용자 정의함수와 라이브러리 함수는 모두 그 프로그램의 코드 영역에 기계어 명령 형태로 존재한다. 따라서 이 두 함수는 프로그램이 실행될 때 해당 프로세스의 주소 공간에 포함되며, 함수호출 시에도 자신의 주소 공간에 있는 스택을 사용한다.
  • 커널함수는 운영체제의 커널 코드에 정의된 함수를 뜻한다. 커널 함수의 종류에는 시스템 콜 함수와 인터럽트 처리 함수가 있다. 이와 같이 커널함수는 운영체제 커널의 주소 공간에 코드가 정의된다.
  • 예를들어 삼각함수인 sin () 함수는 라이브러리 함수고, printf() 함수는 그 자체로는 라이브러리 함수지만 궁극적으로 특권 명령인 입출력을 수반하므로 printf()내에서 커널함수를 호출하는 시스템 콜을 동반한다.
  • 일반적인 함수 호출과 시스템 콜을 비교해서 설명하면, 일반적인 함수호출은 사용자 프로그램 내에 존재하는 코드를 실행하는 것이고
  • 시스템 콜은 운영체제라는 별개의 프로그램에 CPU를 넘겨서 실행하는 것이다.
  • CPU를 운영체제에게 넘기기 위해 시스템 콜은 인터럽트와 동일한 메커니즘 즉, CPU의 인터럽트 라인을 세팅하는 방법을 사용한다.

📌 인터럽트

  • CPU는 매번 프로그램 카운터가 가리키고 있는 지점의 명령을 하나씩 수행하고 나서, 다음 명령을 수행하기 직전에 인터럽트 라인이 세팅되었는지 체크한다.
  • 만약 인터럽트가 발생했으면, CPU는 현재 수행하던 프로세스를 멈추고 운영체제의 인터럽트 처리루틴으로 이동해서 인터럽트 처리를 수행한다.
  • 인터럽트 처리를 마치고 나면 인터럽트가 발생하기 직전의 프로세스에게 CPU의 제어권이 다시 넘어가게 된다.
  • 인터럽트 처리 중에 또 다른 인터럽트가 발생하는 경우는 어떨까?
  • 원칙적으로 인터럽트 처리 중에 다른 인터럽트가 발생하는 것을 허용하지 않는다. 데이터의 일관성 유지를 위함이다.
  • 인터럽트를 처리하는 중에 커널에 정의된 데이터를 변경하고 있는데, 다른 인터럽트가 발생해 앞선 인터럽트에서 변경 중이던 데이터를 또 다시 변경하면 의도하지 않은 데이터 결과가 나올 수 있다.
  • 하지만 예외가 존재할 필요가 있다. 인터럽트마다 중요도가 다르기 때문에 상대적으로 낮은 중요도를 가진 인터럽트를 처리하는 도중에 중요도가 더 높은 인터럽트가 발생하는 것을 허락할 필요가 있다.

📌 시스템 콜

  • 시스템 콜은 함수호출이기는 하지만 자신의 주소 공간을 거스르는 영역에 존재하는 함수를 호출하는 것을 말한다.
  • 다시 말해 자신의 프로그램이 아닌, 커널이라는 다른 프로그램의 주소 공간에 존재하는 함수를 호출하는 것이다.
  • 일반적인 함수호출은 자신의 스택에 복귀 주소를 저장한 후 호출된 함수 위치로 점프하는 것에 비해, 시스템 콜은 주소 공간 자체가 다른 곳으로 이동해야 하므로 일반 함수호출과는 상이한 방법을 사용한다.
  • 그 방법은 프로그램 자신이 인터럽트 라인에 인터럽트를 세팅하는 명령을 통해 이루어진다.

📌 프로세스의 두 가지 실행 상태

  • 예를들어 프로세스 A가 CPU에서 실행되고 있다고 하면, 이는 자신의 주소 공간에 정의된 코드를 실행하는 것과 커널의 시스템 콜 함수를 실행하는 것으로 나누어볼 수 있다.
  • 프로그램이 시작되어 종료될 때까지 다양한 함수호출을 하며 실행되는데, 이를 사용자모드와 커널모드의 실행 상태로 구분 지을 수 있다.
  • 프로그램이 사용자 정의함수나 라이브러리 함수를 호출할 때는 모드의 변경 없이 사용자모드에서 실행을 지속하게 되고, 시스템 콜을 하는 경우에는 커널모드로 진입해 커널의 주소 공간에 정의된 함수를 실행하게 된다.



🔎 출처 & 더 알아보기