# bio: 저장장치 작업의 완료를 누가 받는가

v6.18.37 / block/bio.c

파일을 읽으려면 어느 장치의 어느 구간을 어느 메모리에 옮길지 정해야 합니다. bio는 이 정보를 묶어 아래 계층에 전달하는 객체입니다. 제출한 함수가 돌아왔다고 디스크 작업이 끝난 것은 아닙니다. 여기서는 bio_endio 전체를 따라가며 여러 조각의 완료가 합쳐지고 마지막에 원래 요청자에게 알려지는 과정을 설명합니다.

## bio_endio

```c

void bio_endio(struct bio *bio)
{
again:
	if (!bio_remaining_done(bio))
		return;
	if (!bio_integrity_endio(bio))
		return;

	blk_zone_bio_endio(bio);

	rq_qos_done_bio(bio);

	if (bio->bi_bdev && bio_flagged(bio, BIO_TRACE_COMPLETION)) {
		trace_block_bio_complete(bdev_get_queue(bio->bi_bdev), bio);
		bio_clear_flag(bio, BIO_TRACE_COMPLETION);
	}

	/*
	 * Need to have a real endio function for chained bios, otherwise
	 * various corner cases will break (like stacking block devices that
	 * save/restore bi_end_io) - however, we want to avoid unbounded
	 * recursion and blowing the stack. Tail call optimization would
	 * handle this, but compiling with frame pointers also disables
	 * gcc's sibling call optimization.
	 */
	if (bio->bi_end_io == bio_chain_endio) {
		bio = __bio_chain_endio(bio);
		goto again;
	}

#ifdef CONFIG_BLK_CGROUP
	/*
	 * Release cgroup info.  We shouldn't have to do this here, but quite
	 * a few callers of bio_init fail to call bio_uninit, so we cover up
	 * for that here at least for now.
	 */
	if (bio->bi_blkg) {
		blkg_put(bio->bi_blkg);
		bio->bi_blkg = NULL;
	}
#endif

	if (bio->bi_end_io)
		bio->bi_end_io(bio);
}

```

### 1629행

```c

void bio_endio(struct bio *bio)

```

완료할 bio 포인터를 받습니다. 반환값 없이 bio의 상태와 콜백을 통해 결과를 전달합니다.

### 1631행

```c

again:

```

연결된 부모 bio를 같은 함수 본문에서 다시 처리하기 위한 돌아올 위치입니다.

### 1632행

```c

	if (!bio_remaining_done(bio))

```

연결된 bio라면 남은 완료 계수를 하나 줄이고 0이 됐는지 검사합니다. 연결되지 않은 bio는 첫 완료에서 진행하며, 아직 조각이 남았다면 마지막 콜백을 미룹니다.

### 1633행

```c

		return;

```

기다릴 완료가 남았으므로 여기서 종료합니다.

### 1634행

```c

	if (!bio_integrity_endio(bio))

```

무결성 정보가 있는 bio의 완료 처리를 진행합니다. 이 함수가 아직 후속 처리가 필요하다고 알리면 아래 절차를 미룹니다.

### 1635행

```c

		return;

```

무결성 처리가 완료 통지를 이어 맡을 수 있으므로 여기서 돌아갑니다.

### 1637행

```c

	blk_zone_bio_endio(bio);

```

zoned 장치의 bio 완료에 필요한 상태를 갱신합니다. 해당 기능이 없는 요청에 다른 저장 방식을 강제하는 호출은 아닙니다.

### 1639행

```c

	rq_qos_done_bio(bio);

```

이 bio를 추적한 I/O 서비스 품질 제어 계층에 완료를 알립니다.

### 1641행

```c

	if (bio->bi_bdev && bio_flagged(bio, BIO_TRACE_COMPLETION)) {

```

실제 대상 장치가 있고 완료 추적 플래그도 켜졌을 때만 추적 기록을 남깁니다.

### 1642행

```c

		trace_block_bio_complete(bdev_get_queue(bio->bi_bdev), bio);

```

대상 장치의 큐와 bio를 사용해 block_bio_complete 추적 이벤트를 기록합니다.

### 1643행

```c

		bio_clear_flag(bio, BIO_TRACE_COMPLETION);

```

동일 bio의 완료 추적을 반복하지 않도록 플래그를 지웁니다.

### 1654행

```c

	if (bio->bi_end_io == bio_chain_endio) {

```

콜백이 일반 요청자의 함수인지, 부모로 완료를 전달하는 연결용 함수인지 구분합니다.

### 1655행

```c

		bio = __bio_chain_endio(bio);

```

자식의 오류를 필요하면 부모에 전달하고 자식 bio의 참조를 내려놓습니다. 이어 처리할 부모 bio를 받아 현재 대상에 넣습니다.

### 1656행

```c

		goto again;

```

C 함수를 재귀 호출하지 않고 부모에 대해 남은 완료 검사부터 반복합니다.

### 1659행

```c

#ifdef CONFIG_BLK_CGROUP

```

블록 I/O를 cgroup별로 계상·제어할 공통 기반입니다. 이 기능이 켜진 빌드에서는 완료한 bio가 잡고 있던 I/O 그룹 참조를 내려놓고 연결을 지워 그룹 객체가 불필요하게 유지되지 않게 합니다. 이 선택은 전처리 단계에서 이루어지며, CPU가 실행 중 이 줄의 조건을 검사하지 않습니다.

### 1665행

```c

	if (bio->bi_blkg) {

```

bio가 블록 cgroup 연결 객체를 참조하고 있는지 검사합니다.

### 1666행

```c

		blkg_put(bio->bi_blkg);

```

이제 필요 없는 cgroup 연결 객체의 참조를 내려놓습니다. 다른 참조가 있으면 객체는 계속 존재할 수 있습니다.

### 1667행

```c

		bio->bi_blkg = NULL;

```

이미 내려놓은 참조를 bio가 계속 가리키지 않도록 NULL로 바꿉니다.

### 1669행

```c

#endif

```

블록 cgroup 관련 조건부 코드는 여기서 끝납니다.

### 1671행

```c

	if (bio->bi_end_io)

```

완료 함수를 지정한 bio인지 확인합니다.

### 1672행

```c

		bio->bi_end_io(bio);

```

원래 요청자에게 완료를 알립니다. 이 호출 안에서 bio가 해제될 수도 있습니다.

