본문 바로가기
【Fundamental Tech】/→ 🐧Kernel

블럭 디바이스(Block device) I/O - 2

반응형
* 출처: http://superkkt.com/373

저번 글에 이어서 이번엔 블럭디바이스에 I/O를 할 때 한번에 읽고 쓰는 크기에 대해서 다룬다.

실험용 코드는 저번 글에 사용한걸 그대로 사용하고 아래와 같이 약간 수정을 해준다. 맨 앞에 있는건 소스코드 라인 번호이다.

12   #define BUF_SIZE SECTOR_SIZE
...
...
41     if (pwrite(fd, buf, BUF_SIZE - 1, 0) == -1) {
...
45     if (pread(fd, buf, BUF_SIZE - 1, 0) == -1) {
...
79     memcpy(addr, buf, BUF_SIZE - 1);
80     memcpy(buf, addr, BUF_SIZE - 1);

저번 글에서는 버퍼크기를 4096바이트로 했으나 이번에는 섹터크기와 동일한 512바이트로 변경하였다. 그리고 O_DIRECT 플래그를 사용하는 경우는 반드시 I/O의 크기가 섹터크기의 배수가 되어야 하기 때문에 이전 코드를 그대로 사용하고, 나머지 O_SYNC와 mmap은 섹터크기에서 1을 뺀 511바이트 크기로 I/O를 수행해서 그 결과를 살펴본다.

마찬가지로 blktrace와 blkparse를 사용해서 모니터링을 하는데, 참고로 실험 코드를 컴파일 할 때 최적화를 하게되면 결과가 좀 이상하게 보이는 문제가 있으니 최적화 옵션을 끄고 컴파일을 하자.

아래는 blkparse의 결과물이며 보기 쉽도록 약간 편집을 하였다.

<O_SYNC>
0.000000000 R 63 1
0.007245225 W 63 1
0.007490545 R 64 7

<O_DIRECT>
3.910344027 W 63 1
3.910577464 R 63 1

<mmap>
7.110222011 R 63 128
7.110604020 R 191 128
32.879675707 W 63 8

mmap의 결과는 저번 글과 비교해서 달라진게 없다. 그런데 O_SYNC는 결과가 조금 달라졌다.

분명히 511바이트를 먼저 쓰기를 하고 그 다음에 511바이트 읽기를 수행했는데, 결과는 먼저 한 섹터(512바이트)를 읽고나서 한 섹터를 쓴다. 그 다음에 7개의 섹터를 읽어들인다. 이건 왜 그런걸까?

블럭 디바이스(Block device)는 캐릭터 디바이스(Character device)와 대비되는 개념인데 차이점은 I/O의 기본 단위이다. 블럭 디바이스는 섹터 크기를 기본단위(보통 512바이트)로 사용하고, 캐릭터 디바이스는 바이트 단위를 사용한다. 즉, 블럭 디바이스는 반드시 한 섹터 단위로 I/O를 해야만 한다.

그럼 섹터 크기와 다른 I/O 요청이 들어오면 어떻게 처리를 해야할까? 예를 들어 511바이트를 기록하라고 요청이 들어온다면?

이런 경우에도 블럭 디바이스는 반드시 512바이트(한 섹터가 512바이트라고 가정) 단위로 I/O를 해야하기 때문에 512바이트의 쓰기 연산을 수행해야 한다. 그런데 여기서 문제가 생긴다. I/O 요청에서는 분명히 511바이트만 쓰라고했고 기록할 데이터도 511바이트만 있을것이다. 그럼 디스크에 원래 저장되어 있는 마지막 1바이트는 어떻게 해야하나? 그냥 무턱대고 섹터 단위로 512바이트를 기록해버리면 디스크에 있던 마지막 1바이트가 날라가는 문제가 생긴다.

그래서 이런 문제가 생기지 않도록 디스크에서 먼저 해당 섹터를 읽은 후에 요청된 I/O의 데이터와 병합해서 다시 디스크에 기록을 하는 과정을 거쳐야 한다. 즉, 요청된 I/O의 길이가 블럭 디바이스의 기본 I/O 단위의 배수가 아닌 경우 반드시 먼저 해당 섹터를 읽어들이는 부가적인 처리 과정이 추가되어야 한다.

이런 이유에서 I/O 집중적인 어플리케이션에서는 블럭 디바이스 기본 I/O 단위의 배수로 I/O를 수행해야 더 좋은 성능을 얻을 수 있다. 보통 블럭 디바이스를 직접 제어하지 않고 파일시스템을 사용하기 때문에 엄밀히 말하자면 파일시스템의 페이지 크기로 I/O를 수행하는것이 좋다. 그러나 파일시스템의 페이지 크기도 결국엔 블럭 디바이스 기본 I/O 단위의 배수일테니 결국엔 같은 얘기다.

그리고 마지막 읽기 작업에서 요청된 크기보다 더 많은 섹터를 읽어들였는데 아마도 readahead 기능 때문인걸로 생각된다. 사용자가 요청한 블럭의 뒷부분도 미리 읽어서 캐쉬를 해두는 기능이다. 일반적인 경우 데이터에 순차적으로 접근하는 경우가 많기 때문에 readahead 기능은 I/O 성능을 높여주는 중요한 요소이다.

마지막으로 mmap은 같은 결과를 보여주었는데 이는 mmap의 동작원리상 쓰기를 하기 전에 항상 먼저 읽기 작업이 선행되어야 하기 때문이다.

다음 글에서는 한번에 읽고 쓰는 블럭의 크기에 따른 성능 차이에 대해서 다룬다.
반응형