본문 바로가기
Computer Science/운영체제

Architectural Support for OS

by sepang 2022. 3. 17.
반응형

  이 카테고리의 글 들은 아주대학교 안정섭 교수님의 '운영체제' 수업을 수강하고 복습 겸 정리하는 글이다. 해당 과목을 더 잘 이해하기 위해서는 컴퓨터 구조(블로그 시작 전에 수강해 따로 정리하지 못했다 ㅠㅠ)와 시스템 프로그래밍('시스템 프로그래밍' 카테고리를 참고하자)에 대한 대략적인 이해가 필요할 것 이다.

Computer System Organization

fig 1

  다음 사진은 컴퓨터 시스템의 구성을 나타낸 것이다. 연산을 담당하는 CPU와 프로그램이 올려지는 메모리외에 디스크, 마우스, 프린터 등의 입출력 장치들이 합쳐저 컴퓨터 시스템을 구성한다. 어플리케이션은 입출력 장치들을 사용할텐데, 어플리케이션이 이러한 HW에 직접 접근하는 것이 아니고 OS가 사이에서 동작하여 컴퓨터의 자원을 효율적으로 동작하게끔 한다. 예를 들어 어플리케이션이 모니터에 어떤 화면을 띄우려는 작업에서 어플리케이션이 모니터를 직접 제어하는 것이 아니라 OS에 작업 내용을 전달하면 OS가 모니터를 동작시키는 식이다.

  이러한 과정에서 우리는 몇 가지 문제에 대해 생각할 수 있다. 이것에 대해 알아보자.

Issue 1. I/O

  먼저 I/O를 효율적으로 수행하는 방법에 대한 것이다. 각각의 device controller(장치 컨트롤러, device driver가 있는 HW를 관리하는 HW)는 특정 device 타입을 관리하는데 이를 위해 각각의 device controller는 로컬 버퍼(local buffer)를 가지고 있다.

  정리하자면 CPU가 '메인 메모리의 데이터를 로컬 버퍼로 넣어주고 I/O 장치가 특정한 작업을 하도록 명령을 보내는 것'을 I/O라고 한다. 여기서 생각해봐야 할 것은 I/O 작업에서 I/O 장치는 기본적으로 CPU보다 처리속도가 훨씬 느리기 때문에 I/O 작업을 하고 있는 동안 CPU는 아무것도 하지 않는 상태(idle)가 된다는 것이다. CPU는 귀중한 자원이기에 우리는 이러한 time-consuming한 작업을 어떻게 효율적으로 할 지 생각해봐야 한다. 그러기 위해서는 어떤 어플리케이션이 I/O 작업을 하고 있는 동안 다른 어플리케이션이 놀고 있는 CPU를 활용할 수 있게끔 해야 한다. 이를 위한 방법으로는 두 가지를 생각해볼 수 있다.

  • CPU가 보낸 명령어가 완료되었는지 확인 (1)
  • 메인 메모리와 장치 버퍼(로컬 버퍼)간 데이터가 이동되었는지 확인 (2)

  이를 위한 메커니즘을 살펴보자.

Interrupts

  '시스템 프로그래밍(참고)'에서 다뤘던 주제다. CPU는 하나의 장치가 I/O 작업하는 걸 기다리다가 이것이 끝나고 처리를 하게되면 위에서 말했듯이 CPU는 대부분 idle 상태일 것이다. 그러므로 CPU는 위에서 언급한 두가지 방법을 이용하여 필요할 때만 잠시 하던 일을 멈추고 I/O 작업을 처리해주면 된다. 이를 이루기 위해서 polling, interrupt 기법이 사용된다. polling 기법은 일정 시간마다 I/O 작업이 진행 중인지 CPU가 확인하는 것이다. 확인할 때만 시간이 소요되니깐 나쁜 방법은 아니지만 계속해서 작업 중인지 확인하는 오버헤드가 발생한다.

fig 2. interrupts
fig 3

  그래서 polling 기법이 아닌 interrupt를 주로 사용한다. 장치 컨트롤러는 해당 장치의 I/O operation이 끝나면 전기적 신호를 발생시켜서 즉 (HW)interrupt를 발생시켜 작업의 종료를 알린다. 때문에 CPU는 interrupt를 받기 전까지는 다른 작업을 하고 있으면 되는 것이다. 이외에도 작업 중 SW적인 오류(ex. /0)가 발생했을 때도 (SW)interrupt를 발생시키기도 한다. 

  fig 3을 보면 알겠지만 interrupt를 이용하면 CPU의 어플리케이션 실행과 I/O 장치의 전송 작업이 동시에 진행할 수 있다. 그러다가 I/O의 전송 작업이 종료되면 이를 CPU에게 알려서 이때만 interrupt를 해결하고 다시 CPU는 하던 일을 이어서 할 수 있는 것이다.

Interrupt Handling

fig 4

  그렇다면 이러한 interrupt가 이뤄지는 과정을 살펴보자. fig 4는 메모리를 나타낸 그림이다. 현재 수행 중인 명령과 다음에 수행할 명령을 확인할 수 있는데, 우리는 어떤 명령을 실행하다가 interrupt가 발생할 지는 당연히 알 수 없다. 때문에 (HW)interrupt는 비동기적(asynchronous)이다. 어쨌든 interrupt가 발생하면 메모리에 있는 interrupt handler로 이동하여 주어진 interrupt에 맞는 조치를 한 다음 interrupt 발생 직전 실행 중이었던 명령의 다음 명령을 실행하게 되는 것이다. 이 과정은 우리가 코딩에서 함수를 호출하는 경우와 유사해 보인다.

  이러한 작업을 가능하게 하기 위해서는 interrupt handler로 이동하기 전의 상태를 보존해줘야 한다. 그래야 interrupt 해결 후 돌아갈 곳을 알 수 있으니깐 말이다.

Data Transfer Modes

  fig 3를 보면 알겠지만 I/O 작업은 대부분 전송이다. 전송에는 두가지 방식이 있다.

  Programmed I/O(PIO)는 CPU가 I/O 장치와 메모리 간의 데이터 이동에 관여한다. CPU가 장치에 입출력과 관련된 명령을 쓰거나 I/O 장치에 쓰여지는 공간을 메모리에 맵핑하여 메모리에 데이터를 쓰면 장치에 데이터가 쓰여지게끔 보이게 하는 것이다. 그런데 이러한 방식을 사용하면 CPU는 계속해서 전송에 관여하기 때문에 전송 중에 다른 작업을 할 수 없다.

fig 5. DMA

  Direct Memory Access(DMA) 방식은 전송 과정에서 CPU가 개입하지 않게끔 장치가 메모리에 직접 접근하는 것이다. fig 5를 보자. 현재 상황은 디스크의 데이터를 메인 메모리로 읽어오려는 상황이다. PIO였다면 읽고자 하는게 disk controller의 버퍼로 들어오면 CPU가 메모리로 이를 전송했을 것이다. DMA에서의 과정은 이렇다.

  1. CPU가 DMA controller에게 전송에 대한 정보를 준다.
  2. DMA controller가 Disk controller에게 메모리에 ~한 데이터를 보내라고 정보를 보낸다.
  3. 이를 받은 Disk controller가 CPU 개입없이 메모리에 데이터를 전송한다.
  4. 이후 전송이 다 끝났다면 Disk controller가 전송이 완료되었다는 ACK를 DMA controller에게 날려주고 DMA controller는 interrupt를 발생시켜 CPU가 전송 작업 완료를 인지하게 한다.

  과정을 간단히 하면 A가 실행 중이다가 read()를 만나 데이터를 읽는 동안 CPU는 B를 실행할 수 있을 것이다. 이후 A의 read() 작업이 끝나고 interrupt가 발생하면 CPU는 다시 A로 돌아가 read() 이후 작업을 재개한다는 식으로 생각할 수 있겠다.

 

Issue 2. Protection

  OS가 해결해야할 두번째 문제는 보호(Protection)에 대한 것이다. 어플리케이션이 디스크 드라이브에 직접 접근한다거나, HLT(명령 실행을 멈추고 프로세서를 idle한 상태로 변환) 명령 실행을 자유롭게 할 수 있다면 컴퓨터 시스템이 크게 손상될 위험이 있다. 한 에플리케이션 A가 악의적으로 HLT를 실행하여 다른 프로그램 실행을 막는 것을 상상해보면 이해가 될 것이다. 그렇기 때문에 위에서 말한 두가지 작업은 OS를 통해 실행된다. 즉 OS가 앞서 말한 작업들을 수행하여도 컴퓨터 시스템에 문제가 생기지 않는지 판단한다는 것이다.

Protected Instruction

  이처럼 user mode(유저 모드)에서 수행 할 수 없는 특정 작업을 수행하는 명령을 Proctected or Privileged Instruction이라고 한다. 예를 들어 방금 언급한 직접 접근이나 HLT 명령도 있고, 시스템 레지스터(control register, 여러 시스템 테이블의 위치, ...)에 접근한다거나 메모리 상태와 관련된 데이터에 접근하는 명령도 있다.

CPU Modes of Operation

fig 6

  방금 전 'user mode'라는 용어가 나와서 이와 관련해 말하자면 CPU는 커널 모드(kernel mode)와 유저 모드(user mode)가 존재한다. 어떤 아키텍처인지에 따라 다르지만 두 가지 모드가 아니라 더 다양한 수준의 권한이 존재하기도 한다. fig 6으로 치면 가장 안에 위치하여 가장 높은 권한을 가지는 것은 OS라고 할 수 있다. 모든 privileged instruction을 실행할 수 있다. 반면에 가장 바깥에 위치한 가장 낮은 권한을 가진 유저 모드에서는 privileged instruction을 수행할 수 없는 것이다.

  이러한 mode는 OS에서 관리되는데 어플리케이션을 실행할 때는 유저 모드에서 실행되다가 시스템 콜이 발생하거나 할 때 OS가 mode를 커널 모드로 바꿔서 필요한 privileged instruction을 실행하는 것이다. 각 권한에 대한 정의는 아키텍처에 내장되어 있고 OS는 이 정보를 이용하여 모드를 관리한다.

 

Issue 3. Servicing Requests

System Call

  privileged instruction 유저 모드에서는 실행이 불가능하기 때문에 이를 실행하려면 커널 모드로 변경하는 것이 필요하다고 했다. 즉 privileged instruction를 수행하는 OS에 제어권을 넘겨야 하므로 사용자 프로그램은 privileged instruction을 실행하기 위해서는 OS에게 요청을 해야만 한다. 이를 위해 좀전에 시스템 콜(System Call)을 사용한다고 잠깐 말했었다.

fig 7

  C에 있는 'stdio.h' 라이브러리에 정의된 printf() 함수를 생각해보자. 해당 함수가 실행되면 유저 모드에서는 라이브러리로 이동하여 정의되어 있는 함수를 실행하게 되는데 이 안에는 write()라는 시스템 콜이 존재한다. 이를 실행하기 위해 커널 모드로 전환하여 write()에 해당하는 작업을 수행한 뒤에 다시 유저 모드로 돌아와 이후 코드를 진행하게 된다.

Exceptions

  위에서 말한 HW interrupt 말고도 SW interrupt도 있다고 했었다. 이는 명령어를 실행하면서 발생하는 interrupt라고 생각하면 되는데, 일반적으로 HW interrupt를 interrupt, SW interrupt를 exception이라고 한다. 발생하는 경우는 소프트웨어 오류(ex. 0으로 나누기), 인증되지 않은 데이터 접근, 시스템 서비스 요청 등이 있다. exception 명령을 실행하면서 exception이 발생할 것을 알 수 있으므로 동기적(synchronous)이라고 할 수 있다. interrupt와 exception이 비동기적/동기적이라는 차이는 있지만 작동하는 과정은 즉 interrupt handling은 유사하다. 인텔의 IA-32에서는 exception를 3가지 유형으로 나눈다.

  • Trap: 응용 프로그램이 의도적으로 발생시키는 exception이다. 즉 정상적인 동작이며 시스템 콜처럼 커널을 호출할 때 발생한다.
  • Fault: 의도하지 않은 exception이다. 비정상적인 동작이며 예기치 못한 접근이나 에러가 그 예이다. 그래도 interrupt handler로 처리해 줄 수 있기 때문에 이를 처리하고 돌아와서 다음 명령을 수행할 수 있다.
  • Aborts: 의도되지 않은 exception이다. 기계에 에러가 발생하는 등의  더이상 reliable한 결과를 내줄 수 없을 때 발생하며 프로세스를 종료(abort)시킴으로써 이를 처리한다.

OS Trap

fig 8
fig 9

  Trap은 시스템 콜을 동작시킨다고 했다. fig 9처럼 어플리케이션에서 open()을 이용하여 storage에 어떤 데이터를 읽어오고자 할 때, 어플리케이션이 직접 접근하여 가져오는 것이 아니라, open()에 해당하는 인자를 주면서 시스템 콜을 발생시켜 OS가 커널모드로 전환하여 시스템 콜 handler table(handler의 주소 모음)로 가서 받은 인자를 이용해 handler의 주소를 알아낸 뒤 해당 위치로 이동하여 open()을 처리하는 것이다.

 

Issue 4. Control

  해당 문제는 프로그램이 어떻게 CPU의 control을 가져오느냐에 대한 것이다. 음악 플레이어 음악을 들으면서 웹 서핑을 하듯이, 우리가 컴퓨터를 사용할 때는 하나의 프로그램만 사용하지 않는다. 하지만 CPU(싱글코어 기준)는 한번에 하나의 작업 밖에 할 수 없는데 어떻게 우리는 프로그램이 동시에 실행되는 것 처럼 느낄 수 있을까?

  하나의 접근법으로는 cooperative appraoch가 있는데 하나의 프로그램이 I/O 작업이 오래 걸릴 때 다른 프로그램에게 CPU의 control을 넘겨주는 것이다. 하지만 프로그램이 I/O operation을 하지 않는다면 다른 프로그램을 실행할 수는 없을 것이다.

  때문에 다른 접근법인 non-cooperative approach를 사용한다. 이는 OS가 직접 CPU의 control을 다루는 것이다. 이를 위해 HW적으로 timer를 구현한다. OS는 timer를 이용하여 10ms, 1ms의 간격으로 주기적으로 interrupt를 발생시켜서 CPU의 control을 다른 프로그램에게 번갈아가며 넘겨주는 것이다. 즉 작업이 매우 빠르게 전환되기 때문에 우리의 눈에는 프로그램이 동시에 실행되는 것처럼 보이는 것이다.

 

Issue 5. Memory Protection

  다른 HW 자원과 달리 메모리는 어플리케이션이 OS의 개입없이 직접 접근하는 것을 허용한다. 하지만 악의적인 유저가 다른 실행 중인 어플리케이션의 메모리 영역에 접근한다거나 OS의 메모리에 접근하려고 하는 경우에는 OS가 이를 적절하게 처리해줘야 한다.

  예전에는 간단하게 base&limit register를 이용하여 한 프로그램이 메모리에 올려지는 영역을 설정하였다. 그리고 이를 넘어가면 비정상적인 접근으로 간주하는 것이다. 하지만 최근에는 가상 메모리(virtual memory)를 사용한다. 이는 page라는 단위로 메모리를 쪼개서 각 프로그램이 전체 메모리를 사용하고 있는 것처럼 보이게 한다. 이는 이후에 더 자세히 알아볼 것이다.

 

Issue 6. Synchronization

  동기화(synchronization) 문제는 동시에 공유하는 데이터를 접근했을 때 이를 어떻게 처리해야하는지에 대한 것이다. 여석이 50개가 남은 수강 신청 서버에 한 클라이언트가 신청을 하여(interrupt 발생) 서버는 50이라는 변수를 49로 변경하려고 하는데, 동시에 다른 클라이언트가 신청을 했다고 생각해보자. 이렇게 되면 두 클라이언트 모두 잔여석이 50 -> 49로 보일 수 있는 것이다.

  interrupt를 잠시 중지시키면 해결할 수도 있겠지만 만약 멀티코어를 사용한다면 소용없는 방법이다. 그래서 이를 해결하기 위해서 HW에서는 atomic instruction(더 작은 단위로 쪼개지지 않는 명령)을 사용한다. 방금의 예시를 사용하여 설명하면 한 클라이언트가 신청을 하고 서버가 잔여석을 -1하는 과정을 atomic instruction으로 설정한다는 것이다.

 

Summary: OS and Architecture

  OS는 어플리케이션이 HW를 더 잘 활용하게끔 추상화, 분리 등의 역할을 한다. 이러한 역할을 더 잘 수행하기 위해서는 HW의 도움을 받아 더 효율적으로 구성해야한다. 그래서 OS를 만드는 업체는 HW제작 업체와 긴밀하게 협력한다. 즉 OS의 구조는 아키텍처의 도움을 받아 더욱 단순화 될 수 있는 것이다.


  • Operating System Concepts, Avi Silberschatz et al.
  • Operating Systems: Three Easy Pieces
    • Remzi H.Arpaci-Dusseau andAndreaC.Arpaci-Dusseau
    • Available(withseveraloptions)athttp://ostep.org
반응형

'Computer Science > 운영체제' 카테고리의 다른 글

Synchronization 2  (0) 2022.04.21
Synchronization 1  (0) 2022.04.19
Scheduling 2  (0) 2022.04.12
Scheduling 1  (0) 2022.04.07
Processes & Threads  (0) 2022.03.24

댓글