입출력장치 제어와 운영체제 기능 호출
>2022.02.12.
입출력 장치 제어
컴퓨터에 장착된 각종 입출력장치들은 각각의 제어기에 연결되고, 각 장치에 대한 입출력 작업 명령은 이 제어기를 통하여 이루어집니다. 이 과정은 제어기의 레지스터들에 값을 기록하거나 값을 읽어 내는 것으로 처리합니다. 여기서는 대표적인 입출력장치인 키보드와 모니터만을 가정하여 설명드리겠습니다.
제어기 구성
각 입출력 장치별 제어기에는 여러 개의 레지스터들이 있는데, 장치의 현재 상태를 확인할 수 있는 상태 레지스터, 장치에 특정 명령을 전달하는 제어 레지스터, 실제 데이터를 내보내거나 읽어 들일 수 있는 데이터 레지스터 등으로 구성됩니다. 키보드로부터 문자를 받아들이기 위해서는 먼저 키보드 제거기의 상태 레지스터의 내용을 읽어서 키보드가 눌러졌는지를 검사한 후에 눌러졌다고 확인되면 데이터 레지스터의 내용을 읽어 들이면 완료됩니다. 문자를 읽고 난 후에는 키보드 제어기의 제어 레지스터에 읽기가 완료되었음을 알려주는 특정 명령 값을 기록하는데, 키보드 제어기에게 이전 값은 읽어 갔으니 데이터 레지스터에 다음에 눌러진 문자의 값을 기록해도 무방함을 알려주는 것입니다.
화면에 문자를 출력하는 것도 마찬가지인데, 먼저 그래픽 제어기의 상태 레지스터 내용을 읽어 다음 문자를 내보낼 준비가 되었는지를 확인한 후에 데이터 레지스터에 출력할 문자를 기록합니다. 그래픽 제어기는 제어 레지스터의 이 값을 확인하면 데이터 레지스터에 있는 문자를 그림 형태로 화면에 표시하고, 상태 레지스터에는 출력이 완료되었음을 표시하는 값을 기록합니다.
제어기 레지스터 접근
프로세서가 입출력 제어기의 레지스터들에 값을 쓰거나 읽는 작업을 프로그램으로 표현할 때는 각 레지스터에 할당된 메모리 주소에 쓰거나 읽는 작업으로 처리합니다. 각 제어기의 레지스터별로 별도의 주소가 저장됩니다. 예를 들어 KBSC(키보드의 상태/제어) 레지스터 값을 읽는 것은 LOAD 명령어를 이용하여 해당하는 레지스터 주소 번지 내용을 읽으면 됩니다. DPDATA 레지스터에 값을 기록하는 것은 STORE 명령어를 이용하여 해당 레지스터 주소 번지 내용에 기록합니다. 여기서 상태 레지스터와 제어 레지스터가 같은 주소를 사용하는데, 읽기를 할 때에는 상태 레지스터의 값이 읽혀 오고 쓰기를 할 때에는 제어 레지스터에 기록되는 것으로 가정합니다.
메모리 맵
프로세서의 전체 메모리 주소 공간에서 어느 주소부터 어디까지가 램이고 어느 부분이 롬이고 어느 부분이 각 입출력 장치의 주소인지를 정해 놓은 표를 메모리 맵이라고 합니다. 메모리 맵은 컴퓨터 본체 회로를 설계하는 과정에서 설계자가 적절하게 결정하고, 이 메모리 맵에 맞도록 주소 디코딩 회로를 설계합니다. 각 장치를 구동하기 위한 프로그램은 이 메모리 맵을 바탕으로 개발되어야 합니다.
키보드 장치의 구동
가상의 키보드 제어기 설명서에 '제어기는 키가 눌러지면 해당 키의 문자코드를 KBDATA 레지스터에 기록한 후에 KBSC 레지스터의 최하위 비트를 1로 설정한다'라고 적혀 있다고 가정합니다. 그러면 키보드에서 문자를 읽어 들이기 위한 프로그램에서는 먼저 KBSC 레지스터의 내용을 읽어 들인 다음에 최하위 비트가 1인지를 검사하여 이 비트가 0이라면 다시 KBSC 레지스터의 내용을 읽고 검사하기를 반복합니다. 이 비트가 1이면 정상적으로 문자가 입력된 상태이므로 KBDATA 레지스터에 있는 문자 코드를 읽어냅니다. 키보드 제어기의 설명서에 '임의의 키가 눌러졌을 때 KBSC 레지스터의 최하위 비트를 검사하고, 이 비트가 1이면 이전에 입력된 키를 프로그램에서 읽어가지 않은 상태를 의미하므로 지금 눌러진 키 값은 무시한다'고 적혀 있다면, 키보드에서 문자를 읽어 들이는 프로그램에서는 문자를 읽어낸 후에 반드시 KBSC 레지스터의 최하위 비트를 0으로 재설정하여 키보드 장치가 다음 문자를 KBDATA 레지스터에 기록할 수 있도록 해주어야 합니다.
아래의 프로그램은 이러한 절차를 따라 키보드에서 문자를 하나 읽어들인 다음 R0 레지스터에 담아 반환하는 함수이다.
GetChar : LOAD R1, KBSC ; 키보드 상태/제어 레지스터 주소
gcL0: LDR R0, R1, 0 ; 키보드 상태 읽기
AND R0, R0, 1
BR z, gcL0
LOAD R2, KBDATA ; 키보드 데이터 레지스터 주소
LDR R0, R2, 0 ; 키보드 문자 읽기
COPY R2, 0 ; R2의 최하위 비트를 0으로 하여
STR R2, R1, 0 ; 키보드 제어 레지스터에 기록
RTT ; 운영체제에서 응용 프로그램으로 복귀
KBDATA: .FILL 0xFFFFFF00
KBSC: .FILL 0xFFFFFF01화면 출력장치의 구동
화면 출력장치의 구동도 비슷하게 동작합니다. 만약 가상 그래픽 제어기의 설명서에 '항상 DPSC 레지스터의 내용을 검사하고 있다가 최하위 비트가 1이면 DPDATA 레지스터의 값에 해당하는 문자를 화면에 그려주고, 문자 출력이 완료되면 DPSC 레지스터의 최하위 비트는 0으로 재설정 된다'라고 적혀 있다고 하면, 문자를 출력하기 위한 프로그램에서는 먼저 DPSC 레지스터의 내용을 읽어 들인 다음에 최하위 비트가 1인지 검사합니다. 만약 이 비트가 1이면 이전에 출력을 요청한 문자가 아직 화면에 출력이 완료되지 않은 것을 의미하므로, 이 비트가 0으로 될 때까지 DPSC 레지스터의 내용을 읽고 검사하기를 반복합니다. 이 비트가 0이면 DPDATA 레지스터에 출력할 문자를 기록한 후에, DPSC 레지스터의 최하위 비트를 1로 설정해 줍니다.
아래의 프로그램은 이러한 절차를 따라 문자 하나를 화면에 출력하는 함수이다.
출력할 문자의 아스키 코드는 R0 레지스터에 저장되어 있다고 가정한다.
putChar: LOAD R1, DPSC ; 화면 제어 레지스터 주소
pcL0: LDR R2, R1, 0 ; 화면 상태 읽기
AND R2, R2, 1 ; 최하위 비트 검사
BR p, pcL0 ; 최하위 비트가 1이면 pcL0으로 이동
LOAD R2, DPDATA ; 화면 데이터 레지스터 주소
STR R0, R2, 0 ; 화면에 문자 쓰기
COPY R2, 1 ; R2의 최하위 비트를 1로
STR R2, R1, 0 ; 화면 제어 레지스터에 기록
RTI ; 운영체제에서 응용 프로그램으로 복귀
DPDATA: .FILL 0xFFFFFF02
DPSC: .FILL 0xFFFFFF03디바이스 드라이버
위의 키보드 장치, 화면 출력 장치 구동 프로그램을 디바이스 드라이버라고 합니다. 컴퓨터에 장착된 각 입출력 장치마다 그것을 구동하기 위한 프로그램인 디바이스 드라이버가 운영체제에 설치되어 있어야 실제로 사용할 수 있게 됩니다. 디바이스 드라이버는 운영체제 부분에 처음부터 포함되어 있어서 부팅 과정에서 자동으로 설치되는 경우도 있고, 운영체제가 작동하고 있는 중에 추가로 설치하기도 합니다.
폴링과 인터럽트
입출력장치가 준비된 상태인지를 반복적으로 검사하고 준비가 되었으면 실제 입출력을 진행합니다.
위의 프로그램과 같이 준비 조건이 만족될 때까지 끊임없이 반복적으로 검사하는 방식을 폴링 방식이라고 합니다. 아주 단순한 입출력 장치는 폴링 방식을 적용하지만 보다 성능이 좋은 장치에서는 준비된 시점에 프로세서에게 인터럽트 신호를 발생하는 인터럽트 방식을 사용합니다. 프로세서는 다른 프로그램 부분을 실행하다가 인터럽트 신호가 발생하면 일단 지정된 인터럽트 처리 부분을 먼저 실행한 후에 하던 일을 계속하도록 합니다. 폴링 방식을 사용하면 키보드 입력을 기다리는 동안 프로세서는 다른 일을 할 수가 없습니다. 이 프로그램 하나만 실행하고 있다면 상관이 없지만 여러 개의 프로그램들을 다중으로 실행하는 상황이라면 인터럽트 방식이 꼭 필요합니다. 인터럽트 방식의 입출력 장치에 대한 설명은 여기서는 생략하겠습니다.
운영체제 기능 호출을 위한 명령어
시스템 호출
운영체제상에서 응용 프로그램을 실행하는 경우에 응용 프로그램은 운영체제의 특정 기능을 호출하여 사용할 필요가 있습니다. 키보드 문자 읽기, 화면에 문자 출력하기, 프로그램 종료, 파일 읽기 등이 그 예입니다. 이러한 기능들을 시스템 호출 기능이라고 하는데, 운영체제마다 제공되는 시스템 호출 기능들이 어떤 것인지는 그 운영체제의 설계자에 의해서 명확하게 정의되어 있습니다. 키보드에서 문자 읽기와 같은 입출력장치를 위한 프로그램은 응용 프로그램을 작성하는 사람이 직접 작성하기가 매우 번거로울 것입니다. 또한 한 컴퓨터에 여러 개의 작업들이 동시에 실행되는 상황에서는 작업들 간에 입출력장치를 사용하는 것이 중첩되면서 문제를 일으킬 수도 있습니다. 따라서, 입출력 처리를 위한 내용은 운영체제 프로그램 부분에(디바이스 드라이버로) 포함해 두고, 응용 프로그램을 작성할 때에는 해당 기능을 호출하기만 하면 되도록 합니다.
응용 프로그램에서 운영체제의 특정 기능을 호출하기 위해서는 특별한 절차가 필요한데 이를 위해서 사용되는 명령어가 SWI(소프트웨어 인터럽트)입니다. 운영체제의 n번째 기능을 호출하기 위해서는 'SWI n'을 사용합니다. SWI 명령어를 실행하면 프로세서는 운영체제를 실행하는 모드로 변경되고 운영체제의 n으로 지정된 부분의 프로그램을 실행하도록 실행 위치를 이동합니다. 운영체제 프로그램의 끝 부분에는 다시 응용 프로그램을 실행하던 위치로 복귀하기 위한 명령어를 사용해야 합니다. 이를 위한 명령어가 RTI입니다. RTI는 운영체제 프로그램 부분에서 사용하는 것으로서 프로세서의 모드를 원래의 실행 모드로 복구하고 실행 위치도 SWI 명령어 바로 다음 주소로 복귀하여 응용 프로그램의 다음 명령어 실행을 계속하도록 합니다.