SCSI / iSCSI 서브시스템
Linux 커널의 SCSI 서브시스템을 심층 분석합니다. SCSI 아키텍처 모델(SAM), Upper/Mid/Lower 3계층 구조, 핵심 구조체, 명령 실행·완료 경로, 오류 처리(EH), SAS·FC·iSCSI·UAS 전송 프로토콜, iSCSI 심화(open-iscsi/LIO), SCSI 드라이버 개발, sysfs 인터페이스, 디버깅 기법을 다룹니다.
핵심 요약
- 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 스택에서 처리합니다.
단계별 이해
- SCSI 디바이스 확인 —
lsscsi로 시스템에 연결된 SCSI 디바이스를 확인합니다.SATA HDD/SSD도 libata를 통해 SCSI 디바이스로 표시됩니다.
- 3계층 이해 — Upper Layer(sd 드라이버)가 읽기/쓰기 요청을 SCSI 명령으로 변환하고, Mid Layer가 큐잉/에러 처리를 하며, Lower Layer(HBA)가 하드웨어에 전달합니다.
/sys/class/scsi_host/에서 HBA 정보를 확인할 수 있습니다. - SCSI 명령 관찰 —
sg_inq /dev/sda로 INQUIRY 명령을 보내 디바이스 정보를 조회합니다.sg3_utils패키지가 다양한 SCSI 명령 도구를 제공합니다. - 에러 처리 — SCSI 명령 실패 시 Mid Layer의 Error Handler(EH)가 복구를 시도합니다.
명령 재시도 → 디바이스 리셋 → 버스 리셋 → 호스트 리셋 순으로 에스컬레이션합니다.
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 스택을 경유합니다.
프로토콜 진화
| 세대 | 전송 방식 | 최대 대역폭 | 최대 거리 | 비고 |
|---|---|---|---|---|
| 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 환경 |
| iSCSI | TCP/IP 네트워크 | 네트워크 대역 의존 | 무제한 | IP 기반 SAN |
| UAS (USB Attached SCSI) | USB 3.x | 20 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/BD | READ TOC, GET CONFIGURATION, READ DISC INFORMATION |
| SES (SCSI Enclosure Services) | 디스크 인클로저 | RECEIVE DIAGNOSTIC, SEND DIAGNOSTIC |
sd 드라이버는 SBC, sr은 MMC, st는 SSC, ses는 SES 명령 세트를 구현합니다. sg(SCSI Generic)는 사용자 공간에서 임의의 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)는 명령 완료 시 반환됩니다:
| 상태 코드 | 값 | 의미 |
|---|---|---|
| GOOD | 0x00 | 명령 성공 |
| CHECK CONDITION | 0x02 | 오류 발생 — Sense Data 확인 필요 |
| BUSY | 0x08 | Target 처리 중, 재시도 필요 |
| RESERVATION CONFLICT | 0x18 | 다른 Initiator가 예약한 리소스 |
| TASK SET FULL | 0x28 | 큐 포화, 나중에 재시도 |
| ACA ACTIVE | 0x30 | 자동 비상 연합 활성 |
Linux SCSI 스택 구조
Linux SCSI 서브시스템은 3계층으로 구성됩니다. Upper Layer가 블록/문자 디바이스 인터페이스를 제공하고, Mid Layer(scsi_mod)가 명령 관리·큐잉·오류 처리를 담당하며, Lower Layer의 LLD(Low-Level Driver)가 실제 HBA 하드웨어를 제어합니다.
Upper Layer 드라이버
| 드라이버 | 모듈 | 디바이스 노드 | 용도 |
|---|---|---|---|
| sd | sd_mod | /dev/sd[a-z] | 디스크 (HDD, SSD, SAS, USB 스토리지) |
| sr | sr_mod | /dev/sr[0-9] | CD/DVD/Blu-ray |
| st | st | /dev/st[0-9] | 테이프 드라이브 |
| sg | sg | /dev/sg[0-9] | SCSI Generic — 사용자 공간에서 직접 CDB 전송 |
| ses | ses | — | SES 인클로저 서비스 |
SCSI Mid Layer (scsi_mod)
커널 모듈 scsi_mod는 SCSI 서브시스템의 핵심입니다. 주요 책임:
- 디바이스 스캔: 호스트 등록 시 INQUIRY 명령으로 Target/LUN 탐색
- 명령 큐잉: blk-mq와 연동하여 SCSI 명령을 LLD에 디스패치
- 오류 처리(EH): 타임아웃·오류 발생 시 에스컬레이션 기반 복구
- 전송 클래스: SAS/FC/iSCSI 등 전송별 sysfs 속성 관리
- 전원 관리: suspend/resume, 런타임 PM 연동
Lower Layer — LLD (Low-Level Driver)
| LLD | HBA / 전송 | 벤더 |
|---|---|---|
mpt3sas | SAS 3xxx HBA | Broadcom (LSI) |
megaraid_sas | MegaRAID SAS | Broadcom (LSI) |
hpsa / smartpqi | Smart Array / SmartPQI | HPE (Microchip) |
aacraid | Adaptec RAID | Microsemi |
qla2xxx | Fibre Channel | QLogic (Marvell) |
lpfc | Fibre Channel | Emulex (Broadcom) |
iscsi_tcp | iSCSI (소프트웨어) | Open-iSCSI |
libata + ahci | SATA → SCSI 변환 | (표준) |
uas | USB Attached SCSI | (표준) |
scsi_debug | 가상 SCSI 디바이스 | 테스트/개발용 |
핵심 구조체
SCSI 서브시스템의 동작을 이해하려면 4가지 핵심 구조체를 알아야 합니다.
scsi_host_template
LLD가 Mid Layer에 자신의 능력(capabilities)과 콜백을 알리는 템플릿입니다. 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에 도달하기까지의 경로입니다:
submit_bio()→ blk-mq 소프트웨어 큐에 bio 삽입- blk-mq가 bio를
struct request로 병합·변환 scsi_queue_rq()— SCSI blk-mq ops의.queue_rq콜백scsi_dispatch_cmd()— CDB 구성,scsi_cmnd준비hostt->queuecommand()— LLD에 명령 전달- LLD가 HBA DMA 레지스터에 명령을 기록하거나 네트워크로 전송
완료 경로 (Completion Path)
HBA가 명령 완료를 통지하면 다음 경로로 처리됩니다:
- HBA 인터럽트 → LLD ISR 실행
- LLD가
scsi_done(scsi_cmnd)호출 scsi_decide_disposition()— 상태 판단 (성공/재시도/EH)- 성공 시:
scsi_finish_command()→blk_mq_complete_request() - 오류 시:
scsi_eh_scmd_add()→ EH 큐에 추가
blk-mq와 SCSI 연동
Linux 5.0+ 이후 모든 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 오류 처리 (EH)
SCSI 오류 처리(Error Handling)는 타임아웃이나 CHECK CONDITION 등 오류 발생 시 에스컬레이션 단계별 복구를 시도하는 메커니즘입니다. 커널 스레드 scsi_eh_[N]가 EH를 담당합니다.
EH 동작 흐름
- 타임아웃 감지: blk-mq 타이머가
scsi_timeout()을 호출 - EH 큐 등록:
scsi_eh_scmd_add()가 실패한 명령을 EH 목록에 추가 - EH 스레드 기상:
scsi_eh_[N]커널 스레드가 깨어남 - 에스컬레이션: abort → device reset → target reset → host reset 순서로 시도
- 복구 완료: 성공한 단계에서 실패 명령을 재시도하거나, 모든 단계 실패 시 디바이스를 오프라인으로 전환
/* 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 SENSE | 0x0 | 오류 없음 | 정보성 응답 |
| RECOVERED ERROR | 0x1 | 복구된 오류 | ECC 교정 성공 |
| NOT READY | 0x2 | 디바이스 준비 안 됨 | 스핀업 중, 매체 없음 |
| MEDIUM ERROR | 0x3 | 매체 오류 | 불량 섹터, 읽기 불가 |
| HARDWARE ERROR | 0x4 | 하드웨어 오류 | 컨트롤러 고장 |
| ILLEGAL REQUEST | 0x5 | 잘못된 명령/파라미터 | 지원하지 않는 CDB |
| UNIT ATTENTION | 0x6 | 상태 변경 알림 | 매체 교체, 리셋 발생 |
| DATA PROTECT | 0x7 | 쓰기 보호 | 읽기 전용 매체 |
| ABORTED COMMAND | 0xB | 명령 중단 | 패리티 오류, 타임아웃 |
SCSI 전송 프로토콜
SCSI 명령은 다양한 물리/네트워크 전송 계층을 통해 전달됩니다. Linux 커널은 각 전송 방식에 대한 전송 클래스(Transport Class)를 sysfs로 노출합니다.
| 전송 | 매체 | 최대 대역폭 | 최대 거리 | 주요 용도 | 커널 모듈 |
|---|---|---|---|---|---|
| SAS | 직렬 구리 | 22.5 Gb/s | ~10m | 서버/DAS | scsi_transport_sas |
| FC | 광섬유/구리 | 128 Gb/s | ~10km | SAN | scsi_transport_fc |
| iSCSI | TCP/IP | 네트워크 의존 | 무제한 | IP SAN | scsi_transport_iscsi |
| UAS | USB | 20 Gb/s | ~5m | 외장 | uas |
| SATA | 직렬 구리 | 6 Gb/s | ~1m | 데스크톱/서버 | libata + ahci |
SAS (Serial Attached SCSI)
엔터프라이즈 스토리지의 표준 전송입니다. Expander를 통한 팬아웃, 듀얼 포트, SATA 디바이스 호환이 특징입니다. 커널에서는 scsi_transport_sas 클래스가 SAS 주소(WWN), PHY 속성, Expander 토폴로지를 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*로 나타납니다.
iSCSI 심화
iSCSI(Internet Small Computer System Interface)는 TCP/IP 네트워크를 통해 SCSI 명령을 캡슐화하여 전송하는 프로토콜입니다. RFC 3720(2004)에서 정의되었으며, RFC 7143(2014)에서 통합 개정되었습니다.
iSCSI 프로토콜 기초
iSCSI는 기존 이더넷 인프라 위에서 블록 스토리지 접근을 제공하므로, Fibre Channel 대비 낮은 비용으로 SAN을 구축할 수 있습니다. 주요 개념:
- IQN(iSCSI Qualified Name): 전역적으로 고유한 노드 식별자 —
iqn.yyyy-mm.도메인역순:식별자 - Portal: Target의 IP:포트 쌍 (기본 TCP 3260)
- Session: Initiator–Target 간 논리적 연결 (하나 이상의 TCP 연결)
- Connection: Session 내 단일 TCP 연결
- PDU(Protocol Data Unit): iSCSI 계층의 전송 단위
PDU 구조
모든 iSCSI 통신은 PDU 단위로 이루어집니다. PDU는 48바이트 BHS(Basic Header Segment)로 시작합니다:
| PDU 타입 | Opcode | 방향 | 설명 |
|---|---|---|---|
| Login Request/Response | 0x03/0x23 | I↔T | 인증 및 세션 협상 |
| SCSI Command | 0x01 | I→T | CDB 전달 |
| SCSI Response | 0x21 | T→I | 명령 완료 상태 |
| Data-Out / Data-In | 0x05/0x25 | I→T / T→I | 데이터 전송 |
| NOP-Out / NOP-In | 0x00/0x20 | I↔T | 연결 유지 (keepalive) |
| Text Request/Response | 0x04/0x24 | I↔T | Discovery, 파라미터 교환 |
| Logout Request/Response | 0x06/0x26 | I↔T | 세션/연결 종료 |
| TMF (Task Mgmt) | 0x02/0x22 | I→T | 태스크 abort/리셋 |
세션 관리
iSCSI 세션은 3단계로 수립됩니다:
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
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 오버헤드를 제거하고 제로카피 전송을 가능하게 합니다. ib_iser(Initiator) 및 ib_isert(Target) 모듈이 담당합니다. 일반 iSCSI 대비 대역폭과 레이턴시에서 큰 이점이 있으며, HPC/AI 스토리지에서 활용됩니다.
iSCSI 성능 튜닝
| 파라미터 | 기본값 | 권장값 (10GbE+) | 설명 |
|---|---|---|---|
| node.session.cmds_max | 128 | 256–1024 | 세션당 최대 동시 명령 |
| node.session.queue_depth | 32 | 64–256 | LUN당 큐 깊이 |
| node.conn[0].iscsi.MaxRecvDataSegmentLength | 262144 | 262144 | 최대 수신 데이터 세그먼트 (바이트) |
| node.conn[0].iscsi.FirstBurstLength | 65536 | 262144 | 비솔리시티드(unsolicited) 데이터 최대 크기 |
| node.conn[0].iscsi.MaxBurstLength | 16776192 | 16776192 | 단일 Data-Out 시퀀스 최대 크기 |
| node.session.nr_sessions | 1 | 2–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
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
디버깅 도구
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_SCSI | scsi_mod | SCSI 서브시스템 핵심 (필수) |
CONFIG_BLK_DEV_SD | sd_mod | SCSI 디스크 (sd) 드라이버 |
CONFIG_CHR_DEV_SG | sg | SCSI Generic 인터페이스 |
CONFIG_CHR_DEV_ST | st | SCSI 테이프 드라이버 |
CONFIG_BLK_DEV_SR | sr_mod | SCSI CD-ROM 드라이버 |
CONFIG_SCSI_SAS_ATTRS | scsi_transport_sas | SAS 전송 클래스 |
CONFIG_SCSI_FC_ATTRS | scsi_transport_fc | FC 전송 클래스 |
CONFIG_ISCSI_TCP | iscsi_tcp | 소프트웨어 iSCSI Initiator |
CONFIG_ISCSI_TARGET | iscsi_target_mod | LIO iSCSI Target |
CONFIG_SCSI_DEBUG | scsi_debug | 가상 SCSI 디바이스 (테스트) |
CONFIG_SATA_AHCI | ahci | AHCI 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.c | sysfs 인터페이스 |
drivers/scsi/sd.c | SCSI 디스크 (sd) 드라이버 |
drivers/scsi/sg.c | SCSI 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 헤더 파일 |