일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 문제풀이
- 알고리즘
- 자료구조
- 정석
- 정석학술정보관
- 개발
- 너비우선탐색
- 스택
- 오퍼레이팅시스템
- DP
- vector
- bfs
- 브루트포스
- 오에스
- 컴공
- 컴퓨터공학과
- 그래프
- Computer science
- 구현
- c++
- OS
- 컴공과
- 백준
- 북리뷰
- cs
- 코테
- coding
- Operating System
- Stack
- 코딩
- Today
- Total
Little Jay
[OS] Computer System Overview II (Interrupt Handling with Linux Codes) 본문
[OS] Computer System Overview II (Interrupt Handling with Linux Codes)
Jay, Lee 2022. 7. 9. 15:53Interrupt Mechanism
Interrupt는 Processor Utilization은 높이는 기법이다. Interrupt 자체는 시스템 프로그래밍 파트에서도 설명을 했었는데 이제는 이 Interrupt가 어떻게 동작하는지 그 원리와 내부 구조, 이유에 대해 OS의 관점에서 정리하겠다. Interrupt를 사용하는 이유는 대부분의 I/O Device가 Processor보다 느리기 때문이다. 만약 Processor가 I/O Device와 바로 상호작용하기 위해서는 Processor는 Device의 작업이 끝날 때까지 기다려야 한다. 이는 Processor라는 자원을 놀리고 있는 것이고 이는 Resource에 대한 자원 낭비이다. 예를 들어서 Read Operation이 일어난다면 Processor는 하던 작업을 멈추고 그 Device의 일이 끝날 때까지 idle 한 상태로 기다려야 한다. 따라서 Instruction이 일어 나는 Cycle에 대한 그림을 다시 고쳐서 그리면 아래와 같이 그릴 수 있다.
Interrupt를 Handling할 수 있는 코드와 Handler는 Kernel 모드에서 수행이 된다. Interrupt가 들어오게 된다면 PC의 주소를 Interrupt Handler의 주소로 바뀌게 되고, 만약 PC의 주소가 PC + 4가 된다면 이는 정상적으로 Interrupt가 없이 수행되고 있다는 뜻이 되겠다.
이전 글에서 그린 그림에 PIC를 추가 했다. 앞서서도 잠깐 설명했지만 다시 한번 정리하겠다. 일반적으로 Instruction Fetch - Decode - Execute 순서로 명령을 수행하고 Instruction의 종류에 따라 결과를 레지스터나 메모리에 저장할 수 있다. 회색 박스가 끝나게 되면 보라색 박스로 넘어와서 Interrupt Request Bit이 있는지 확인한다. 이 Bit은 PIC로부터 받는 것이다. 만약 있다면 Processor는 IRQ 번호를 PIC로부터 받아서 Interrupt Handler을 호출한다. 이때 PC의 주소는 Interrupt Handler의 주소가 된다. 그러나 앞서서 현재 PC와 PSW를 Stack에 저장해야 한다. 우리는 이 Interrupt Handler가 끝나면 원래 수행하던 Process로 다시 돌아와야 한다. Memory주소는 Linear 하게 나열되어 있기 때문에 Handler가 끝나면 Handler의 끝 Address + 4가 될 수 있는데, 우리는 이것을 기대하는 것이 아니라 Interrupt를 처리하고 나와서 다시 Program으로 돌아오는 것을 기대한다. 따라서 복귀 주소인 PC와 PSW를 같이 Stack에 저장하는 것이다. 그 후에는 다시 Handler를 처리하기 위해 PSW를 Kernel Mode로 변경한다. 외부의 I/O Device와 통신해야 하는 것이기 때문에 이를 Kernel Mode에서 수행하는 것이다. Interrupt는 다른 Interrupt가 들어올 수 없도록 Disable 된다. Interrupt에 대한 처리가 다 끝나면 이제 Stack에 push 되었던 복귀 주소와 PSW를 pop 시켜 다시 Processor에 끌고 와야 한다.
아래의 그림은 Linux의 창시자인 리누스 토발즈가 만든 Linux/arch/i386/kernel/entry.s에 들어있는 IRQ 처리에 대한 Assembly Code이다. 보면 SAVE_ALL 하고 movl %esp, %eax를 한 다음에 .call do_IRQ를 하는 부분이 있다. 이것이 Interrupt Handler가 호출되어 Kernel Mode로 들어가는 진입점이다. 그리고 362번째 줄에 ret을 하면서 restore 하는 것을 볼 수 있다. 이 코드는 여기에서 확인할 수 있다. 옆에 있는 그림은 do_IRQ의 코드이다. 여기에서 가져왔다. 보면 do_IRQ의 argument의 pt_regs에는 다양한 것들이 인자로 들어가는데 여기에 Interrupt Handler의 번호도 들어가 있다. 그 내부가 궁금하다면 여기를 참조하길 바란다. 이것이 번호로 들어오기 때문에 IDT라는 Handler의 Mapping Table을 참조할 수 있다. 이 array는 함수 포인터 배열이며, 디바이스의 특정 Operation을 처리하는 ISR의 함수 포인터 주소가 들어가 있다. 일반적으로 IDT의 0~31번은 예약이 되어있으며, 외부 Device는 32번부터 시작한다. 이러한 Linux의 Interrupt Handling 방식은 매우 빠르게 동작한다. 오른쪽 그림의 572번째 줄을 보면 int irq가 IRQ 번호가 되는 것이다. 이를 574번 줄에서 irq_desc라는 IDT(Instruction Descriptor Table)에 단순히 irq를 더해줌으로써 Handler에 따르게 접근할 수 있는 것이다. 그림이 조금 잘려있지만 링크를 따라 들어가서 594번째 줄에 action을 할당하는 부분이 있고, 621번 줄에는 handle_IRQ_event를 call 해서 실제 Handler을 수행하는 부분이 있다.
Linux를 전체 다 분석한다는 것은 말이 안 되고 아마 대학원을 가야 전부 뜯어볼 수 있지 않을까 싶다. (물론 안갈예정) OS는 몇백만 줄의 코드로 이루어져 있기에 수업시간에 Optional 하게 배운 내용을 Linux의 코드를 찾아보면서 이해를 돕자 했다. 마지막으로 Interrupt Processing을 HW영역, SW 영역으로 나누어 정리해보고 넘어가자.
- Device Controller나 다른 System HW가 Interrupt를 Issue 한다. (HW)
- Processor는 수행하고 있던 Execution을 다 끝내고 Interrupt를 본다. (HW)
- Processor는 PIC에게 Interrupt를 잘 받았다는 ACK Signal을 보낸다. (HW/SW)
- Processor은 PSW와 PC를 Control Stack에 저장한다. (SW)
- Processor는 Interrupt Handler의 주소 값을 PC에 Load 한다. (SW)
- Processor는 나머지 Process에 대한 정보 (PCB, Process Image)를 저장한다.
- Interrupt를 Kernel Level에서 수행한다.
- Stack에 있던 정보들을 restore 시킨다.
- 다시 PSW와 PC를 restore 시킨다.
좋은 그림이 있는데 William Stalling의 교재 그림 Figure 1.11을 찾아보면 Memory적으로 어떻게 동작하는지 확인할 수 있다. 저작권으로 그림을 담지 못하기에 양해를 구한다.
Interrupt & Exception
이 부분에 대해서는 마찬가지로 시스템 프로그래밍 파트에서 자세히 정리하여 놓았으므로 짧게 설명하고 넘어가겠다. Interrupt는 Asynchronous Interrupt이다. 이는 외부의 HW Device들에 의해 발생하였기에 비동기적이라고 하는 것이다. 쉽게 말하자면 주변기기들에 의해 발생하는 것이다. 반면에 Exception은 Synchronous Interrupt이다. CPU에 의해 발생하는 것이기 때문에 Code의 흐름에 따라 발생하기에 동기적이라고 말한다. Exception에는 주로 'Division by Zero', 'Segmentation Fault', 'Page Fault' 등이 있다. Exception은 주로 System Call까지 의미한다. 사실 둘 다 넓은 범위에서 Interrupt로 간주되기 때문에 Linux에서는 동일한 방식으로 처리가 된다.
Exception은 주로 맨 위의 두 번째 그림의 회색 박스에서 발생하는 것이다. 이렇게 되면 그 즉시 Interrupt Request Bit이 1로 변경이 된다. 즉 내부적으로 Interrupt가 발생하는 것이다. 이때 역시 IRQ 번호가 존재하는데 아까 IDT에서 외부 디바이스는 주로 32번부터 그 ISP의 포인터 주소를 가지고 있다고 언급했다. 이때 0~31번까지는 Exception을 위한 ISP가 주로 들어가 있다.
Interrupt | Exception |
External to CPU | Internal to CPU |
Asynchronous to Clock | Synchoronous to Clock |
Current Instructions must be Completed | Current Instruction may not completed |
앞서서 Interrupt는 CPU Utilization을 높이기 위한 방식으로 사용이 된다고 했다. 이 내용에 대해서 좀 더 자세히 설명을 해보자면 Interrupt는 외부 Device를 처리하기 위한 Interface이다. 결과적으로 Processor는 외부 Device가 처리를 끝낼 때까지 기다려야 하는 현상이 발생한다. 그러나 우리는 이제 외부 디바이스가 처리할 때까지 기다리는 것이 아니라 원래의 Program으로 돌아와서 원래 하던 일을 계속하게 된다. 그리고 Interrupt Handler에서 외부 Device가 일을 다 끝냈다면 다시 Interrupt Handler로 돌아가서 Interrupt 처리를 하고 다시 원래의 Program으로 돌아가게 되는 것이다. 이렇게 하면 CPU는 끊임없이 동작을 하는 것이고 외부 Device를 기다리는 작업은 Concurrently 하게 동작하게 된다. 따라서 이 Interrupt Mechanism은 CPU를 idle 한 상태로 내버려 두는 것이 아니라 계속 돌림으로서 CPU의 Utilization을 높이는 방향으로 효율적이라고 하는 것이다.
Multiple Interrupts
그러나 Interrupt가 항상 한 개만 들어온다고 상황을 가정할 수가 없다. Interrupt는 꼬리에 꼬리를 물기도, 동시다발적으로도 일어날 수 있는 것이기 때문이다. 따라서 Multiple Interrupt가 들어오는 상황을 처리하는 방식에는 두 가지가 존재한다.
첫 번째로는 Sequential Interrupt Processing이다. 이 방식은 앞서서 우리가 배웠던 것처럼 Interrupt가 처리되고 있을 때 다른 Interrupt의 진입을 금지 즉 Disable 시키는 방식이다. 따라서 들어오는 Interrupt Request들은 Pending 되는 상태로 임의의 Queue 자료구조에 들어가 있을 것이다. 따라서 Processor 입장에서는 새로운 Interrupt Signal을 단순히 무시하면 되는 것이다. 이 구현 방식은 당연하게도 구현의 난이도가 쉬우며 비용이 적게 든다. 그러나 최대의 단점으로는 시간 긴급도를 고려하지 못하게 된다. 이게 어떤 의미냐면, 일의 우선순위가 있듯이 Interrupt에도 우선순위가 있을 수 있다. Interrupt를 처리하고 있는데 Interrupt 도중 새로운 Interrupt가 들어오게 된다면 잘못하다가는 Deadlock에 빠질 수도 있다. 또한 Scheduling 함수에 의해 먼저 처리해야 하는 Interrupt가 나중에 들어오게 된다면 전체적인 컴퓨터 입장에서는 효율성이 떨어질 수 있는 것이다.
반대로 Nested Interrupt Processing이 있다. 이를 중첩 Interrupt 처리방식이라고 하는데, 들어오는 Interrupt에 Priority를 주는 것이다. 따라서 실행 중인 Interrupt보다 Higher Priority의 ISR이 들어온다면 높은 우선순위를 가진 Interrupt를 먼저 처리하고 돌아와야 한다. 당연하게도 이때 Control Stack에 처리하고 있던 Interrupt의 상태를 저장해야 한다.
Pros/Cons
Interrupt를 실행하기 위해서는 필연적으로 Control Stack에 Current State와 Information들을 저장, 복구하는 작업들을 든다. 또한 Interrupt의 상태를 파악 후 적절한 action을 실행하는 데에 있어 이를 판단하는 추가적인 비용과 이 모든 코드들(Instructions)들이 요구가 된다. 그러나 명백한 것은, 이런 방식으로 동작하는 것이 CPU의 Utilization을 극대화해서 Processor의 운용을 최대한으로 사용할 수 있고 I/O Operation으로 인한 wait을 줄일 수 있기 때문에 현대에는 이 방식을 사용하고 있다.
많이 부족하지만 지적 및 저작권 침해 문제는 항상 환영이다.
Reference
William Stallings. (2018). Operating Systems: Internals and Design Principles (8th Edition): Pearson.
'Univ > Operating System(OS)' 카테고리의 다른 글
[OS] Operating System Overview II (Evolution through History) (0) | 2022.07.19 |
---|---|
[OS] Operating System Overview I (Intro) (0) | 2022.07.18 |
[OS] Computer System Overview IV (I/O Device) (0) | 2022.07.15 |
[OS] Computer System Overview III (Memory Hierarchy) (0) | 2022.07.11 |
[OS] Computer System Overview I (Components, Processor, PIC) (0) | 2022.07.08 |