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

파티션 검색: 디스크 구간을 장치로 등록하기까지

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

파티션 표는 디스크의 어떤 범위를 독립된 구간으로 다룰지 기록한 메타데이터입니다. 표를 읽었다고 각 파티션 장치가 저절로 생기지는 않습니다. blk_add_partitions는 읽기 결과를 검사한 뒤 파티션을 등록하며, 장치 용량 때문에 다시 읽어야 할 때와 실제 오류도 구분합니다.

읽을 범위: v6.6 · block/partitions/core.c · blk_add_partitions 585–647행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

gendisk와 파티션

gendisk는 디스크를 표현하고 파티션은 그 안의 구간을 나타냅니다. 파티션의 시작 섹터와 길이는 파일시스템의 파일 경로와 다른 층의 정보입니다.

오류 포인터

커널은 포인터 반환형에 ERR_PTR로 오류 번호를 담기도 합니다. IS_ERR로 검사하기 전에는 일반 구조체처럼 역참조해서는 안 됩니다.

EOD와 재시도

EOD는 현재 알려진 장치 끝입니다. 원래 용량을 다시 활성화할 수 있는 경우 -EAGAIN으로 더 큰 용량에서의 재시도를 요청합니다.

처음 읽을 때

파티션 표를 읽는 단계와 읽은 결과로 파티션 장치를 만드는 단계를 나누십시오. 예시로 시작 섹터 2048을 가정할 수 있지만, 이 코드가 모든 파티션을 그 위치에 만드는 것은 아닙니다.

더 깊이 살펴볼 때

NULL, 오류 포인터, 유효한 parsed_partitions의 세 반환 형태를 구분하고 각 경로의 해제 책임을 확인하십시오. 이미 상태 객체를 얻었다면 실패와 성공이 out_free_state에서 합쳐집니다.

그림으로 보는 변화

검색 생략과 파서 세 결과, 유효 상태의 zoned 제외와 용량 재시도, 항목 순회의 계속과 중단 분기를 구분한 도식
가로 화살표는 다음 검사로 진행하는 조건이고, 아래 화살표는 해당 단계에서 갈라지는 결과입니다. 파서가 유효한 상태 객체를 반환한 뒤에만 공통 해제 단계가 실행됩니다.
1단계: 검색 생략 조건이 참이면 파서를 호출하지 않고 0으로 종료

1단계 · 검색 조건

이전 GIF 원본 (분기 생략)

1. 검색 조건에서 빠져나갈 수 있습니다

GENHD_FL_NO_PART 또는 GD_SUPPRESS_PART_SCAN이 설정되면 check_partition()을 호출하지 않고 0을 반환합니다.

2. 파서의 세 결과를 구분합니다

NULL이면 0으로 끝납니다. 오류 포인터라면 -ENOSPC와 용량 해제 성공이 함께 있어야 -EAGAIN이고, 그렇지 않으면 -EIO입니다. 유효한 state만 다음 검사로 진행합니다.

3. 유효한 상태에서도 등록을 건너뛸 수 있습니다

queue->limits.zoned == BLK_ZONED_HM가 참이면 상태를 해제하고 0으로 끝납니다. access_beyond_eod에서 원래 용량을 활성화할 수 있으면 상태를 해제하고 -EAGAIN을 돌려줍니다.

4. 항목별 결과와 재시도 신호는 다릅니다

용량을 다시 열어야 해서 blk_add_partition()이 거짓을 반환하면 루프를 멈추고 상태를 해제한 뒤 -EAGAIN을 돌려줍니다. 용량을 열 수 없으면 시작점이 장치 끝 밖인 항목은 건너뛰고, 길이가 끝을 넘는 항목은 길이를 잘라 등록을 시도합니다. add_partition()이 -ENXIO가 아닌 오류를 돌리면 로그를 남기고 참을 반환해 다음 항목으로 진행합니다. -ENXIO는 이 조기 반환 분기를 건너뛰고 뒤의 RAID 검사 위치로 흐릅니다. 전체 순회가 끝나면 상태를 해제하고 0을 반환합니다. bdev_disk_changed()는 -EAGAIN을 받으면 rescan:으로 돌아갑니다. 열린 파티션이 있으면 -EBUSY로 끝나고, 그렇지 않으면 기존 항목을 정리한 뒤 용량이 있을 때 다시 검색합니다(660–687행).

blk_add_partitions를 한 줄씩 읽기

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

static int blk_add_partitions(struct gendisk *disk)
{
	struct parsed_partitions *state;
	int ret = -EAGAIN, p;

	if (disk->flags & GENHD_FL_NO_PART)
		return 0;

	if (test_bit(GD_SUPPRESS_PART_SCAN, &disk->state))
		return 0;

	state = check_partition(disk);
	if (!state)
		return 0;
	if (IS_ERR(state)) {
		/*
		 * I/O error reading the partition table.  If we tried to read
		 * beyond EOD, retry after unlocking the native capacity.
		 */
		if (PTR_ERR(state) == -ENOSPC) {
			printk(KERN_WARNING "%s: partition table beyond EOD, ",
			       disk->disk_name);
			if (disk_unlock_native_capacity(disk))
				return -EAGAIN;
		}
		return -EIO;
	}

	/*
	 * Partitions are not supported on host managed zoned block devices.
	 */
	if (disk->queue->limits.zoned == BLK_ZONED_HM) {
		pr_warn("%s: ignoring partition table on host managed zoned block device\n",
			disk->disk_name);
		ret = 0;
		goto out_free_state;
	}

	/*
	 * If we read beyond EOD, try unlocking native capacity even if the
	 * partition table was successfully read as we could be missing some
	 * partitions.
	 */
	if (state->access_beyond_eod) {
		printk(KERN_WARNING
		       "%s: partition table partially beyond EOD, ",
		       disk->disk_name);
		if (disk_unlock_native_capacity(disk))
			goto out_free_state;
	}

	/* tell userspace that the media / partition table may have changed */
	kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE);

	for (p = 1; p < state->limit; p++)
		if (!blk_add_partition(disk, state, p))
			goto out_free_state;

	ret = 0;
out_free_state:
	free_partitions(state);
	return ret;
}
static int blk_add_partitions(struct gendisk *disk)

파티션을 검색하고 등록할 디스크를 받으며, 결과를 정수 상태 코드로 반환합니다.

	struct parsed_partitions *state;

파티션 표를 해석한 임시 상태 객체를 가리킵니다.

	int ret = -EAGAIN, p;

초기 결과를 재시도 오류로 설정합니다. p는 등록할 파티션 번호입니다.

	if (disk->flags & GENHD_FL_NO_PART)

디스크에 파티션을 사용하지 않는 GENHD_FL_NO_PART 플래그가 있는지 확인합니다.

		return 0;

파티션을 사용하지 않는 디스크이므로 표 검색 없이 정상 반환합니다.

	if (test_bit(GD_SUPPRESS_PART_SCAN, &disk->state))

현재 디스크 상태에 파티션 검색을 억제하는 GD_SUPPRESS_PART_SCAN 비트가 설정됐는지 확인합니다.

		return 0;

검색을 억제한 상태이면 파티션을 추가하지 않고 정상 반환합니다.

	state = check_partition(disk);

지원하는 파티션 형식의 해석기로 디스크의 표를 읽습니다.

	if (!state)

해석할 파티션 정보가 없다는 NULL 결과인지 검사합니다.

		return 0;

표가 없다는 사실 자체를 I/O 오류로 만들지 않습니다.

	if (IS_ERR(state)) {

포인터처럼 보이는 반환값이 사실 오류 번호인지 확인합니다.

		if (PTR_ERR(state) == -ENOSPC) {

읽으려던 표가 알려진 장치 용량을 넘어갔다는 -ENOSPC인지 구분합니다.

			printk(KERN_WARNING "%s: partition table beyond EOD, ",

장치 끝을 넘는 파티션 표가 발견됐다는 경고 출력을 시작합니다.

			       disk->disk_name);

경고에 실제 디스크 이름을 넣습니다.

			if (disk_unlock_native_capacity(disk))

숨겨진 원래 장치 용량을 활성화할 수 있었는지 확인합니다.

				return -EAGAIN;

용량 조건이 바뀌었으므로 호출자에게 다시 검색하도록 -EAGAIN을 반환합니다.

		return -EIO;

이 경로에서 해결할 수 없는 파티션 표 읽기 실패는 -EIO로 알립니다.

	if (disk->queue->limits.zoned == BLK_ZONED_HM) {

큐의 zoned 종류가 host-managed인지 확인합니다. v6.6의 이 분기는 모든 zoned 종류를 무조건 배제하는 조건이 아닙니다.

		pr_warn("%s: ignoring partition table on host managed zoned block device\n",

이 장치의 파티션 표를 무시한다는 경고를 출력합니다.

			disk->disk_name);

경고 메시지의 대상 장치 이름을 전달합니다.

		ret = 0;

표를 무시하는 정책으로 처리했으므로 정상 결과를 선택합니다.

		goto out_free_state;

이미 할당한 파티션 해석 상태를 해제하는 위치로 갑니다.

	if (state->access_beyond_eod) {

표 해석이 끝났어도 장치 끝을 넘은 접근이 있었는지 확인합니다.

		printk(KERN_WARNING

부분적으로 장치 끝을 넘는 경우의 경고를 시작합니다.

		       "%s: partition table partially beyond EOD, ",

파티션 표 일부가 현재 용량 밖이었다는 문구입니다.

		       disk->disk_name);

어떤 디스크에서 발생했는지 이름을 출력합니다.

		if (disk_unlock_native_capacity(disk))

이 경우에도 원래 용량을 다시 사용할 수 있는지 시도합니다.

			goto out_free_state;

용량을 활성화했으면 초기값 -EAGAIN을 유지하며 임시 상태를 해제합니다.

	kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE);

매체나 파티션 표가 바뀌었을 수 있음을 사용자 공간에 KOBJ_CHANGE 이벤트로 알립니다.

	for (p = 1; p < state->limit; p++)

전체 디스크를 나타내는 0번을 제외하고 해석 가능한 파티션 항목을 순회합니다.

		if (!blk_add_partition(disk, state, p))

해당 파티션의 범위를 확인하고 등록을 시도합니다. 하위 함수가 원래 용량을 활성화해 재검색이 필요해지면 거짓을 반환합니다.

			goto out_free_state;

용량을 다시 확인해야 하므로 초기 결과 -EAGAIN을 유지한 채 임시 상태를 해제합니다.

	ret = 0;

모든 항목 처리를 마쳐 성공값 0을 설정합니다.

out_free_state:

유효한 해석 상태를 얻은 경로들이 모이는 정리 위치입니다.

	free_partitions(state);

임시 파티션 해석 결과를 해제합니다. 이미 등록한 장치 객체와는 구분됩니다.

	return ret;

성공 또는 재시도 상태를 호출자에게 전달합니다.

함께 생각해 볼 질문

표가 없으면 반드시 오류인가요?

이 함수는 해석 결과가 NULL이면 0을 반환합니다. 파티션 없는 장치를 사용할 수도 있기 때문입니다.

파티션 번호가 1부터인 이유는 무엇인가요?

이 등록 순회는 개별 파티션을 대상으로 하며 전체 디스크를 나타내는 part0과 구분합니다.

왜 EAGAIN과 EIO를 구분하나요?

용량을 다시 확인하면 성공할 수 있는 상황과 읽기 실패로 끝낼 상황을 호출자에게 다르게 알려야 합니다.

출처와 읽은 범위

Linux stable v6.6 · block/partitions/core.c

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

맨 위로 ↑