본문 바로가기

23년 1학기 학교공부/운영체제및실습

[OS] I/O Control

목차

    728x90
    반응형
    SMALL

    운영체제가 디바이스 드라이버를 통해 장치를 제어하는 방법.

    운영체제는 장치의 상태를 체크하면서 장치를 제어하는데, 이 방법은 총 3가지가 있다.

    1. Polling
    2. Interrupt-driven I/O
    3. Direct Memory Access (DMA)


    Polling

    사용자 프로그램에 printf라는 인스트럭션이 있다고 하자.
    이 printf 문장은 컴파일되면서 출력에 해당하는 시스템콜로 바뀐다. 출력을 하기 위해서는 프린트 장치를 제어해야하는데, 사용자 프로그램에서 장치를 직접 제어할 권한이 없기 때문에 권한을 가진 운영체제가 일을 대신할 수 있도록 시스템콜로 요청하는 것이다.

    이 시스템콜을 부른 사용자 프로세스는 유저모드에서 커널모드로 모드체인지하여 커널 스페이스로 들어가, 커널로 바뀌어서 운영체제 기능을 실행하게 된다.

    운영체제에서는 받은 시스템 콜을 처리해야하는 기능이 무엇인지를 따져보고, 그것이 입출력에 관한 것임을 확인한 후 입출력을 담당하는 운영체제 기능인 I/O management (커널 io시스템)으로 전달된다.

    그러면 커널 io 서브시스템이 실제로 출력을 담당할 출력장치가 무엇인지 알아낸다.
    만약 출력 장치가 모니터라고 하면,
    디바이스 드라이버 인터페이스에 해당되는 함수를 부른다.
    그러면 cpu는 커널 io 서브 시스템에서 디바이스 드라이버로 옮겨와서, 디바이스 드라이버에 있는 코드를 실행한다.
    커널 io 서브 시스템이 디바이스 드라이버의 출력 함수를 부르면서, 출력데이터나 기타 정보를 모두 커널 스택에 저장하기 때문에 디바이스 드라이버 출력 함수는 모니터 하드웨어를 제어해가며 출력할 수 있게 된다.

    디바이스드라이버는 장치를 만든 회사에서 구현한 함수이다.
    그래서 디바이스 드라이버는 출력 장치 하드웨어를 잘 알고있기 때문에, 이를 어떻게 제어해야하는지, 즉 컨트롤 레지스터 어디에 어떤 명령을 내려야 하드웨어가 동작하는지를 알고있기 때문에, 그 컨트롤 레지스터의 해당 비트를 1로 설정함으로써 출력 명령을 내린다.

    물론 출력 명령을 내리기 전에 장치 상태를 먼저 확인하고, 장치가 현재 사용 가능할 때 명령한다.

    하드웨어는 기계적인 부분과, 컨트롤러 혹은 어댑터라고 하는 전자부품이 결합되어있는 형태로 되어있다.
    전자부품 부분에는 컨트롤레지스터, status 레지스터, 입출력데이터가 잠시 저장되는 데이터버퍼가 있다.

    어떤 출력장치의 정보를 출력한다고 하자.
    디바이스 드라이버는 먼저 그 장치의 컨트롤러 내부에 있는 status레지스터에서 장치가 일하고있는 상태인지를 알아보기 위해 'busy bit'를 읽는다.
    만약 1이면 다른 출력을 위해 작업중이라는 뜻이므로 작업이 끝날때까지 기다리면서 반복적으로 busy bit를 확인한다.
    busy bit가 0이 되면 운영체제는 컨트롤 레지스터의 write bit를 1로 세팅한다.
    이후 쓸 내용을 데이터 버퍼인 데이터 아웃 레지스터에 쓴다.
    그리고 새 명령이 내려졌음을 장치에게 알리기 위해 컨트롤 레지스터의 command bit를 1로 설정한다.
    그러면 컨트롤러 하드웨어는 command bit가 1이 되었음을 읽고 status 레지스터의 busy bit를 1로 설정한 후 컨트롤 레지스터를 읽고, write bit가 1로 설정되었음을 확인하여 방금 도착한 명령이 쓰기 명령임을 읽는다.
    이후 데이터 아웃 레지스터의 내용을 출력한다.
    출력이 모두 끝나면 command ready bit와 busy bit를 0으로 바꾸고,
    디바이스 드라이버 소프트웨어는 프린트 컨트롤러에게 명령을 내린 후, 프린트 출력이 끝났는지를 status 레지스터의 busy bit가 0으로 바뀌었는지를 주기적으로 확인한다.
    이처럼 status 레지스터 비특밧을 체크하면서 상태를 주기적으로 물어보는것을 poll한다라고 말한다.

    즉 디바이스드라이버가 status 레지스터의 busy bit를 반복적으로 읽는 이 기간은 디바이스 드라이버의 소프트웨어가 실행중이라는 뜻이다.
    이때 cpu가 다른일을 하지 않고 디바이스 드라이버 인스트럭션을 반복적으로 실행하는 것이다.
    busy bit가 0으로 바뀌면 디바이스드라이버가 확인후 cpu가 io management로 되돌아간다.
    이후 일을 마친 io management는 사용자 프로그램에 돌아가는데, 이때 바로 돌아가지 않고, 일단 커널에 들어와서 운영체제로서 해야하는 시스템 관리 역할들을 수행하고 나서 사용자 프로그램으로 돌아간다.

    위 과정에서 컨텍스트 스위치, 즉 프로세스 스위치는 일어나지 않는다.

    폴링 방버은 입출력이 끝날때까지 계속 cpu를 잡고 busy bit를 반복적으로 확인하며 기다린다.
    이것은 busy waiting이다.
    입출력이 빨리 끝나는 장치인 경우 나쁜 방법이 아니지만,
    만약 입출력이 느린 장치인 경우 cpu가 오랜 시간 기다리므로 cpu의 효율이 안좋아진다.

    즉 polling방법은 디바이스가 빨리 끝나는 장치가 아닌 경우 오랫동안 비지웨이팅을 해야하기 때문에 효율적이지 않지만, 반면에 입출력이 빨리 끝나는 장치라면 매우 효율적인 방법이다.

     

    2. 인터럽트 기반 IO

    위 그림은 인터럽트 기반 IO의 처리 절차이다.

    1) 유저모드에서 실행되는 어느 프로그램이 print 인스트럭션을 실행한다고 가정하자.
    프린트라는 인스트럭션은 컴파일시 출력에 해당되는 시스템 콜로 바뀌므로, printf문을 실행하는것은 곧 시스템콜을 부르는것이다 

    2) 시스템콜은 커널에서 실행되는 함수이므로 모드체인지가 일어나서 유저프로세스 a가 운영체제로 바뀌어 입출력에 해당하는 시스템 콜을 담당할 IO Management로 가서 실행한다.
    그러면 이 IO Management는 a가 요청한 출력이 어떤 장치에서 일어나야하는지를 알아낸다.
    만약 모니터라고 하면, 모니터를 제어하는 디바이스 드라이버를 호출하여 출력을 지시한다.

    3) 디바이스드라이버는 디바이스의 상태를 보고 명령을 내린다. 3번까지의 과정은 폴링과 같다.

    4)이때 폴링에서는, 디바이스 드라이버를 반복적으로 실행하며 출력이 끝날때까지 busy waiting하며 기다렸다. 결국 a 프로세스가 계속 cpu를 가진 채로 운영체제를 실행하므로 현재 프로세스 a의 상태는 러닝상태이다.
    하지만 인터럽트 기반 IO에서는, cpu를 계속 가지고 있어 효율이 떨어지는 폴링의 단점을 개선하기 위해, 출력 명령 후 출력이 끝날때까지 기다리지 않고 프로세스 관리자로 넘어가서 컨텍스트 스위치를 진행한다.
    5) a가 디바이스 드라이버를 실행해서 디바이스 하드웨어에게 명령을 내린 후, 프로세스 매니지먼트를 실행하여 a의 상태를 block상태로 바꾼다.
    이제 출력이 끝날때까지 a는 cpu를 놓고 기다린다.
    6) 이 컨텍스트 스위치때 a의 상태값, cpu 레지스터값을 pcb에 다 저장하고 스케줄러를 실행하여 컨텍스트를 스위치한다.
    7) 이후 cpu는 프로세스 b를 실행한다.
    이후 출력이 끝나게 되면, 출력장치의 컨트롤러 하드웨어는 status 레지스터의 busy bit와 command ready bit를 0으로 변경된다. 8) 그러면 디바이스 드라이버 컨트롤러가 운영체제에게 인터럽트를 보내 출력이 busy bit와 command ready bit의 상태를 알린다
    IRQ는 인터럽트 리퀘스트를 말한다. 현재 화살표가 cpu를 지닌 프로세스 b로 향하고 있다. 현재 cpu는 b 프로그램의 인스트럭션 한줄마다 인터럽트를 검사하고 있을것이므로, 이때 디바이스가 인터럽트 신호를 걸면 b 프로세스를 중단하고 커널 스택에 cpu 레지스터값을 저장하고, 모드체인지를 한다.
    9) 커널로 들어가서 인터럽트를 처리하는 인터럽트 핸들러를 실행한다.
    10) 인터럽트 핸들러는 pic칩에게 인터럽트를 건 장치가 무엇인지 물어보고 알아낸 후, 해당되는 인터럽트 서비스 루틴을 호출하여 인터럽트를 처리한다. 이때 이 인터럽트를 처리하는 실행 흐름의 주체, 즉 운영체제를 실행하고 있는 주체는 b이다.
    11) 이 인터럽트 서비스 루틴에서는 프로세스 매니지먼트를 다시 불러 입출력을 기다리며 block상태였던 a를 block queue에서 빼내 ready queue에 넣어 ready상태로 바꾼다.
    이렇게 인터럽트 처리를 마치고, 운영체제로서 시스템 관리 업무를 하고 유저 모드로 돌아간다. 이때 미뤄둔 인터럽트 처리작업을 끝내는 것이다. 두 부분으로 나눈 인터럽트는 아래 포스팅에서 설명한다.
    또한 이 프로세스에 시그널이 도착했는지 조사하고, 도착했다면 이를 처리한다.
    또한 새로 ready 상태가 된 프로세스가 있는지 조사하고, 있다면 스케줄러를 부른다. 지금같은 경우 a가 ready상태로 바뀌었으므로 스케줄링 원칙에 따라 a 또는 b 중 우선순위가 높은 프로세스를 실행한다. 이때 a가 실행되면 컨텍스트 스위치가 일어나는 것이다.

    위 인터럽트 기반 IO에서는 b가 인터럽트 때문에 중단당했지만, 손해라고 볼 수 없다.

     

    이제 장단점을 비교해보자.

     

    주어진 시간에 더 많은 서비스를 할 수 있다는 장점이 있다.

    하지만 단점도 있다.

    폴링방법에서의 단점은 입출력이 느리게 진행되는 장치의 경우 폴링이 좋지 않다.

    즉 장치가 느린데 폴링에서 오랫동안 비지웨이팅하면서 기다리는것은 좋지 않다.

    하지만 입출력이 금방 끝나는 장치인 경우 폴링이 효율적이다.

     

    interrupt 기반 I/O는 위와 반대이다.

    만약 장치가 I/O가 느린 장치라면 이를 기다리는동안 b 프로세스로 컨텍스트 스위치해서 넘어가서 실행하니까 cpu의 효율이 좋아진다.

    하지만 만약에 입출력이 빨리 끝나는 경우에는 비효율적이다.

    폴링에서는 I/O management를 거쳐서 운영체제로 해야할 일을 하고 유저 프로그램으로 돌아가니까 비지 웨이팅으로 낭비하는 시간이 별로 없어서 입출력이 빨리 끝나는 장치에서 효율적인 방법인건데,

    interrupt 기반 I/O는 명령을 내려두고 프로세스 관리자를 실행해서 컨텍스트 스위치를 한다. 이때 컨텍스트 스위치에서는 a의 cpu 레지스터값을 pcb에 저장하고, 여러 상태정보를 다 저장한 후 b의 컨텍스트를 복구하는 일을 해야하므로 운영체제가 할 일이 많다.

    그런데 이런 일을 하는 도중에 장치의 I/O가 끝나서 인터럽트가 걸린다면, 컨텍스트 스위치를 중단하고 인터럽트를 처리한 후, 만약 a의 우선순위가 더 높다면 b로 컨텍스트 스위치하던 것을 취소하고 다시 a로 돌아와야한다.

    이런 불필요한 일들이 일어날 수 있기 때문에 빠르게 동작하는 장치에서 interrupt 기반 I/O는 좋지 않은 방법이다.

     

     

    3. DMA(Direct Memory Access)

    컴퓨터 시스템에서의 입출력이라고 하는 것은 메인 메모리의 내용을 입출력 장치로 보내는 것인데, 이때 정보가 메모리에서 장치로 직접 가지 않는다.

    예를 들어 출력 작업은 출력할 대상이 들어있는 메모리에서 출력할 내용이 bus를 타고 와서 cpu 레지스터로 가져오면, 다시 이것을 cpu가 I/O 컨트롤러에게 보내고, I/O 컨트롤러가 출력하는 것이다.

    반대로 입력 작업도 입력된 내용이 cpu 레지스터를 거쳐 메모리로 들어간다.

     

    이런식으로 입출력은 항상 cpu라는 프로세서를 거쳐서 전달되는 방식으로 동작하는데,

    만약 입출력 데이터의 개수가 한 두개이면 cpu를 거쳐서 가도 크게 문제되지 않지만,

    내용이 수천 수만 바이트라면 위와 같이 메모리로 읽어와서 프로세서가 I/O 컨트롤러에게 보내는 사이클이 수천 수만번 반복되어야 한다.

    이때 데이터 하나 전달이 끝나면 cpu에 인터럽트가 걸리기 때문에 cpu가 커널로 들어가서 interrupt를 처리해야한다.

    즉 수천 수만 바이트를 입출력할때 인터럽트가 수천 수만번 걸리므로 사용자 프로그램이 수천 수만번 중단된다.

     

    결국 cpu의 효율이 아주 안좋아지는데, 이 방법을 해결하기 위해 나온 방법이 DMA이다.

     

    DMA는 입출력 데이터가 cpu를 거치지 않고 DMA 컨트롤러라는 별도의 프로세서를 두어서, cpu가 DMA 컨트롤러에게 메모리 몇번지부터 몇개의 데이터를 읽어서 출력해라 등의 명령을 내리면 DMA 컨트롤러가 메모리에 가서 해당 내용을 읽어오고, I/O 장치에 전달해서 출력하도록 명령을 내린다.

    즉 수천 수만번 반복하는 사이클을 cpu가 아닌 DMA 컨트롤러라는 보조 프로세서가 하게 되면, cpu는 그 사이에 사용자 프로그램을 실행할 수 있는 것이다.

    이후 DMA가 명령받은 일을 모두 끝내면 cpu에게 인터럽트를 걸고, 인터럽트를 받은 cpu는 사용자 프로그램을 중단하고 모드체인지를 해서 운영체제를 실행하는 것이다.

     

     

     

    728x90
    반응형
    LIST