들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제'를 참고로 작성되었습니다. 

 

 프로그램 입출력(Programmed I/O)

 cpu가 입출력장치의 장치컨트롤러(device controller)와 상호작용하여 입출력 작업을 하는 것이다.

 이에대한 이야기는 디바이스컨트롤러 글에서 다뤘다.

 

 이 때 cpu는 명령을 내릴 장치 컨트롤러의 레지스터의 주소를 알아야하는데 해당 주소를 저장하는 방법이 크게 두 가지있다.

 

 메모리 맵 입출력(memory mapped I/O)

 메모리 맵 입출력은 한마디로 장치컨트롤러의 레지트터를 메모리(RAM)의 '메모리 주소 영역'의 일부처럼 쓰는 것이다. 

 장치컨트롤러의 레지스터를 메모리 주소 영역의 주소로 맵핑하여 해당 주소에 명령을 내리면 레지스터에 명령을 내리는 것과 동일하며, 이는 메모리 접근 명령어와 레지스터 접근 명령어가 다르지 않다는 장점이 있다.

 

 예를 들어 '100'이라는 주소가 장치 컨트롤러의 데이터 레지스터를 가리키고 있다면 cpu는 장치 컨트롤러에 따로 접근할 필요없이 그냥 '주소 100에 데이터를 써라' 라는 명령만 내리면 된다.

 

 다만, 메모리영역이 일반적인 메모리 영역과 입출력장치의 레지스터를 저장할 메모리 영역으로 나뉘게 돼 메모리 영역이 축소가 된다는 것이 단점이다. 또한 입출력 장치의 주소 공간이 메모리 주소 공간에 일부로 할당되기 때문에 충돌이 발생하지 않도록 주의해야 한다.

 

 그렇다면 해당 메모리 주소가 장치 컨트롤러의 레지스터와 연결 돼 있다는 걸 어떻게 알고 cpu는 명령을 보낼까? 그 역할을 하는 것이 바로 운영체제다.  

 

 고립형 입출력(isolated I/O)

 메모리 맵 입출력은 메모리 주소 영역을 분리하여 저장하고 마치 하나의 메모리 영역을 쓰는 것 처럼 데이터를 읽는 방식이라면 고립형 입출력은 반대로 다른 입출력 장치 만의 메모리 영역을 두어 전용 명령어로 접근하는 방식이다. 

 

 해당 방법은 메모리 맵과 달리 메모리 주소 공간이 축소되지 않는 특징을 가지고 있다.  

 

 

 

 인터럽트 기반 입출력(Interrupt-Driven I/O)

 인터럽트 기반 입출력은 인트럽트 글에서 다룬 비동기 인터럽트를 말한다. 장치 컨트롤러를 통해 cpu가 명령을 한 후 다른 작업을 하고 명령이 완료 되면 입출력장치는 장치 컨트롤러로 cpu에 인터럽트를 건다. 인터럽트를 받은 cpu는 현재 진행 중인 작업을 백업 한 후 인터럽트 서비스 루틴을 실행하고 작업이 완료되면 전에 진행했던 작업을 복구하여 계속 진행한다.

 

그럼 컴퓨터에는 입출력장치가 여러가지가 있는데 이 많은 장치들의 인터럽트를 cpu는 어떻게 처리할까?

 기본적으로 인터럽트를 건 순서대로 처리하는 방법이 있겠다. 다만 순차적으로만 처리한다면 효율적으로 동작할 수 없다. 뒷순서에 왔지만 지금 당장 급한 인터럽트를 처리해야 할 경우도 있을 것이다.

 

 때문에 각 인터럽트에는 우선순위가있으며, cpu는 우선순위가 높은 순서대로 인터럽트를 처리한다. 참고로 인터럽트 중 NMI(Non-Maskable Interrupt)는 우선순위가 가장 높아 인터럽트 비트를 비활성화 돼있어도 무시하고 제일먼저 처리 된다. NMI는 심각한 오류에 의한 인터럽트기 때문에 바로 처리 되어야한다.

 

 인터럽트의 우선순위는 cpu가 하는 것이 아니라 PIC(Programmable Interrupt Controller) 라는 하드웨어에서 인터럽트의 우선순위를 정해 인트럽트 우선순위가 높은 것부터 cpu로 보내게 된다.

 

 결국, 비동기 인터럽트 과정의 중간 단계에 PIC가 들어가며 처리 단계는 다음과 같다.

일반적으로 해당 그림처럼 PIC를 두 개 이상 계층적으로 구성한다.

 

 1. cpu가 아닌 PIC가 인터럽트 요청 신호를 받는다.

 2. PIC가 인터럽트의 우선순위를 판단한 후, cpu에 우선순위가 높은 순서 대로 인터럽트 요청을 한다. 

 3. cpu는 인터럽트 확인 신호를 보낸다.

 4. PIC는 데이터 버스를 통해 cpu에 인터럽트 벡터를 보낸다.

 5. cpu는 받은 인터럽트 벡터를 참조하여 해당 장치의 인터럽트 서비스 루틴을 실행하게 된다.

 

 

 DMA(Direct Memory Access) 입출력

 PIC와 같이 인터럽트의 우선순위를 정하는 하드웨어를 따로 둔 것 처럼 데이터의 입출력을 대신하는 장치가 있다. 그것이 DMA controller 라는 장치다. 인터럽트는 하던 작업을 중단하고 해당 인트럽트 서비스 루틴을 작업 하는 것인데, PIC가 우선순위를 정하여 하나씩 밀어준다 해도 많은 장치들의 인터럽트를 자주 받는다. 와중에 디스크 백업과 같은 대용량의 데이터를 옮긴다면 cpu의 부담은 높아 질 것이다.

 

 DMA 컨트롤러는 데이터 전송에 대한 작업을 대신해준다. cpu가 DMA에게 입출력 작업을 명령하면 DMA는 마치 cpu처럼 입출력 장치와 상호 작용하며, 필요할 땐 이름과 같이 메모리에 직접 접근하여 작업하기도 한다. 그렇게 작업이 완료되면 cpu에게 완료했다는 인터럽트를 걸게 된다. 

 

 다만, cpu의 역할은 대신하는 만큼 DMA또한 데이터 전달 시 시스템 버스를 쓰게 된다. 시스템 버스는 공용 자원이기 때문에 cpu가 쓰면 DMA가 쓰지 못하고 DMA가 쓰면 cpu가 쓰지 못한다.   

 

 

 이를 해결하기 위해 DMA는 cpu가 시스템버스를 쓰지 않을 때 쓰거나 cpu에게 허락을 구하고 사용하지만, 현재 대부분 DMA와 입출력 장치들간에 입출력 버스 라는 연결 통로를 하나 더 두어 따로 쓴다.

 

 참고로 앞서 말했듯 DMA는 데이터의 전송을 담당하는 장치이기 때문에 대량의 데이터 전송을 요구하는 저장 장치(SSD/HDD)나 프린터, 그래픽 카드, 사운드 카드(스피커) 등의 장치들에 쓰이며 키보드와 같은 간단한 인터럽트는 cpu에서 그대로 처리한다.

 

 

 

 

 

 

 

 

'개발공부 > 운영체제 등' 카테고리의 다른 글

CISC와 RISC  (1) 2023.11.21
레지스터와 CPU 동작원리  (0) 2023.11.21
인터럽트(Interrupt)  (0) 2023.11.16
디바이스 컨트롤러(Device Controller)  (0) 2023.11.16
버퍼와 캐시  (0) 2023.11.15

 들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제'를 참고로 작성되었습니다. 

 

 

 인터럽트는 명령어 사이클이 도는 와중 이를 중단 시키는 걸 가리킨다. 

 인터럽트는 다시 동기 인터럽트(synchronous interrupt)비동기 인터럽트 (asynchronous interrupt) 로 나뉜다.

 

 동기 인터럽트(synchronous interrupt)

 동기 인터럽트는 '예외'라고도 불리며 cpu에서 발생하는 인터럽트다. 예외가 발생 했을 때 cpu는 잠시 하던 작업을 멈추고 예외를 처리하게 된다. 

 동기 인터럽트의 종류는 다음과 같다.

 

 폴트(Flault) 

 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개한다.

 예를 들어 현재 어떤 명령어를 실행하는데 특정 데이터가 필요한 상황이다. 하지만 해당 데이터가 메모리가 아닌 보조기억장치에 있으면 해당 보조기억 장치에 요청을 해야한다. 데이터를 요청을 하고 데이터가 준비되었으면 인터럽트가 발생한 시점부터 작업을 진행하게 된다.

 

 트랩(Trap)

 폴트와 유사하다. 다만 인터럽트가 발생한 시점이 아닌 그 다음 명령어부터 실행을 한다는 것에서 차이가 있다. 간단하게 디버깅을 예로 들 수 있겠다.

 

 중단(Adort)

 대단히 심각한 오류다. 대표적으로 잘못된 메모리 값. 가령 배열 크기를 넘어선 값을 읽어 온다든지 하는 경우를 예로 들 수 있겠다.  

 

외에 소프트웨어 인터럽트가 있는데 이는 운영체제를 다룰 때 같이 다루겠다.

 

 비동기 인터럽트 (asynchronous interrupt)

 비동기 인터럽트는 입출력장치에 의해 발생하는 인터럽트다.

 cpu가 입출력장치에게 작업을 명령 후 입출력장치가 해당 작업을 완료했을 때 그 완료 신호로, 또는 키보드나 마우스 이벤트가 발생했을 때 그 이벤트 처리를 할 때 저마다 cpu에게 인트럽트를 요청하게 된다. 글을 쓰고 있는 지금도 cpu에게 입력알림 인터럽트를 보내고 있다. 

 

 그럼 인트럽트 발생시 cpu는 어떻게 처리하는지 알아보자

 

 1. cpu가 보낸 작업을 완료했을 시 또는 입출력장치의 입력 인트럽트 등의 발생 시, 입출력장치는 cpu에 인트럽트 요청신호를 보낸다. 즉, 인트럽트를 바로 발생시켜 cpu의 실행중인 작업을 바로 중단 시키지 않는다는 것이다.

 

 2. cpu는 실행 사이클이 끝나고 다시 명령어를 인출하기 전 인트럽트를 확인한다.

 

 3. cpu는 인트럽트 요청을 확인하고 플래그 레지스터에서 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 판단한다.

 참고로 인터럽트 우선순위가 높은 인터럽트(하드웨어 고장) 등은 인터럽트 플래그로 무시할 수 없다.

 

 4. 인터럽트를 받아들일 수 있다면 cpu는 현재 실행중인 작업을 백업한다. 백업은 메모리의 스택영역에 임시로 저장을 한다.

 

 5. cpu는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행한다. 

 인트럽트 서비스 루틴이란 인트럽트가 발생 했을 시 해당 인트럽트를 처리하는 프로그램이다. 쉽게말해 키보드의 입력 신호를 받았을 때 어떠한 식으로 처리하겠다 라는 메뉴얼인 셈이다.

  인터럽트 벡터는 인터럽트 서비스 루틴의 시작주소의 정보를 담고 있으며 시스템이 부팅될 때 인터럽트 벡터 테이블( Interrupt Vector Table )이라는 특정 메모리 영역에 저장된다.

 

 6. 인터럽트 서비스 루틴이 끝나면 백업했던 작업을 복구하여 작업을 재개한다. 

 

 

 

 인터럽트 요청 및 인터럽트 벡터 참조->실행 중인 프로그램 데이터 백업->참조한 주소로 점프하여 인터럽트 서비스 루틴 실행->작업이 끝나면 백업했던 작업을 복구하여 이어서 작업하는 순이다.

 

 사실 1번 과정 중간에 거치는 하드웨어가 있는데 이는 다음 글에서 다루겠다. 

 

 들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제'를 참고로 작성되었습니다. 

 

 

 모든 입출력장치는 cpu와의 상호작용으로 동작한다. 이 때 그 역할을 하는 것이 입출력장치의 디바이스 컨트롤러(Device controller)다.

 

 cpu에 데이터를 전송하기 위해 버퍼에 임시로 저장하는 것도 이 장치가 수행하며 cpu와 비슷한 내부구조를 가진다.

 

간단한 장치 컨트롤러 내부 구조

 

 장치 컨트롤러에는 대표적으로 데이터 레지스터, 상태 레지스터, 제어 레지스터가 있다.

 

 데이터 레지스터는 cpu와 입출력장치간에 주고 받을 데이터가 담기는 레지스터다. 즉, 버퍼역할을 하는 레지스터다.

 

 상태 레지스터는 말 그대로 입출력 장치의 상태 정보를 저장하는 레지스터다. 입출력 작업을 할 수 있는 상태 인지, 요청한 작업이 완료되었는지, 입출력장치의 오류 등을 저장한다. 

 

 제어 레지스터는 입출력장치가 수행할 제어 정보와 명령 정보를 저장한다. 

 

 

 

 해당 레지스터들은 대략적으로 위 그림과 같이 동작한다.

'개발공부 > 운영체제 등' 카테고리의 다른 글

여러 입출력 방법 (프로그램 , 인터럽트 , DMA)  (0) 2023.11.20
인터럽트(Interrupt)  (0) 2023.11.16
버퍼와 캐시  (0) 2023.11.15
컴퓨터 구조와 CPU 개괄  (0) 2023.11.15
운영체제를 왜 공부하는 가  (0) 2023.11.13

 들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제'를 참고로 작성되었습니다. 

 

 

 cpu는 아주 빠른 속도로 작업을 하며 작업에 필요한 내용들을 여러 장치에 요청하기도 한다. 하지만 cpu외 보조기억장치나 입출력장치들은 cpu보다 엄청 느리기도 하고 물리적거리도 멀기 때문에 cpu가 요청을 하여 읽어 오는데 긴 시간이 걸린다.

 이런 연산속도의 간극을 메우기 위한 것이 바로 버퍼와 캐시라는 개념이다.

 

 

 버퍼(Buffer)

 쉽게말해 입출력장치와 cpu의 속도 간극을 메우기위해 '임시로 저장하는 메모리영역'이다. 

 가뜩이나 속도가 느린 입출력장치에 cpu가 매우 빠른속도로 계속 요청을 하면 입출력장치는 해당 요청을 수행하기가 어렵다. 때문에 버퍼라는 영역을 만들어 해당 요청들을 큐(Queue) 형태로 저장하고 해당 데이터가 필요할 때 마다 꺼내 쓰게 된다.  

 즉, cpu가 입출력장치에 필요한 작업이 있으면 해당 작업을 버퍼라는 메모리에 저장을 한 뒤 기다릴 필요없이 할일을 계속 하고 입출력장치 또한 전달할 데이터가 있다면 버퍼를 통해 cpu에게 전달하게 된다. 이렇게 중간 단계가 있으면 각 장치의 속도대로 작업을 할 수 있게 된다는 것이다.  

 

 

 cpu가 속도가 빠른 만큼 많은 데이터를 저장한 후 입출력장치에서 필요할 때 꺼내 쓰며, 반대로 입출력장치가 느린만큼 데이터를 모아 cpu에 한번에 전달하는 식이다.

 

 캐시 메모리(Cash Memory)

저장 장치 계층 구조

 

 캐시메모리는 cpu(레지스터)와 메모리사이에 있는 저장장치다. cpu가 메모리에 접근하는 것 또한 느리기 때문에 필요할 때마다 메모리에 계속 접근하는 것은 좋지 않다. 때문에 캐쉬 메모리를 만들어 중간 단계를 만들었다.  

 

 저장장치들은 cpu와 가까울수록 속도가 빠르고 용량이 작응며, 비용(cpu 접근 효율) 또한 작다.   

 

 캐시 메모리는 하나만 있는 것이 아니라 여러 개의 메모리가 있는데, 이 또한 cpu의 코어와 가까울수록 빠르며 거리에 따라 각각 L1, L2, L3라고 부른다. 일반적으로 L1과 L2는 cpu 코어 내부에 있으며 L3는 밖에 위치해 있다.

 

 

 버퍼 vs 캐시 메모리

 처음에 언뜻 봤을 땐 버퍼와 캐시가 비슷해 무슨 차이인가 싶었다. 그리고 하나의 의문점이 생겼는데

 

 '왜 cpu와 입출력장치 사이에는 똑같이 캐시 메모리같은 중간 저장장치를 넣지 않았을까?' 이다.

 

 이 의문은 캐시 메모리만의 특징으로 설명 가능하다.

 캐시 메모리는 용량이 적기 때문에 저장할 데이터가 한정 돼 있는데, 그 한정된 데이터란 'cpu가 자주 사용될 것으로 예측한 데이터'이다. 하지만 입출력장치는 유저가 조작하는 것이기 때문에 예측하기 어렵기도 하고, 메모리가 cpu보다 느리기 때문에 캐시 메모리를 넣었다곤 하지만 입출력장치 사이 또한 캐시 메모리를 넣기에는 위의 그림과 같이 어마어마한 속도 차이가 나기 때문에 그다지 효율적인 방법이 아니다라는 것이다. 

 만약 굳이 캐시 메모리를 넣는다면 많은 캐시 메모리가 필요할 것이다. 당장 cpu와 메모리 사이의 캐시 메모리만 봐도 3개나 되는데 이는 상당히 비효율적일 것이다. 

 

 그렇다면 캐시 메모리는 어떻게 자주 쓸 데이터를 예측할까?

 바로 '참조 지역성의 원리' 라는 원칙으로 예측을 하는데, 이는 다시 시간 지역성공간 지역성으로 나뉜다.

  

 시간 지역성이란, 최근에 접근했던 메모리 공간에 다시 접근하려는 경향을 말한다. 예를 들면 밑과 같이 for문을 돌릴 때 result 변수와 for문 조건의 i변수에 자주 접근하게 된다. 

시간 지역성의 예

 

 공간 지역성이란, 접근한 메모리 공간 근처를 접근하려는 경향을 말한다. 보통 프로그램의 데이터들을 관련있는 데이터들 끼리 모여서 저장된다. 가령 게임, 웹, 동영상들을 실행 시켰다면 각각의 프로세스의 데이터들은 모여서 저장된다(cpu가 동작하는 원리를 생각한다면 당연하다). 또한 해당 프로세스안의 기능들도 관련데이터들 끼리 모여서 저장되는데, 예를 들면 게임의 채팅이나 검색을 위한 입력 기능에 관련된 기능은 모여서 저장된다.

 그러면 굳이 중구난방으로 데이터를 찾을 필요 없이 해당 데이터 주소의 근처만 접근하면 효율적으로 데이터를 찾을 수 있게된다.

 

 이와 같은 원칙으로 캐시 메모리는 자주 쓰일 것 같은 데이터를 예측하여 일부만 저장하여 cpu가 빠르게 데이터를 읽어 동작하게 만들어 준다. 

 

 

 

+ Recent posts