운영체제가 장치를 제어하면서 입출력을 수행하는 방법에 크게 3가지 방법이 있다고 했었습니다.
첫번째가 폴링, 다른 말로 프로그램 기반 아이오, 두번째가 인터럽트 아이오, 세번째가 dma방법입니다.
이 시간에는 interrupt i/o에 대해 자세히 알아보도록 하겠습니다.

먼저, 폴링에서처럼 유저 모드에서 실행되는 어느 프로그램이 프린트 인스트럭션을 실행한다고 합시다. 그림에서 1번입니다. 프린트라는 인스트럭션은 컴파일 할 때 출력에 해당되는 시스템 콜로 바뀐다고 했었습니다. 그래서 시스템 콜이 불리어집니다. 2번 과정입니다. 시스템 콜은 커널에서 실행되는 함수이고, 그러기 위해서 user space에서 kernel space로, 즉 user mode에서 kernel mode로 바뀌는 mode change가 일어납니다. 모드 체인지가 일어나서 이제 유저 프로세스 a는 운영체제로 바뀌어서 입출력에 해당되는 시스템 콜을 담당할 i/o management로 가서 실행을 합니다. i/o management는 a라는 프로세스가 요청한 출력이 어떤 장치에서 실행되어야 하는지를 알아냅니다. 출력 장치가 모니터라면, 모니터를 제어하는 디바이스 드라이버를 호출해서 모니터 디바이스 드라이버에게 출력을 지시합니다. 디바이스 드라이버는 디바이스의 상태를 보고 명령(command)을 내립니다. 이떄까지가 3번이며, 3번까지의 과정은 폴링이라는 방법과 같습니다. 이때, 폴링 방법을 떠올려 보세요. 디바이스 드라이버는 출력 장치의 status 레지스터와 control register의 비트들을 이용해서 장치에게 출력을 명령합니다. 그래서 busy bit를 읽고 0이면, control register의 write bit command를 내리고, command ready bit를 설정하여 출력 버퍼에 넣으면 출력 명령이 전달 되는 것입니다. 폴링 방법과 완전히 같습니다. 지금부터 달라집니다.
폴링에서는 디바이스 드라이버를 반복적으로 실행을 하면서 (busy waiting) 출력이 끝날 때까지 기다렸습니다. 결국, a라는 프로세스가 운영체제를 실행하면서 계속 running 상태가 됩니다.
그런데, 인터럽트 기반 아이오에서는 그러지 않습니다. a가 cpu를 계속 가지면서 기다리려서 cpu를 좋은 일에 활용하지 못하는 단점을 개선합니다. 인터럽트 기반 아이오에서는 출력명령을 일단 내려놓고, 출력이 끝날때까지 기다리지 않고, 프로세스 관리자로 넘어가서 다른 프로세스로 context switch를 해서 cpu를 다른 프로세스에게 줍니다. a가 디바이스 드라이버를 실행해서 디바이스 하드웨어에게 명령을 내려놓고, 4번처럼 process management를 실행을 하는데, 관리 기능은 a 상태를 running 상태에서 blocked 상태로 바꿉니다. 즉, 입출력 장치에서 출력이 끝날때까지 a는 cpu를 놓고 기다리는 것입니다. 그리고 나서 그 다음 누구에게 cpu를 줄 것인지 스케쥴링을 한 후, 6번과 같이 context switch를 합니다. 이떄, a의 상태값, a가 이때 당시의 cpu register 값을 pcb에 저장을 합니다. 그리고 스케쥴러를 실행을 합니다. 스케쥴러를 실행해서 context switch를 하기 직전까지는 a의 context로 합니다. a의 프로그램 실행환경에서 process management를 실행을 하는 것입니다. b를 실행대상으로 선택했다고 하면, 아직까지는 a의 context입니다. 가장 마지막 단계에서 context swtich를 하면 a에서 b로 바뀌는 것입니다. 이제 7 번에서 cpu는 b라는 프로그램을 실행을 하게 됩니다. 3번에서 장치에게 출력을 하라고 명령을 했기 때문에 언젠가는 장치가 다 출력을 끝내면, 끝냈다고 가정하면, 장치의 controller hardware는 분명 자기의 status register bit 내의 busy bit와 command ready bit를 0으로 변경했을 것입니다.
폴링 방법에서는 busy bit가 0이 되었는지를 계속 디바이스 드라이버가 cpu를 가지면서 코드를 실행해서 0이 됬음을 알았는데, 이 방법에서는 그럴 수가 없습니다. 디바이스 드라이버가 cpu를 안 가지고 있으며, b가 가지고 있어서 디바이스 드라이버는 명령만 내려놓으면서 잠시 중단된 상태입니다. 별도로 명령을 받은 하드웨어가 출력을 다 끝내면 ready bit를 busy bit와 command ready bit를 0으로 변경한 후에 디바이스 드라이버 컨트롤러가 다른 방법으로 운영체제에게 출력이 끝났음을 알리는 인터럽트를 보내는데, 8번의 IRG(interrup request)입니다. 화살표를 프로세스 b쪽으로 했는데, cpu에게 가기 위함이며, cpu는 사용자 b코드를 한 줄 실행하고, 인터럽트가 걸리는지 보고, 또 한줄 하며, 매번 인스트럭션 한 줄 실행 한 후에 인터럽트를 체크를 합니다. 언젠가 인터럽트 신호를 건다고 하면, 사용자 b코드를 실행 중단하고, cpu register 값을 kernel stack에 저장합니다. 커널 스택에 저장한 후에 다시 ㅕb의 user mode에서 kernel mode로 mode change를 합니다. interrupt를 처리하는 핸들러라는 커널 함수를 9번 과정에서 실행을 합니다. 만약에 사용자 프로세스를 실행하는 것이 아니라 커널 코드 실행 도중에 인터럽트를 받게 되는 경우에는 이미 커널 안에 들어왔으므로 mode change를 하지 않습니다. 그러나 지금의 경우는 프로세스 b를 실행하고 있었던 상태여서 모드 체인지를 해서 인터럽트 핸들러를 실행하고 PIC 칩에게 어떤 장치 인터럽트인지 물어보며, 그걸 알게되면, 실제 인터럽트를 처리하는 ISR(interrup service routine)을 실행하는 함수를 다시 호출해 인터럽트를 처리합니다. 그것이 10번 과정입니다. 인터럽트를 처리하는 실행 흐름의 주체는 바로 b입니다. b가 mode change를 해서 커널로 들어와서 바뀌어서 인터럽트 핸들러와 인터럽트서비스 루틴을 실행하는 것입니다.
인터럽트 서비스 루틴이 실행되어서 인터럽트를 처리를 합니다. 그러면 인터럽트 서비스 루틴에서는 process management를 다시 부릅니다. 입출력이 끝난 인터럽트 이기 때문에 ISR에서 process managent를 불러서 프로세스 관리자에서 입출력을 기다리면서 블록되었던 프로세스 a를 ready 상태로 바꿉니다. 그러니까 a를 블록큐에서 뺴서 레디큐에 넣는 것입니다. 바로 b라는 프로세스가 커널로 바뀌어서 a를 깨워서 ready queue에 넣어준 좋은 일을 해서 인터럽트 처리를 마친 것입니다.
인터럽트 처리를 마친 후에는 운영체제가 시스템 관리 역할(리눅스 운영체제의 경우, return from interrupt라는 코드 실행: 미뤄둔 인터럽트 처리 작업을 끝냅니다. 인터럽트 부분에서 설명을 했는데, 인터럽트 처리는 두 부분으로 나눠서 합니다. 급히 해야 할 부분을 ISR에서 하고, 급하지 않은 부분은 미뤄눴다가 여기서 실행을 합니다. 시그널이 왔는지 체크를 하며 처리를 합니다. 새로운 프로세스가 ready 상태인지 조사를 합니다. 그러면, 스케쥴러를 불러줍니다.)을 합니다. 지금의 경우는 a가 ready 상태로 바뀌었기 때문에 이 코드에서는 스케쥴러를 불러주며, 그 결과 a가 실행될 수도 있고, b가 실행될 수 도 있습니다. 이것은 스케쥴링 원칙에 따라서 우선순위가 높은 프로세스를 선택해 스케쥴링합니다. 만약, a가 큰형님이면, b가 커널로 바뀌어서 인터럽트 처리도 해주고, a도 꺠워주고 프로세스 매니지먼트를 실행하고, 스케쥴러를 불러주는데 그건 전부 b가 하는 것입니다. 스케쥴러 실행을 하니까 a가 서열이 높은 프로세스이면, 스케쥴러는 b에게서 cpu를 뺏고, 프로세스 a가 실행됩니다. 우선순위가 같거나 현재 cpu를 갖는 b가 더 우선순위가 높다고 하면, cpu가 a로 바뀌지 않고 b를 가진 상태로 결정되어서 mode change를 해서 b를 계속 실행합니다. 즉, 7번에서 인터럽트를 받고 난 이후의 문장부터 연이어 실행을 합니다.
a의 우선순위가 b보다 높으냐 낮으냐에 따라서 a가 실행될 수도 있고, b도 실행될 수도 있는 것입니다. 만약, a가 실행될 경우, context switch가 2번 일어나게 됩니다.

우리가 프로세스 관리를 배울 때, 프로세스 스위치가 일어나는 경우, 언제 context가 일어나느냐인 이 경우를 4가지를 봤었는데, I/O interrupt가 걸리면, 프로세스 스위치가 일어날 수 있다라고 했었습니다. 이 경우가 바로, 아까 그림에서 인터럽트가 걸려서 인터럽트 처리 결과, 잠자던 프로세스가 ready로 꺠어나고, 스케쥴러를 실행했는데, 잠자던 프로세스가 우선순위가 높으면, 그 프로세스로 context switch가 일어나야 되는 것이며, 우선순위가 같거나 작으면 b로 되돌아 갑니다. 그러니까 프로세스가 일어나서 context switch가 일어날 수도 있고, 안 일어날 수도 있는 것입니다.
그러면 인터럽트 기반 I/O에서는 누가 인터럽트 때문에 중단을 당한 것일까요? 바로 b가 중단당한 것입니다. 인터럽트 신호가 와서 7번에서 자기 프로그램을 계속 실행하지 않고, 인터럽트 실행을 위해 커널로 들어갔습니다. 즉, 방해를 받은 것입니다. 커널에 들어가서 mode change를 하느라 시간도 걸리고, 또 커널 안에서 인터럽트 처리하느라 시간 걸려서 b의 실행이 늦어지는 것입니다. 게다가 스케쥴러를 실행해서 A에게 cpu까지 뺏긴다고 하면, B 입장에서는 I/O interrupt 때문에 아주 큰 손해를 받는 것입니다.
반대로 이득을 얻은 프로세스는 누구이죠? 바로 a입니다. a는 명령을 내려놓고 cpu를 잠깐 b에게 양보를 했지만, 결과적으로 출력이 끝나면 다시 cpu를 뺏어옵니다. 우선순위가 높은 경우입니다. 어차피 a입장에서는 폴링방법과 같이 디바이스 드라이버에서 busy waiting을 하며 기다려야 했으므로 출력문이 나온 그 다음 문장으로 못 넘어가고 기다려야 했으므로, 디바이스 드라이버에서 기다리나, b를 실행하면서 기다리나 크게 손해보는 것이 없습니다. 결국 b가 방해를 받고 손해를 받는 셈입니다. 그럼 이런 현상 때문에 interrupt기반 I/O는 b가 손해를 봤기 때문에 안 좋은 방법이다라고 이야기 할 수 있을까요? 그렇지 않습니다. b입장에서는 불공정한것만은 아닙니다. 우리가 지금 주로 이용한 현대 컴퓨터는 타임 쉐어링 시스템이기 때문에 이와 같은 상황은 a에게 cpu time slice가 주어지고, a다음에 b에게 time slice가 주어지는 상황입니다. 만약에 폴링 방법으로 입출력 관리를 하면, a가 cpu를 가지고 running해서 b는 cpu를 가지고 running을 할 수 없습니다. 즉, a가 cpu time이 끝나야 b에게 넘어오기 때문에 a가 busy waiting을 해서 b에게 안 주어지는 것입니다. 그러나 인터럽트 기반 아이오에서는 a가 폴링을 하지 않고 cpu를 양보하고 b에게 줬기 때문에 cpu를 일찍 받은 것이기 때문에 좋은 것입니다. 또, 반대로, b가 i/o 요청을 하고 자기가 기다리다가 a로 cpu가 넘어갔는데, 자기의 i/o가 끝나서인터럽트가 걸리면, a가 똑같이 중단을 당하고, 인터럽트 처리를 하면서 자기에게 cpu를 되돌려줄 수도 있습니다. 그러니까 이것은 모든 프로세스에게 공평하게 이런 일이 일어나기 때문에 평균적으로 보면 일방적으로 이득이고, 손해라고 얘기를 할 수 없습니다. 결국 공평한 방법!
인터럽트 기반 아이오 방법을 이해했다면, 장단점 비교를 해봅시다.
우리가 우체국 직원 예처럼 a고객이 입출력(신청서 작성) 하는 동안 b고객으로 context switch를 하여 b를 서비스를 합니다. 그러면, 주어진 시간에 더 많은 고객을 서비스를 하는 장점이 있습니다.

앞서 폴링 방법의 장단점을 따져봤는데, 입출력이 느리게 진행되는 장치의 경우, 폴링의 방법이 좋지 않습니다. 폴링에서 오랜 시간동안 busy waiting을 하는 경우, 안 좋습니다. 그렇지만, 만약 입출력이 금방 끝나는 경우, 폴링을 쓰는 것이 더 효율적입니다.
인터럽트 기반 아이오는 이것과 반대로, 장치가 만약 느리게 진행되는 장치라면, 입출력이 천천히 일어나면, 오랫동안 기다리지 않고, b라는 프로세스로 cpu가 넘어갑니다. b를 실행을 하니까 cpu의 효율이 좋게 됩니다. 즉, context switch에서 다른 프로그램을실행을 하는 것이 유익한 방법입니다. 만약, 입출력이 빨리 끝나는 경우에서는, 인터럽트 기반 아이오가 더 비효율적입니다. a의 여러가지 상태 정보를 저장하고, context switch를 해서 시간이 많이 걸립니다. context switch를 하는 도중에 interrupt를 걸 수 있는데, context switch를 중단하고 다시 interrupt 처리를 한 다음, 스케쥴러를 실행을 했는데 a가 우선순위가 높은 경우, 다시 a로 돌아와야 합니다. 장치가 빠르게 동작하면 쓸데없이 context switch를 하고 다시 돌아와서 불필요하기 때문에 비효율적입니다.


다음은 커널이 입출력을 제어하는 3가지 방법 중 마지막인 DMA방법에 대해 알아봅시다.
컴퓨터 시스템 내부의 주요 부품은 cpu, main memory, i/o controller(=i/o adapter)입니다. 컴퓨터 시스템에서 입출력이라고 하는 것은 메인 메모리와 입출력 장치의 사이에 메모리 든 내용을 입출력 장치로 보내는 것이는데, 직접 가는 것이 아닙니다. 출력에서는 출력 대상이 든 메모리에서 출력 내용을 버스를 타고 processor인 cpu register로 가지고 옵니다. 그러면 cpu register에서 cpu가 i/o 장치인 i/o controller에게 보내고 i/o controller가 출력을 합니다. 반대로, 입력이면, 입력된 내용은 cpu register를 거쳐서 다시 memory로 들어가는 것입니다. 입출력이 cpu라는 프로세서를 거쳐서 입출력 장치에게 전달이 됩니다.
만약에 입출력 할 데이터의 개수가 1~2개 정도면, 이런 일이 일어나도 cpu를 거쳐서 가도 크게 문제가 되지 않습니다.
그런데 입출력 할 데이터가 수천, 수만개의 바이트일 경우에는, 오고가는 사이클이 수천, 수만번 반복되어야 합니다. 사용자 프로그램 중단이 수천, 수만 번 일어나서, 사용자 프로그램 실행을 하는 일이 자꾸 중단되어 단위시간당 실행해야 할 사용자 프로그램의 수가 떨어져 cpu효율이 아주 안 좋아지게 됩니다.
이런 문제의 해결방법으로서 dma가 나온 것입니다. dma는 입출력될 데이터가 cpu를 거치게 하는 것이고, dma controller라는 별도의 process를 둡니다. cpu는 dma controller에게 메모리 몇 번지에서부터 몇 개의 데이터를 읽어서 출력해라라고 지시를 합니다. dma controller가 memory에 가서 byte를 읽어서 자기에게 가져옵니다. 가져온 내용을 해당 i/o 장치에게 전달해서 출력을 하도록 명령을 내립니다. 수천, 수만 바이트의 내용을 메모리에 가서 읽어오고 출력을 시키는 내용을 cpu가 아닌 dma controller라는 보조 프로세스가 이 역할을 합니다. 그 동안 cpu는 사용자 프로세스를 실행할 수 있어 단위시간당 실행할 수 있는 사용자 프로그램이 증가해 더 좋은 방법입니다.

일반적인 입출력 방법이 폴링이라는 프로그램기반 i/o, interrupt기반 i/o, dma를 구분하는 것입니다.
인터럽트를 사용 여부, 프로세스 거쳐서 가는지 유무 등으로 구분합니다. 프로세스를 안 거쳐 가는 것은 direct라고 부릅니다. direct 용어는 cpu를 거치지 않고 간다는 것이지, 메모리에서 입출력 장치로 직접 가는 것이 아닙니다!!!!!!!!!!!!!
그런 관점에서 이 3가지 방법을 분류해서 표로 정리한 것입니다.
'운영체제및실습' 카테고리의 다른 글
| [운영체제및실습] 11주차 1회차: 디바이스 드라이버 (0) | 2025.05.25 |
|---|---|
| [운영체제및실습] 10주차 4회차: 디스크 스케줄링 (0) | 2025.05.25 |
| [운영체제및실습] 10주차 2회차: I/O제어 : 폴링 (0) | 2025.05.10 |
| [운영체제및실습]10주차 1회차: 인터럽트 처리 (0) | 2025.05.10 |
| [운영체제및실습] 9주차 3회차 : 입출력관리 개요 (0) | 2025.05.07 |