SCSI / iSCSI 서브시스템

Linux 커널의 SCSI 서브시스템을 호스트 어댑터부터 원격 타겟 스토리지까지 이어지는 전체 데이터 경로 관점으로 심층 분석합니다. SCSI 아키텍처 모델(SAM)과 Upper/Mid/Lower 3계층 역할 분리, 핵심 구조체(Struct)와 큐잉 모델, 명령 제출·완료·재시도 흐름, 오류 처리(EH) 스레드(Thread)와 타임아웃 복구 전략, SAS/FC/iSCSI/UAS 전송별 특성과 병목(Bottleneck) 포인트, open-iscsi와 LIO를 포함한 iSCSI 실무 구성, SCSI LLD 드라이버 개발 패턴, sysfs/tracepoint 기반 관측과 문제 재현 절차까지 안정성 중심 운영에 필요한 내용을 폭넓게 다룹니다.

관련 표준: SCSI Architecture Model (SAM-6), SCSI Primary Commands (SPC-5) — SCSI 프로토콜 표준 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
전제 조건: Block I/O 서브시스템VFS 문서를 먼저 읽으세요. 스토리지 경로는 큐잉, 병합, 플러시(Flush) 정책이 연쇄적으로 동작하므로, 요청 수명주기와 완료 경로를 먼저 추적해야 합니다.

핵심 요약

  • SCSI — 스토리지 디바이스와 통신하는 명령 프로토콜 패밀리입니다. NVMe를 제외한 거의 모든 블록 디바이스가 경유합니다.
  • 3계층 구조 — Upper(sd, sr, sg) / Mid(SCSI 코어) / Lower(HBA 드라이버) 계층으로 분리됩니다.
  • CDB — Command Descriptor Block. SCSI 명령의 표준 형식(READ/WRITE/INQUIRY 등)입니다.
  • iSCSI — TCP/IP 위에서 SCSI 명령을 전송하는 프로토콜. 원격 스토리지를 로컬처럼 사용합니다.
  • libata — SATA 디바이스를 SCSI 명령으로 변환하여 SCSI 스택에서 처리합니다.

단계별 이해

  1. SCSI 디바이스 확인lsscsi로 시스템에 연결된 SCSI 디바이스를 확인합니다.

    SATA HDD/SSD도 libata를 통해 SCSI 디바이스로 표시됩니다.

  2. 3계층 이해 — Upper Layer(sd 드라이버)가 읽기/쓰기 요청을 SCSI 명령으로 변환하고, Mid Layer가 큐잉/에러 처리를 하며, Lower Layer(HBA)가 하드웨어에 전달합니다.

    /sys/class/scsi_host/에서 HBA 정보를 확인할 수 있습니다.

  3. SCSI 명령 관찰sg_inq /dev/sda로 INQUIRY 명령을 보내 디바이스 정보를 조회합니다.

    sg3_utils 패키지가 다양한 SCSI 명령 도구를 제공합니다.

  4. 에러 처리 — SCSI 명령 실패 시 Mid Layer의 Error Handler(EH)가 복구를 시도합니다.

    명령 재시도 → 디바이스 리셋 → 버스(Bus) 리셋 → 호스트 리셋 순으로 에스컬레이션합니다.

SCSI 서브시스템 개요

SCSI(Small Computer System Interface)는 1986년 ANSI 표준(X3.131)으로 시작하여, 40년 가까이 스토리지 I/O의 핵심 프로토콜 패밀리로 진화해왔습니다. 물리 버스 규격에서 출발했지만, 오늘날에는 전송 프로토콜과 독립적인 명령 세트(Command Set)로 기능하며, SAS·Fibre Channel·iSCSI·USB(UAS) 등 다양한 전송 계층 위에서 동작합니다.

Linux 커널의 SCSI 서브시스템(drivers/scsi/)은 이 SCSI 프로토콜 패밀리 전체를 지원하는 통합 프레임워크를 제공합니다. 직접적인 SCSI 디바이스뿐 아니라, libata를 통해 SATA 디바이스도 SCSI 명령으로 변환하여 처리하므로, 사실상 NVMe를 제외한 거의 모든 블록 스토리지가 SCSI 스택을 경유합니다.

프로토콜 진화

세대전송 방식최대 대역폭(Bandwidth)최대 거리비고
Parallel SCSI (SPI)병렬 버스640 MB/s (Ultra-640)~12m레거시, 커널에서 단종
SAS (Serial Attached SCSI)직렬 점대점22.5 Gb/s (SAS-4)~10m엔터프라이즈 표준
Fibre Channel (FC)직렬 (광섬유/구리)128 Gb/s (64GFC)~10km (광)SAN 환경
iSCSITCP/IP 네트워크네트워크 대역 의존무제한IP 기반 SAN
UAS (USB Attached SCSI)USB 3.x20 Gb/s (USB 3.2)~5m외장 스토리지

SCSI 명령 세트 표준

SCSI 표준은 T10 위원회(현 INCITS)에서 관리하며, 용도별로 분리된 명령 세트로 구성됩니다:

표준대상 디바이스주요 명령
SPC (SCSI Primary Commands)모든 SCSI 디바이스 공통INQUIRY, TEST UNIT READY, REQUEST SENSE, MODE SELECT/SENSE
SBC (SCSI Block Commands)디스크 (HDD/SSD)READ(10/16), WRITE(10/16), SYNCHRONIZE CACHE, UNMAP
SSC (SCSI Stream Commands)테이프 드라이브READ, WRITE, REWIND, SPACE
MMC (Multi-Media Commands)CD/DVD/BDREAD TOC, GET CONFIGURATION, READ DISC INFORMATION
SES (SCSI Enclosure Services)디스크 인클로저RECEIVE DIAGNOSTIC, SEND DIAGNOSTIC
참고: Linux 커널에서 sd 드라이버는 SBC, sr은 MMC, st는 SSC, ses는 SES 명령 세트를 구현합니다. sg(SCSI Generic)는 사용자 공간(User Space)에서 임의의 CDB를 전송할 수 있는 범용 인터페이스입니다.

SCSI 아키텍처 모델 (SAM)

SAM(SCSI Architecture Model)은 SCSI 프로토콜의 추상적 동작 모델을 정의합니다. 전송 매체와 무관하게 Initiator–Target 간 명령 전달 체계를 규격화합니다.

핵심 구성 요소

SAM 개념설명Linux 커널 대응
Initiator명령을 발행하는 주체struct Scsi_Host (HBA 드라이버)
Target명령을 수신·실행하는 주체struct scsi_target
LUN (Logical Unit)Target 내 논리적 디바이스struct scsi_device
HBA (Host Bus Adapter)호스트와 SCSI 버스를 연결하는 하드웨어struct scsi_host_template + LLD
CDB (Command Descriptor Block)SCSI 명령 패킷 (6/10/12/16/32바이트)scsi_cmnd.cmnd[]
Sense Data오류/상태 상세 정보scsi_cmnd.sense_buffer[]

SCSI 명령 모델

SCSI 명령은 CDB(Command Descriptor Block)라는 고정/가변 길이 패킷으로 구성됩니다. CDB의 첫 바이트가 Operation Code이며, 명령 길이를 결정합니다:

/* CDB 구조 예시: READ(10) — 10바이트 CDB */
/*  Byte 0: Operation Code (0x28 = READ(10))  */
/*  Byte 1: [7:5] Reserved, [4:0] Service Action (보통 0) */
/*  Byte 2-5: Logical Block Address (빅엔디안)   */
/*  Byte 6: Reserved / Group Number              */
/*  Byte 7-8: Transfer Length (블록 수, 빅엔디안) */
/*  Byte 9: Control                               */

u8 cdb[10] = {
    0x28,                           /* READ(10) */
    0x00,                           /* flags */
    (lba >> 24) & 0xff,           /* LBA [31:24] */
    (lba >> 16) & 0xff,           /* LBA [23:16] */
    (lba >> 8)  & 0xff,           /* LBA [15:8]  */
    lba         & 0xff,           /* LBA [7:0]   */
    0x00,                           /* group */
    (len >> 8)  & 0xff,           /* length MSB */
    len         & 0xff,           /* length LSB */
    0x00                            /* control */
};

SCSI 상태 코드(Status Byte)는 명령 완료 시 반환됩니다:

상태 코드의미
GOOD0x00명령 성공
CHECK CONDITION0x02오류 발생 — Sense Data 확인 필요
BUSY0x08Target 처리 중, 재시도 필요
RESERVATION CONFLICT0x18다른 Initiator가 예약한 리소스
TASK SET FULL0x28큐 포화, 나중에 재시도
ACA ACTIVE0x30자동 비상 연합 활성

주요 SCSI 명령 코드

자주 사용되는 SCSI Operation Code(Opcode) 목록입니다. CDB 첫 바이트가 Opcode를 결정하며, 명령의 길이도 함께 결정됩니다:

명령OpcodeCDB 길이설명주요 용도
TEST UNIT READY0x006디바이스 준비 상태 확인초기화, 미디어 감지
REQUEST SENSE0x036Sense Data 읽기오류 상세 정보 수집
INQUIRY0x126디바이스 정보 조회장치 식별, Vital Product Data
READ(6)0x086블록 읽기 (21비트 LBA)레거시 호환
READ(10)0x2810블록 읽기 (32비트 LBA)일반 디스크 읽기
READ(16)0x8816블록 읽기 (64비트 LBA)대용량 디스크 (>2TB)
WRITE(6)0x0A6블록 쓰기 (21비트 LBA)레거시 호환
WRITE(10)0x2A10블록 쓰기 (32비트 LBA)일반 디스크 쓰기
WRITE(16)0x8A16블록 쓰기 (64비트 LBA)대용량 디스크
SYNCHRONIZE CACHE0x3510캐시(Cache) 플러시데이터 일관성 보장
READ CAPACITY(10)0x2510디바이스 용량 조회파티션 크기 확인
READ CAPACITY(16)0x9E1664비트 용량 조회대용량 디스크
MODE SENSE(6/10)0x1A/0x5A6/10모드 페이지(Page) 읽기캐싱 정책, 에러 복구
MODE SELECT(6/10)0x15/0x556/10모드 페이지 설정디바이스 구성
START STOP UNIT0x1B6스핀업/다운, 이젝트전원 관리(Power Management)
UNMAP0x4210블록 할당 해제TRIM/DISCARD (SSD)
COMPARE AND WRITE0x8916원자적(Atomic) 비교-쓰기클러스터 잠금(Lock)
팁: READ(10)WRITE(10)이 가장 빈번히 사용되며, 2TB 이상 디스크에서는 READ(16)/WRITE(16)으로 자동 전환됩니다. UNMAP은 파일시스템(Filesystem)의 fstrim 또는 discard 마운트(Mount) 옵션과 연결됩니다.
Initiator CDB Target Data-Out Data-In LUN (디바이스) Status + Sense Data ① Command → ② Data → ③ Status

Linux SCSI 스택 구조

Linux SCSI 서브시스템은 3계층으로 구성됩니다. Upper Layer가 블록/문자 디바이스 인터페이스를 제공하고, Mid Layer(scsi_mod)가 명령 관리·큐잉·오류 처리를 담당하며, Lower Layer의 LLD(Low-Level Driver)가 실제 HBA 하드웨어를 제어합니다.

Block Layer (blk-mq) Upper Layer sd (disk) sr (cdrom) st (tape) sg (generic) ses Mid Layer (scsi_mod) 명령 관리 · 큐잉 · 오류 처리(EH) · 장치 스캔 · 전송 클래스 Lower Layer (LLD) mpt3sas megaraid_sas hpsa libata (SATA) iscsi_tcp / qla2xxx HBA Hardware / Network

Upper Layer 드라이버

드라이버모듈디바이스 노드용도
sdsd_mod/dev/sd[a-z]디스크 (HDD, SSD, SAS, USB 스토리지)
srsr_mod/dev/sr[0-9]CD/DVD/Blu-ray
stst/dev/st[0-9]테이프 드라이브
sgsg/dev/sg[0-9]SCSI Generic — 사용자 공간에서 직접 CDB 전송
sessesSES 인클로저 서비스

SCSI Mid Layer (scsi_mod)

커널 모듈(Kernel Module) scsi_mod는 SCSI 서브시스템의 핵심입니다. 주요 책임:

Lower Layer — LLD (Low-Level Driver)

LLDHBA / 전송벤더
mpt3sasSAS 3xxx HBABroadcom (LSI)
megaraid_sasMegaRAID SASBroadcom (LSI)
hpsa / smartpqiSmart Array / SmartPQIHPE (Microchip)
aacraidAdaptec RAIDMicrosemi
qla2xxxFibre ChannelQLogic (Marvell)
lpfcFibre ChannelEmulex (Broadcom)
iscsi_tcpiSCSI (소프트웨어)Open-iSCSI
libata + ahciSATA → SCSI 변환(표준)
uasUSB Attached SCSI(표준)
scsi_debug가상 SCSI 디바이스테스트/개발용

핵심 구조체

SCSI 서브시스템의 동작을 이해하려면 4가지 핵심 구조체를 알아야 합니다.

scsi_host_template

LLD가 Mid Layer에 자신의 능력(capabilities)과 콜백(Callback)을 알리는 템플릿입니다. scsi_host_alloc() 호출 시 이 템플릿을 기반으로 Scsi_Host가 생성됩니다.

/* include/scsi/scsi_host.h — 핵심 필드만 발췌 */
struct scsi_host_template {
    const char  *name;              /* 드라이버 이름 */
    const char  *proc_name;         /* /proc/scsi/ 디렉터리명 */

    /* ── 필수 콜백 ── */
    int  (*queuecommand)(struct Scsi_Host *, struct scsi_cmnd *);
    /* CDB를 HBA에 제출 — 반드시 구현 */

    /* ── 오류 처리 콜백 ── */
    int  (*eh_abort_handler)(struct scsi_cmnd *);
    int  (*eh_device_reset_handler)(struct scsi_cmnd *);
    int  (*eh_target_reset_handler)(struct scsi_cmnd *);
    int  (*eh_host_reset_handler)(struct scsi_cmnd *);

    /* ── 디바이스 수명 주기 ── */
    int  (*slave_alloc)(struct scsi_device *);
    int  (*slave_configure)(struct scsi_device *);
    void (*slave_destroy)(struct scsi_device *);

    /* ── 큐 제한 ── */
    int  can_queue;                /* HBA가 동시에 처리 가능한 최대 명령 수 */
    int  cmd_per_lun;              /* LUN당 최대 동시 명령 */
    short sg_tablesize;            /* scatter-gather 세그먼트 최대 수 */
    unsigned short max_sectors;    /* 단일 명령 최대 섹터 수 */

    struct module *module;
};

Scsi_Host

HBA 인스턴스를 나타내며, scsi_host_alloc()으로 할당됩니다. LLD는 이 구조체의 hostdata[] 영역에 private 데이터를 저장합니다.

/* include/scsi/scsi_host.h — 핵심 필드만 발췌 */
struct Scsi_Host {
    struct scsi_host_template  *hostt;       /* 템플릿 포인터 */
    struct list_head           __devices;    /* scsi_device 리스트 */
    struct list_head           __targets;    /* scsi_target 리스트 */

    unsigned int  host_no;                   /* 고유 호스트 번호 */
    unsigned int  max_id;                     /* 최대 Target ID */
    unsigned int  max_lun;                    /* 최대 LUN */
    unsigned int  max_channel;                /* 최대 채널 */
    unsigned int  can_queue;                  /* 동시 명령 제한 */

    struct device  shost_dev;                /* sysfs 장치 */
    struct workqueue_struct  *tmf_work_q;   /* Task Management 워크큐 */

    unsigned long  hostdata[0]               /* LLD private data */
        __attribute__((aligned(sizeof(unsigned long))));
};

scsi_device

SCSI LUN 하나를 나타냅니다. 디바이스 스캔 시 INQUIRY 응답을 기반으로 Mid Layer가 생성합니다.

/* include/scsi/scsi_device.h — 핵심 필드만 발췌 */
struct scsi_device {
    struct Scsi_Host      *host;
    struct request_queue  *request_queue;
    struct scsi_target    *sdev_target;

    unsigned int  id;                      /* Target ID */
    u64           lun;                     /* Logical Unit Number */
    unsigned int  channel;                 /* SCSI 채널 */

    unsigned      sector_size;             /* 논리 섹터 크기 (보통 512) */
    unsigned char type;                    /* SCSI 디바이스 타입 (TYPE_DISK 등) */
    unsigned char scsi_level;              /* SCSI 버전 */
    char          vendor[8];               /* INQUIRY 벤더 */
    char          model[16];               /* INQUIRY 모델 */
    char          rev[4];                  /* INQUIRY 리비전 */

    unsigned int  queue_depth;             /* 동시 명령 큐 깊이 */
    struct device sdev_gendev;              /* sysfs 장치 */
};

scsi_cmnd

하나의 SCSI 명령을 나타내는 구조체입니다. blk-mq의 request에 내장(embed)되어 할당됩니다.

/* include/scsi/scsi_cmnd.h — 핵심 필드만 발췌 */
struct scsi_cmnd {
    struct scsi_device   *device;
    struct request       *request;         /* blk-mq request */

    unsigned char  cmnd[32];               /* CDB 버퍼 (최대 32바이트) */
    unsigned short cmd_len;                 /* CDB 길이 */

    unsigned int   result;                  /* 완료 상태 (host_byte | msg_byte | status_byte) */
    unsigned char  sense_buffer[96];        /* Sense Data */

    enum dma_data_direction  sc_data_direction;
    unsigned int   transfersize;            /* 전송 블록 크기 */
    struct scsi_data_buffer  sdb;           /* scatter-gather 데이터 */

    unsigned int   allowed;                 /* 최대 재시도 횟수 */
    int            retries;                 /* 현재 재시도 횟수 */
};

SCSI 명령 실행 경로

제출 경로 (Submit Path)

블록 I/O가 SCSI 명령으로 변환되어 HBA에 도달하기까지의 경로입니다:

  1. submit_bio() → blk-mq 소프트웨어 큐에 bio 삽입
  2. blk-mq가 bio를 struct request로 병합·변환
  3. scsi_queue_rq() — SCSI blk-mq ops의 .queue_rq 콜백
  4. scsi_dispatch_cmd() — CDB 구성, scsi_cmnd 준비
  5. hostt->queuecommand() — LLD에 명령 전달
  6. LLD가 HBA DMA 레지스터(Register)에 명령을 기록하거나 네트워크로 전송

완료 경로 (Completion Path)

HBA가 명령 완료를 통지하면 다음 경로로 처리됩니다:

  1. HBA 인터럽트(Interrupt) → LLD ISR(Interrupt Service Routine) 실행
  2. LLD가 scsi_done(scsi_cmnd) 호출
  3. scsi_decide_disposition() — 상태 판단 (성공/재시도/EH)
  4. 성공 시: scsi_finish_command()blk_mq_complete_request()
  5. 오류 시: scsi_eh_scmd_add() → EH 큐에 추가
Block Layer SCSI Mid LLD HBA scsi_queue_rq() queuecommand() DMA / Network 완료 경로 IRQ → ISR scsi_done() scsi_decide_disposition() blk_mq_complete_request() 제출 경로 완료 경로

blk-mq와 SCSI 연동

현대 커널의 메인라인 SCSI 경로는 blk-mq 기반으로 동작합니다. 다만 특정 구형 커널/벤더 포크에서는 예외가 있을 수 있으므로, 대상 트리에서 큐잉 경로를 확인해야 합니다. SCSI Mid Layer는 scsi_mq_ops를 blk-mq에 등록합니다:

/* drivers/scsi/scsi_lib.c */
static const struct blk_mq_ops scsi_mq_ops = {
    .queue_rq       = scsi_queue_rq,
    .complete       = scsi_complete,
    .timeout        = scsi_timeout,
    .init_request   = scsi_mq_init_request,
    .exit_request   = scsi_mq_exit_request,
    .map_queues     = scsi_map_queues,
};

/* scsi_cmnd는 blk-mq request의 driver private 영역에 내장됨 */
struct scsi_cmnd *scsi_cmd = blk_mq_rq_to_pdu(rq);
팁: can_queue는 HBA가 동시에 처리 가능한 명령 수를 결정하며, blk-mq의 queue_depth에 직접 영향을 줍니다. NVMe 수준의 높은 동시성이 필요한 SAS HBA에서는 이 값을 수천 이상으로 설정합니다.

SCSI Tagged Command Queuing

현대 SCSI 디바이스는 Tagged Command Queuing을 지원하여 여러 명령을 동시에 처리할 수 있습니다. 각 명령에는 고유한 태그가 부여되며, 디바이스는 순서에 관계없이 완료된 명령부터 응답할 수 있습니다(Out-of-Order Completion). 이는 디스크 헤드 스케줄링 최적화와 높은 IOPS에 필수적입니다.

Host (Initiator) Device Internal Queue Cmd Tag=1 (LBA 1000) Tag=1 | READ LBA 1000 Cmd Tag=2 (LBA 5000) Tag=2 | READ LBA 5000 Cmd Tag=3 (LBA 3000) Tag=3 | READ LBA 3000 ↓ 디바이스 내부에서 재정렬 ① Tag=1 (LBA 1000) 완료 ② Tag=3 (LBA 3000) 완료 ③ Tag=2 (LBA 5000) 완료 Response Tag=1 Response Tag=3 Response Tag=2 헤드 이동 최소화: 1000 → 3000 → 5000

SCSI-2부터는 Simple, Ordered, Head of Queue 태그 타입을 지원했으며, SCSI-3(SPC-3 이후)에서는 ACA(Auto Contingent Allegiance) 메커니즘도 추가되었습니다. 현대 리눅스 커널은 대부분 Simple 태그만 사용하며, blk-mq의 태그 할당과 연동됩니다.

팁: /sys/block/sda/device/queue_depth에서 디바이스별 큐 깊이를 확인하고, 워크로드에 따라 조정할 수 있습니다. SSD에서는 32-64, 엔터프라이즈 HDD에서는 128-256이 일반적입니다.

SCSI 오류 처리 (EH)

SCSI 오류 처리(Error Handling)는 타임아웃이나 CHECK CONDITION 등 오류 발생 시 에스컬레이션 단계별 복구를 시도하는 메커니즘입니다. 커널 스레드(Kernel Thread) scsi_eh_[N]가 EH를 담당합니다.

① Abort eh_abort_handler 실패 ② Device Reset eh_device_reset 실패 ③ Target Reset eh_target_reset 실패 ④ Host Reset eh_host_reset 성공 → 명령 재시도 실패 → 디바이스 오프라인 점점 넓은 범위를 리셋하며 복구를 시도 — 최소 영향 원칙

EH 동작 흐름

  1. 타임아웃 감지: blk-mq 타이머(Timer)가 scsi_timeout()을 호출
  2. EH 큐 등록: scsi_eh_scmd_add()가 실패한 명령을 EH 목록에 추가
  3. EH 스레드 기상: scsi_eh_[N] 커널 스레드가 깨어남
  4. 에스컬레이션: abort → device reset → target reset → host reset 순서로 시도
  5. 복구 완료: 성공한 단계에서 실패 명령을 재시도하거나, 모든 단계 실패 시 디바이스를 오프라인으로 전환
/* drivers/scsi/scsi_error.c — EH 에스컬레이션 핵심 로직 */
static void scsi_unjam_host(struct Scsi_Host *shost)
{
    /* 1단계: 명령 abort 시도 */
    if (!scsi_eh_abort_cmds(&eh_work_q, &done_q))
        goto done;

    /* 2단계: 디바이스 리셋 */
    if (!scsi_eh_bus_device_reset(shost, &eh_work_q, &done_q))
        goto done;

    /* 3단계: 타겟 리셋 */
    if (!scsi_eh_target_reset(shost, &eh_work_q, &done_q))
        goto done;

    /* 4단계: 호스트 리셋 (가장 파괴적) */
    scsi_eh_bus_reset(shost, &eh_work_q, &done_q);
    scsi_eh_host_reset(shost, &eh_work_q, &done_q);

done:
    scsi_eh_flush_done_q(&done_q);  /* 복구된 명령 재발행 */
}

Sense Data 해석

CHECK CONDITION 상태 시 Target이 반환하는 Sense Data는 3단계 키로 오류를 분류합니다:

Sense Key의미대표적 원인
NO SENSE0x0오류 없음정보성 응답
RECOVERED ERROR0x1복구된 오류ECC 교정 성공
NOT READY0x2디바이스 준비 안 됨스핀업 중, 매체 없음
MEDIUM ERROR0x3매체 오류불량 섹터, 읽기 불가
HARDWARE ERROR0x4하드웨어 오류컨트롤러 고장
ILLEGAL REQUEST0x5잘못된 명령/파라미터지원하지 않는 CDB
UNIT ATTENTION0x6상태 변경 알림매체 교체, 리셋 발생
DATA PROTECT0x7쓰기 보호(Write Protection)읽기 전용(Read-Only) 매체
ABORTED COMMAND0xB명령 중단패리티 오류, 타임아웃
주의: UNIT ATTENTION은 오류가 아닌 알림입니다. 매체 교체, 버스 리셋 후 첫 명령에서 발생하며, 보통 자동 재시도로 해결됩니다. 하지만 반복 발생 시 하드웨어 문제를 의심해야 합니다.

SCSI 전송 프로토콜

SCSI 명령은 다양한 물리/네트워크 전송 계층을 통해 전달됩니다. Linux 커널은 각 전송 방식에 대한 전송 클래스(Transport Class)를 sysfs로 노출합니다.

전송매체최대 대역폭최대 거리주요 용도커널 모듈
SAS직렬 구리22.5 Gb/s~10m서버/DASscsi_transport_sas
FC광섬유/구리128 Gb/s~10kmSANscsi_transport_fc
iSCSITCP/IP네트워크 의존무제한IP SANscsi_transport_iscsi
UASUSB20 Gb/s~5m외장uas
SATA직렬 구리6 Gb/s~1m데스크톱/서버libata + ahci

SAS (Serial Attached SCSI)

엔터프라이즈 스토리지의 표준 전송입니다. Expander를 통한 팬아웃, 듀얼 포트, SATA 디바이스 호환이 특징입니다. 커널에서는 scsi_transport_sas 클래스가 SAS 주소(WWN), PHY 속성, Expander 토폴로지(Topology)를 sysfs에 노출합니다.

Fibre Channel (FC)

대규모 SAN(Storage Area Network) 환경에서 사용됩니다. FC 스위치를 통한 Fabric 토폴로지가 일반적이며, WWNN/WWPN으로 노드와 포트를 식별합니다. scsi_transport_fc 클래스가 포트 상태, 속도, 통계를 관리합니다.

iSCSI (개요)

TCP/IP 네트워크 위에서 SCSI 명령을 전달합니다. 기존 이더넷 인프라를 활용할 수 있어 비용 효율적입니다. 자세한 내용은 iSCSI 섹션을 참조하세요.

UAS (USB Attached SCSI)

USB BOT(Bulk-Only Transport)를 대체하는 프로토콜로, 명령 큐잉과 스트림 파이프를 지원하여 USB 스토리지 성능을 크게 향상시킵니다. uas 모듈이 담당합니다.

SATA (libata)

SATA 디바이스는 원래 ATA 프로토콜을 사용하지만, Linux에서는 libata가 ATA 명령을 SCSI 명령으로 변환(SAT — SCSI-ATA Translation)하여 SCSI 스택을 그대로 활용합니다. 따라서 SATA HDD/SSD도 /dev/sd*로 나타납니다.

Block Layer (blk-mq) SCSI Mid Layer (scsi_mod) sd_mod → /dev/sda libata SAT (SCSI-ATA Translation) READ(10) → READ DMA EXT, WRITE(10) → WRITE DMA EXT INQUIRY → IDENTIFY DEVICE AHCI Driver (ahci.ko) SATA HDD/SSD SCSI 명령 변환 계층 ATA 명령
팁: libata 덕분에 SATA 디스크와 SCSI/SAS 디스크가 동일한 sd 드라이버와 /dev/sd* 인터페이스를 공유합니다. smartctl -d ata /dev/sda로 ATA 네이티브 명령을 직접 실행할 수도 있습니다.

iSCSI

iSCSI(Internet Small Computer System Interface)는 TCP/IP 네트워크를 통해 SCSI 명령을 캡슐화(Encapsulation)하여 전송하는 프로토콜입니다. RFC 3720(2004)에서 정의되었으며, RFC 7143(2014)에서 통합 개정되었습니다.

iSCSI 프로토콜 기초

iSCSI는 기존 이더넷 인프라 위에서 블록 스토리지 접근을 제공하므로, Fibre Channel 대비 낮은 비용으로 SAN을 구축할 수 있습니다. 주요 개념:

iSCSI Discovery 절차

iSCSI Initiator가 네트워크상의 Target을 발견하는 방법에는 여러 가지가 있습니다. 가장 일반적인 SendTargets Discovery 흐름은 다음과 같습니다:

Initiator Target (Discovery Portal) ① TCP 연결 TCP SYN → 3260 ② Login Login Request (SessionType=Discovery) Login Response (Success) ③ SendTargets Text Request: SendTargets=All Text Response: TargetName, TargetAddress 목록 TargetName=iqn.2023-01... TargetAddress=192.168... TargetName=iqn.2023-02... ④ Logout Logout Request

Discovery가 완료되면 Initiator는 얻은 Target IQN과 Portal 정보를 사용하여 Normal Session을 수립하고 실제 SCSI 명령을 전송합니다. 다른 Discovery 방법으로는 iSNS(Internet Storage Name Service) 서버를 이용한 중앙집중식 Discovery, SLP(Service Location Protocol) 등이 있습니다.

PDU 구조

모든 iSCSI 통신은 PDU 단위로 이루어집니다. PDU는 48바이트 BHS(Basic Header Segment)로 시작합니다:

PDU 타입Opcode방향설명
Login Request/Response0x03/0x23I↔T인증 및 세션 협상
SCSI Command0x01I→TCDB 전달
SCSI Response0x21T→I명령 완료 상태
Data-Out / Data-In0x05/0x25I→T / T→I데이터 전송
NOP-Out / NOP-In0x00/0x20I↔T연결 유지 (keepalive)
Text Request/Response0x04/0x24I↔TDiscovery, 파라미터 교환
Logout Request/Response0x06/0x26I↔T세션/연결 종료
TMF (Task Mgmt)0x02/0x22I→T태스크(Task) abort/리셋

세션 관리

iSCSI 세션은 3단계로 수립됩니다:

Initiator Target ① Discovery SendTargets (Text Request) Target IQN + Portal 목록 ② Login Login Request (CHAP 인증, 파라미터 협상) Login Response (TSIH, MaxBurstLength 등) ③ Full Feature SCSI Command PDU (CDB) Data-In PDU + SCSI Response PDU TCP 3260 포트 (또는 커스텀 포트)

Linux iSCSI Initiator — open-iscsi

open-iscsi는 Linux의 표준 iSCSI Initiator 구현입니다. 커널 모듈(iscsi_tcp, libiscsi)과 사용자 공간 데몬(iscsid)으로 구성됩니다.

# 1. Target 검색 (Discovery)
$ iscsiadm -m discovery -t sendtargets -p 192.168.1.100:3260
192.168.1.100:3260,1 iqn.2024-01.com.example:storage.lun0

# 2. Target에 로그인 (세션 수립)
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    -p 192.168.1.100:3260 --login

# 3. 세션 상태 확인
$ iscsiadm -m session -P 3
Target: iqn.2024-01.com.example:storage.lun0
    Current Portal: 192.168.1.100:3260,1
    Attached scsi disk sdb   State: running

# 4. 자동 로그인 설정 (부팅 시 자동 연결)
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    -p 192.168.1.100:3260 --op update \
    -n node.startup -v automatic

# 5. 로그아웃
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    -p 192.168.1.100:3260 --logout

Linux iSCSI Target — LIO

LIO(Linux-IO Target)는 커널에 내장된 iSCSI Target 프레임워크입니다. targetcli를 사용하여 설정합니다.

# targetcli를 사용한 iSCSI Target 설정 예제

# 1. 블록 디바이스를 백스토어로 등록
$ targetcli
/> /backstores/block create myblock /dev/sdc

# 2. iSCSI Target 생성
/> /iscsi create iqn.2024-01.com.example:storage.lun0

# 3. LUN 매핑
/> /iscsi/iqn.2024-01.com.example:storage.lun0/tpg1/luns \
    create /backstores/block/myblock

# 4. ACL 설정 (Initiator IQN 허용)
/> /iscsi/iqn.2024-01.com.example:storage.lun0/tpg1/acls \
    create iqn.2024-01.com.example:client1

# 5. 포털 확인 (기본 0.0.0.0:3260)
/> /iscsi/iqn.2024-01.com.example:storage.lun0/tpg1/portals ls

# 6. 설정 저장
/> saveconfig
/> exit
팁: LIO는 iSCSI 외에도 FC(tcm_fc), SRP(ib_srpt), vhost(tcm_vhost) 등 다양한 fabric을 지원하는 통합 Target 프레임워크입니다. /sys/kernel/config/target/에서 configfs로 직접 설정도 가능합니다.

iSER (iSCSI Extensions for RDMA)

iSER은 InfiniBand/RoCE RDMA 전송 위에서 iSCSI를 실행하여, TCP 오버헤드(Overhead)를 제거하고 제로카피 전송을 가능하게 합니다. ib_iser(Initiator) 및 ib_isert(Target) 모듈이 담당합니다. 일반 iSCSI 대비 대역폭과 레이턴시에서 큰 이점이 있으며, HPC/AI 스토리지에서 활용됩니다.

iSCSI 성능 튜닝

파라미터기본값권장값 (10GbE+)설명
node.session.cmds_max128256–1024세션당 최대 동시 명령
node.session.queue_depth3264–256LUN당 큐 깊이
node.conn[0].iscsi.MaxRecvDataSegmentLength262144262144최대 수신 데이터 세그먼트 (바이트)
node.conn[0].iscsi.FirstBurstLength65536262144비솔리시티드(unsolicited) 데이터 최대 크기
node.conn[0].iscsi.MaxBurstLength1677619216776192단일 Data-Out 시퀀스 최대 크기
node.session.nr_sessions12–4멀티세션 (MC/S)
# 파라미터 조정 예시
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    --op update -n node.session.queue_depth -v 128
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    --op update -n node.session.cmds_max -v 512

# Jumbo Frame 활성화 (네트워크 측)
$ ip link set eth0 mtu 9000
경고: 프로덕션 환경에서 iSCSI를 사용할 때 반드시 CHAP 인증을 설정하세요. 인증 없이 운영하면 네트워크에 접근 가능한 누구나 스토리지에 접근할 수 있습니다. 또한 스토리지 트래픽을 별도 VLAN으로 분리하는 것을 권장합니다.

LIO 타겟 프레임워크 개요

LIO(Linux I/O Target)는 리눅스 커널 2.6.38부터 메인라인에 포함된 범용 스토리지 타겟 프레임워크입니다. iSCSI 이니시에이터(open-iscsi)와 짝을 이루어, 리눅스 서버를 SAN 스토리지 타겟으로 구성할 수 있습니다.

기술 문서: LIO의 전체 아키텍처, NVMe-oF 타겟, ALUA, Persistent Reservations, 커널 소스 구조는 LIO 타겟 프레임워크 문서를 참고하세요.

configfs 기반 설정 흐름

LIO는 /sys/kernel/config/target/ configfs를 통해 설정됩니다. targetcli는 이 인터페이스를 래핑한 사용자 친화적 도구입니다.

# targetcli 핵심 명령 예제

# 1. Block 백엔드 생성 (/dev/sdb 블록 디바이스)
$ targetcli /backstores/block create name=lun0 dev=/dev/sdb

# 2. iSCSI 타겟 생성
$ targetcli /iscsi create iqn.2024-01.com.example:storage.lun0

# 3. Network Portal 추가 (포트 3260)
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/portals/ create

# 4. LUN 매핑
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/luns/ create \
    /backstores/block/lun0

# 5. 이니시에이터 ACL 설정
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/ create \
    iqn.1991-05.com.redhat:client

# 6. 설정 저장
$ targetcli saveconfig

iSCSI 타겟 단계별 설정

# 이니시에이터(클라이언트)에서 타겟 발견
$ iscsiadm -m discovery -t sendtargets -p 192.168.1.100

# 타겟에 로그인
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    -p 192.168.1.100 --login

# 연결된 iSCSI 디바이스 확인
$ lsscsi | grep disk
$ ls /dev/sd*

# LIO 타겟 상태 확인
$ targetcli ls /

# 활성 iSCSI 세션 확인
$ cat /proc/net/iscsi/session 2>/dev/null || \
  ls /sys/kernel/config/target/iscsi/

LIO Fabric 모듈 비교

Fabric전송포트커널 모듈용도
iSCSITCP/IP3260iscsi_target_mod범용 SAN (이더넷)
FCFibre Channel-tcm_qla2xxx엔터프라이즈 SAN
NVMe-oF/TCPTCP/IP4420nvmet-tcp저레이턴시 NVMe SAN
NVMe-oF/RDMAInfiniBand/RoCE4420nvmet-rdma초저레이턴시 SAN

SCSI 드라이버 스켈레톤

최소한의 가상 SCSI HBA 드라이버 예제입니다. 실제 하드웨어 대신 요청을 즉시 완료하는 구조입니다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>

#define DRIVER_NAME   "my_scsi_hba"
#define MAX_CMDS      64
#define MAX_SECTORS   1024
#define SG_TABLESIZE  128

/* ── queuecommand: CDB를 HBA에 제출 ── */
static int my_queuecommand(struct Scsi_Host *shost,
                            struct scsi_cmnd *cmd)
{
    /* 실제 드라이버: DMA 매핑 → HBA 레지스터/메일박스에 CDB 기록 */
    /* 여기서는 즉시 완료 시뮬레이션 */
    cmd->result = (DID_OK << 16);
    scsi_done(cmd);
    return 0;
}

/* ── EH 콜백 ── */
static int my_eh_abort(struct scsi_cmnd *cmd)
{
    dev_warn(&cmd->device->sdev_gendev, "aborting cmd\\n");
    return SUCCESS;
}

static int my_eh_host_reset(struct scsi_cmnd *cmd)
{
    dev_warn(&cmd->device->sdev_gendev, "host reset\\n");
    return SUCCESS;
}

/* ── Host Template ── */
static struct scsi_host_template my_sht = {
    .module                 = THIS_MODULE,
    .name                   = DRIVER_NAME,
    .proc_name              = DRIVER_NAME,
    .queuecommand           = my_queuecommand,
    .eh_abort_handler       = my_eh_abort,
    .eh_host_reset_handler  = my_eh_host_reset,
    .can_queue              = MAX_CMDS,
    .this_id                = -1,
    .sg_tablesize           = SG_TABLESIZE,
    .max_sectors            = MAX_SECTORS,
    .cmd_per_lun            = 32,
};

static struct Scsi_Host *my_shost;

static int __init my_scsi_init(void)
{
    my_shost = scsi_host_alloc(&my_sht, 0);
    if (!my_shost)
        return -ENOMEM;

    if (scsi_add_host(my_shost, NULL)) {
        scsi_host_put(my_shost);
        return -ENODEV;
    }
    scsi_scan_host(my_shost);
    pr_info("my_scsi_hba: loaded\\n");
    return 0;
}

static void __exit my_scsi_exit(void)
{
    scsi_remove_host(my_shost);
    scsi_host_put(my_shost);
    pr_info("my_scsi_hba: unloaded\\n");
}

module_init(my_scsi_init);
module_exit(my_scsi_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal SCSI HBA skeleton driver");

sysfs 인터페이스

SCSI 서브시스템은 sysfs를 통해 풍부한 디바이스 정보와 제어 인터페이스를 제공합니다.

경로설명
/sys/class/scsi_host/host<N>/HBA 속성 (scan, state, proc_name 등)
/sys/class/scsi_device/<H:C:T:L>/SCSI 디바이스 속성
/sys/block/sd<X>/device/블록 디바이스에서 SCSI 디바이스로의 링크
/sys/class/scsi_disk/SCSI 디스크 클래스
/sys/class/iscsi_session/iSCSI 세션 속성
/sys/class/iscsi_connection/iSCSI 연결 속성
/sys/class/sas_phy/SAS PHY 속성
/sys/class/fc_host/FC HBA 포트 속성
# H:C:T:L 형식으로 디바이스 식별 (Host:Channel:Target:LUN)

# HBA 재스캔 — 새로 추가된 디바이스 탐지
$ echo "- - -" > /sys/class/scsi_host/host0/scan

# 특정 LUN 재스캔
$ echo "0 0 1" > /sys/class/scsi_host/host0/scan  # Channel:0, Target:0, LUN:1

# 디바이스 큐 깊이 조정
$ echo 64 > /sys/block/sda/device/queue_depth

# 디바이스 안전 제거 (hot-remove)
$ echo 1 > /sys/block/sdb/device/delete

# SCSI 디바이스 상태 확인
$ cat /sys/block/sda/device/state    # running / offline / blocked
$ cat /sys/block/sda/device/vendor
$ cat /sys/block/sda/device/model

디버깅(Debugging) 도구

lsscsi

# 모든 SCSI 디바이스 목록
$ lsscsi
[0:0:0:0]    disk    ATA      SAMSUNG MZ7LH1T9 404Q  /dev/sda
[2:0:0:0]    disk    LIO-ORG  block0           4.0   /dev/sdb

# 상세 정보 (-g: sg 디바이스, -t: 전송, -L: LUN 정보)
$ lsscsi -gtvL
# HBA 호스트 정보
$ lsscsi -H

sg3_utils

# INQUIRY 명령 전송
$ sg_inq /dev/sda
    Vendor identification: ATA
    Product identification: SAMSUNG MZ7LH1T9
    Product revision level: 404Q

# 디바이스 상태 확인 (TEST UNIT READY)
$ sg_turs /dev/sda

# Sense Data 직접 조회
$ sg_requests /dev/sda

# 로그 페이지 조회 (에러 카운터 등)
$ sg_logs /dev/sda

# SMART/Health 정보 (SBC)
$ sg_sat_identify /dev/sda

커널 로깅

# SCSI 로깅 레벨 설정 (비트마스크)
# 0x1=error, 0x2=timeout, 0x4=scan, 0x8=mlqueue, 0x10=mlcomplete
# 0x20=llqueue, 0x40=llcomplete, 0x80=hlqueue, 0x100=hlcomplete
$ echo 0x1ff > /proc/sys/dev/scsi/logging_level

# scsi_debug 가상 디바이스로 테스트
$ modprobe scsi_debug num_tgts=1 max_luns=2 dev_size_mb=256
$ lsscsi | grep scsi_debug

# 커널 메시지 필터링
$ dmesg | grep -i scsi
$ journalctl -k --grep="scsi|sd |sr "

/proc/scsi

# SCSI 서브시스템 요약
$ cat /proc/scsi/scsi
Attached devices:
Host: scsi0 Channel: 00 Id: 00 Lun: 00
  Vendor: ATA      Model: SAMSUNG MZ7LH1T9 Rev: 404Q
  Type:   Direct-Access                    ANSI  SCSI revision: 05

# 특정 HBA 드라이버 정보
$ ls /proc/scsi/
$ cat /proc/scsi/sg/devices

주요 Kconfig 옵션 및 소스 구조

주요 Kconfig 옵션

옵션모듈설명
CONFIG_SCSIscsi_modSCSI 서브시스템 핵심 (필수)
CONFIG_BLK_DEV_SDsd_modSCSI 디스크 (sd) 드라이버
CONFIG_CHR_DEV_SGsgSCSI Generic 인터페이스
CONFIG_CHR_DEV_STstSCSI 테이프 드라이버
CONFIG_BLK_DEV_SRsr_modSCSI CD-ROM 드라이버
CONFIG_SCSI_SAS_ATTRSscsi_transport_sasSAS 전송 클래스
CONFIG_SCSI_FC_ATTRSscsi_transport_fcFC 전송 클래스
CONFIG_ISCSI_TCPiscsi_tcp소프트웨어 iSCSI Initiator
CONFIG_ISCSI_TARGETiscsi_target_modLIO iSCSI Target
CONFIG_SCSI_DEBUGscsi_debug가상 SCSI 디바이스 (테스트)
CONFIG_SATA_AHCIahciAHCI SATA 컨트롤러

커널 소스 디렉터리 구조

경로내용
drivers/scsi/SCSI 서브시스템 핵심 + LLD 드라이버
drivers/scsi/scsi_lib.c블록 계층 연동, scsi_mq_ops
drivers/scsi/scsi_error.c오류 처리(EH) 로직
drivers/scsi/scsi_scan.c디바이스 스캔/탐지
drivers/scsi/scsi_sysfs.csysfs 인터페이스
drivers/scsi/sd.cSCSI 디스크 (sd) 드라이버
drivers/scsi/sg.cSCSI Generic 드라이버
drivers/scsi/scsi_transport_*.c전송 클래스 (SAS, FC, iSCSI, SRP)
drivers/target/LIO Target 프레임워크
drivers/target/iscsi/iSCSI Target 구현
drivers/ata/libata + AHCI/SATA 드라이버
include/scsi/SCSI 헤더 파일

SCSI Error Handling

기본적인 EH 에스컬레이션 개요는 앞 절에서 다루었습니다. 이 절에서는 scsi_eh_* 함수 체인의 내부 구현, EH 스레드 수명주기, 타임아웃 정책, 그리고 LLD가 EH를 커스터마이즈하는 방법을 심층적으로 분석합니다.

EH 스레드 수명주기

SCSI 호스트가 등록될 때(scsi_add_host()), 커널 스레드 scsi_eh_<N>가 생성됩니다. 이 스레드는 평소에는 TASK_INTERRUPTIBLE 상태로 잠들어 있다가, 오류가 발생하면 깨어나 복구를 수행합니다.

Sleep TASK_INTERRUPTIBLE 오류 발생 ① Abort 시도 scsi_eh_abort_cmds() ② Device Reset scsi_eh_bus_device_reset() ③ Bus Reset scsi_eh_bus_reset() ④ Host Reset 실패 실패 실패 명령 재시도 / 완료 scsi_eh_flush_done_q() 성공 복구 완료 → 다시 Sleep
그림: EH 스레드 상태 전이와 에스컬레이션 흐름

주요 EH 함수 체인

함수역할호출 시점
scsi_timeout()blk-mq 타이머 콜백, 명령 타임아웃 판단요청 타임아웃 만료
scsi_eh_scmd_add()실패 명령을 EH 큐에 등록, EH 스레드 기상타임아웃 또는 CHECK CONDITION
scsi_error_handler()EH 스레드 메인 루프스레드 생성 시 시작
scsi_unjam_host()에스컬레이션 로직 총괄EH 스레드 내부
scsi_eh_abort_cmds()개별 명령 abort1단계
scsi_eh_bus_device_reset()LUN/디바이스 리셋2단계
scsi_eh_target_reset()타겟 전체 리셋3단계 (선택적)
scsi_eh_bus_reset()SCSI 버스 리셋4단계
scsi_eh_host_reset()HBA 전체 리셋5단계 (최후 수단)
scsi_eh_flush_done_q()복구된 명령 재발행 또는 에러 반환EH 완료 시

타임아웃 정책과 커스터마이즈

SCSI 명령 타임아웃은 scsi_device->timeout 필드(기본 30초)와 블록 계층의 rq_timeout이 함께 결정합니다. LLD는 scsi_host_template의 콜백으로 타임아웃 동작을 커스터마이즈할 수 있습니다:

/* LLD가 EH를 커스터마이즈하는 scsi_host_template 콜백들 */
struct scsi_host_template my_template = {
    /* 개별 명령 abort — 성공 시 SUCCESS, 실패 시 FAILED */
    .eh_abort_handler    = my_abort,

    /* 디바이스(LUN) 레벨 리셋 */
    .eh_device_reset_handler = my_device_reset,

    /* 타겟(ID) 레벨 리셋 — 같은 타겟의 모든 LUN 영향 */
    .eh_target_reset_handler = my_target_reset,

    /* 버스 리셋 — 같은 채널의 모든 디바이스 영향 */
    .eh_bus_reset_handler    = my_bus_reset,

    /* 호스트 리셋 — HBA 전체 재초기화 */
    .eh_host_reset_handler   = my_host_reset,

    /* 타임아웃 시 blk-mq가 호출 — BLK_EH_DONE / BLK_EH_RESET_TIMER */
    .eh_timed_out        = my_timed_out,

    .cmd_per_lun = 32,
    .can_queue   = 256,
};

복구 패턴과 실무 대응

# EH 데드라인 설정 (초 단위, 0=무제한)
$ echo 30 > /sys/class/scsi_host/host0/eh_deadline

# 디바이스별 타임아웃 확인/설정
$ cat /sys/block/sda/device/timeout     # 기본 30초
$ echo 60 > /sys/block/sda/device/timeout  # 60초로 변경

# EH 동작 실시간 관찰
$ echo 0x3 > /proc/sys/dev/scsi/logging_level  # error+timeout
$ dmesg -w | grep -i "scsi_eh\|abort\|reset"

Tagged Command Queuing과 blk-mq 매핑(Mapping)

TCQ(Tagged Command Queuing)는 SCSI 디바이스에 여러 명령을 동시에 전송하여 병렬 처리하는 메커니즘입니다. 초기에는 레거시 single-queue 모델이었으나, Linux 3.16 이후 blk-mq(Multi-Queue Block I/O)로 전환되면서 SCSI 스택의 큐잉 모델이 근본적으로 변경되었습니다.

레거시 (단일 큐) CPU 0 요청 CPU 1 요청 CPU N 요청 단일 request_queue spinlock 경합! HBA (1 hw queue) blk-mq (멀티 큐) sw queue 0 sw queue 1 sw queue N hctx 0 hctx 1 HBA (N hw queues) 높은 lock 경합 NUMA 불친화 Per-CPU 소프트웨어 큐 NUMA 친화, 낮은 지연 진화
그림: 레거시 단일 큐에서 blk-mq 멀티 큐로의 진화

SCSI blk-mq 구조

SCSI 서브시스템은 scsi_mq_ops를 블록 계층에 등록하여 blk-mq와 통합됩니다. 핵심 매핑 관계는 다음과 같습니다:

blk-mq 개념SCSI 대응설명
blk_mq_hw_ctx (hctx)HBA 하드웨어 큐LLD가 nr_hw_queues로 개수 결정
blk_mq_ctx (ctx)Per-CPU 소프트웨어 큐CPU별 요청 수집
blk_mq_tag_setScsi_Host->tag_set태그 관리 (큐 깊이 = 태그 수)
.queue_rq()scsi_queue_rq()요청을 SCSI 명령으로 변환, LLD에 전달
/* SCSI blk-mq ops — drivers/scsi/scsi_lib.c */
static const struct blk_mq_ops scsi_mq_ops = {
    .queue_rq      = scsi_queue_rq,     /* 요청 → scsi_cmnd 변환 → LLD dispatch */
    .complete      = scsi_complete,     /* 완료 처리 */
    .timeout       = scsi_timeout,      /* 타임아웃 → EH 트리거 */
    .init_request  = scsi_mq_init_request,
    .exit_request  = scsi_mq_exit_request,
    .map_queues    = scsi_map_queues,   /* CPU → hctx 매핑 */
};

/* LLD가 멀티 큐를 지원하는 예시 */
static struct scsi_host_template my_template = {
    .nr_hw_queues  = 4,      /* 4개 하드웨어 큐 */
    .can_queue     = 1024,   /* 전체 최대 outstanding 명령 */
    .cmd_per_lun   = 64,     /* LUN당 최대 명령 */
    .map_queues    = my_map_queues,  /* NUMA 인식 매핑 */
};

큐 깊이 튜닝

SCSI 디바이스의 큐 깊이는 성능에 직접적인 영향을 미칩니다. 큐가 너무 얕으면 디바이스 활용도가 떨어지고, 너무 깊으면 지연(Latency)이 증가합니다:

# 현재 큐 깊이 확인
$ cat /sys/block/sda/device/queue_depth
32

# 큐 깊이 변경
$ echo 128 > /sys/block/sda/device/queue_depth

# blk-mq 큐 매핑 확인
$ cat /sys/block/sda/mq/*/cpu_list
# hctx별 CPU 매핑을 보여줌

# SCSI 태그 사용 현황
$ cat /sys/kernel/debug/block/sda/hctx*/tags
팁: SAS SSD는 queue_depth=64-128, 엔터프라이즈 HDD는 queue_depth=32-64가 일반적입니다. TASK SET FULL 상태가 빈번하면 큐 깊이를 줄여야 합니다.

SAS 토폴로지

SAS(Serial Attached SCSI)는 엔터프라이즈 스토리지의 표준 직렬 인터커넥트입니다. 점대점 연결, Expander를 통한 팬아웃, 듀얼 포트 고가용성이 특징이며, SATA 디바이스와 하위 호환됩니다.

SAS HBA 4× Wide Port 50:00:00:00:00:00:00:01 4× SAS link SAS Expander 36포트 (24 Narrow) 50:00:00:00:00:00:00:10 SMP 프로토콜 라우팅 테이블 관리 SAS SSD Dual-port SAS HDD Dual-port SATA HDD STP 브리지 Edge Expander 추가 디스크 연결 SAS ×N SATA ×N
그림: SAS 토폴로지 — HBA, Expander, Wide/Narrow Port 구조

SAS 핵심 개념

개념설명
SAS 주소 (WWN)64비트 고유 식별자. 50:xx:xx:xx:xx:xx:xx:xx 형식. PHY, Expander, 디바이스마다 할당
PHY단일 직렬 링크. 하나의 물리적 연결 단위
Wide Port동일 SAS 주소로 묶인 여러 PHY. 대역폭 집약 (예: 4× Wide = 4배 대역폭)
Narrow Port단일 PHY로 구성된 포트
ExpanderSAS 스위치. Fanout Expander(상위)와 Edge Expander(하위)로 구분
SMP (SAS Management Protocol)Expander 관리용 프로토콜. 토폴로지 탐색, PHY 제어
STP (SATA Tunneling Protocol)SAS 도메인에서 SATA 디바이스를 투명하게 연결하는 프로토콜
Dual-portSAS 디바이스의 두 포트를 서로 다른 경로로 연결하여 고가용성 확보

libsas 프레임워크

Linux 커널의 libsas(drivers/scsi/libsas/)는 SAS HBA 드라이버 개발을 위한 공용 프레임워크입니다. 토폴로지 탐색, Expander 관리, PHY 이벤트 처리를 담당합니다:

/* libsas가 LLD에 요구하는 콜백 — struct sas_ha_struct */
struct sas_ha_struct {
    /* PHY 제어 */
    int (*phy_enable)(struct asd_sas_phy *, int enable);
    int (*phy_reset)(struct asd_sas_phy *, int hard_reset);
    int (*phy_set_link_rate)(struct asd_sas_phy *,
                             struct sas_phy_linkrates *);

    /* 태스크 실행 (SCSI 명령 전송) */
    int (*lldd_execute_task)(struct sas_task *, gfp_t);

    /* 디바이스 탐색 이벤트 */
    void (*lldd_dev_found)(struct domain_device *);
    void (*lldd_dev_gone)(struct domain_device *);
};
# SAS 토폴로지 확인
$ lsscsi -t
[0:0:0:0]    disk    sas:0x5000c50098765432  /dev/sda

# SAS PHY 속성
$ cat /sys/class/sas_phy/phy-0:0/negotiated_linkrate
12.0 Gbit

# Expander 정보
$ cat /sys/class/sas_expander/expander-0:0/product_id
$ smp_discover /dev/bsg/expander-0:0

# SAS 주소 확인
$ cat /sys/class/sas_device/end_device-0:0:0/sas_address
0x5000c50098765432

SCSI Reservation

SCSI Reservation은 여러 Initiator가 같은 LUN에 접근하는 공유 스토리지 환경에서 배타적 또는 공유 접근을 제어하는 메커니즘입니다. 레거시 RESERVE/RELEASE에서 발전한 Persistent Reservation (PR)이 현대 클러스터의 표준입니다.

Reservation 유형 비교

구분레거시 RESERVE/RELEASEPersistent Reservation (PR)
명령RESERVE(6/10), RELEASE(6/10)PERSISTENT RESERVE IN/OUT (0x5E/0x5F)
Initiator 식별I_T Nexus (포트 기반)Registration Key (소프트웨어 기반)
리셋 후 유지소멸유지 (Persistent)
공유 접근불가 (배타적만)Write Exclusive, Exclusive Access 등 다양한 모드
클러스터 지원미약펜싱, 다중 등록, Preempt & Abort

PR 타입과 활용

PR 타입코드설명사용 사례
Write Exclusive0x01등록된 한 Initiator만 쓰기 가능단일 마스터 쓰기
Exclusive Access0x03등록된 한 Initiator만 읽기/쓰기 가능배타적 단일 접근
Write Exclusive, Registrants Only0x05등록된 모든 Initiator가 쓰기 가능클러스터 공유 쓰기
Exclusive Access, Registrants Only0x06등록된 Initiator만 접근 가능클러스터 배타적 접근
Write Exclusive, All Registrants0x07모든 등록자가 쓰기 가능, 예약 보유자 개념 없음다중 등록자 쓰기
Exclusive Access, All Registrants0x08모든 등록자만 접근 가능다중 등록자 배타적

클러스터 펜싱 활용

HA 클러스터(Pacemaker, VCS 등)는 SCSI PR을 스토리지 기반 펜싱에 활용합니다. 장애 노드를 공유 스토리지에서 격리(Isolation)하여 split-brain을 방지합니다:

# sg_persist를 이용한 PR 조작 (sg3_utils)

# 1. Registration Key 등록
$ sg_persist --out --register --param-sark=0xABCD0001 /dev/sda

# 2. 등록된 키 확인
$ sg_persist --in --read-keys /dev/sda
  PR generation=1, 2 registered reservation key(s):
    0xabcd0001
    0xabcd0002

# 3. Reservation 획득 (Write Exclusive, Registrants Only)
$ sg_persist --out --reserve --param-rk=0xABCD0001 --prout-type=5 /dev/sda

# 4. 현재 Reservation 확인
$ sg_persist --in --read-reservation /dev/sda

# 5. 장애 노드 강제 추방 (Preempt & Abort)
$ sg_persist --out --preempt-abort --param-rk=0xABCD0001 \
    --param-sark=0xABCD0002 --prout-type=5 /dev/sda

# Pacemaker에서 SCSI PR 펜싱 설정
# stonith:fence_scsi 리소스 사용
주의: SCSI PR은 전송 프로토콜에 독립적이지만, iSCSI 환경에서는 세션 재연결 시 Registration Key 복원이 필요합니다. pr_preempt은 기존 예약을 즉시 무효화(Invalidation)하므로, 프로덕션에서는 반드시 정확한 키를 지정해야 합니다.

Sense Data 해석

SCSI 디바이스가 CHECK CONDITION 상태를 반환하면, Sense Data를 통해 오류의 상세 원인을 제공합니다. Sense Data는 Sense Key, ASC(Additional Sense Code), ASCQ(Additional Sense Code Qualifier)의 3단계 계층으로 오류를 분류합니다.

고정 형식 Sense Data (70h/71h) 바이트 레이아웃 Byte 0 Response Code Byte 1 Obsolete Byte 2 Sense Key Byte 3-6 Information Byte 7 추가 길이 Byte 12 ASC Byte 13 ASCQ Byte 14+ FRU 등 3단계 오류 분류 계층 Sense Key 대분류 (16종) ASC 중분류 (~256종) ASCQ 세분류 (상세 원인) 예시: Sense Key=0x03 (MEDIUM ERROR) / ASC=0x11 / ASCQ=0x00 → "읽기 재시도 불가 오류 (Unrecovered Read Error)"
그림: Sense Data 바이트 레이아웃과 3단계 오류 분류 계층

자주 발생하는 ASC/ASCQ 코드

Sense KeyASCASCQ의미일반적 원인 / 대응
0x02 (NOT READY)0x040x00Logical Unit Not Ready, Cause Not Reportable디바이스 초기화 중, 잠시 대기 후 재시도
0x020x040x01Becoming Ready스핀업 중, START STOP UNIT 명령 대기
0x020x040x02Initializing Command RequiredFORMAT UNIT 또는 START 명령 필요
0x03 (MEDIUM ERROR)0x110x00Unrecovered Read Error불량 섹터, 디스크 교체 검토
0x030x110x04Unrecovered Read Error - Auto Reallocate Failed자동 재할당 실패, 즉시 백업
0x030x0C0x00Write Error쓰기 실패, 매체 손상
0x04 (HARDWARE ERROR)0x090x00Track Following Error헤드 위치 오류, HDD 교체
0x040x440x00Internal Target Failure컨트롤러 내부 오류
0x05 (ILLEGAL REQUEST)0x200x00Invalid Command Operation Code미지원 명령, CDB 확인
0x050x240x00Invalid Field in CDB잘못된 파라미터, CDB 필드 검증
0x06 (UNIT ATTENTION)0x280x00Not Ready to Ready Transition매체 교체, 자동 재시도
0x060x290x00Power On, Reset, or Bus Device Reset리셋 후 첫 명령, 자동 재시도
0x0B (ABORTED COMMAND)0x470x00SCSI Parity Error케이블/신호 문제, 하드웨어 점검
0x0B0x4E0x00Overlapped Commands태그 충돌, 큐잉 설정 확인
# Sense Data 직접 조회
$ sg_requests -d /dev/sda
  Sense Key: Medium Error [0x3]
  ASC/ASCQ: 0x11/0x00 — Unrecovered Read Error

# 커널 로그에서 Sense Data 확인
$ dmesg | grep -A5 "sense key"
sd 0:0:0:0: [sda] Sense Key : Medium Error [current]
sd 0:0:0:0: [sda] Add. Sense: Unrecovered read error

# SCSI 로그에서 Sense Data 디코딩
$ sg_decode_sense --hex "70 00 03 00 00 00 00 0a 00 00 00 00 11 00"

scsi_debug 가상 장치

scsi_debug는 실제 하드웨어 없이 SCSI 디바이스를 에뮬레이션하는 커널 모듈입니다. SCSI 스택 테스트, 에러 주입, 성능 벤치마크, 드라이버 개발 시 필수적인 도구입니다.

사용자 공간 fio / dd / sg_* Block Layer blk-mq SCSI Mid Layer scsi_mod scsi_debug 가상 HBA + LUN RAM 기반 저장 에러 주입 every_nth=N medium_error_start opts=0x20 (timeout) ndelay=N (지연) 디바이스 에뮬레이션 dev_size_mb=N num_tgts=N, max_luns=N sector_size=512/4096 physblk_exp=N 고급 기능 zbc=managed/host-aware unmap_granularity dix/dif (Data Integrity) clustering, vpd_use_hostno blk-mq 테스트 submit_queues=N host_max_queue=N no_rwlock=1 statistics=1
그림: scsi_debug 동작 원리와 주요 파라미터 카테고리

주요 파라미터

파라미터기본값설명
dev_size_mb8가상 디바이스 크기 (MB)
num_tgts1타겟 수
max_luns1타겟당 최대 LUN 수
sector_size512섹터 크기 (512 또는 4096)
ndelay0명령 완료 지연 (나노초)
every_nth0N번째 명령마다 에러 발생
opts0동작 옵션 비트마스크
medium_error_start-1매체 오류를 발생시킬 시작 LBA
medium_error_count1매체 오류 블록 수
submit_queues1blk-mq 하드웨어 큐 수
zbcnoneZoned Block 에뮬레이션 (managed/host-aware)

테스트 시나리오

# 기본: 256MB 가상 디스크 생성
$ modprobe scsi_debug dev_size_mb=256 num_tgts=1 max_luns=2
$ lsscsi | grep scsi_debug

# 4K 섹터 디바이스 에뮬레이션
$ modprobe scsi_debug sector_size=4096 physblk_exp=3 dev_size_mb=512

# 에러 주입: 100번째 명령마다 CHECK CONDITION
$ modprobe scsi_debug every_nth=100 opts=1 dev_size_mb=128

# 특정 LBA 범위에서 매체 오류 발생
$ modprobe scsi_debug medium_error_start=1000 medium_error_count=5 dev_size_mb=128
$ dd if=/dev/sdX bs=512 skip=1000 count=1 of=/dev/null  # Medium Error 발생

# 명령 타임아웃 시뮬레이션
$ modprobe scsi_debug opts=0x20 every_nth=50  # 50번째마다 타임아웃

# 지연 시뮬레이션 (1ms 지연)
$ modprobe scsi_debug ndelay=1000000 dev_size_mb=128

# 멀티 큐 테스트 (4 hw queues)
$ modprobe scsi_debug submit_queues=4 dev_size_mb=256

# 런타임 파라미터 변경
$ echo 50 > /sys/bus/pseudo/drivers/scsi_debug/every_nth
$ echo 0 > /sys/bus/pseudo/drivers/scsi_debug/every_nth  # 에러 주입 중지

# 정리
$ rmmod scsi_debug
팁: scsi_debug는 CI/CD 파이프라인(Pipeline)에서 SCSI 스택 관련 테스트를 자동화하는 데 이상적입니다. opts 비트마스크를 조합하면 다양한 장애 시나리오를 재현할 수 있습니다.

sysfs 인터페이스

기본적인 sysfs 경로는 앞 절에서 다루었습니다. 이 절에서는 /sys/class/scsi_host, /sys/class/scsi_device의 주요 속성을 상세히 분석하고, 운영 중 활용 가능한 제어 인터페이스를 정리합니다.

scsi_host 주요 속성

속성읽기/쓰기설명
scanW디바이스 재스캔. "- - -"(와일드카드) 또는 "C T L" 형식
stateR/W호스트 상태: running, recovery, cancel, del
proc_nameRHBA 드라이버 이름
can_queueR호스트 최대 outstanding 명령 수
cmd_per_lunRLUN당 기본 큐 깊이
sg_tablesizeRscatter/gather 세그먼트 최대 수
eh_deadlineR/WEH 데드라인 (초). 0=무제한
host_busyR현재 실행 중인 명령 수
active_modeRInitiator/Target 모드
nr_hw_queuesR하드웨어 큐 수 (blk-mq)

scsi_device 주요 속성

속성읽기/쓰기설명
stateR/W디바이스 상태: running, cancel, del, quiesce, offline, transport-offline, blocked
queue_depthR/WLUN 큐 깊이
queue_typeR/W큐 타입: none, simple, ordered
timeoutR/W명령 타임아웃 (초)
deleteW1 기록 시 디바이스 안전 제거
rescanW1 기록 시 디바이스 재스캔
vendorR제조사 식별 문자열
modelR제품 모델명
revR펌웨어(Firmware) 리비전
typeRSCSI 디바이스 타입 (0=disk, 1=tape, 5=cdrom ...)
scsi_levelRSCSI 규격 버전
max_sectorsR/W단일 요청 최대 섹터 수
# 디바이스 전체 속성 탐색
$ ls /sys/class/scsi_device/0:0:0:0/device/

# 디바이스를 quiesce 모드로 전환 (I/O 일시 중지)
$ echo "quiesce" > /sys/class/scsi_device/0:0:0:0/device/state

# 다시 running으로 복귀
$ echo "running" > /sys/class/scsi_device/0:0:0:0/device/state

# 전송 클래스별 속성 확인
$ ls /sys/class/sas_phy/phy-*
$ ls /sys/class/fc_host/host*
$ ls /sys/class/iscsi_session/session*

SCSI 성능 카운터와 통계

SCSI 서브시스템의 성능 분석은 /proc/scsi, scsi_logging_level, blktrace/ftrace를 조합하여 수행합니다. 이 절에서는 각 도구의 활용법과 성능 병목 식별 방법을 다룹니다.

Application fio / iostat Block Layer blktrace SCSI Mid scsi_logging LLD / HBA ftrace 디바이스 sg_logs iostat -x IOPS, 지연 blktrace Q→G→D→C logging_level 명령 추적 ftrace/perf 함수 지연 sg_logs 디바이스 통계 I/O 경로 전체에 걸쳐 관찰 포인트를 배치하여 병목 구간을 식별
그림: SCSI I/O 경로별 성능 관찰 포인트

scsi_logging_level 비트마스크

비트카테고리설명
00x1SCSI_LOG_ERROR오류 로그
10x2SCSI_LOG_TIMEOUT타임아웃 이벤트
20x4SCSI_LOG_SCAN디바이스 스캔
30x8SCSI_LOG_MLQUEUEMid Layer 큐잉 (제출)
40x10SCSI_LOG_MLCOMPLETEMid Layer 완료
50x20SCSI_LOG_LLQUEUELow Level 큐잉 (HBA 전달)
60x40SCSI_LOG_LLCOMPLETELow Level 완료
70x80SCSI_LOG_HLQUEUEHigh Level 큐잉 (sd/sr)
80x100SCSI_LOG_HLCOMPLETEHigh Level 완료

blktrace 연동

# blktrace로 SCSI I/O 추적
$ blktrace -d /dev/sda -o trace &
$ fio --name=test --filename=/dev/sda --rw=randread --bs=4k --numjobs=4 --time_based --runtime=10
$ kill %1

# 추적 결과 분석
$ blkparse -i trace.blktrace.* -d trace.bin
$ btt -i trace.bin

# D2C (dispatch-to-complete) 지연 — SCSI 명령 실행 시간
$ btt -i trace.bin | grep D2C
D2C    Q2C    Avg    Min    Max    N
       sda    0.000234   0.000098   0.001234   52340

# ftrace로 SCSI 함수 추적
$ echo "scsi_dispatch_cmd" > /sys/kernel/debug/tracing/set_ftrace_filter
$ echo function > /sys/kernel/debug/tracing/current_tracer
$ cat /sys/kernel/debug/tracing/trace_pipe

# SCSI 트레이스포인트 사용
$ perf trace -e "scsi:*" -- fio --name=test --filename=/dev/sda --rw=read --bs=4k --size=1M

# 디바이스 수준 에러 통계 (SCSI 로그 페이지)
$ sg_logs -p 0x03 /dev/sda   # Read Error Counter
$ sg_logs -p 0x05 /dev/sda   # Write Error Counter

# iostat으로 SCSI 디바이스 통계
$ iostat -x sda 1
Device  r/s   w/s   rkB/s   wkB/s  await  svctm  %util
sda     12500 3200  50000  12800  0.23   0.06   75.2

Multipath I/O

Multipath I/O는 동일한 스토리지 LUN에 여러 물리적 경로를 통해 접근하는 기술입니다. 경로 장애 시 자동 failover를 제공하고, 정상 시에는 부하 분산(Load Balancing)으로 성능을 향상시킵니다. Linux에서는 dm-mpath(Device Mapper Multipath)가 이를 구현합니다.

dm-mpath (mpath0) /dev/mapper/mpath0 Path Group A (Active) Path Group B (Standby) sda (Path 0) sdb (Path 1) sdc (Path 2) sdd (Path 3) HBA 0 HBA 1 HBA 0 HBA 1 스토리지 어레이 (LUN 0) ALUA: Controller A (Active/Optimized), Controller B (Standby)
그림: dm-mpath 아키텍처 — Active/Standby 경로 그룹과 ALUA

핵심 개념

개념설명
dm-mpathDevice Mapper Multipath. 여러 SCSI 경로를 하나의 블록 디바이스로 통합
Path Group같은 우선순위(Priority)의 경로 묶음. Active/Standby로 구분
Path SelectorI/O를 어느 경로로 보낼지 결정하는 알고리즘
ALUAAsymmetric Logical Unit Access. 스토리지 컨트롤러별 경로 최적화 상태 보고
Failover장애 경로에서 정상 경로로 자동 전환
Failback장애 복구 후 원래 경로로 자동 복귀
multipathd유저 스페이스 데몬. 경로 모니터링, 설정 관리

Path Selector 알고리즘

Selector알고리즘적합한 워크로드
round-robin 0순환 방식, 모든 경로에 균등 분배동일 지연의 경로들
queue-length 0큐 길이가 가장 짧은 경로 선택비대칭 부하
service-time 0예상 서비스 시간이 가장 짧은 경로 선택이기종 경로 혼합

ALUA 우선순위

ALUA(Asymmetric Logical Unit Access)는 스토리지 어레이가 각 컨트롤러의 LUN 접근 상태를 Initiator에 보고하는 메커니즘입니다. multipathd는 ALUA 상태에 따라 경로 우선순위를 자동 설정합니다:

ALUA 상태의미우선순위
Active/Optimized0x0최적 경로, 지연 최소최고 (50)
Active/Non-Optimized0x1접근 가능하지만 비최적중간 (10)
Standby0x2대기 상태, 활성화 필요낮음 (1)
Unavailable0x3접근 불가0
Transitioning0xF상태 전환 중0
# multipath 설치 및 활성화
$ apt install multipath-tools    # Debian/Ubuntu
$ yum install device-mapper-multipath  # RHEL/CentOS
$ systemctl enable --now multipathd

# 기본 설정 생성
$ mpathconf --enable

# multipath 상태 확인
$ multipath -ll
mpath0 (360000000000000001) dm-0 VENDOR,PRODUCT
size=100G features='1 queue_if_no_path' hwhandler='1 alua' wp=rw
|-+- policy='service-time 0' prio=50 status=active
| |- 0:0:0:0 sda 8:0   active ready running
| `- 1:0:0:0 sdb 8:16  active ready running
`-+- policy='service-time 0' prio=10 status=enabled
  |- 0:0:1:0 sdc 8:32  active ready running
  `- 1:0:1:0 sdd 8:48  active ready running

# 경로 장애 시뮬레이션
$ echo offline > /sys/block/sda/device/state
$ multipath -ll  # sda가 faulty로 변경됨을 확인

# 경로 복구
$ echo running > /sys/block/sda/device/state
$ multipathd reconfigure
참고: ALUA 설정은 스토리지 어레이 제조사별로 다릅니다. 대부분의 현대 어레이(NetApp, EMC, HPE 3PAR 등)는 ALUA를 기본 지원합니다. /etc/multipath.conf에서 벤더별 최적 설정을 적용하세요.

Zoned Block 장치

Zoned Block 장치는 디스크 영역을 Zone 단위로 나누어 순차 쓰기를 강제하는 스토리지 모델입니다. SMR(Shingled Magnetic Recording) HDD가 대표적이며, SCSI에서는 ZBC(Zoned Block Commands), ATA에서는 ZAC(Zoned-device ATA Commands)로 표준화되어 있습니다.

Zone 상태 전이도 EMPTY WP = Zone Start IMPLICIT OPEN 쓰기로 자동 오픈 WRITE EXPLICIT OPEN OPEN ZONE 명령 OPEN ZONE OPEN CLOSED 리소스 해제 CLOSE ZONE WRITE/OPEN FULL WP = Zone End WP 끝 도달 RESET ZONE → EMPTY
그림: ZBC Zone 상태 전이도 — EMPTY → OPEN → CLOSED → FULL → RESET

ZBC/ZAC 핵심 개념

개념설명
Zone디스크 공간의 고정 크기 영역 (일반적으로 256MB)
Write Pointer (WP)Zone 내 다음 쓰기 위치. 순차 Zone은 WP 위치에만 쓰기 가능
Conventional Zone기존 디스크처럼 랜덤 쓰기 가능한 Zone (CMR 영역)
Sequential Write Required반드시 WP에서 순차적으로 써야 하는 Zone
Sequential Write Preferred순차 쓰기를 권장하지만 랜덤 쓰기도 허용 (성능 저하 가능)
Host-Managed디바이스가 Zone 규칙을 엄격히 적용, 위반 시 에러 반환
Host-AwareZone 규칙을 권장하지만, 위반을 허용 (호환 모드)

ZBC SCSI 명령

명령Opcode설명
REPORT ZONES0x95Zone 목록과 상태 조회
OPEN ZONE0x94 (SA=0x03)Zone을 명시적으로 오픈
CLOSE ZONE0x94 (SA=0x01)Zone 닫기 (리소스 해제)
FINISH ZONE0x94 (SA=0x02)Zone을 FULL로 전환
RESET WRITE POINTER0x94 (SA=0x04)Zone을 EMPTY로 리셋 (데이터 삭제)

Linux 커널의 Zoned Block 지원

Linux 커널 4.10부터 ZBC/ZAC 디바이스를 네이티브로 지원합니다. sd 드라이버가 Zone 관리를 수행하고, 블록 계층이 쓰기 순서를 보장합니다:

# Zoned Block 디바이스 확인
$ cat /sys/block/sda/queue/zoned
host-managed

# Zone 크기 확인
$ cat /sys/block/sda/queue/chunk_sectors
524288    # 256MB (512바이트 섹터 기준)

# Zone 상태 조회 (sg3_utils)
$ sg_rep_zones /dev/sda
Report zones response:
  Zone type: Sequential write required, Zone cond: Empty, Start: 0x0, Len: 0x80000, WP: 0x0
  Zone type: Sequential write required, Zone cond: Full, Start: 0x80000, Len: 0x80000, WP: 0x100000

# blkzone 도구로 Zone 관리
$ blkzone report /dev/sda
$ blkzone reset -o 0 -l 1 /dev/sda    # 첫 번째 Zone 리셋

# scsi_debug로 Zoned 디바이스 에뮬레이션
$ modprobe scsi_debug zbc=managed dev_size_mb=1024 zone_nr_conv=4
$ cat /sys/block/sdX/queue/zoned     # host-managed

Zoned Block 대응 파일시스템

파일시스템ZBC 지원특징
Btrfs5.12+Zoned 모드 네이티브 지원, 순차 할당 보장
f2fs5.10+LFS 기반 순차 쓰기에 자연 적합
zonefs5.6+Zone을 파일로 1:1 매핑하는 특수 파일시스템
dm-zoned4.13+Device Mapper로 Conventional Zone을 버퍼(Buffer)로 사용하여 랜덤 쓰기 지원
참고: SMR HDD는 Zone 내 순차 쓰기 제약이 있으므로, 기존 파일시스템(ext4, XFS)은 Host-Managed 디바이스에서 동작하지 않습니다. dm-zoned를 중간 계층으로 사용하거나, Zoned 인식 파일시스템을 사용해야 합니다.

iSCSI HBA 오프로드

iSCSI HBA 오프로드는 iSCSI 프로토콜 처리의 일부 또는 전부를 전용 하드웨어로 이관하여 호스트 CPU 부하를 줄이는 기술입니다. 소프트웨어 iSCSI(open-iscsi/iscsid)가 모든 처리를 커널 TCP 스택과 사용자 공간 데몬에 의존하는 반면, HBA 오프로드는 TCP 처리부터 iSCSI PDU 조립까지 하드웨어에서 수행합니다.

소프트웨어 iSCSI vs HBA 오프로드

소프트웨어 iSCSI는 open-iscsi 패키지의 iscsid 데몬과 커널 모듈(iscsi_tcp, libiscsi)로 구현됩니다. 네트워크 카드(NIC)는 일반 이더넷 프레임만 처리하고, TCP/IP 스택과 iSCSI 계층은 모두 호스트 CPU가 담당합니다. 반면 HBA 오프로드는 전용 ASIC이 TCP 세그먼트 조립, iSCSI PDU 파싱, 데이터 DMA 전송을 하드웨어에서 처리합니다.

유형TCP 처리iSCSI PDU데이터 전송CPU 부하
SW iSCSI커널 TCP 스택iscsid / iscsi_tcpCPU bounce buffer높음
Partial HW (TOE)HW TCP (TOE)커널 iSCSIHW DMA중간
Full HW iSCSIHW TCPHW iSCSI 엔진HW DMA (zero-copy)낮음

주요 iSCSI 오프로드 드라이버

리눅스 커널은 다양한 iSCSI HBA 오프로드 드라이버를 포함합니다:

드라이버벤더/칩셋오프로드 수준특징
cxgb4iChelsio T6Full HW (TOE + iSCSI)TCP Offload Engine 기반, DDP(Direct Data Placement), 10/25/40/100GbE
qla4xxxQLogic/MarvellFull HWiSCSI/FCoE 통합 HBA, 하드웨어 iSCSI 엔진, Boot from iSCSI
bnx2iBroadcom NetXtreme IIPartial (TOE)TOE 기반 iSCSI 오프로드, CNIC 계층 활용
be2iscsiEmulex/Broadcom BEFull HWBladeEngine 기반 iSCSI 오프로드, MCC ring

TOE 기반 iSCSI 오프로드

TOE(TCP Offload Engine)는 TCP/IP 프로토콜 스택 전체를 NIC 하드웨어로 이관합니다. Chelsio T6 어댑터의 cxgb4i 드라이버는 TOE 위에 iSCSI 오프로드를 구현하여, TCP 연결 관리부터 iSCSI PDU 처리까지 하드웨어에서 수행합니다. DDP(Direct Data Placement)는 수신 데이터를 중간 버퍼 없이 최종 목적지 메모리에 직접 배치하여 CPU 개입과 메모리 복사를 제거합니다.

# Chelsio cxgb4i 드라이버 로드
$ modprobe cxgb4i

# iSCSI HBA 오프로드 인터페이스 확인
$ iscsiadm -m iface
# cxgb4i.00:0a:00.4  tcp,cxgb4i,...

# 오프로드 인터페이스로 타겟 검색
$ iscsiadm -m discovery -t sendtargets -p 192.168.1.100:3260 -I cxgb4i.00:0a:00.4

# 세션 로그인 (HW 오프로드 사용)
$ iscsiadm -m node -T iqn.2024.com.example:storage -p 192.168.1.100:3260 -I cxgb4i.00:0a:00.4 -l

# 오프로드 상태 확인
$ iscsiadm -m session -P 3 | grep -i offload

iSER — iSCSI Extensions for RDMA

iSER(iSCSI Extensions for RDMA)은 RDMA(Remote Direct Memory Access) 기반으로 iSCSI 데이터를 전송하는 확장 프로토콜입니다. InfiniBand, RoCE(RDMA over Converged Ethernet), iWARP를 통해 zero-copy 데이터 전송을 실현하며, TCP 오버헤드를 완전히 제거합니다. 커널에서는 ib_iser 모듈이 initiator를, LIO 타겟의 iser-target 모듈이 target을 담당합니다.

# iSER initiator 모듈 로드
$ modprobe ib_iser

# RDMA 인터페이스로 iSCSI 검색
$ iscsiadm -m discovery -t sendtargets -p 192.168.1.100:3260 -I iser

# iSER 세션 로그인
$ iscsiadm -m node -T iqn.2024.com.example:target -p 192.168.1.100:3260 -I iser -l
NAS 시나리오: NAS에서 iSCSI LUN을 제공할 때, 클라이언트 수가 많거나 I/O가 집중되면 소프트웨어 iSCSI의 CPU 부하가 병목이 됩니다. TOE 또는 Full HW 오프로드 HBA를 사용하면 CPU 부하를 50~80% 줄일 수 있으며, iSER을 통한 RDMA 전송은 지연 시간을 10μs 이하로 낮춥니다.

커널 소스 분석: iscsi_host_template 구조체

/* drivers/scsi/libiscsi.c — iSCSI 호스트 템플릿 핵심 필드 */
struct iscsi_transport cxgb4i_iscsi_transport = {
    .owner          = THIS_MODULE,
    .name           = "cxgb4i",
    .caps           = CAP_RECOVERY_L0 | CAP_MULTI_R2T |
                      CAP_HDRDGST | CAP_DATADGST |
                      CAP_DIGEST_OFFLOAD |
                      CAP_TEXT_NEGO,
    .create_session = cxgb4i_session_create,
    .destroy_session = iscsi_session_teardown,
    .create_conn    = cxgb4i_conn_create,
    .bind_conn      = cxgb4i_conn_bind,
    .send_pdu       = iscsi_conn_send_pdu,
    .xmit_task      = iscsi_tcp_task_xmit,
    .get_ep_param   = cxgb4i_ep_get_param,
    .ep_connect     = cxgb4i_ep_connect,      /* HW TCP 연결 수립 */
    .ep_disconnect  = cxgb4i_ep_disconnect,
};
코드 설명
  • 핵심 iscsi_transport 구조체는 iSCSI 전송 드라이버의 인터페이스를 정의합니다. caps 필드의 CAP_DIGEST_OFFLOAD는 CRC 다이제스트 계산을 하드웨어에서 수행함을 나타냅니다. ep_connectep_disconnect는 TOE 기반 TCP 연결을 하드웨어에서 관리하며, 소프트웨어 iSCSI(iscsi_tcp)는 이 콜백 대신 커널 소켓 API를 사용합니다.

SCSI XCOPY와 SMB ODX

SCSI Extended Copy(XCOPY)는 SPC-4 표준에 정의된 third-party copy 메커니즘으로, 데이터를 호스트 CPU를 거치지 않고 스토리지 장치 간에 직접 복사합니다. 기존 복사 방식이 소스에서 호스트 메모리로 읽고 다시 대상에 쓰는 2단계 과정을 거치는 반면, XCOPY는 스토리지 컨트롤러가 직접 소스 LUN에서 대상 LUN으로 데이터를 전송합니다.

XCOPY 동작 메커니즘

XCOPY 명령(Opcode 0x83)은 호스트가 복사 관리자(Copy Manager) 역할의 디바이스에 전송합니다. 명령에는 소스 디스크립터, 대상 디스크립터, 복사할 블록 범위가 포함됩니다. 복사 관리자는 소스에서 데이터를 읽어 대상에 직접 쓰며, 호스트는 완료 상태만 수신합니다.

XCOPY (Extended Copy) 데이터 흐름 호스트 (Initiator) XCOPY 명령 전송만 수행 소스 LUN LBA 0x0 ~ 0xFFFF Copy Manager (스토리지 컨트롤러) XCOPY 명령 처리 엔진 대상 LUN LBA 0x0 ~ 0xFFFF XCOPY 명령 (0x83) READ (내부) 직접 데이터 전송 (CPU 우회) WRITE (내부) 기존 방식: Source → Host Memory → Dest (CPU 부하 + 메모리 복사 2회) XCOPY는 이 경로를 제거하여 CPU/네트워크 대역폭 절약
그림: XCOPY 데이터 흐름 — 호스트는 명령만 전송하고, 스토리지 컨트롤러가 소스-대상 간 직접 복사 수행

Token 기반 복사: POPULATE TOKEN / WRITE USING TOKEN

SPC-4에서 추가된 Token 기반 복사는 XCOPY의 확장으로, 복사 작업을 2단계로 분리합니다:

  1. POPULATE TOKEN: 소스 디바이스에서 지정된 블록 범위의 데이터를 나타내는 ROD(Representation of Data) 토큰을 생성합니다.
  2. WRITE USING TOKEN: 생성된 토큰을 사용하여 대상 디바이스에 데이터를 씁니다.

이 방식은 소스와 대상이 서로 다른 스토리지 어레이에 있어도 동작할 수 있으며, 토큰이 유효한 동안 여러 대상에 반복 쓰기가 가능합니다.

SMB ODX (Offloaded Data Transfer)

SMB ODX는 Windows Server의 SMB 3.0+ 프로토콜에서 SCSI Token 기반 복사를 활용하는 기능입니다. 파일 복사 시 데이터가 클라이언트를 경유하지 않고 서버 측 스토리지에서 직접 처리됩니다. Linux에서는 LIO 타겟이 XCOPY 및 Token 기반 복사를 지원하여, Samba를 통한 ODX 호환 파일 서버 구성이 가능합니다.

# LIO 타겟에서 XCOPY 지원 확인
$ cat /sys/kernel/config/target/core/iblock_0/lun0/attrib/emulate_3pc
1    # 1 = Third-Party Copy 활성화

# Token 기반 복사 최대 크기 설정
$ echo 65536 > /sys/kernel/config/target/core/iblock_0/lun0/attrib/max_write_same_len

# sg3_utils로 XCOPY 수동 테스트
$ sg_xcopy --src=/dev/sda --dst=/dev/sdb --count=10000
NAS 시나리오: XCOPY/ODX는 VM 클론, 대용량 파일 백업 복사, 스토리지 마이그레이션에서 큰 효과를 발휘합니다. 예를 들어 50GB VM 디스크 이미지 복제 시, 기존 방식은 호스트 메모리를 경유하여 수 분이 소요되지만, XCOPY는 스토리지 내부에서 직접 복사하여 수 초 내에 완료할 수 있습니다.

커널 소스 분석: target_xcopy_do_work() 핵심 경로

/* drivers/target/target_core_xcopy.c — XCOPY 핵심 처리 경로 */
static void target_xcopy_do_work(struct work_struct *work)
{
    struct xcopy_op *xop = container_of(work, struct xcopy_op, xop_work);
    struct se_cmd *ec_cmd = xop->xop_se_cmd;  /* Extended Copy 명령 */
    sector_t src_lba = xop->src_lba;
    sector_t dst_lba = xop->dst_lba;
    unsigned int nolb = xop->nolb;  /* Number of Logical Blocks */
    unsigned int max_sectors;
    int rc;

    /* 한 번에 복사할 최대 블록 수 결정 */
    max_sectors = min(xop->src_dev->dev_attrib.max_bytes_per_io,
                      xop->dst_dev->dev_attrib.max_bytes_per_io)
                  / xop->src_dev->dev_attrib.block_size;

    while (nolb > 0) {
        unsigned int cur_nolb = min(nolb, max_sectors);

        /* 1단계: 소스에서 읽기 */
        rc = target_xcopy_read_source(ec_cmd, xop,
                                      src_lba, cur_nolb);
        if (rc < 0) break;

        /* 2단계: 대상에 쓰기 */
        rc = target_xcopy_write_destination(ec_cmd, xop,
                                            dst_lba, cur_nolb);
        if (rc < 0) break;

        src_lba += cur_nolb;
        dst_lba += cur_nolb;
        nolb    -= cur_nolb;
    }
    /* 완료 상태를 호스트에 보고 */
    target_complete_cmd(ec_cmd, rc ? SAM_STAT_CHECK_CONDITION
                                   : SAM_STAT_GOOD);
}
코드 설명
  • 핵심 target_xcopy_do_work()는 LIO 타겟에서 XCOPY 명령을 처리하는 워커 함수입니다. 소스 LBA에서 블록을 읽고(target_xcopy_read_source) 대상 LBA에 쓰는(target_xcopy_write_destination) 루프를 반복합니다. max_sectors로 한 번에 처리할 블록 수를 제한하여 메모리 사용량을 조절합니다. 전체 복사가 완료되면 원래 XCOPY 명령에 대한 완료 상태를 호스트에 보고합니다.

Fibre Channel HBA

Fibre Channel(FC)은 SAN(Storage Area Network) 환경에서 가장 널리 사용되는 고속 스토리지 전송 프로토콜입니다. FC HBA(Host Bus Adapter)는 서버를 FC 스위치 패브릭에 연결하여 블록 수준의 스토리지 접근을 제공합니다. 현재 16/32/64 Gbps 속도를 지원하며, 낮은 지연 시간과 무손실 전송 특성으로 엔터프라이즈 스토리지의 핵심 인프라입니다.

주요 FC HBA 드라이버

드라이버벤더속도특징
lpfcEmulex / Broadcom8/16/32/64 GbpsFC, FCoE, FC-NVMe 지원. scsi_transport_fc 기반, NPIV, SR-IOV
qla2xxxQLogic / Marvell8/16/32/64 Gbps가장 폭넓은 FC 호환성. 멀티큐, Target 모드(qla2x00t), FC-NVMe
zfcpIBM System z (s390)FICON/FC메인프레임용 FC 어댑터, QDIO 기반 I/O
bfaBrocade / Broadcom8/16 GbpsBrocade HBA, CEE/FCoE, BFA 펌웨어 계층

FC 아키텍처

FC는 패브릭(Fabric) 토폴로지를 기반으로, 각 디바이스가 FC 스위치를 통해 연결됩니다. 각 포트는 고유한 WWPN(World Wide Port Name)WWNN(World Wide Node Name)으로 식별되며, 패브릭 로그인 시 동적으로 N_Port ID가 할당됩니다.

# FC HBA 목록 확인
$ ls /sys/class/fc_host/
host0  host1

# HBA 포트 상태 및 속도
$ cat /sys/class/fc_host/host0/port_state
Online
$ cat /sys/class/fc_host/host0/speed
32 Gbit

# WWPN / WWNN 확인
$ cat /sys/class/fc_host/host0/port_name
0x2100001b329a5f00
$ cat /sys/class/fc_host/host0/node_name
0x2000001b329a5f00

# 패브릭에 연결된 원격 포트 (타겟) 확인
$ ls /sys/class/fc_remote_ports/
rport-0:0-0  rport-0:0-1

# 원격 포트 정보
$ cat /sys/class/fc_remote_ports/rport-0:0-0/port_name
0x5000c5000a1b2c3d
$ cat /sys/class/fc_remote_ports/rport-0:0-0/roles
FCP Target

# LIP (Loop Initialization Primitive) — 패브릭 재스캔
$ echo 1 > /sys/class/fc_host/host0/issue_lip

NPIV (N_Port ID Virtualization)

NPIV는 하나의 물리 FC 포트에 여러 가상 N_Port ID를 할당하여, 가상 머신마다 독립된 FC 포트를 제공하는 기술입니다. 하이퍼바이저(KVM, VMware)에서 VM에 FC LUN을 직접 매핑할 때 필수적입니다. 각 가상 포트는 고유한 WWPN을 가지므로, 스토리지 측에서 VM별 LUN 마스킹과 조닝이 가능합니다.

# NPIV 지원 확인
$ cat /sys/class/fc_host/host0/npiv_vports_inuse
0
$ cat /sys/class/fc_host/host0/max_npiv_vports
254

# 가상 포트 생성 (WWPN:WWNN)
$ echo "2100001b329a5f10:2000001b329a5f10" > /sys/class/fc_host/host0/vport_create

# 생성된 가상 포트 확인
$ ls /sys/class/fc_host/
host0  host1  host2   # host2가 NPIV 가상 포트

FC-NVMe: NVMe over Fibre Channel

FC-NVMe는 기존 FC 패브릭 인프라 위에서 NVMe 프로토콜을 전송하는 기술입니다. SCSI 대신 NVMe 명령을 FC 프레임에 캡슐화하여, FC SAN의 안정성과 NVMe의 낮은 지연 시간을 결합합니다. Linux 커널의 nvme-fc 모듈이 initiator를, nvmet-fc 모듈이 target을 담당합니다.

# FC-NVMe initiator 모듈
$ modprobe nvme-fc

# lpfc 드라이버에서 FC-NVMe 활성화
$ modprobe lpfc lpfc_enable_fc4_type=3   # 3 = FCP + NVMe

# FC-NVMe 서브시스템 검색
$ nvme discover -t fc -a traddr:nn-0x2000001b329a5f00:pn-0x2100001b329a5f00 \
                     -w traddr:nn-0x2000001b329a6f00:pn-0x2100001b329a6f00

FC vs iSCSI vs NVMe-oF 비교

항목Fibre ChanneliSCSINVMe-oF (RDMA/TCP)
전송 매체FC 광섬유 / 구리이더넷 (TCP/IP)이더넷 / InfiniBand / FC
최대 속도64 Gbps (Gen7)100 Gbps (이더넷)400 Gbps (이더넷)
지연 시간~5 μs~50-100 μs (SW), ~10 μs (HW)~2-10 μs (RDMA)
CPU 부하매우 낮음 (HW)높음 (SW) / 낮음 (HW)매우 낮음 (RDMA)
인프라 비용높음 (전용 스위치)낮음 (기존 이더넷)중간 (RDMA NIC 필요)
프로토콜 오버헤드낮음 (경량 FC 프레임)높음 (TCP/IP 스택)매우 낮음 (NVMe 네이티브)
멀티패스dm-multipath, ALUAdm-multipath, ALUAnvme multipath (in-kernel)
적합 환경대규모 SAN, 미션 크리티컬소규모~중규모, NAS/SAN 통합고성능 올플래시, AI/ML
NAS에서 FC: NAS 어플라이언스가 백엔드 스토리지를 FC SAN으로 연결하는 구성은 엔터프라이즈 환경에서 일반적입니다. 프런트엔드는 NFS/SMB로 파일 서비스를 제공하고, 백엔드는 FC로 SAN 어레이에 접근하여 높은 처리량과 안정성을 확보합니다. NPIV를 통해 NAS 컨트롤러 페일오버 시에도 스토리지 경로를 유지할 수 있습니다.

외부 참고 자료

SCSI/iSCSI와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.