이 코드는 어떤 문제를 푸나요?
여러 프로세스가 동시에 I/O를 내면 장치가 처리할 순서를 정해야 합니다. mq-deadline은 우선순위뿐 아니라 오래 기다린 요청도 고려합니다. dd_dispatch_request는 다음 요청 하나를 고르는 상위 흐름이며, 실제 읽기·쓰기 선택과 섹터 순서 비교는 하위 함수로 이어집니다.
읽을 범위: v6.18.37 · block/mq-deadline.c · dd_dispatch_request 453–487행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
발행과 완료
스케줄러가 요청을 선택하는 것은 장치로 보낼 후보를 정하는 일입니다. 실제 장치 완료는 더 뒤에 일어납니다.
기아 방지
높은 우선순위 작업만 계속 선택하면 낮은 우선순위 작업이 오래 기다릴 수 있습니다. 오래된 요청을 먼저 검사하는 절차는 이런 대기를 제한하려는 정책입니다.
jiffies
커널의 틱 기반 시간 값입니다. 여기서는 현재 시각의 비교 기준으로 한 번 읽으며, 나노초 단위 시간이 아닙니다.
처음 읽을 때
이 함수의 선택 순서를 특별 발행 목록, 오래 기다린 요청, 우선순위별 선택으로 나누어 보십시오. 단순히 디스크 위치가 가까운 것부터 고르는 알고리즘으로 이해하면 분기 이유를 놓치게 됩니다.
더 깊이 살펴볼 때
우선순위가 높은 쪽에 대기 요청이 남아 있으면 rq가 NULL이어도 낮은 우선순위로 내려가지 않는 조건을 보십시오. 지금 즉시 발행할 수 없음과 큐에 요청이 없음은 다른 상태입니다.
그림으로 보는 변화

1. 우선 처리 목록을 봅니다
dispatch 목록에 요청이 있으면 첫 항목을 꺼내 발행 상태를 준비합니다.
화살표는 목록에서 선택한 요청을 빼내는 동작입니다.
2. 오래 기다린 요청을 검사합니다
우선순위별 일반 선택에 앞서 대기 시간이 길어진 요청을 찾습니다.
화살표는 정책 검사 순서입니다. 시간 축의 길이는 실제 지연 시간을 표시하지 않습니다.
3. 우선순위에 따라 선택합니다
높은 우선순위부터 후보를 찾고 잠금을 풀어 request 또는 NULL을 반환합니다.
화살표는 선택 결과가 blk-mq 쪽으로 전달되는 방향입니다.
dd_dispatch_request를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx)
{
struct deadline_data *dd = hctx->queue->elevator->elevator_data;
const unsigned long now = jiffies;
struct request *rq;
enum dd_prio prio;
spin_lock(&dd->lock);
if (!list_empty(&dd->dispatch)) {
rq = list_first_entry(&dd->dispatch, struct request, queuelist);
list_del_init(&rq->queuelist);
dd_start_request(dd, rq_data_dir(rq), rq);
goto unlock;
}
rq = dd_dispatch_prio_aged_requests(dd, now);
if (rq)
goto unlock;
/*
* Next, dispatch requests in priority order. Ignore lower priority
* requests if any higher priority requests are pending.
*/
for (prio = 0; prio <= DD_PRIO_MAX; prio++) {
rq = __dd_dispatch_request(dd, &dd->per_prio[prio], now);
if (rq || dd_queued(dd, prio))
break;
}
unlock:
spin_unlock(&dd->lock);
return rq;
}static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx)해당 하드웨어 큐가 다음에 발행할 request 하나를 요청하는 진입점입니다.
struct deadline_data *dd = hctx->queue->elevator->elevator_data;하드웨어 큐에서 공통 큐와 elevator를 따라가 mq-deadline의 정책 상태를 얻습니다.
const unsigned long now = jiffies;이번 선택 동안 사용할 현재 틱 값을 한 번 읽습니다.
struct request *rq;최종 선택한 request를 담을 포인터입니다. 후보가 없으면 NULL이 될 수 있습니다.
enum dd_prio prio;우선순위 그룹을 순회할 변수를 선언합니다.
spin_lock(&dd->lock);공유하는 스케줄러 목록과 계수를 안전하게 다루도록 잠급니다.
if (!list_empty(&dd->dispatch)) {별도 dispatch 목록에 우선 내보낼 요청이 있는지 확인합니다.
rq = list_first_entry(&dd->dispatch, struct request, queuelist);연결 리스트의 첫 노드를 request 구조체로 복원해 가져옵니다. list_first_entry는 링크 자체와 이를 포함한 객체를 연결하는 매크로입니다.
list_del_init(&rq->queuelist);선택한 요청을 이 목록에서 빼고 그 링크를 빈 목록 상태로 초기화합니다.
dd_start_request(dd, rq_data_dir(rq), rq);선택한 요청의 읽기·쓰기 방향을 넘겨 스케줄러 측 발행 상태를 갱신합니다.
goto unlock;후보를 찾았으므로 공통 잠금 해제 경로로 갑니다.
rq = dd_dispatch_prio_aged_requests(dd, now);너무 오래 기다린 다른 우선순위의 요청이 있는지 먼저 살펴 기아를 완화합니다.
if (rq)이 검사에서 실제 후보를 얻었는지 확인합니다.
goto unlock;얻은 후보를 유지한 채 공통 반환 절차로 갑니다.
for (prio = 0; prio <= DD_PRIO_MAX; prio++) {우선순위 값 0부터 마지막 그룹까지 차례로 살펴봅니다. 숫자가 메모리 주소를 뜻하는 것은 아닙니다.
rq = __dd_dispatch_request(dd, &dd->per_prio[prio], now);해당 우선순위 내부의 읽기·쓰기와 순서 정책으로 발행할 후보를 선택합니다.
if (rq || dd_queued(dd, prio))후보를 찾았거나 해당 높은 우선순위에 아직 대기 작업이 있으면 아래 그룹으로 내려가지 않습니다.
break;우선순위 순회를 끝냅니다.
unlock:성공과 후보 없음 모두 잠금을 풀기 위해 모이는 위치입니다.
spin_unlock(&dd->lock);선택 중 보호한 스케줄러 상태의 잠금을 해제합니다.
return rq;선택한 request를 반환합니다. NULL이면 이 호출에서 발행할 후보가 없습니다.
함께 생각해 볼 질문
낮은 우선순위는 항상 마지막에만 실행되나요?
일반 우선순위 순회에 앞서 오래 기다린 요청을 검사하므로 무조건 그렇게 동작하지는 않습니다.
NULL을 반환하면 디스크가 고장 난 것인가요?
아닙니다. 이 호출에서 내보낼 요청을 선택하지 못했다는 뜻이며 오류 코드와 다릅니다.
왜 선택 과정 전체에 스핀락이 필요한가요?
요청 삽입과 다른 선택 작업이 공유 목록 및 상태를 동시에 바꾸지 못하게 해야 하기 때문입니다.
출처와 읽은 범위
Linux stable v6.18.37 · block/mq-deadline.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
