dm-integrity — 블록 레벨 무결성 보호

dm-integrity는 Device Mapper 프레임워크 위에서 읽기/쓰기 모두 지원하는 블록 레벨 무결성 보호를 제공합니다. 저널링(Journaling) 기반 무결성 태그로 crash-safety를 보장하며, Bitmap 모드로 대용량 디스크에서 성능을 최적화하고, dm-crypt와 AEAD 결합으로 암호화+무결성을 단일 레이어에서 달성합니다. 커널 내부 구현(drivers/md/dm-integrity.c)을 심층 분석합니다.

관련 Device Mapper 문서:
  • dm-verity — Merkle Tree 기반 읽기 전용 블록 검증
  • dm-crypt — 블록 레벨 투명 암호화, LUKS, 성능 최적화
  • Device Mapper / LVM — DM 프레임워크 기본
전제 조건: 블록 I/O 문서와 Device Mapper / LVM 문서를 먼저 읽으세요. 블록 디바이스의 기본 I/O 경로와 Device Mapper의 target 개념을 이해해야 합니다.
일상 비유: dm-integrity는 은행 금고의 감사 로그와 비슷합니다. 금고에 물건을 넣거나 뺄 때마다 기록(무결성 태그)을 남기고, 기록이 변조되지 않았는지 실시간으로 검증합니다. 저널링은 정전이 발생해도 기록의 일관성을 보장하는 안전 장치입니다.

핵심 요약

  • Device Mapper — 블록 디바이스 위에 가상 매핑(Mapping) 레이어를 제공하는 커널 프레임워크
  • dm-integrity — 읽기/쓰기 모두 지원하는 무결성 태그 + 저널링
  • Journal 모드 — 데이터+태그를 저널에 먼저 기록하여 crash-safety 완전 보장
  • Bitmap 모드 — dirty 블록 추적으로 성능 향상, crash 후 부분 재검증
  • AEAD 결합 — dm-crypt + dm-integrity를 결합한 인증 암호화 (AES-GCM)

단계별 이해

  1. Device Mapper 레이어 이해
    커널의 블록 I/O 스택에서 DM은 bio를 가로채 target 드라이버에 전달합니다.
  2. dm-integrity로 실시간 무결성
    매 쓰기마다 무결성 태그를 계산하고 저널에 기록하여 crash-safety를 보장합니다.
  3. 저널링과 crash-safety
    저널은 순환 버퍼로 데이터+태그를 원자적으로 기록하며, 정전 후에도 일관성을 유지합니다.
  4. AEAD 모드로 암호화 결합
    dm-crypt + dm-integrity AEAD 모드로 암호화와 무결성을 동시에 달성합니다.

Device Mapper 개요

Device Mapper(DM)는 Linux 커널의 블록 레이어에서 가상 블록 디바이스를 생성하는 프레임워크입니다. drivers/md/dm.c에 구현된 DM 코어는 struct mapped_device로 가상 디바이스를 표현하고, struct dm_table로 매핑 테이블을 관리합니다. 각 target 드라이버(struct target_type)는 특정 기능(암호화, 검증, 스냅샷 등)을 구현하며, DM 코어가 bio를 적절한 target에 라우팅(Routing)합니다.

DM은 원래 Heinz Mauelshagen이 LVM(Logical Volume Manager)을 위해 개발했으며, 현재는 dm-crypt, dm-verity, dm-integrity, dm-thin, dm-cache, dm-snapshot 등 다양한 target을 통해 리눅스 스토리지 스택의 핵심 계층으로 자리잡았습니다. 사용자 공간(User Space)에서는 dmsetup, cryptsetup, veritysetup, integritysetup 등의 도구로 DM 디바이스를 관리합니다.

사용자 공간: dmsetup / cryptsetup / veritysetup / integritysetup ioctl(DM_TABLE_LOAD) DM Core (dm.c) mapped_device / dm_table / dm_target dm-verity Merkle Tree 검증 dm-integrity 저널링 무결성 dm-crypt 블록 암호화 블록 레이어 (bio / request) 물리 블록 디바이스 (HDD / SSD / NVMe)

DM 코어의 핵심 자료 구조를 살펴봅니다:

/* include/linux/device-mapper.h */
struct target_type {
    uint64_t features;
    const char *name;
    struct module *module;

    int (*ctr)(struct dm_target *ti, unsigned argc, char **argv);
    void (*dtr)(struct dm_target *ti);
    int (*map)(struct dm_target *ti, struct bio *bio);
    int (*end_io)(struct dm_target *ti, struct bio *bio,
                  blk_status_t *error);
    void (*status)(struct dm_target *ti, status_type_t type,
                   unsigned status_flags, char *result,
                   unsigned maxlen);
    int (*iterate_devices)(struct dm_target *ti,
                          iterate_devices_callout_fn fn, void *data);
};

struct dm_target {
    struct dm_table *table;
    struct target_type *type;
    sector_t begin;          /* 매핑 시작 섹터 */
    sector_t len;            /* 매핑 길이 (섹터) */
    void *private;           /* target 별 사적 데이터 */
    unsigned num_discard_bios;
    unsigned num_secure_erase_bios;
    unsigned num_write_zeroes_bios;
};

DM의 I/O 경로는 다음과 같이 진행됩니다: 사용자 공간에서 가상 디바이스(/dev/dm-N)에 I/O를 발행하면, dm_submit_bio()가 호출됩니다. 이 함수는 dm_table에서 bio가 속하는 target을 찾아 해당 target의 map() 콜백(Callback)을 호출합니다. target은 bio를 변환(remap, split, encrypt 등)하여 하위 디바이스로 전달합니다. map() 콜백의 반환값에 따라 DM 코어가 bio의 후속 처리를 결정합니다: DM_MAPIO_REMAPPED는 bio가 하위 디바이스로 redirect되었음을, DM_MAPIO_SUBMITTED는 target이 bio를 완전히 처리했음을 의미합니다.

DM 구성 요소역할소스 파일
mapped_device가상 블록 디바이스 표현drivers/md/dm.c
dm_tabletarget 매핑 테이블drivers/md/dm-table.c
dm_target개별 매핑 세그먼트drivers/md/dm.h
dm_ioI/O 요청 추적drivers/md/dm.c
dm_ioctl사용자 공간 인터페이스drivers/md/dm-ioctl.c
dm_bufio해시/메타데이터 블록 캐시(Cache)drivers/md/dm-bufio.c
DM 테이블 형식: dmsetup으로 전달하는 테이블은 시작섹터 길이 target_name [target_args...] 형식입니다. 예: 0 2097152 integrity /dev/sdb1 0 32 J sha256
/* drivers/md/dm-ioctl.c - 주요 ioctl 명령 */
/* 사용자 공간 (dmsetup) <-> 커널 DM 인터페이스 */

/* DM_DEV_CREATE: 새 mapped_device 생성 */
/* DM_DEV_REMOVE: mapped_device 제거 */
/* DM_TABLE_LOAD: 매핑 테이블 로드 */
/* DM_DEV_SUSPEND: I/O 일시 중지 (테이블 교체 준비) */
/* DM_DEV_STATUS: 디바이스 상태 조회 */
/* DM_TABLE_STATUS: 테이블 상태 (dmsetup status) */
/* DM_TARGET_MSG: target에 메시지 전달 (dmsetup message) */

static int table_load(struct file *filp,
                      struct dm_ioctl *param,
                      size_t param_size)
{
    struct dm_table *t;
    struct mapped_device *md;

    /* 1. 테이블 생성 */
    r = dm_table_create(&t, get_mode(param), param->target_count, md);

    /* 2. 각 target 파싱 및 추가 */
    for (i = 0; i < param->target_count; i++) {
        dm_table_add_target(t, type_name, start, len, params);
        /* 내부에서 target_type->ctr() 호출 */
    }

    /* 3. 테이블 완료 (무결성 검증) */
    r = dm_table_complete(t);

    /* 4. 테이블을 mapped_device에 연결 */
    dm_swap_table(md, t);
    return 0;
}

/* bio 라우팅 - dm_submit_bio() 핵심 경로 */
static void dm_submit_bio(struct bio *bio)
{
    struct mapped_device *md = bio->bi_bdev->bd_disk->private_data;
    struct dm_table *map;
    struct dm_target *ti;

    map = dm_get_live_table(md, &srcu_idx);

    /* bio가 속하는 target 찾기 */
    ti = dm_table_find_target(map, bio->bi_iter.bi_sector);

    /* target의 map() 콜백 호출 */
    r = ti->type->map(ti, bio);

    switch (r) {
    case DM_MAPIO_SUBMITTED:
        break;  /* target이 처리 완료 */
    case DM_MAPIO_REMAPPED:
        submit_bio_noacct(bio);  /* 하위 디바이스로 전달 */
        break;
    case DM_MAPIO_REQUEUE:
        bio_io_error(bio);  /* 재시도 */
        break;
    }

    dm_put_live_table(md, srcu_idx);
}
DM 스택 쌓기: DM 디바이스 위에 다른 DM 디바이스를 쌓을 수 있습니다. 예: 물리 디바이스 -> dm-integrity -> dm-crypt -> ext4 이 경우 각 레이어의 map()이 순서대로 호출됩니다. 스택 깊이가 깊을수록 I/O 지연(Latency)이 증가하므로, AEAD 모드처럼 단일 레이어에서 암호화+무결성을 처리하는 것이 성능상 유리합니다.

dm-integrity 개요

dm-integrity는 읽기/쓰기 모두 지원하는 블록 레벨 무결성 보호를 제공합니다. dm-verity와 달리 쓰기 시에도 무결성 태그를 자동으로 계산하여 저장하며, 저널링을 통해 crash-safety를 보장합니다. 커널 소스 drivers/md/dm-integrity.c에 구현되어 있으며, Journal, Bitmap, Direct(no-journal) 세 가지 모드를 제공합니다.

Superblock magic, version Journal commit 단위 기록 Data 0 4096B Tag0 32B Data 1 Tag1 ... Data N TagN dm-integrity 동작 모드 Journal 모드 (기본): 데이터+태그를 저널에 먼저 기록 후 실제 위치로 commit Bitmap 모드: 비트맵으로 dirty 블록 추적, crash 후 dirty 블록만 재검증 No-journal 모드: 저널 없이 직접 기록 (dm-crypt AEAD 모드 전용)
/* drivers/md/dm-integrity.c */
struct dm_integrity_c {
    struct dm_dev *dev;
    struct dm_dev *meta_dev;       /* 별도 메타 디바이스 (선택) */
    unsigned int tag_size;          /* 태그 크기 (바이트) */
    unsigned int sectors_per_block; /* 블록당 섹터 수 */
    __u64 provided_data_sectors;    /* 사용 가능 데이터 섹터 */
    unsigned int journal_entries;
    unsigned int journal_sections;
    struct journal_entry *journal;
    struct crypto_shash *internal_hash;
    struct crypto_shash *journal_mac;
    enum integrity_mode mode;  /* J, B, D (journal/bitmap/direct) */
    bool recalculate;
};
모드저널Crash Safety성능주요 용도
Journal (J)전체 저널완전 보장쓰기 2x 오버헤드단독 사용
Bitmap (B)비트맵만dirty 블록 재검증저널보다 빠름대용량 디스크
Direct (D)없음비보장최소 오버헤드AEAD 모드 전용

저널링 메커니즘

dm-integrity의 저널은 데이터와 무결성 태그를 원자적(Atomic)으로 기록하여 crash-safety를 보장합니다. 저널은 순환 버퍼(circular buffer)로 구현되며, commit 단위로 데이터+태그 쌍을 기록합니다. Bitmap 모드에서는 저널 대신 비트맵으로 dirty 블록을 추적하며, crash 후 복구 시 dirty 비트가 설정된 블록만 재검증합니다.

/* drivers/md/dm-integrity.c - 저널 엔트리 구조 */
struct journal_entry {
    union {
        struct {
            __le64 sector;       /* 대상 섹터 번호 */
            __le32 data_csum;    /* 데이터 체크섬 */
            __le32 tag_csum;     /* 태그 체크섬 */
        } s;
        __le64 commit_id;         /* 커밋 ID */
    } u;
};
저널 크기 계산: 기본 저널 크기는 약 64MB이며, --journal-watermark 옵션으로 저널 flush 임계값을 설정할 수 있습니다. 저널이 가득 차면 쓰기가 차단되므로, 워크로드에 맞는 적절한 크기 설정이 중요합니다.

dm-integrity 설정

dm-integrity는 integritysetup 도구로 설정합니다. cryptsetup 패키지에 포함되어 있으며, 포맷 후 활성화하는 2단계로 진행합니다.

# 1. dm-integrity 포맷 (SHA-256 해시, 저널 모드)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --tag-size 32 \
    --sector-size 4096 \
    --journal-size 64M

# 2. dm-integrity 디바이스 활성화
$ integritysetup open /dev/sdb1 integrity_disk \
    --integrity sha256

# 3. 파일시스템 생성 및 마운트
$ mkfs.ext4 /dev/mapper/integrity_disk
$ mount /dev/mapper/integrity_disk /mnt/secure

# 4. Bitmap 모드로 설정 (성능 우선)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --integrity-bitmap-mode \
    --bitmap-sectors-per-bit 65536

$ integritysetup open /dev/sdb1 integrity_bitmap \
    --integrity sha256 \
    --integrity-bitmap-mode
옵션설명기본값
--integrity해시 알고리즘sha256
--tag-size무결성 태그 크기 (바이트)알고리즘 출력 크기
--sector-size섹터 크기512
--journal-size저널 크기자동 계산
--journal-watermark저널 flush 임계값 (%)50%
--journal-commit-time저널 commit 주기 (ms)10000
--integrity-bitmap-mode비트맵 모드 사용비활성

dm-integrity 커널 내부 구현

dm-integrity의 커널 구현은 drivers/md/dm-integrity.c에 있으며, 약 4,500줄에 달하는 복잡한 코드입니다. 핵심 동작은 bio를 분할하여 데이터와 무결성 태그를 동시에 처리하는 것입니다.

/* drivers/md/dm-integrity.c - map 함수 */
static int integrity_map(struct dm_target *ti, struct bio *bio)
{
    struct dm_integrity_c *ic = ti->private;
    struct dm_integrity_io *dio;

    dio = dm_per_bio_data(bio, ti->per_io_data_size);
    dio->ic = ic;
    dio->bi_sector = bio->bi_iter.bi_sector;
    dio->bi_integrity = bio_integrity(bio);

    if (bio_data_dir(bio) == WRITE) {
        if (ic->mode == 'J') {
            /* Journal 모드: 저널에 먼저 기록 */
            integrity_journal_write(ic, dio);
        } else if (ic->mode == 'B') {
            /* Bitmap 모드: dirty 비트 설정 */
            integrity_bitmap_write(ic, dio);
        }
        /* 무결성 태그 계산 및 저장 */
        integrity_metadata_write(ic, dio);
    } else {
        /* READ: 데이터 읽기 후 태그 검증 */
        integrity_metadata_read(ic, dio);
    }

    /* bio를 데이터 디바이스로 remap */
    bio_set_dev(bio, ic->dev->bdev);
    bio->bi_iter.bi_sector = integrity_data_sector(ic, dio->bi_sector);

    return DM_MAPIO_REMAPPED;
}

/* 무결성 태그 검증 (읽기 시) */
static int integrity_check_tag(struct dm_integrity_c *ic,
                               u8 *data, u8 *stored_tag,
                               sector_t sector)
{
    u8 computed_tag[MAX_TAG_SIZE];
    int r;

    /* 해시 계산 */
    r = crypto_shash_digest(ic->internal_hash_desc,
                            data, ic->sectors_per_block << SECTOR_SHIFT,
                            computed_tag);
    if (r)
        return r;

    /* 저장된 태그와 비교 */
    if (memcmp(computed_tag, stored_tag, ic->tag_size)) {
        DMERR("integrity checksum failed at sector %llu",
              (unsigned long long)sector);
        return -EILSEQ;
    }
    return 0;
}

dm-integrity의 I/O 경로에서 주의할 점은 메타데이터 I/O입니다. 데이터와 태그가 디스크 상에서 인터리빙되어 있기 때문에, 연속적인 데이터 I/O가 비연속적인 디스크 I/O로 변환될 수 있습니다. 이로 인해 HDD에서는 성능 저하가 심각할 수 있으며, SSD에서는 상대적으로 영향이 적습니다. 별도의 메타 디바이스(--data-device)를 사용하면 이 문제를 완화할 수 있습니다.

dm-integrity의 내부 동작을 더 깊이 살펴보면, 데이터 섹터와 태그 영역의 매핑이 핵심입니다. 논리 섹터 번호를 물리 섹터 번호로 변환할 때, 태그 영역의 크기를 고려하여 오프셋(Offset)을 계산합니다. 이 변환은 get_data_sector() 함수에서 수행됩니다.

/* drivers/md/dm-integrity.c - 섹터 변환 */
static sector_t get_data_sector(struct dm_integrity_c *ic,
                                 area_t area, sector_t offset)
{
    sector_t result;

    /* 데이터 영역과 태그 영역이 인터리빙됨
     * area = 논리 섹터 / interleave_sectors
     * offset = 논리 섹터 % interleave_sectors */
    result = area * (ic->interleave_sectors + ic->tag_area_sectors);
    result += ic->initial_sectors;  /* 슈퍼블록 + 저널 공간 */
    result += offset;

    return result;
}

/* 태그 읽기/쓰기 - 메타데이터 I/O */
static void rw_tag(struct dm_integrity_c *ic,
                   unsigned int tag_offset,
                   u8 *tag, unsigned int tag_size,
                   int op)
{
    sector_t tag_sector;
    unsigned int tag_sector_offset;

    /* 태그의 디스크 위치 계산 */
    tag_sector = tag_offset / (ic->tag_size * ic->journal_section_entries);
    tag_sector_offset = tag_offset % (ic->tag_size * ic->journal_section_entries);

    if (op == TAG_READ)
        dm_bufio_read(ic->tag_bufio, tag_sector, ...);
    else
        dm_bufio_write_dirty(ic->tag_bufio, tag_sector, ...);
}
인터리빙 오버헤드: 데이터와 태그가 인터리빙되면 연속 읽기가 비연속 디스크 I/O로 변환됩니다. HDD에서는 이 패턴이 심각한 성능 저하를 유발합니다. SSD에서도 약 5-15%의 오버헤드가 발생합니다. 별도 메타 디바이스(--data-device)를 사용하면 데이터와 태그를 물리적으로 분리하여 이 문제를 완화할 수 있습니다.
# 별도 메타 디바이스로 dm-integrity 설정 (성능 최적화)
$ integritysetup format --data-device /dev/nvme0n1p1 /dev/nvme1n1p1     --integrity sha256 --sector-size 4096

# 결과: 데이터는 nvme0n1p1, 태그는 nvme1n1p1에 저장
# 두 NVMe를 병렬로 사용하여 성능 극대화
dm-integrity 커널 함수역할호출 시점
integrity_ctr()target 생성, 슈퍼블록(Superblock) 로드테이블 로드
integrity_dtr()target 해제, 저널 flush디바이스 제거
integrity_map()bio remap + 태그 처리매 I/O
integrity_end_io()읽기 태그 검증읽기 완료
do_journal_write()저널 -> 실데이터 플러시(Flush)저널 watermark
integrity_recalc()기존 데이터 태그 재계산recalculate 모드
integrity_bitmap_flush()비트맵 dirty 플러시bitmap 모드

dm-crypt + dm-integrity 결합

dm-crypt와 dm-integrity를 결합하면 인증 암호화(AEAD)를 구현할 수 있습니다. AES-GCM 또는 ChaCha20-Poly1305 같은 AEAD 알고리즘을 사용하여 암호화와 무결성 검증을 단일 연산으로 수행합니다. 이 결합 모드에서 dm-crypt는 암호화 담당, dm-integrity는 인증 태그 저장 담당입니다.

평문 데이터 dm-crypt (AEAD) AES-GCM: 암호화 + 인증 태그 생성 암호문 (ciphertext) 인증 태그 (16B) dm-integrity 데이터 + 태그를 디스크 기록 AEAD 모드 설정 명령어 cryptsetup luksFormat --type luks2 --cipher aes-gcm-random --integrity aead --sector-size 4096 --key-size 256 /dev/sdb dm-crypt가 자동으로 dm-integrity 레이어를 생성하여 태그 저장
# AEAD 모드 LUKS2 볼륨 생성 (AES-GCM)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-gcm-random \
    --integrity aead \
    --sector-size 4096 \
    --key-size 256 \
    /dev/sdb1

# HMAC 무결성 (비 AEAD, 별도 해시)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --integrity hmac-sha256 \
    --sector-size 4096 \
    --key-size 512 \
    /dev/sdb1

# dm-integrity 디바이스가 자동 생성됨
$ dmsetup ls
encrypted        (254:1)
encrypted_dif    (254:0)  # dm-integrity 레이어

# 상태 확인
$ cryptsetup status encrypted
  type:    LUKS2
  cipher:  aes-gcm-random
  keysize: 256 bits
  integrity: aead
  integrity keysize: 0 bits  # AEAD는 별도 키 불필요
모드암호화무결성태그 크기성능 오버헤드
AES-GCM (AEAD)AES-GCMGCM 내장16B~15%
AES-XTS + HMACAES-XTSHMAC-SHA25632B~25%
ChaCha20-Poly1305ChaCha20Poly130516B~12%
AES-XTS 단독AES-XTS없음0B~8%
AEAD 모드 주의사항: AEAD 모드에서는 dm-integrity가 no-journal 모드로 동작합니다. 정전 시 데이터+태그 불일치로 해당 섹터가 읽기 불가능해질 수 있습니다. UPS 또는 배터리 백업이 없는 환경에서는 저널 모드가 권장됩니다.

저널 Write-Ahead 로깅 상세

dm-integrity의 저널은 Write-Ahead Logging(WAL) 패턴을 따릅니다. 데이터와 무결성 태그를 실제 위치에 쓰기 전에 저널 영역에 먼저 기록함으로써 원자성(Atomicity)을 보장합니다. 정전 시 저널을 재생(replay)하여 일관성을 복구합니다.

dm-integrity 저널 Write-Ahead 로깅 흐름 WRITE bio 데이터 + 섹터 번호 1. 무결성 태그 계산 SHA-256(data) = tag 2. 저널에 원자적 기록 journal_entry: sector + data + tag commit_id로 트랜잭션 경계 표시 저널 순환 버퍼 (Circular Buffer) Section 0 (committed) Section 1 (committed) Section 2 (writing) Section 3 (free) ... Section N flush_head write_head 3. 실제 위치에 Flush do_journal_write(): 저널 -> 데이터+태그 영역 Flush 트리거 조건 journal_watermark(50%) 또는 journal_commit_time(10s) 4. 저널 섹션 해제 (reclaim) flush_head 전진, 공간 재사용 Crash Recovery (정전 복구) 1. 슈퍼블록에서 마지막 commit_id 확인 2. 유효한 commit_id를 가진 저널 엔트리만 선택하여 replay 3. 미완료 트랜잭션은 폐기 (commit_id 불일치) 4. 저널 MAC 검증 (journal_mac 설정 시)
/* drivers/md/dm-integrity.c - 저널 쓰기 핵심 경로 */

/* 저널 섹션 헤더 */
struct journal_section_header {
    __le64 commit_id;   /* 트랜잭션 식별자 */
    __le64 commit_seq;  /* 순서 번호 */
};

/* 저널 커밋 처리 */
static void integrity_commit(struct dm_integrity_c *ic)
{
    struct journal_section_header *header;
    unsigned int section;

    section = ic->free_section;
    header = journal_section_header(ic, section);

    /* commit_id 기록 (홀수 = 커밋 완료 표시) */
    header->commit_id = cpu_to_le64(ic->commit_id);
    header->commit_seq = cpu_to_le64(ic->commit_seq);

    /* 저널 MAC 계산 (선택적 보호) */
    if (ic->journal_mac) {
        crypto_shash_digest(ic->journal_mac_desc,
                            (u8 *)header,
                            ic->journal_section_size,
                            header->mac);
    }

    /* 저널 섹션을 디스크에 동기 쓰기 */
    write_journal_section(ic, section);
    flush_journal(ic);

    ic->commit_seq++;
}

/* 저널 -> 실데이터 영역 플러시 */
static void do_journal_write(struct dm_integrity_c *ic)
{
    unsigned int section, entry;

    /* committed 섹션을 순서대로 처리 */
    while (ic->flush_section != ic->free_section) {
        section = ic->flush_section;

        /* 섹션 내 각 엔트리 처리 */
        for (entry = 0; entry < ic->journal_section_entries; entry++) {
            struct journal_entry *je;
            sector_t sector;
            u8 *data, *tag;

            je = journal_entry_get(ic, section, entry);
            sector = le64_to_cpu(je->u.s.sector);

            /* 데이터를 실제 위치에 쓰기 */
            data = journal_data_get(ic, section, entry);
            integrity_write_data(ic, sector, data);

            /* 태그를 태그 영역에 쓰기 */
            tag = journal_tag_get(ic, section, entry);
            rw_tag(ic, sector, tag, ic->tag_size, TAG_WRITE);
        }

        /* 섹션 플러시 완료, flush_head 전진 */
        ic->flush_section = (section + 1) % ic->journal_sections;
    }
}
저널 MAC 보호: --journal-integrity 옵션으로 저널 자체의 무결성도 보호할 수 있습니다. 예: integritysetup format --journal-integrity hmac-sha256 이 옵션은 저널 섹션마다 HMAC을 추가하여 저널 변조를 탐지합니다. --journal-crypt 옵션으로 저널을 암호화하여 기밀성도 제공 가능합니다.

Bitmap 모드 vs Journal 모드 비교

dm-integrity는 Journal 모드와 Bitmap 모드 두 가지 crash-safety 메커니즘을 제공합니다. Journal 모드는 완전한 crash-safety를 보장하지만 쓰기 증폭(Write Amplification)이 발생하며, Bitmap 모드는 dirty 블록만 추적하여 성능을 개선하지만 crash 후 재검증이 필요합니다.

Journal 모드 vs Bitmap 모드 쓰기 경로 비교 Journal 모드 (기본) WRITE 요청: 데이터 D, 섹터 S 1. SHA-256(D) = Tag 계산 2. 저널에 기록 (1차 쓰기) {commit_id, sector, D, Tag} -> journal 3. 실제 위치에 기록 (2차 쓰기) 4. 저널 섹션 해제 장점: 완전한 crash-safety 단점: 쓰기 2x 증폭, 지연시간 증가 Bitmap 모드 WRITE 요청: 데이터 D, 섹터 S 1. SHA-256(D) = Tag 계산 2. 비트맵 dirty 비트 설정 bitmap[S/sectors_per_bit] = 1 3. 데이터+태그 직접 기록 (1차 쓰기만) 4. 비트맵 flush (주기적) 장점: 저널 대비 ~2x 빠른 쓰기 단점: crash 후 dirty 블록 재검증 필요 Crash 후 복구 비교 Journal: 저널 replay (수 초), 데이터 손실 없음 Bitmap: dirty 블록 재검증 (수 분~수 시간, 디스크 크기에 비례), 미커밋 쓰기 무효화
/* drivers/md/dm-integrity.c - Bitmap 모드 핵심 */

/* 비트맵 dirty 비트 설정 */
static void integrity_bitmap_mark_dirty(struct dm_integrity_c *ic,
                                         sector_t sector,
                                         sector_t n_sectors)
{
    unsigned long bit;
    unsigned long end_bit;

    /* 섹터를 비트맵 인덱스로 변환 */
    bit = sector / ic->sectors_per_bitmap_bit;
    end_bit = (sector + n_sectors - 1) / ic->sectors_per_bitmap_bit;

    /* dirty 비트 설정 (원자적) */
    while (bit <= end_bit) {
        if (!test_and_set_bit(bit, ic->dirty_bitmap))
            ic->dirty_bitmap_count++;
        bit++;
    }
}

/* Crash 후 비트맵 기반 재검증 */
static void integrity_bitmap_recalculate(struct dm_integrity_c *ic)
{
    unsigned long bit;

    /* dirty 비트가 설정된 블록만 재검증 */
    for (bit = 0; bit < ic->bitmap_size; bit++) {
        if (test_bit(bit, ic->dirty_bitmap)) {
            sector_t start = bit * ic->sectors_per_bitmap_bit;
            sector_t count = ic->sectors_per_bitmap_bit;

            /* 데이터를 읽고 태그를 재계산 */
            integrity_recalc_range(ic, start, count);

            /* 재검증 완료 후 dirty 비트 해제 */
            clear_bit(bit, ic->dirty_bitmap);
        }
    }
}
비교 항목Journal 모드Bitmap 모드No-journal (Direct)
쓰기 증폭2x (저널 + 실데이터)1x + 비트맵 I/O1x
Crash Safety완전 보장dirty 블록 재검증비보장
복구 시간수 초 (저널 replay)수 분~시간없음
메모리 사용저널 크기만큼비트맵 크기 (작음)최소
순차 쓰기 처리량기준 대비 ~50%기준 대비 ~80%기준 대비 ~90%
랜덤 쓰기 IOPS기준 대비 ~40%기준 대비 ~70%기준 대비 ~85%
주요 용도단독 사용, 데이터 보호대용량 디스크AEAD 결합 전용
Bitmap 모드 권장 시나리오: Bitmap 모드는 대용량 디스크(1TB 이상)에서 Journal 모드의 쓰기 증폭 오버헤드를 줄이려 할 때 적합합니다. --bitmap-sectors-per-bit 값을 크게 설정하면 비트맵 I/O가 줄지만 crash 후 재검증 범위가 넓어집니다. 기본값 65536은 각 비트가 약 32MB 범위를 커버합니다.

내부 자료 구조 상세

dm-integrity의 커널 구현은 여러 핵심 자료 구조에 의존합니다. dm_integrity_c는 target 인스턴스의 전체 상태를 관리하며, dm_integrity_io는 개별 I/O 요청의 컨텍스트(Context)를 추적합니다. 디스크 상의 슈퍼블록 구조와 저널 엔트리 포맷도 중요한 구성 요소입니다.

/* drivers/md/dm-integrity.c - 슈퍼블록 디스크 포맷 */
struct superblock {
    __u8 magic[8];         /* "integrt\1" 매직 넘버 */
    __u8 version;            /* 포맷 버전 (현재 5) */
    __s8 log2_interleave_sectors;  /* 인터리빙 크기 (log2) */
    __le16 integrity_tag_size;  /* 태그 크기 (바이트) */
    __le32 journal_sections;    /* 저널 섹션 수 */
    __le64 provided_data_sectors;  /* 제공 가능 데이터 섹터 */
    __le32 flags;               /* SB_FLAG_* */
    __u8 log2_sectors_per_block;  /* 블록당 섹터 수 (log2) */
    __le64 recalc_sector;       /* 재계산 진행 위치 */
    __u8 pad[4];
    __u8 salt[SALT_SIZE];      /* HMAC salt */
};

#define SB_FLAG_HAVE_JOURNAL_MAC    0x1
#define SB_FLAG_RECALCULATING       0x2
#define SB_FLAG_DIRTY_BITMAP        0x4
#define SB_FLAG_FIXED_PADDING       0x8
#define SB_FLAG_FIXED_HMAC          0x10

/* dm_integrity_c 구조체 상세 필드 */
struct dm_integrity_c {
    struct dm_dev *dev;              /* 데이터 디바이스 */
    struct dm_dev *meta_dev;         /* 별도 메타 디바이스 (선택) */
    struct dm_target *ti;

    /* 해시/암호 */
    struct crypto_shash *internal_hash;  /* 태그 해시 알고리즘 */
    struct crypto_shash *journal_mac;   /* 저널 MAC 알고리즘 */
    struct crypto_skcipher *journal_crypt;  /* 저널 암호화 */

    /* 디스크 레이아웃 */
    unsigned int tag_size;           /* 태그 크기 (바이트) */
    unsigned int sectors_per_block;  /* 블록당 섹터 수 */
    __u64 provided_data_sectors;     /* 사용 가능 데이터 섹터 */
    sector_t initial_sectors;        /* SB + 저널 + 패딩 */
    sector_t metadata_run;           /* 태그 영역 크기 */
    __s8 log2_interleave_sectors;

    /* 저널 관련 */
    unsigned int journal_entries;
    unsigned int journal_sections;
    unsigned int journal_section_entries;
    unsigned int journal_section_sectors;
    unsigned int free_section;        /* 현재 쓰기 섹션 */
    unsigned int flush_section;       /* 플러시 대기 섹션 */
    __u64 commit_id;                  /* 현재 커밋 ID */
    __u64 commit_seq;                 /* 커밋 시퀀스 번호 */

    /* 비트맵 모드 */
    unsigned long *dirty_bitmap;      /* dirty 블록 비트맵 */
    unsigned long bitmap_size;        /* 비트맵 크기 (비트) */
    unsigned int sectors_per_bitmap_bit;

    /* dm-bufio 캐시 */
    struct dm_bufio_client *bufio;    /* 태그 블록 캐시 */
    struct dm_bufio_client *journal_bufio;

    /* 워크큐 */
    struct workqueue_struct *commit_wq;
    struct workqueue_struct *writer_wq;
    struct workqueue_struct *recalc_wq;

    /* 성능 카운터 */
    enum integrity_mode mode;  /* 'J', 'B', 'D' */
    bool recalculate;          /* 태그 재계산 진행 중 */
    atomic64_t number_of_mismatches;  /* 불일치 카운터 */
};

/* I/O별 컨텍스트 */
struct dm_integrity_io {
    struct dm_integrity_c *ic;
    sector_t bi_sector;       /* 원래 섹터 번호 */
    struct bio_integrity_payload *bi_integrity;
    struct completion comp;    /* I/O 완료 대기 */
    int bi_status;
    struct work_struct work;   /* 워크큐 처리용 */
    unsigned int range_start;
    unsigned int range_end;
};

AEAD 통합 상세

AEAD(Authenticated Encryption with Associated Data) 결합 모드에서 dm-crypt는 인증 암호화를 수행하고, dm-integrity는 인증 태그의 저장/조회만 담당합니다. 이 구조에서 dm-integrity는 no-journal(Direct) 모드로 동작하며, 태그 계산을 dm-crypt의 AEAD 알고리즘에 위임합니다.

/* dm-crypt + dm-integrity AEAD 결합 내부 동작 */

/* dm-crypt가 AEAD 모드로 bio를 처리하는 경우:
 * 1. dm-crypt가 bio를 가로채 AEAD 암호화 수행
 * 2. 암호화 결과: ciphertext + authentication tag (16B for GCM)
 * 3. ciphertext는 bio 데이터에, auth tag는 bio_integrity에 저장
 * 4. dm-integrity가 bio_integrity에서 tag를 추출하여 디스크에 기록 */

/* drivers/md/dm-crypt.c - AEAD 처리 */
static int crypt_convert_block_aead(
    struct crypt_config *cc,
    struct convert_context *ctx,
    struct aead_request *req,
    unsigned int tag_offset)
{
    struct bio_integrity_payload *bip = ctx->bio_out->bi_integrity;
    u8 *tag;

    /* bio_integrity에서 태그 저장 위치 획득 */
    tag = bip->bip_buf + tag_offset;

    /* AEAD 요청 구성: src=평문, dst=암호문, assocdata=IV+섹터 */
    aead_request_set_crypt(req, &src, &dst,
                           cc->sector_size,  /* 평문 길이 */
                           iv);
    aead_request_set_ad(req, cc->integrity_iv_size);

    if (bio_data_dir(ctx->bio_out) == WRITE) {
        /* 암호화: 암호문 + 인증 태그 생성 */
        r = crypto_aead_encrypt(req);
        /* tag에 인증 태그가 저장됨 (GCM: 16바이트) */
    } else {
        /* 복호화: 인증 태그 검증 + 복호화 */
        r = crypto_aead_decrypt(req);
        /* 태그 불일치 시 -EBADMSG 반환 */
    }
    return r;
}

/* dm-integrity 측: AEAD 태그 수신/저장 */
/* integrity_map()에서 bio_integrity payload를 통해
 * dm-crypt가 생성한 인증 태그를 디스크에 기록
 * 읽기 시에는 디스크에서 태그를 읽어 bio_integrity에 채움 */
static void integrity_aead_tag_io(struct dm_integrity_c *ic,
                                   struct dm_integrity_io *dio,
                                   int rw)
{
    struct bio_integrity_payload *bip = dio->bi_integrity;
    u8 *tag_buf = bip ? bip->bip_buf : NULL;

    if (rw == WRITE && tag_buf) {
        /* dm-crypt가 생성한 인증 태그를 디스크에 기록 */
        rw_tag(ic, dio->bi_sector, tag_buf,
               ic->tag_size, TAG_WRITE);
    } else if (rw == READ && tag_buf) {
        /* 디스크에서 인증 태그를 읽어 bio_integrity에 전달 */
        rw_tag(ic, dio->bi_sector, tag_buf,
               ic->tag_size, TAG_READ);
    }
}
AEAD 알고리즘키 크기태그 크기특징
AES-GCM128/256비트16바이트AES-NI 가속, 널리 지원, nonce 재사용 취약
AES-CCM128/256비트16바이트소프트웨어 구현 용이, GCM보다 느림
ChaCha20-Poly1305256비트16바이트AES-NI 없는 환경에서 빠름, ARM 최적화
Aegis128128비트16바이트AES-NI 환경에서 GCM보다 빠름 (커널 6.0+)
AEAD no-journal 모드의 위험: AEAD 결합에서 dm-integrity는 no-journal 모드로 동작합니다. 정전 시 데이터는 갱신되었지만 태그는 이전 값인 상태가 될 수 있습니다. 이 경우 해당 섹터의 읽기 시 인증 실패(-EBADMSG)가 발생하며, 해당 데이터는 복구 불가능합니다. UPS 또는 배터리 백업이 필수적이며, 중요 데이터에는 별도 백업 전략이 필요합니다.

dm-integrity + LUKS2 연동

LUKS2(Linux Unified Key Setup version 2)는 dm-crypt와 dm-integrity를 통합 관리하는 상위 레벨 인터페이스를 제공합니다. cryptsetup을 사용하면 dm-integrity 레이어가 자동으로 생성되며, 키 관리와 무결성 설정을 단일 명령으로 처리할 수 있습니다.

# LUKS2 + dm-integrity 결합 설정 가이드

## 방법 1: AEAD 모드 (암호화 + 무결성 단일 연산)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-gcm-random \
    --integrity aead \
    --sector-size 4096 \
    --key-size 256 \
    /dev/sdb1

## 방법 2: HMAC 모드 (별도 해시, 더 강력한 무결성)
$ cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --integrity hmac-sha256 \
    --sector-size 4096 \
    --key-size 512 \
    /dev/sdb1

# HMAC 모드에서는 키가 2개: 256-bit 암호화 + 256-bit HMAC
# --key-size 512 = AES-XTS(256) + HMAC-SHA256(256)

## 방법 3: Poly1305 (ARM/소프트웨어 환경 최적)
$ cryptsetup luksFormat --type luks2 \
    --cipher chacha20,aead \
    --integrity poly1305 \
    --sector-size 4096 \
    --key-size 256 \
    /dev/sdb1

## 볼륨 열기 및 마운트
$ cryptsetup open /dev/sdb1 secure_vol
$ mkfs.ext4 /dev/mapper/secure_vol
$ mount /dev/mapper/secure_vol /mnt/secure

## LUKS2 헤더에서 integrity 정보 확인
$ cryptsetup luksDump /dev/sdb1
# ...
# Segments:
#   0: crypt
#     cipher: aes-xts-plain64
#     integrity: hmac(sha256)
#     sector: 4096 [bytes]
# ...
# Integrity:
#   type: LUKS2_INTEGRITY
#   journal_size: 67108864
#   interleave_sectors: 32768

## dm-integrity 하위 디바이스 상태 확인
$ dmsetup ls
secure_vol      (254:1)
secure_vol_dif  (254:0)  # dm-integrity 레이어

$ dmsetup status secure_vol_dif
# 0 4194304 integrity 0 0

## LUKS2 + integrity 볼륨의 크기 오버헤드 확인
$ integritysetup dump /dev/sdb1
# Info for integrity device /dev/sdb1
#   superblock_version: 5
#   log2_interleave_sectors: 15
#   integrity_tag_size: 32
#   journal_sections: 88
#   provided_data_sectors: 4063232
#   sector_size: 4096

## 성능 비교: 무결성 없음 vs AEAD vs HMAC
# AES-XTS만 (무결성 없음)
$ cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 \
    --key-size 512 /dev/sdb1
# fio 순차 쓰기: ~1.8 GB/s

# AES-GCM AEAD
# fio 순차 쓰기: ~1.5 GB/s (약 17% 오버헤드)

# AES-XTS + HMAC-SHA256
# fio 순차 쓰기: ~1.2 GB/s (약 33% 오버헤드)
LUKS2 integrity 선택 가이드: AEAD 모드(AES-GCM)는 성능이 우수하지만 no-journal로 동작하여 crash 시 데이터 손실 가능성이 있습니다. HMAC 모드(AES-XTS + HMAC-SHA256)는 저널링으로 crash-safety를 보장하지만 성능 오버헤드가 큽니다. 서버 환경에서는 UPS + AEAD 모드가, 노트북/모바일에서는 HMAC 모드가 권장됩니다.

성능 최적화

dm-integrity의 성능은 저널 설정, 섹터 크기, 인터리빙 파라미터에 크게 좌우됩니다. 워크로드 특성에 맞는 적절한 튜닝이 필수적입니다.

# dm-integrity 성능 튜닝 가이드

## 1. journal_watermark 최적화
# 기본값 50%: 저널이 50% 차면 플러시 시작
# 높은 값: 배치 플러시로 처리량 증가, 지연시간 증가
# 낮은 값: 즉시 플러시, 지연시간 감소, 처리량 감소
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --journal-watermark 75 \
    --journal-commit-time 5000
# 75%에서 플러시, 또는 5초마다 강제 커밋

## 2. journal_commit_time 조정
# 기본값 10000ms (10초)
# 순차 쓰기 워크로드: 30000ms (30초) - 배치 효과 극대화
# OLTP 워크로드: 1000ms (1초) - 지연시간 최소화
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --journal-commit-time 1000

## 3. 저널 크기 최적화
# 큰 저널: 더 많은 쓰기를 배치 처리, 메모리 사용 증가
# 작은 저널: 빈번한 플러시, 쓰기 차단 가능
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --journal-size 256M
# 고부하 쓰기 워크로드에서 256MB 저널 권장

## 4. 섹터 크기 조정
# 4096B 섹터: 태그 오버헤드 비율 감소 (32B/4096B = 0.78%)
# 512B 섹터: 태그 오버헤드 비율 증가 (32B/512B = 6.25%)
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --sector-size 4096

## 5. 별도 메타 디바이스 사용 (최고 성능)
# 데이터와 태그를 물리적으로 분리하여 병렬 I/O
$ integritysetup format \
    --data-device /dev/nvme0n1p1 \
    /dev/nvme1n1p1 \
    --integrity sha256 \
    --sector-size 4096
# 데이터: NVMe 0, 태그: NVMe 1 (병렬 접근)

## 6. Bitmap 모드로 쓰기 성능 개선
$ integritysetup format /dev/sdb1 \
    --integrity sha256 \
    --integrity-bitmap-mode \
    --bitmap-sectors-per-bit 65536 \
    --bitmap-flush-time 5000

## 7. 벤치마크 실행
# 순차 쓰기
$ fio --name=seq_write --filename=/dev/mapper/integrity_disk \
    --rw=write --bs=128k --numjobs=4 --iodepth=16 \
    --size=4G --direct=1 --group_reporting

# 랜덤 쓰기 (저널 모드 병목 테스트)
$ fio --name=rand_write --filename=/dev/mapper/integrity_disk \
    --rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \
    --size=1G --direct=1 --group_reporting

# 혼합 읽기/쓰기
$ fio --name=mixed --filename=/dev/mapper/integrity_disk \
    --rw=randrw --rwmixread=70 --bs=4k --numjobs=4 \
    --iodepth=32 --size=1G --direct=1 --group_reporting
튜닝 파라미터기본값순차 쓰기 최적랜덤 쓰기 최적영향
journal-watermark50%80%30%플러시 빈도 조절
journal-commit-time10000ms30000ms1000ms커밋 주기
journal-size자동256MB64MB배치 크기
sector-size512B4096B4096B태그 오버헤드 비율
bitmap-sectors-per-bit6553613107232768재검증 범위
성능 병목 진단: dm-integrity의 쓰기 성능이 기대보다 낮다면 저널 flush가 병목일 가능성이 높습니다. iostat -x 1로 디바이스 사용률을 확인하고, %util이 100%에 근접하면 저널 크기 확대 또는 Bitmap 모드 전환을 고려하세요. HDD에서는 인터리빙으로 인한 seek 오버헤드가 지배적이므로 --data-device 옵션으로 메타 디바이스를 분리하는 것이 가장 효과적입니다.

디버깅 및 모니터링

dm-integrity 문제 진단에는 dmsetup, integritysetup, 커널 로그, 그리고 성능 모니터링 도구를 활용합니다.

# 1. dm-integrity 상태 확인
$ dmsetup status integrity_disk
# 0 4194304 integrity 0 0
# 형식: start_sector num_sectors integrity mismatch_count provision_mode
# mismatch_count가 0이 아니면 무결성 오류 발생

# 2. integritysetup dump - 디스크 레이아웃 상세
$ integritysetup dump /dev/sdb1
# Info for integrity device /dev/sdb1
#   superblock_version   5
#   log2_interleave_sectors 15
#   integrity_tag_size   32
#   journal_sections     88
#   provided_data_sectors 4063232
#   sector_size          4096
#   flags                FIXED_HMAC

# 3. 커널 로그 확인
$ dmesg | grep -i integrity
# device-mapper: integrity: dm-0: started in journal mode
# device-mapper: integrity: dm-0: initialized with tag size 32
# device-mapper: integrity: dm-0: checksum failed at sector 12345

# 4. 무결성 오류 카운터 실시간 모니터링
$ watch -n 1 'dmsetup status integrity_disk | awk "{print \"mismatches:\", \$5}"'

# 5. I/O 성능 모니터링
# 실제 디바이스와 DM 디바이스의 I/O 비교
$ iostat -x 1 /dev/sdb /dev/dm-0
# sdb (물리)와 dm-0 (dm-integrity)의 처리량 차이 = 오버헤드

# 6. dmsetup table로 현재 설정 확인
$ dmsetup table integrity_disk
# 0 4063232 integrity /dev/sdb1 0 32 J 6 journal_sectors:180224 ...

# 7. 저널 상태 확인
# 저널 사용률 추정 (쓰기 지연과 상관)
$ dmsetup message integrity_disk 0 stats
# (커널 버전에 따라 지원 여부 다름)

# 8. ftrace로 dm-integrity I/O 경로 추적
$ echo function_graph | sudo tee /sys/kernel/tracing/current_tracer
$ echo integrity_map | sudo tee /sys/kernel/tracing/set_graph_function
$ echo 1 | sudo tee /sys/kernel/tracing/tracing_on
# ... I/O 발생 ...
$ cat /sys/kernel/tracing/trace
# integrity_map -> integrity_journal_write -> do_journal_write

# 9. 강제 무결성 검증 (전체 디스크)
# 모든 블록을 읽어 태그 검증
$ dd if=/dev/mapper/integrity_disk of=/dev/null bs=1M status=progress
# -EIO 발생 시 해당 블록에 무결성 오류 존재

# 10. 재계산 모드 활성화 (기존 데이터에 태그 추가)
$ integritysetup open /dev/sdb1 integrity_disk \
    --integrity sha256 --integrity-recalculate
# 백그라운드에서 모든 블록의 태그를 재계산
# 진행 상황: dmesg에서 확인
증상원인해결 방법
읽기 시 -EIO (EILSEQ)데이터-태그 불일치해당 블록 재기록, 백업에서 복구
쓰기 속도 급감저널 full, 플러시 차단journal-size 증가, watermark 조정
부팅 시 오래 걸림bitmap 모드 재검증sectors-per-bit 증가, 또는 journal 모드
recalculate 완료 안됨대용량 디스크 재계산정상 동작, 백그라운드 진행 대기
mismatch_count 증가하드웨어 결함 또는 AEAD 미커밋SMART 확인, UPS 사용, 백업
메타 디바이스 오류태그 디바이스 장애메타 디바이스 교체, 재포맷

에러 복구 메커니즘

dm-integrity의 에러 복구는 모드에 따라 다르게 동작합니다. Journal 모드는 crash-safety가 보장되므로 저널 replay로 복구되지만, Bitmap 모드와 No-journal 모드에서는 데이터 손실이 발생할 수 있습니다.

dm-integrity Crash 후 복구 흐름 integritysetup open (장치 활성화) 슈퍼블록 읽기 및 플래그 확인 Journal 모드 복구 1. 저널 섹션 스캔 2. 유효한 commit_id 엔트리 선택 3. 저널 -> 실데이터 replay 4. 완전 복구 (데이터 손실 없음) Bitmap 모드 복구 1. dirty 비트맵 로드 2. dirty 비트 설정된 블록 읽기 3. 해시 재계산 및 태그 갱신 4. 부분 데이터 손실 가능 (미커밋 쓰기) No-journal (Direct) 복구 1. 복구 메커니즘 없음 2. 데이터-태그 불일치 섹터 존재 3. 해당 섹터 읽기 시 -EIO 정상 동작 재개 재검증 후 동작 재개 손상 섹터 접근 불가 복구 시간: Journal: 수 초 Bitmap: 수 분~시간 (디스크 크기 비례) No-journal: 복구 불가
/* drivers/md/dm-integrity.c - 저널 replay 구현 */
static void replay_journal(struct dm_integrity_c *ic)
{
    unsigned int section;
    __u64 last_commit_id = 0;

    /* 모든 저널 섹션을 스캔 */
    for (section = 0; section < ic->journal_sections; section++) {
        struct journal_section_header *header;
        header = journal_section_header(ic, section);

        /* commit_id 유효성 검증 */
        if (le64_to_cpu(header->commit_id) != ic->commit_id)
            continue;  /* 미완료 트랜잭션: 건너뜀 */

        /* journal MAC 검증 (설정된 경우) */
        if (ic->journal_mac) {
            r = verify_journal_mac(ic, section);
            if (r) {
                DMWARN("journal MAC mismatch in section %u",
                       section);
                continue;  /* 변조된 저널 엔트리 */
            }
        }

        /* 유효한 엔트리를 실제 위치에 replay */
        for (entry = 0; entry < ic->journal_section_entries; entry++) {
            struct journal_entry *je;
            je = journal_entry_get(ic, section, entry);

            if (journal_entry_is_unused(je))
                break;

            /* 데이터를 실제 위치에 쓰기 */
            write_journal_entry_to_disk(ic, je, section, entry);
        }
    }

    /* commit_id 증가 (다음 사용 준비) */
    ic->commit_id++;
    write_superblock(ic);

    DMINFO("journal replay completed, %u sections replayed",
           replayed_count);
}

/* recalculate 모드 - 백그라운드 태그 재계산 */
static void integrity_recalc(struct work_struct *work)
{
    struct dm_integrity_c *ic =
        container_of(work, struct dm_integrity_c, recalc_work);
    sector_t sector = ic->sb->recalc_sector;

    while (sector < ic->provided_data_sectors) {
        u8 data_buf[PAGE_SIZE];
        u8 tag[MAX_TAG_SIZE];

        /* 데이터 블록 읽기 */
        integrity_read_data(ic, sector, data_buf);

        /* 해시 태그 계산 */
        crypto_shash_digest(ic->internal_hash_desc,
                            data_buf, ic->sectors_per_block << SECTOR_SHIFT,
                            tag);

        /* 태그 저장 */
        rw_tag(ic, sector, tag, ic->tag_size, TAG_WRITE);

        /* 진행 상황 슈퍼블록에 기록 (체크포인트) */
        if (!(sector % RECALC_CHECKPOINT_INTERVAL)) {
            ic->sb->recalc_sector = cpu_to_le64(sector);
            write_superblock(ic);
        }

        sector += ic->sectors_per_block;
        cond_resched();  /* 다른 작업에 양보 */
    }

    /* 재계산 완료 */
    ic->sb->flags &= ~cpu_to_le32(SB_FLAG_RECALCULATING);
    write_superblock(ic);
    DMINFO("integrity recalculation completed");
}
에러 복구 전략 권장: Journal 모드에서는 데이터 손실이 거의 없지만, Bitmap 및 No-journal 모드에서는 crash 시 데이터-태그 불일치가 발생할 수 있습니다. 불일치 발생 시 해당 섹터를 재기록하면 태그가 갱신되어 정상화됩니다. integrity-recalculate 옵션으로 전체 디스크의 태그를 재계산할 수도 있지만, 이는 기존 태그를 모두 덮어쓰므로 변조 탐지 기능이 일시적으로 무효화됩니다.

dm-flakey / dm-dust

dm-flakey와 dm-dust는 테스트 및 결함 주입(fault injection)용 Device Mapper target입니다. 파일시스템이나 스토리지 스택의 에러 처리 경로를 테스트하는 데 사용됩니다.

/* dm-flakey: 주기적으로 I/O 에러를 발생시킴 */
# 10초 정상 동작 후 5초간 모든 I/O에 에러 반환
$ echo "0 $(blockdev --getsz /dev/sdb1) flakey /dev/sdb1 0 10 5" | \
    dmsetup create flakey_disk

# 옵션: 쓰기만 실패, 읽기는 정상
$ echo "0 $(blockdev --getsz /dev/sdb1) flakey /dev/sdb1 0 10 5 \
    1 drop_writes" | dmsetup create flakey_write

# dm-flakey 커널 구현 (간략화)
static int flakey_map(struct dm_target *ti, struct bio *bio)
{
    struct flakey_c *fc = ti->private;
    unsigned elapsed = jiffies - fc->start_time;

    if (elapsed % (fc->up_interval + fc->down_interval)
        >= fc->up_interval) {
        /* down 구간: 에러 반환 또는 데이터 변조 */
        if (fc->drop_writes && bio_data_dir(bio) == WRITE) {
            bio_endio(bio);  /* 쓰기 무시 */
            return DM_MAPIO_SUBMITTED;
        }
        bio->bi_status = BLK_STS_IOERR;
        bio_endio(bio);
        return DM_MAPIO_SUBMITTED;
    }

    /* up 구간: 정상 통과 */
    flakey_map_bio(ti, bio);
    return DM_MAPIO_REMAPPED;
}
/* dm-dust: 특정 블록에 읽기 에러를 주입 */
# dm-dust 디바이스 생성
$ echo "0 $(blockdev --getsz /dev/sdb1) dust /dev/sdb1 0 4096" | \
    dmsetup create dusty_disk

# 특정 블록에 bad block 마킹
$ dmsetup message dusty_disk 0 addbadblock 100
$ dmsetup message dusty_disk 0 addbadblock 200

# bad block 활성화
$ dmsetup message dusty_disk 0 enable

# 블록 100, 200 읽기 시 -EIO 반환
# 파일시스템의 에러 핸들링 로직 테스트에 활용

# bad block 제거
$ dmsetup message dusty_disk 0 removebadblock 100
테스트 활용: dm-flakey는 ext4, Btrfs 등 파일시스템의 crash consistency 테스트에 널리 사용됩니다. xfstests의 많은 테스트 케이스가 dm-flakey를 사용하여 정전 시나리오를 시뮬레이션합니다.
내부 관련 문서:
외부 참고 자료: