컴퓨터의 주요 작업은 계산과 입출력 작업이다.
보통 입출력 작업이 더 중요하다.
컴퓨터의 운영 체제는
I/O 연산과 I/O 장치를 관리하고 제어하며,
이를 통해 하드웨어 인터페이스와 애플리케이션 인터페이스 사이의 간극을 메우고,
I/O 성능을 향상시킨다.
운영체제의 주요 고려사항은 컴퓨터에 연결된 장치들을 제어하는 것이다.
이를 위해, I/O 장치를 다루는 커널의 I/O 하위 시스템(드라이버)이 존재하며,
이는 하드웨어 및 소프트웨어 기법의 결합을 통해 해결된다.
이런 복잡성은 장치 드라이버 모듈을 사용하여 기기별 세부사항을 캡슐화함으로써 관리된다.
컨트롤러와 드라이버 사이의 handshaking
두 요소 간의 동기화를 위한 프로세스로, 둘 사이의 데이터 전송에서 중요하다
프로세스는 다음과 같다.
1. 먼저, 장치 드라이버는 컨트롤러의 상태 레지스터를 반복적으로 읽어 busy bit가 클리어될 때까지 기다린다.
이는 컨트롤러가 준비되고 다음 명령을 받을 준비가 되었음을 나타낸다.
2. 준비가 되면, 드라이버는 명령 레지스터의 "쓰기" 비트를 설정하고,
데이터를 데이터-아웃 레지스터에 쓴다.
3. 그런 다음, 드라이버는 "명령 준비" 비트를 설정한다.
4. 컨트롤러가 "명령 준비" 비트가 설정되었음을 인지하면, "busy" 비트를 설정한다.
5. 컨트롤러는 명령 레지스터를 읽어 "쓰기" 명령을 인식하고,
데이터-아웃 레지스터를 읽어 바이트를 가져온 다음, 장치에 I/O를 수행한다.
6. 마지막으로, 컨트롤러는 "명령 준비" 비트를 클리어하고,
상태 레지스터의 "에러" 비트를 클리어하여 장치 I/O가 성공적으로 수행되었음을 나타내고,
"busy" 비트를 클리어하여 작업이 완료되었음을 나타낸다.
이러한 순환은 각 바이트에 대해 반복된다.
이렇게 함으로써, 드라이버와 컨트롤러 사이에 데이터의 일관성과 정확성이 유지되며,
둘 사이의 효율적인 상호 작용이 가능해진다.
다양한 I/O 장치는 컴퓨터 시스템과 신호를 주고받아 통신하며,
이 통신은 케이블이나 무선을 통해 이루어진다.
이는 port 혹은 bus를 통해 이루어진다.
컨트롤러는 이러한 포트, 버스, 장치를 운영할 수 있는 전자 장치 모음이며,
종류에 따라 간단한 칩에서 복잡한 프로토콜을 처리하는 별도의 회로 보드까지 다양하다.
버스가 뭐야?
버스(bus)는 컴퓨터 내의 다양한 하드웨어 구성 요소들이 데이터를 주고받을 수 있도록 하는 데이터 경로이다.
버스는 주로 데이터 버스, 주소 버스, 제어 버스 등의 종류로 나뉜다.
Data Bus : 실제 데이터를 전송하는 경로로,
데이터 버스의 너비(비트 수)는 한 번에 전송할 수 있는 데이터의 양을 결정한다.
Address Bus : 메모리의 특정 위치나 입출력 포트를 식별하는 주소 정보를 전송하는 경로로,
주소 버스의 너비는 시스템이 지원할 수 있는 메모리 용량을 결정합니다.
Control Bus : 시스템의 다른 부분들 사이에서 명령, 타이밍 신호, 특정 조건에 대한 응답 등을 전송하는 경로다.
버스를 통해 데이터, 명령, 상태 정보 등이 프로세서, 메모리, 주변 장치 간에 주고받아진다.
이렇게 하면 하드웨어 구성 요소 간의 통신을 효율적으로 관리할 수 있다.
버스를 이렇게 나눈 이유는 구조적 명확성과 효율성을 높이기 위한 것입니다.
컨트롤러와 드라이버가 뭐야?
장치 컨트롤러는 하드웨어로, 물리적인 신호를 제어하여 장치의 동작을 조정한다.
장치 드라이버는 소프트웨어로, 운영체제와 장치 컨트롤러 사이에서 인터페이스 역할을 한다.
드라이버는 운영체제로부터 명령을 받아, 이를 장치 컨트롤러가 이해할 수 있는 명령어로 변환한다.
이를 통해 운영체제는 하드웨어의 세부 사항을 알 필요 없이 그 하드웨어를 제어할 수 있게 된다.
프로세서는 컨트롤러에 데이터 및 제어 신호를 전송하기 위해 일련의 레지스터를 사용한다.
이러한 통신은 특별한 I/O 명령을 사용하거나,
장치가 메모리 매핑 I/O를 지원하는 경우,
장치 제어 레지스터가 프로세서의 주소 공간에 매핑되어 이루어진다.
일반적으로 I/O 장치 제어는 상태, 제어, 데이터 입력, 데이터 출력 레지스터를 사용하며,
이들은 각각
호스트가 읽을 수 있는 상태 비트,
제어 명령을 시작하거나 장치 모드를 변경하는 제어 비트,
입력을 위한 데이터,
출력을 위한 데이터를 저장합니다.
특별한 I/O 명령이 뭔데?
"특별한 I/O 명령"이란 일반적인 데이터 처리 명령어와는 다르게,
외부장치와 통신하기 위해 설계된 프로세서의 명령어를 의미한다.
이들 명령은 프로세서가 장치의 컨트롤러와 직접 통신하는 데 사용된다.
예를 들어, 인텔 x86 아키텍처에서는 IN과 OUT이라는 I/O 명령이 있는데,
IN 명령은 특정 I/O 포트에서 데이터를 읽어오는 데 사용되며,
OUT 명령은 특정 I/O 포트에 데이터를 쓰는 데 사용된다.
폴링은 CPU가 주기적으로 I/O 장치의 상태를 확인하는 방식이다.
즉, CPU는 특정 I/O 장치가 데이터를 전송하거나 수신할 준비가 되었는지를 반복적으로 검사한다.
I/O 요청이 많으면 효율이 좋지만 I/O 요청이 별로 없으면 인터럽트 방식이 더 효율적이다.
인터럽트는 I/O 장치가 준비되었을 때 CPU에게 자신의 상태를 알리는 방식이다.
CPU는 인터럽트가 발생하면 현재 수행중인 작업을 중단하고 인터럽트를 처리한 후에 원래의 작업으로 돌아간다.
인터럽트 라인
CPU는 보통 두 개의 인터럽트 요청 라인을 가진다.
하나는 복구할 수 없는 메모리 오류와 같은 이벤트에 대해 예약된 nonmaskable 인터럽트이고,
다른 하나는 중요한 명령어 시퀀스의 실행이 방해받지 않게 CPU에 의해 꺼질 수 있는 maskable 인터럽트이다.
인터럽트 벡터와 체이닝
인터럽트 요청이 있을 때, 인터럽트 벡터라는 테이블 내의 주소를 통해 특정 인터럽트 핸들러가 호출된다.
인터럽트 체이닝은 해시 테이블처럼 옆으로 리스트를 쭉 둬서 테이블 크기를 줄이는 방식이다.
인터럽트 우선순위
인터럽트 메커니즘은 인터럽트의 우선순위를 구현하며,
이를 통해 CPU는 낮은 우선순위의 인터럽트 처리를 연기할 수 있다.
또한, 높은 우선순위의 인터럽트가 낮은 우선순위의 인터럽트를 선점하는 것이 가능해진다.
1차 인터럽트 처리기(FLIH)와 2차 인터럽트 처리기(SLIH)
인터럽트 처리는 구현이 복잡하기 때문에 인터럽트 관리를 2단계로 나눈다.
FLIH는 인터럽트를 감지하고, 인터럽트의 원인을 식별하며, 관련 정보를 저장한다.
SLIH는 FLIH에 의해 준비가 된 작업을 실질적으로 처리한다.
인터럽트의 추가적인 사용 예시
운영체제는 인터럽트를 I/O 처리외에 다른 용도로도 사용하는데,
가상 메모리 페이징에서 페이지 폴트 시나, 시스템 콜 구현 등에서 사용된다.
DMA는 입출력 장치가 시스템 버스를 통해 메인 메모리에 직접 접근할 수 있는 기능을 의미한다.
cpu는 메모리의 DMA 블록에다 데이터 전송에 관한 데이터를 작성하고, (데이터의 위치, 전송할 위치, 전송할 바이트 수)
DMA에게 알린 뒤, 자기는 다른 일을 한다.
그러면 DMA는 DMA 블록을 보고 입출력을 수행한다.
전송이 완료되면 DMA 컨트롤러는 CPU에 인터럽트를 보내서 작업이 끝났음을 알린다.
DMA는 빠른 데이터 전송이 필요한 고성능 시스템에서 흔히 사용되는 기법이다.
장치들은 나날이 다양해져가고 각각 제어방법도 다를텐데
어떻게 운영체제의 수정없이 이들을 추가할 수 있을까?
이를 위한 입출력 인터페이스 구성법을 알아보자.
흔히 쓰는 해결방법을 여기서도 쓴다.
바로 추상화와 캡슐화, 계층화이다.
커널 -> 커널 I/O 서브 시스템 -> 장치 드라이버 -> 장치 컨트롤러 -> 장치로 접근한다.
장치드라이버는 I/O 하드웨어 간의 차이를 숨기고 간단한 인터페이스로 포장해서 상위 계층에 제공한다.
(SATA도 인터페이스다.)
이로 인해 새로운 장치가 나와도 운영체제에 큰 수정 없이 컴퓨터에 장착할 수 있게 된다.
하지만 운영체제마다 인터페이스에 대한 규격이 달라서
드라이버는 운영체제마다의 버전을 제공해야 하긴 한다.
입출력 장치들은 다양한 방면에서 다양하다.
장치는 이렇게 다양하지만 장치 범주는 매우 표준적이다.
범주들로는 블록 입출력, 문자 스트림 입출력, 메모리 맵드 파일 접근, 클록이나 타이머, 비디오와 오디오 장치 등이 있다.
운영체제는 각각 이에 대한 시스템 콜 집합을 제공한다.
또한 대부분의 운영체제는 응용 프로그램이 입출력 장치로 명령을 보낼 수 있게 하는 escape(또는 back-door) 시스템 콜을 갖고 있다.
예로 UNIX의 ioctl()
시스템 콜은 인자로 장치 식별자와, 명령, 포인터(메모리의 특정 데이터를 가리키는)를 넘겨
장치를 제어할 수 있게 해준다.
블록 장치는 블록 단위로 데이터를 저장하고 불러오는 장치로,
이에 대한 인터페이스는 read
와 write
, 또 다음에 전송할 위치를 지정하는 seek
명령을 제공한다.
네트워크 I/O는 디스크 I/O와 크게 다르므로,
대부분의 운영체제는 디스크에 사용되는 read()-write()-seek() 인터페이스와는 다른 네트워크 I/O 인터페이스를 제공합니다.
네트워크 소켓 인터페이스는
애플리케이션이 소켓을 생성하고,
다른 응용이 만든 소켓과 연결하고,
연결이 됐는지 확인 후,
패킷을 송수신할 수 있게 해준다.
컴퓨터는 현재 시간 제공, 경과 시간 측정, 특정 시간에 작업 X를 실행하도록
하드웨어 시계와 타이머를 갖추고 있다.
이러한 기능들은 운영 체제와 시간에 민감한 애플리케이션에서 많이 사용되지만,
이를 구현하는 시스템 콜은 운영 체제 간에 표준화되어 있지 않다.
운영 체제는 가상 시계를 통해 실제 타이머 하드웨어 채널 수를 초과하는 타이머 요청을 지원할 수 있다.
시스템 콜 인터페이스는 블로킹 I/O와 논블로킹 I/O 사이의 선택과 관련이 있다.
블로킹 시스템 호출이 발생하면 해당 스레드의 실행이 일시 중단되고,
논블로킹 I/O는 멀티스레드 애플리케이션을 작성하거나
논블로킹 시스템 콜을 제공하는 운영 체제를 통해 실행과 I/O를 같이 할수 있다.
비동기 시스템 호출은 I/O 완료를 기다리지 않고 즉시 반환하며,
I/O가 완료되면 스레드에게 알려진다.
벡터 I/O는 하나의 시스템 콜을 통해 여러 위치에서 여러 I/O 작업을 수행할 수 있도록 해주는 운영 체제 기능이다.
이는 컨텍스트 스위칭 및 시스템 호출 오버헤드를 피하며,
데이터의 효율적인 전송을 가능하게 하며,
일부 버전에서는 모든 I/O 작업이 중단 없이 이루어지도록 보장하는 원자성을 제공한다.
커널은 입출력과 관련된 많은 서비스를 제공한다.
각 서비스에 대해 알아보자.
각각의 장치마다 대기 큐를 유지함으로써 스케줄링 한다.
비동기적 입출력을 제공하려면 각 장치 상태 테이블에 대기 큐를 연동하여 제공한다.
이중 버퍼링을 통해 송신과 수신 사이의 속도 차를 완화한다.
또한 전송 크기가 큰 거를 나눠서 받고 결합할 때도 버퍼를 사용한다.
마지막으로 입출력 복제 시맨틱을 지원하기 위함이다.
이를 위해 write() 시스템콜 반환 직전에 응용 버퍼의 내용을 커널 버퍼로 복사한다.
복제 시맨틱이란?
복제 시맨틱스는 변수 간의 작업이 서로에게 영향을 미치지 않는 모델을 가리킨다.
이는 그들이 값 자체가 아닌 값의 복사본에 작용하기 때문이다.
커널이 파일 I/O 요청을 받으면 우선 버퍼 캐시를 확인한다.
spool은 프린터와 같이 인터리브(두 개의 일을 병행)하게 동작할 수 없는 장치를 위해 출력 데이터를 보관하는 버퍼이다.
운영체제는 응용의 출력을 각각 대응되는 보조저장장치 파일에 저장한다.
출력이 끝나면 그때까지 모아놓은 데이터를 출력 대기열에 삽입한다.
또다른 방법으로 VMS처럼 한 프로그램이 장치를 독점하게 하는 방법이 있다.
에러코드등으로 오류를 알리고 이를 처리한다.
입출력 명령은 시스템에 영향을 줄 수 있으므로 함부로 실행 못하게 특권 명령으로 등록해둔다.
하지만 그래픽 게임같이, 그래픽 성능을 위해 직접 접근해야 하는 경우, 잠금 기법을 제공하여 메모리의 일관성을 유지시켜야 한다.
안쓰는건 끄다시피 해서 전력 관리, 배터리 수명 증가.
구성 요소 토폴로지를 기기트리로 구축해 각각이 사용중인지 추적
로딩되길 기다리는 중이나 게임중 같은 경우는 wakelock을 걸어서 전원축소 못하게.
최신 컴퓨터는 하드웨어 코드 집합인 ACPI(고급 구성 및 전원 인터페이스)를 통해 관리
파일명을 파싱
마운트테이블에서 이름 매칭해서 장치드라이버와 장치컨트롤러의 포트주소 찾음