Linux v6.18.37 · 개념과 코드 읽기

요청 큐: 대기 중인 작업이 장치에서 진행 중인 작업이 될 때

이 코드는 어떤 문제를 푸나요?

파일시스템이 만든 bio는 장치가 처리할 request로 묶일 수 있습니다. request_queue는 그 장치의 요청 처리 규칙을 제공하며 blk-mq는 소프트웨어 쪽 요청을 하드웨어 큐로 연결합니다. 여기서는 드라이버가 요청을 시작할 때 부르는 blk_mq_start_request를 통해 태그, 시간 제한, 진행 상태가 왜 필요한지 살펴봅니다.

읽을 범위: v6.18.37 · block/blk-mq.c · blk_mq_start_request 1351–1376행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

request

장치에 전달하고 완료를 추적하는 블록 계층의 작업 단위입니다. 하나의 request가 여러 bio를 포함할 수 있으므로 bio와 일대일이라고 단정할 수 없습니다.

태그

진행 중인 요청을 찾는 데 사용하는 정수 식별자입니다. 완료 통지가 태그로 돌아오면 이를 request 포인터로 연결합니다. 태그는 디스크의 섹터 번호가 아닙니다.

시간 제한

장치가 영원히 응답하지 않는 상황에 대비해 요청 시작 시 타이머를 준비합니다. 제한을 넘겼다는 사실과 최종 복구 정책은 별개입니다.

처음 읽을 때

대기열에 들어간 시점과 실제 요청을 시작한 시점을 구분하십시오. 이 함수는 두 번째 시점의 상태와 계측을 준비하며, 데이터가 이미 저장됐다는 뜻은 아닙니다.

더 깊이 살펴볼 때

WRITE_ONCE는 해당 상태 접근을 한 번의 명시적 접근으로 유지하게 합니다. 이것만으로 모든 필드의 메모리 순서를 보장한다고 해석하지 말고 태그 수명, 큐 동기화, 완료 경로를 함께 확인해야 합니다.

그림으로 보는 변화

요청 큐: 대기 중인 작업이 장치에서 진행 중인 작업이 될 때의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 요청이 준비됩니다

request는 bio와 장치 큐를 참조하며 아직 완료되지 않은 작업을 나타냅니다.

화살표는 bio가 request로 묶여 전달되는 관계입니다.

2. 시작을 기록합니다

시작 시각과 시간 제한을 준비하고 상태를 IN_FLIGHT로 바꿉니다. 태그 표에 request를 연결합니다.

화살표는 대기 상태에서 진행 상태로 바뀌는 순서입니다.

3. 완료할 요청을 찾습니다

장치 완료 경로는 태그 등의 정보로 원래 request를 찾아 완료를 이어갑니다.

화살표는 식별자로 객체를 찾는 방향입니다. 이 마지막 단계는 별도의 완료 경로에서 실행됩니다.

blk_mq_start_request를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

void blk_mq_start_request(struct request *rq)
{
	struct request_queue *q = rq->q;

	trace_block_rq_issue(rq);

	if (test_bit(QUEUE_FLAG_STATS, &q->queue_flags) &&
	    !blk_rq_is_passthrough(rq)) {
		rq->io_start_time_ns = blk_time_get_ns();
		rq->stats_sectors = blk_rq_sectors(rq);
		rq->rq_flags |= RQF_STATS;
		rq_qos_issue(q, rq);
	}

	WARN_ON_ONCE(blk_mq_rq_state(rq) != MQ_RQ_IDLE);

	blk_add_timer(rq);
	WRITE_ONCE(rq->state, MQ_RQ_IN_FLIGHT);
	rq->mq_hctx->tags->rqs[rq->tag] = rq;

	if (blk_integrity_rq(rq) && req_op(rq) == REQ_OP_WRITE)
		blk_integrity_prepare(rq);

	if (rq->bio && rq->bio->bi_opf & REQ_POLLED)
	        WRITE_ONCE(rq->bio->bi_cookie, rq->mq_hctx->queue_num);
}
void blk_mq_start_request(struct request *rq)

시작할 request를 받습니다. 드라이버가 장치 처리를 시작하는 경계에서 호출합니다.

	struct request_queue *q = rq->q;

요청이 소속된 request_queue를 지역변수로 꺼내 통계와 서비스 품질 처리를 연결합니다.

	trace_block_rq_issue(rq);

요청 발행 추적 이벤트를 남깁니다. 이 기록은 완료 이벤트와 구분됩니다.

	if (test_bit(QUEUE_FLAG_STATS, &q->queue_flags) &&

이 큐가 통계를 수집하도록 설정되어 있는지 검사합니다.

	    !blk_rq_is_passthrough(rq)) {

일반 데이터 I/O 통계를 적용하기 어려운 통과 명령은 제외합니다. 두 조건을 모두 만족해야 본문을 실행합니다.

		rq->io_start_time_ns = blk_time_get_ns();

I/O 시작 시각을 나노초 단위로 기록해 이후 지연 시간을 계산할 기준을 만듭니다.

		rq->stats_sectors = blk_rq_sectors(rq);

통계에 사용할 요청 크기를 섹터 수로 저장합니다.

		rq->rq_flags |= RQF_STATS;

이 요청에 통계 자료가 준비됐다는 RQF_STATS 비트를 켭니다.

		rq_qos_issue(q, rq);

큐의 서비스 품질 제어 계층에 요청이 발행됐음을 알립니다.

	WARN_ON_ONCE(blk_mq_rq_state(rq) != MQ_RQ_IDLE);

시작 전에 IDLE이어야 한다는 상태 규칙이 깨졌으면 한 번 경고합니다. 경고가 요청을 자동으로 취소하는 것은 아닙니다.

	blk_add_timer(rq);

요청의 시간 제한을 감시할 타이머를 준비합니다.

	WRITE_ONCE(rq->state, MQ_RQ_IN_FLIGHT);

상태를 MQ_RQ_IN_FLIGHT로 기록합니다. WRITE_ONCE는 이 필드 접근이 임의로 합쳐지거나 나뉘는 것을 제한합니다.

	rq->mq_hctx->tags->rqs[rq->tag] = rq;

하드웨어 큐의 태그 표에서 rq->tag 항목이 이 request를 가리키게 합니다.

	if (blk_integrity_rq(rq) && req_op(rq) == REQ_OP_WRITE)

무결성 보호 대상이면서 쓰기 요청인지 함께 검사합니다.

		blk_integrity_prepare(rq);

쓰기에 필요한 무결성 처리를 준비합니다. 일반 읽기에는 이 분기가 적용되지 않습니다.

	if (rq->bio && rq->bio->bi_opf & REQ_POLLED)

연결된 bio가 있으며 REQ_POLLED가 켜진 요청인지 확인합니다.

	        WRITE_ONCE(rq->bio->bi_cookie, rq->mq_hctx->queue_num);

폴링할 하드웨어 큐 번호를 bio의 cookie에 기록합니다. cookie는 저장장치 주소가 아닙니다.

함께 생각해 볼 질문

태그 7은 디스크의 7번 섹터인가요?

아닙니다. 예시 태그 7은 진행 중인 요청을 식별하는 번호이고 저장 위치는 요청의 섹터 정보로 나타냅니다.

IN_FLIGHT면 I/O가 성공했나요?

아직 진행 중이라는 상태입니다. 성공 또는 실패는 완료 시 결정됩니다.

폴링 요청의 cookie에 왜 큐 번호를 쓰나요?

어느 하드웨어 큐를 살펴보아야 해당 요청의 완료를 찾을 수 있는지 알려주기 위해서입니다.

출처와 읽은 범위

Linux stable v6.18.37 · block/blk-mq.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑