리눅스 - 블록 입출력 계층(Block I/O Layer)
블록 입출력(Block I/O) 계층
1. 개요
블록 장치는 고정된 크기의 데이터 덩어리를 임의 접근한다는 특징이 있는 하드웨어 장치이다. 고정된 크기의 데이터 덩어리를 블록이라고 부르는데 이 블록에 접근하기위한 장치인 것이다. 가장 대표적인 블록장치는 하드디스크가 있고, SSD 또한 이 블록 장치라고 할 수 있다. 그 외 플로피 드라이브나 플래시 메모리 등 많은 블록 장치가 있으며 이 모든 장치는 파일시스템을 통해 마운트하여 엑세스 할 수 있다.
커널에서 블록 장치를 관리하려면 많은 준비와 작업이 필요한데 이러한 블록 장치 관리를 위하여 전용 서브 시스템을 제공한다. 이 전용 서브 시스템을 운용하기 위한 부분이 커널에서 블록 입출력 계층이다.
2. 블록 장치 구조
블록 장치에서 접근 가능한 가장 작은 단위는 섹터이다. 여러가지 2의 거듭제곱값을 섹터로 사용하지만 전통적으로 사용하기에는 512 바이트를 사용한다. (HDD의 한 섹터가 512 바이트이기 때문, 물론 CD-ROM 같은 것들은 2KB 크기의 섹터를 사용하여 다른 크기인 경우도 있다 저장 장치에 대한 포스팅은 아래의 링크를 참고하면 좋다)
파일 시스템에서는 블록의 배수 단위로만 접근 할 수 있는데 장치에 접근 가능한 최소 단위가 섹터이므로 블록 크기는 섹터 크기보다 작을 수 없으며 섹터 크기의 배수가 된다. 또한 커널에서 블록의 크기도 2의 거듭제곱 형태가 되어야하며 페이지 크기보다 클 수 없다.
3. 버퍼와 버퍼헤드
블록을 읽고 난 후, 또는 블록 쓰기를 준비할 때처럼 블록이 메모리상에 존재할 때 블록은 버퍼에 저장된다. 각 버퍼는 하나의 블록에 대응되며 이 버퍼는 디스크상의 블록을 메모리상에 표현하는 객체 역할을 한다. 블록은 하나 이상의 섹터로 구성되며 페이지 크기를 넘지 않는다. 커널은 데이터와 함께 관련 제어 정보를 필요로 하기 때문에 각 버퍼에는 서술자가 붙어있다. 이 서술자를 버퍼헤드라고 하며 “include/linux/buffer_head.h” 파일에 정의된 buffer_head 구조체를 사용해 표현한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct buffer_head {
unsigned long b_state; /* 버퍼 상태 비트맵 */
struct buffer_head *b_this_page;/* 페이지의 버퍼들의 순환 리스트 */
union {
struct page *b_page; /* 이 버퍼 헤드가 매핑된 페이지 */
struct folio *b_folio; /* 이 버퍼 헤드가 매핑된 folio */
};
sector_t b_blocknr; /* 시작 블록 번호 */
size_t b_size; /* 매핑된 크기 */
char *b_data; /* 페이지 내 데이터에 대한 포인터 */
struct block_device *b_bdev; /* 블록 디바이스 */
bh_end_io_t *b_end_io; /* I/O 완료 처리 함수 */
void *b_private; /* b_end_io를 위한 예약 공간 */
struct list_head b_assoc_buffers; /* 다른 매핑과 연관된 버퍼들 */
struct address_space *b_assoc_map; /* 이 버퍼가 연관된 매핑 */
atomic_t b_count; /* 이 buffer_head를 사용하는 사용자 수 */
spinlock_t b_uptodate_lock; /* 페이지의 첫 번째 버퍼 헤드에서 사용됨.
* 페이지 내 다른 버퍼들의 I/O 완료를
* 직렬화하기 위해 사용 */
};
제일 위에 b_state는 파일의 가장 위에 있는 항목인 bh_state_bits로 정의되는데 내용은 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum bh_state_bits {
BH_Uptodate, /* 유효한 데이터를 포함함 */
BH_Dirty, /* 변경됨 */
BH_Lock, /* 버퍼에 입출력 작업 중이니까 동시 접근이 금지중이다 */
BH_Req, /* I/O 작업 중이다.*/
BH_Mapped, /* 디스크 매핑이 존재함 */
BH_New, /* 디스크 매핑이 get_block에 의해 새로 생성됨 */
BH_Async_Read, /* end_buffer_async_read I/O 중 */
BH_Async_Write, /* end_buffer_async_write I/O 중 */
BH_Delay, /* 버퍼가 아직 디스크에 할당되지 않음 */
BH_Boundary, /* 블록 뒤에 불연속성이 이어짐 */
BH_Write_EIO, /* 쓰기 중 I/O 오류 발생 */
BH_Unwritten, /* 버퍼가 디스크에 할당되었지만 기록되지 않음 */
BH_Quiet, /* 버퍼 오류 메시지를 출력하지 않음 */
BH_Meta, /* 버퍼가 메타데이터를 포함함 */
BH_Prio, /* 버퍼가 REQ_PRIO로 제출되어야 함 */
BH_Defer_Completion, /* AIO 완료를 workqueue로 지연 처리 */
BH_PrivateStart,/* 상태 비트가 아니며, 다른 엔터티에서
* 사적으로 할당할 수 있는 첫 번째 비트
*/
};
추가 업데이트 예정
4. 입출력 스케줄러
I/O 스케줄러는 blk-mq의 소프트웨어 큐와 하드웨어 큐사이에 위치하여, request의 제출 순서를 조정한다. 디바이스 유형에 따라 자동 선택 되며 6.6.7버전 커널에서의 입출력 스케줄러는 아래와 같다.
1) MQ deadline I/O scheduler
멀티큐(MQ) 환경에서 사용 가능한 데드라인 기반 FIFO 정렬 큐이다. 범용적이고 HDD에 적합하다. 읽기 쓰기에 대한 데드라인을 보장하며 Starvation을 방지하고 오버헤드가 낮다.
2) Kyber I/O scheduler
토큰 기반 지연 목표(latency target) 알고리즘을 사용하며 고속 SSD/NVMe 디바이스에 적합하다. 읽기/쓰기 대기시간을 목표로 자동 조절하며 오버헤드는 매우 낮은 특징이 있다.
3) BFQ(Budget Fair Queueing) I/O scheduler
이름 대로 예산 기반 공정 큐잉(Budget Fair Queueing) 알고리즘에 따라 스케줄링하며, 프로세스별 대역폭을 공정하게 분배해야할때나 대화형 작업이 우선시 될 때 사용하는 스케줄러이다. 예산을 기반으로 하기 때문에 아무래도 오버헤드가 큰편이며 데스크톱이나 느린 디바이스에 적합하다.
4) FIFO
FIFO라고 적어는 놨지만 사실 스케줄링이 없는것이다. 스케줄링이 없으니 오버헤드도 당연히 없다. 디바이스 자체 스케줄링에 위임하는 방식으로 NVMe나 가상 다바이스에 적합하다.
추가 업데이트 예정
참고문헌
- 리눅스 커널 심층분석 (에이콘 임베디드 시스템프로그래밍 시리즈 33, 로버트 러브 저자(글) · 황정동 번역)
- 리눅스 커널 6.6.7 버전
- 리눅스 커널 정리 /with MINZKN - Block I/O 서브시스템