비동기 I/O와 APC
비동기 I/O
I/O와 시스템 클럭의 관계
CPU 클럭이 높아지면 높은 퍼포먼스를 보여준다.
여기서 얘기하는 시스템 클럭은 CPU 클럭과 I/O 클럭을 포함한다.
시스템 클럭이 I/O와 어떤 관계를 가지는지 알아보자.
두 개의 시스템이 존재한다. 시스템은 데이터를 가공해서 목적지로 보내야 한다.
A 시스템은 클럭이 100이고 B 시스템은 클럭이 200이다.
보통 클럭이 높으면 I/O가 빠르다고 생각하지만 반드시 그렇지는 않다.
보편적으로 시스템 클럭이 높으면 I/O도 빠르지만 그러지 않은 경우도 있다는 소리다.
보통 I/O를 한다면 그 대상이 파일이건 네트워크이건 반드시 버퍼링을 한다.
버퍼링을 한다는 것은 버퍼가 있다는 소리다. 버퍼를 둔 이유는 데이터를 한 번에 모아서 보내면 훨씬 더 빠른 시간에 많은 데이터를 보낼 수 있기 때문에 버퍼링을 한다.
A, B 시스템 둘다 버퍼가 있다고 가정한다.
버퍼를 비우는 정책은 정할 수 있는데 여기서는 10 클럭에 한 번씩 버퍼가 비워진다고 가정한다.
B는 초당 200번 클럭이 발생한다. A 보다 연산 횟수가 2배 크다.
그러면 B는 1초에 20번 버퍼가 비워진다. A는 1초에 10번 버퍼가 비워진다. A 시스템에서는 클럭이 100이다.
그래서 버퍼에 최소 a, b, c 정도의 데이터가 있어야 하나로 묶어서 목적지로 전송을 할 수 있다.
하지만 B는 클럭이 상당히 빨리 발생하기 때문에 버퍼를 비우는 속도가 빠르다.
그래서 버퍼에 a 데이터가 들어와도 비워버리고 b 데이터가 들어와도 비워버리고 c 데이터가 들어와도 비워버리는 상황이다. A 시스템은 버퍼에 있는 데이터를 묶어서 보냈기 때문에 A 시스템과 목적지 간에 통신 프로토콜을 한 사이클로만 가져가면 된다. 예를 들어 TCP/IP 통신으로 설명하면 A 시스템은 한 번만 3-way handshake가 발생하는 반면 B 시스템은 세번 3-way handshake가 발생한다.
정리하면 I/O 같은 경우 CPU가 100클럭인지 200 클럭인지 중요하지 않다.
네트워크 환경은 CPU에 비해서 훨씬 느린 환경이다.
그래서 B 시스템이 200클럭이 아니라 300 클럭으로 동작을 한다 해도 다른 외부 시스템과의 통신 프로토콜을 한번 더 사용하면 부담스러워진다. 네트워크 상에 연결된 두 개의 시스템이 데이터를 주고받는 데 걸리는 시간은 클럭이 높아도 줄일 수 없다. 그래서 위 예시에서는 B 시스템이 A 시스템보다 3배 가까운 느린 속도를 보이기도 한다.
I/O 연산이 묶이게 될 경우에는 CPU 클럭이 차지하는 영향은 상당히 작다.
버퍼를 비우는 정책은 중요하다.
비동기 I/O의 이해
비동기와 동기의 기준
동기 I/O가 왜 동기 I/O인지 얘기 해보자.
write라는 함수가 데이터를 보내는 함수라고 가정해보자.
write 함수가 호출되는 순간 데이터의 전송이 시작된다.
write 함수가 반환되는 순간(끝나는 순간)이 전송이 끝나는 타이밍이다.
그러면 함수의 호출과 데이터 전송 시작이 동기화가 되어있다.
함수의 반환과 전송의 끝이 동기화 되어있다. 이것이 바로 동기화된 I/O 다.
그럼 어떤것이 비동기냐면 위와 비교했을 때 함수의 호출과 데이터 전송 시작까지는 동기화되어 있지만 반환하는 시점이 데이터 전송의 끝을 의미하지 않는다. 즉 write 함수를 호출하자마자 바로 반환을 하고 내부적으로 전송을 시작해 나간다.
동기 I/O에서는 write 함수를 호출하면 데이터 전송이 끝날 때까지 반환을 안한다.
그러면 데이터 전송이 끝날때까지 다른 일을 할 수 없다는 소리다.
비동기 I/O에서는 write 함수를 호출하자마자 바로 반환이 이루어지고 나서 내부적으로는 데이터 전송이 계속 진행이 된다. 따라서 write 함수를 호출하고 반환이 이루어지니 또 다른 일을 할 수 있다는 소리다.
아무리 반환을 했다고 해도 다른 일을 할 수 있는가? 할 수 있다 왜냐면 I/O 연산은 CPU에 도움을 많이 받지 않는다. I/O는 I/O 자체적으로 독립된 연산이다. I/O를 처리해주는 모듈과 CPU는 그 역할이 나눠져 있다. 그렇기 때문에 CPU가 계속 도와줘야 I/O가 가능한 것은 아니다. CPU에 대한 의존도가 상당히 낮다. 즉 I/O가 진행 중임에도 불구하고 별도의 다른 연산이 가능하다.
- 동기 I/O는 CPU를 원활히 사용하지 못한다.
- I/O 연산이나 별도의 연산을 위해서 CPU가 블럭킹 상태가 된 것을 볼 수 있다.
- 비동기 I/O는 CPU가 계속 사용이 되고 I/O 연산을 할 수 있다.
- 즉 CPU가 일을 할 수 있는 적절한 양을 분배하면서 동시에 I/O 연산을 할 수 있다.
- 그렇다고 비동기 I/O가 반드시 좋은 프로그램 모델은 아니다.
비동기 I/O의 종류 1
중첩(Overlapped) I/O
중첩 I/O 구현모델
중첩 I/O를 할 때는 일반적인 I/O 함수를 호출하면 된다.
OVERLAPPED는 중첩 I/O를 하겠다는 걸 알리는 효과를 가진다.
중첩 I/O에 필요한 정보도 담을 수 있는 구조체다. 즉 중첩 I/O를 하겠다는 건 OVERLAPPED 구조체를 마련해야 한다.
Completion Routine I/O는 중첩 I/O를 확장한 거다. 그래서 Completion Routine I/O도 중첩 I/O라 할 수 있다.
따라서 Completion Routine I/O나 중첩 I/O나 둘 다 OVERLAPPED라는 구조체를 초기화를 해야 한다.
- hEvent
- I/O가 끝나면 끝났다는 알림을 받아야 하는데 hEvent를 통해 I/O가 끝났는지 확인할 수 있다.
OVERLAPPED구조체 초기화하는 방법은 전체를 다 초기화하고 EVENT 오브젝트를 생성을 해서 그것을 hEvent에 저장을 한다. 즉 OVERLAPPED 구조체의 5번째 멤버가 이벤트 handle을 가리킨다. 그다음 WriteFile 함수를 호출하는데 2가지가 필요하다. OVERLAPPED 구조체와 I/O를 누구와 할 것인지 대상이 되는 핸들 정보를 넘겨줘야 한다. 예제에서는 파이프의 핸들 정보를 넘겨주고 있다. 전달된 인자를 가지고 I/O를 하는데 비동기로 I/O가 진행이 되고 I/O가 끝나면 자동적으로 event가 signaled 상태로 바뀐다.
비동기 I/O의 종류 2
완료루틴(Completion Routine) 기반 확장 I/O
완료루틴 I/O 구현 모델
- 완료루틴 I/O는 OVERLAPPED I/O에 비교해 완료루틴이란 게 존재한다.
- I/O 연산이 끝나면 특정 루틴 함수가 호출되게 하겠다는 소리다.
- WriteFileEx가 위 내용에 대한 예시를 보여주고 있다.
- WriteFileEx는 CompletionRoutin 이란 것을 마련해서 인자로 전달하면 WriteFileEx 함수를 호출해주고 있다. 그럼 WriteFileEx는 I/O 연산과 CompletionRoutin을 연결시켜 줘서 I/O 연산이 끝나면 자동적으로 CompletionRoutin 함수가 호출이 된다. I/O 연산이 끝난다는 것은 CompletionRoutin이 자동적으로 호출되면 알 수 있다. 따라서 OVERLAPPED 구조체의 이벤트 핸들이 불필요하다. 하지만 OVERLAPPED 인자는 여전히 넘길 필요가 있다. 왜냐면 내부적으로 사용되기 때문이다.
- CompletionRoutin은 윈도우 시스템에 의해서 자동적으로 호출이 된다. 즉 호출의 대상이 내가 아니라 윈도우 시스템 콜백함수이다.
알림 가능한 상태
지금까지 연산을 끝내 났으니 지금부터는 CompletionRoutin에 호출돼도 괜찮다 CompletionRoutin의 우선순위를 높여주겠다는 게 알림 가능한 상태이다. 프로그램을 실행하다가 I/O 연산이 끝났을 때 CompletionRoutin이 실행이 되도록 우선순위를 높이겠다는 것을 명시적으로 선언해줘야 한다. 방법은 위와 같이 세 가지가 있다.
APC(비동기 함수 호출 메커니즘)
- 모든 스레드는 자신만의 APC Queue를 소유
- APC Queue는 각각의 스레드별로 독립적이다.
- 해당 스레드가 알람 가능한 상태가 되었을때 호출할 콜백 함수들을 모아놓은 큐다.
- 큐에 들어가 있는 정보들을 참조해서 해당 함수들을 호출한다. 언제 호출이 되냐면 스레드가 알람가능한 상태가 됐을때 APC Queue 안에 있는 함수들이 다 호출이 된다.
- 큐에 총 3개의 함수가 등록되어 있다고 해서 스레드가 알람 가능한 상태가 되도록 3번되는게 아니다. 스레드가 알람 가능한 상태가 한번 되면 큐에 있는 모든 함수들을 호출해서 큐가 비워진다.
- WriteFileEx() 함수 같은 경우에도 I/O가 완료가 될 경우에 APC Queue에다가 콜백 함수 정보를 입력한다. 그러면 큐에 대기하고 있다가 스레드가 알람 가능한 상태가 되면 큐에 있는 함수들이 호출된다. 이런 APC 메커니즘을 통해서 CompletionRoutin이 호출이 된다.
'운영체제 > 뇌를 자극하는 윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
뇌를 자극하는 윈도우즈 시스템 프로그래밍 20장 (0) | 2024.09.22 |
---|---|
뇌를 자극하는 윈도우즈 시스템 프로그래밍 18장 (0) | 2024.09.22 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 16장 (5) | 2024.09.22 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 15장 (0) | 2024.09.21 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 14장 (0) | 2024.09.21 |