그리고 이들을 통한 I/O가 이루어지는 과정을 간략히 설명한다. Terminal을 통한 CLI방식으로 I/O가 이루어지는 과정을 살펴보면서, I/O Interrupt와 Context switching에 대한 개념을 소개하며, OS가 제공하는 I/O를 위한 system call의 개념과 이를 이용하는 표준 입출력 라이브러리에 대한 개념도 간략히 살펴본다.
현대의 OS는 대부분 Time Division Technique을 이용하여 마치 여러 Program이 동시에 실행되는 것처럼 보이는 Time Sharing System이다.
하지만, 실제로는 한 순간에 하나의 core 당 하나의 program이 동작하는 것이고 core를 사용하는 시간을 나누어 여러 개의 program들이 동시에 동작되도록 보이는 것 뿐이다.
process and process context
실제 동작하는 program의 명령어들과 데이터는 core의 register들에 저장되어 있어야 한다. 한 core에서 동작하는 단위 를 process라고 부르며 이 process가 core에서 재실행되기 위해 필요한 데이터들을 process context라고 부른다.
context switching
하나의 core에서 여러 process가 시분할(Time Dividing)으로 실행되는 경우, 실행되던 process의 context가 기억장치에 저장(stack이 이용됨)이 되고, 실행될 process의 context가 register등에 load되어야 하며, 이런 과정을 context switching 이라고 부름.
CLI 를 사용하는 User application이 단일 process로 동작한다고 가정할 때, 이 역시 하나의 core에서 수행되기 위해서는 자신의 context가 cpu의 register들에 load 되어야 한다. 사용자의 키보드로부터의 입력을 대기하는 순간에 core를 사용하고 있는 건 비효율적이므로 OS가 I/O(입출력)을 대기하는 process들은 보통 context switching을 시켜서 sleep상태로 두는 경우가 일반적이다.
즉, User application이 I/O를 수행하기 위해 OS에게 system call을 하는 순간, OS는 사용자가 키보드 및 모니터로부터 I/O을 완료하기 전까지 해당 process를 sleep 시킨다. (이는 system call이 buffered input 을 지원하면서 요구한 경우임)
I/O가 이루어지고 있는 동안,
OS는 해당 User application의 process를 sleep 시키고,
다른 process를 수행할 수 있다.
이후 I/O가 종료되면
OS는 해당 User application의 process를 깨워서
core에서 수행되도록 context switching을 수행.
다시 말하면, I/O 종료 event가 발생하면 interrupt 에 의해 User application은 sleep 상태에서 나와 다시 core에서 수행되는 것으로 생각할 수 있다.
위의 내용은 매우 간단히 애기한 것으로 실제 동작은 보다 복잡하다.
context switching은 부하가 많이 걸리는 작업 으로
지나치게 많이 발생할 경우 오히려 효율이 매우 떨어지게 되어
사용자가 컴퓨터가 매우 느리다고 생각할 수 있다.
때문에, 이를 효과적으로 수행하기 위해 OS는 다양한 알고리즘을 사용하며 보다 많은 구성요소들의 도움을 받는다.
5-2. Blocking Mode Function vs. Non-Blocking Mode Function¶
CLI에서도 I/O 동작은 매우 느린 수행에 해당한다.
때문에 I/O를 수행하는 동작을 할 때, 해당 I/O가 확실히 이루어지고 나서 다음 명령어를 실행해야하는 경우라면, 해당 system call에 대한 응답을 대기하면서 해당 process를 멈추고 있는 block mode function 으로 I/O를 처리하는게 맞지만,
I/O에 상관없이 다음 구문을 수행할 수 있는 경우라면
I/O작업과 process 수행이 병렬로 이루어지도록 non-blocking mode function 으로 I/O처리하는 것을 고려해볼 수 있다.
위에서 살펴봤듯이 I/O는 매우 느린 작업이며, 어디서 수행되고 있느냐에 따라 속도 차이가 매우 크다. Core 에서 I/O 작업을 위한 처리 가능 속도와 사용자와 상호작용 중인 키보드에서의 처리 가능 속도는 매우 큰 차이를 보일 수 밖에 없다. 이처럼 처리 속도 차이가 큰 요소들이 결합할 경우, 해당 속도차로 인한 문제를 줄여주기 위해 buffer가 도입 된다.
CLI S/W 에서 사용자와의 I/O는 사용자가 Terminal을 통해 User application과 상호작용하는 것이라고도 볼 수 있다.
즉, CLI는 "키보드와 모니터의 드라이버들"과 "운영체제(OS)", "Terminal" 이 모두 함께 참여하여야 가능하다.
이들 구성 요소들의 처리 속도에 차이가 존재하므로
이들 사이에서 I/O(입출력)을 원활하게 하기 위해 input buffer와 output buffer가 존재함: Kernel Buffering.
아래 그림을 보면 각 장치 드라이버 뿐 아니라 User application이 사용하는 system call 라이브러리에도 buffer들이 존재함을 확인할 수 있다.
이들은 지나치게 빈번한 system call을 막아줌.
input buffer로 terminal에 해당하는 device driver의 buffer의 문자를 한 번의 system call로 가져올 수 있는 최대량을 가져옴.
output buffer도 다 차고 나서 device driver로 보내던지, 아니면 newline 과 같은 특수한 문자가 들어올면 device driver의 버퍼로 보내는 방식으로 buffering을 수행.
High Lever I/O 는 buffered I/O를 수행함.
위 설명과 그림에서는 키보드의 드라이버 처럼 애기했지만, 실제로는 OS가 Terminal을 Device로 보고 이에 대한 Device Driver가 존재한다. 즉, 위 그림에서 Device Driver는 Terminal에 대한 Driver임.
Low Level I/O의 경우, Kernel Level에서의 buffering을 사용함.
Kernel Level Buffering은 I/O에서의 일반적인 버퍼링이라고 불리지 않음을 주의할 것.
일반적인 buffering은 User Level Buffering 을 가르키기 때문에
High Level I/O가 보통 버퍼링을 제공한다고 할 때의 buffer는 User Level Buffering임.
terminal은
단순히 computer가 사용자에게 보내는 텍스트만을 출력하지 않으며,
사용자가 키보드로 입력하고 있는 글자 하나 하나를 echo처리(화면에 그대로 출력해줌)한다.
위 그림에서 buffer들의 연결을 잘 보면 echo를 위한 연결 을 확인할 수 있다.
참고로 buffer는
S/W적으로 FIFO (First in First out)인 Queue 로 구현됨 (엄밀하게 애기하면 Circular Queue).