LinuxのDevice Driverの仕組みを徹底解説|Block I/O・DMA・Interrupt・Storageまで

はじめに

Linuxでファイルを読み書きすると、Applicationからの要求はFilesystem、Page Cache、Block Layerを経由します。その先で、Linux Kernelの共通的なI/O Requestを実際のStorage Hardwareが理解できるCommandへ変換するのがDevice Driverです。

HDD、SATA SSD、SAS SSD、NVMe SSDでは、接続InterfaceやCommand、Queueの構造が異なります。それでも上位のFilesystemが同じread()やwrite()を利用できるのは、Block LayerとDevice DriverがHardwareの違いを吸収しているためです。

また、データ転送ではCPUがすべてのByteを一つずつコピーし続けるのではなく、一般にDMAが利用されます。I/Oが完了するとDeviceはInterruptなどでCPUへ通知し、Device DriverからBlock Layer、Filesystem、Applicationへ結果が戻ります。

本記事では、LinuxのDevice Driverの役割、/devに見えるDevice Fileとの関係、Storage Controller、DMA、Interrupt、I/O完了処理までを一連の流れとして解説します。

Device Driverとは

Device Driverの役割

Device Driverは、Hardwareを制御するためのKernel内のSoftwareです。Deviceの初期化、I/O Commandの発行、Queueの管理、Interrupt処理、Error Recovery、Power Managementなどを担当します。

  • 接続されたDeviceを検出して初期化する
  • KernelのI/O RequestをHardware Commandへ変換する
  • DMAに必要なMemory Mappingを準備する
  • DeviceへCommandを送信する
  • InterruptやPollingで完了を検出する
  • 結果やErrorを上位レイヤーへ返す

Device DriverはKernelに組み込まれている場合と、Kernel Moduleとして必要時に読み込まれる場合があります。lsmodで読み込み済みModuleを確認できますが、Kernelへ組み込まれたDriverはlsmodに表示されません。

KernelとHardwareの橋渡し

FilesystemやBlock Layerは、Read、Write、Flushといった共通的な操作を扱います。一方、Hardware側ではRegister、Command Descriptor、Submission Queueなど、Interfaceごとに異なる方法で命令を受け取ります。

Device Driverは両者の間に入り、KernelのRequestをDevice固有の形式へ変換します。また、Hardwareから返されたCompletion StatusをLinuxが理解できる結果へ変換します。

なぜDevice Driverが必要なのか

DeviceごとにCommand形式、Register、Queue数、転送制限、Error処理が異なります。もしDriverによる抽象化がなければ、FilesystemやApplicationが各Hardwareの制御方法を個別に実装しなければなりません。

LinuxはDevice Driverと各SubsystemのInterfaceを定めることで、上位レイヤーとHardware固有処理を分離します。新しいStorage Deviceでも対応DriverがBlock Layerへ接続すれば、既存のFilesystemやApplicationからBlock Deviceとして利用できます。

Linuxから見たストレージデバイス

Block Deviceとして認識されるストレージ

LinuxはHDD、SSD、NVMe SSD、Virtual DiskなどをBlock Deviceとして扱います。Block Deviceは、論理的なSectorを指定して任意の位置へReadやWriteを実行できるDeviceです。

以下のコマンドを実行することで、各種情報を取得することができます。

lsblk

lsblkコマンドを実行することで、認識されたBlock Device、Partition、Size、Mount Pointなどを一覧表示できます。

項目意味
NAMEデバイス名。例:sdasda1sr0
MAJ:MINKernelがデバイスを識別するための Major番号 : Minor番号
RMRemovable Deviceかどうか。1 = 取り外し可能、0 = 固定デバイス
SIZEデバイスやPartitionの容量
RORead Onlyかどうか。1 = 読み取り専用、0 = 読み書き可能
TYPEデバイスの種類。diskpartromlvm など
MOUNTPOINTSそのデバイスがMountされている場所。例://boot

本例では、sda が物理Disk、その配下の sda1・sda2 がPartitionという意味となります。

lspci

lspciではPCI接続されたStorage ControllerやNVMe Controllerを確認することができます。

lspci -nnk

-nn 名前 + 数値IDの両方を表示
-k Kernel Driver / Kernel Moduleを表示

上記の例では、Virtio SCSIのStorage Controllerが認識されており、Kernel driver in useからvirtio-pciが使用されていることを確認できます。

udevadm

udevadm infoではDeviceに関連する属性を確認できます。

udevadm info –query=all –name=/dev/sda

主要な項目を説明します。

項目本例意味
P:/devices/pci0000:00/.../block/sdasysfs上のDevice Path。PCI → Virtio → SCSI → Block Deviceという階層が分かる
N:sdaDevice名
U:block所属するSubsystem。Block Deviceであることを示す
T:diskDevice Type。Diskとして認識されている
D:b 8:0bはBlock Device、8:0はMajor / Minor Number
DEVNAME/dev/sda/dev配下に作成されたDevice File
ID_VENDORQEMUDeviceのVendor
ID_MODELQEMU_HARDDISKDeviceのModel
ID_BUSscsiSCSI Deviceとして認識されていることを示す
ID_SERIAL...drive-scsi0Deviceを識別するSerial情報
DEVLINKS/dev/disk/by-id/... など/dev/sdaを指すSymbolic Link

/dev/sda・/dev/vda・/dev/nvme0n1とは

Device名の例一般的な意味関連するDriver Stackの例
/dev/sdaSCSI Diskとして公開される最初のDisk。SATA、SAS、USB Storageなども該当し得るsdとSCSI Mid-Layer、その下のHost Driver
/dev/vda仮想環境のVirtio Block Devicevirtio_blk
/dev/nvme0n1NVMe Controller 0のNamespace 1NVMe Driver

/dev/sdaという名前だけでは、物理的にSAS接続なのか、SATA Deviceがlibataを通じてSCSI Layerへ公開されたものなのか、USB Storageなのかを断定できません。Device名はLinuxから見える分類を表し、物理Interfaceの詳細とは必ずしも一致しません。

末尾に付くPartition名も命名規則によって異なります。/dev/sdaの最初のPartitionは/dev/sda1ですが、/dev/nvme0n1では/dev/nvme0n1p1のようにpを挟みます。

/devとDevice Driverの関係

/dev配下のFileはDevice Nodeと呼ばれ、Device Driverそのものではありません。User SpaceからKernel内のDeviceへアクセスするための入口です。Block Device NodeはMajor NumberとMinor Numberを持ち、Kernel内の対応するDeviceを識別します。

現在の一般的なLinuxでは、Kernelがsysfsへ公開したDevice情報をもとに、udevが/dev配下のNodeやSymbolic Linkを管理します。/dev/disk/by-uuidや/dev/disk/by-idのような永続的な名前を利用すると、検出順序で変わり得る/dev/sdaなどへの依存を減らせます。

Block LayerとDevice Driverの関係

blk-mqからDevice Driverへ

LinuxのBlock Layerでは、blk-mqがBlock I/O Requestを複数のQueueで管理します。Filesystemなどから提出されたbioはrequestへ関連付けられ、Software QueueとHardware Dispatch Queueを通じてDriverへ渡されます。

Driverはblk-mqが定めるInterfaceを実装し、Dispatchされたrequestを受け取ります。これにより、Block Layerは個々のHardwareのCommand形式を知らなくても、共通した方法でDriverへI/Oを渡せます。

I/O Requestが渡される流れ

Block Layerは、操作種別、開始Sector、Data Length、Memory Segment、Flagなどをrequestへまとめます。I/O Schedulerが有効であればDispatch順序を調整し、Hardware Queueへ送ります。noneが選ばれている場合は、高度な並べ替えを行わずにDispatchします。

DriverまたはDeviceが一時的に追加のCommandを受け付けられない場合、requestはQueueで待機します。Deviceへ送信可能になるとDispatchが再開されます。

Hardware Queueとの関係

blk-mqのHardware Queueは、Device Driverへrequestを渡すHardware Dispatch Queueです。必ずしも物理Hardware内のQueueそのものではありませんが、Driverは一般にDevice側のCommand Queueとの対応を考慮して設定します。

NVMeのように複数のSubmission QueueとCompletion Queueを持つDeviceでは、複数のHardware QueueをCPUへ分散して高い並列性を活用できます。一方、利用できるDevice Queueが少ない場合は、複数のCPUやSoftware Queueが同じHardware Queueを共有します。

Device DriverはI/O要求をどう処理するのか

Read / Write要求を受け取る

Device Driverはblk-mqからrequestを受け取ると、Read、Write、Flush、Discardなどの操作、対象Sector、転送するData Length、Memory Segmentを確認します。

さらに、Deviceの最大転送Size、Segment数、Alignment、Queue Depthなどの制限に適合していることを前提に処理します。多くの制限はBlock Layerへ事前に登録され、上位でのMergeやSplitにも利用されます。

デバイスが理解できるCommandへ変換する

Driverはrequestを、対象Interfaceが定めるCommandへ変換します。SATAではATA Command、SASではSCSI Command、NVMeではNVMe Commandが使用されます。実際にはTransport Driver、SCSI Mid-Layer、Controller Driverなど、複数層が変換に関与する場合があります。

Block操作Device Commandの例
Read指定Logical BlockからMemoryへデータを転送するCommand
WriteMemoryのデータを指定Logical Blockへ書き込むCommand
FlushDeviceの揮発性Write Cacheを不揮発媒体へ反映するCommand
Discard使用しなくなったLogical Block RangeをDeviceへ通知するCommand

すべてのDeviceがすべての操作へ対応するとは限りません。DriverはDeviceのCapabilityを検出し、上位レイヤーへ公開します。

HardwareへCommandを発行する

DriverはCommand DescriptorやDMA対象の情報をMemoryへ準備し、Deviceから参照できる状態にします。その後、Memory-Mapped I/O Registerへの書き込みやDoorbell通知など、Interface固有の方法でControllerへCommandの存在を知らせます。

Commandを発行したrequestはIn-Flight状態となり、Driverとblk-mqがTagなどで追跡します。Deviceが同時に処理できるQueue Depthへ達している場合、新しいrequestは空きが生じるまで待機します。

Storage Controllerとストレージデバイス

Storage Controllerの役割

Storage Controllerは、Host側からCommandを受け取り、Storage MediaへのReadやWriteを制御します。Command Queueの管理、DMA、Error Detection、Retry、Cache管理などを担当します。

Controllerの位置や形態は構成によって異なります。SATA Host Controllerの先に個別のDriveが接続される場合、SAS HBAやHardware RAID Controllerを使う場合、NVMe SSD内にControllerが組み込まれてPCIeへ直接接続される場合などがあります。

SATA / SAS / NVMe

Interface主な特徴一般的な利用例
SATAAHCIなどを通じて接続され、Queueの並列性はNVMeより限定的Consumer向けHDD・SSD、一般Server
SASEnterprise向け機能、Dual Port、Expanderなどに対応する構成があるServer向けHDD・SSD、Storage Array
NVMePCIe向けに設計され、多数のQueueと低Latencyを重視する高性能SSD、Database、仮想化基盤

SATAとSASは似たDevice名で見えることがありますが、ProtocolやController Stackは異なります。NVMeは従来のSCSI Disk名ではなく、ControllerとNamespaceを表す独自のDevice名を使用します。

Linux KernelからStorageまでの位置関係

Linux Kernel内のDevice DriverはControllerを操作し、ControllerがStorage MediaとのCommand実行やData Transferを進めます。

ただし、NVMe SSDのようにControllerとFlash Mediaが一つのDevice内に収まる場合もあるため、ControllerとStorage Deviceが常に別筐体というわけではありません。

Virtual Machineにおける物理StorageまでのI/Oの流れ

Virtual Machineの/dev/vdaでは、Guest Kernelのvirtio DriverからVirtual Deviceへ要求が渡り、Host側のHypervisorやBackendがさらにHostのFilesystemまたはBlock Deviceへ処理します。この場合、物理Storageまでに複数のSoftware Layerが存在します。

DMAの仕組み

DMAとは

DMAはDirect Memory Accessの略で、DeviceがCPUによる逐次的なData Copyを介さず、Memoryとの間でDataを転送する仕組みです。Storage I/OだけでなくNetworkやGraphicsなど、多くの高速Deviceで利用されます。

CPUは転送対象、方向、SizeなどをDriver経由で設定し、ControllerへCommandを発行します。転送そのものはDMA Engineが進め、完了時にCPUへ通知します。

なぜCPUがデータを直接コピーし続けないのか

CPUがStorage InterfaceからMemoryへDataを一つずつコピーし続けると、転送中は多くのCPU時間を消費します。大容量Dataや多数のI/Oを扱うほど、Applicationの計算などに使えるCPU時間が減ってしまいます。

DMAを使えば、CPUは転送を設定した後に別のProcessを実行できます。転送が終わった時点で完了処理を行えばよいため、CPUとI/Oを並行して進められます。

MemoryとStorage間でデータが転送される仕組み

Driverはrequestに含まれるMemory SegmentをDMA用にMapし、Deviceが利用できるDMA Addressを取得します。物理的に連続していない複数のMemory領域は、Scatter-Gather ListとしてControllerへ渡せる場合があります。

IOMMUが有効な環境では、Deviceが使用するI/O Virtual Addressと物理MemoryをIOMMUが対応付けます。これにより、Deviceがアクセスできる範囲を制限し、Virtual MachineへのDevice割り当てなども支援できます。

DMAを使ってもCPU処理が完全になくなるわけではありません。DriverによるMapping、Command発行、Cache Coherencyへの対応、完了処理などは必要です。また、HardwareやArchitectureによってDMAとCPU Cacheの整合性を保つ方法は異なります。

I/O完了はLinuxへどう通知されるのか

Storage DeviceによるI/O処理

Storage DeviceはDriverから受け取ったCommandを処理します。Readでは指定されたLogical BlockからDataを取得してMemoryへ転送し、WriteではMemoryから受け取ったDataをDevice側へ書き込みます。

HDDではHeadのSeekやPlatterの回転待ちが発生します。SSDではControllerがLogical AddressをNAND Flashの位置へ変換し、Garbage CollectionやWear Levelingなども内部で管理します。

Interruptによる完了通知

I/Oが完了すると、ControllerはCompletion Statusを記録し、一般にInterruptを発生させてCPUへ通知します。PCIe DeviceではMSIまたはMSI-Xが使われ、複数QueueのInterruptを複数CPUへ分散できる場合があります。

Interruptごとに大きな処理を行うと、高IOPS環境ではCPU負荷が増えます。そのため、複数Completionをまとめて処理するInterrupt Coalescingや、Interruptを使わずにCompletionを確認するPollingが利用されることもあります。

Device Driverによる完了処理

Interruptを受けたDriverはControllerのCompletion情報を確認し、完了したCommandと対応するrequestを特定します。そして、Status Code、転送量、Errorの有無などを確認します。

DMA Mappingを解除するなどの後処理を行い、必要に応じてError RecoveryやRetryを開始します。Timeout、Media Error、Transport Errorなど、Errorの種類によって対応は異なります。

Block Layerへ完了を返す

Driverはblk-mqへrequestの完了を通知します。Block Layerはrequestに含まれるbioへ結果を反映し、FilesystemやPage Cacheなどの上位レイヤーの完了処理を呼び出します。

Readの場合はDataが格納されたPageを利用可能にし、同期read()で待っていたProcessを起床させます。Writebackの場合は書き出し中のPageを更新し、正常に反映されたDirty PageをCleanな状態へ移します。

Processは起床直後に必ず実行されるわけではありません。Runnableな状態となり、CPU SchedulerによってCPUを割り当てられた後に処理を再開します。

HDD・SSD・NVMeで何が違うのか

Device記録媒体主な特性
HDD回転する磁気Disk大容量・低単価だが、Seekと回転待ちによりRandom I/OのLatencyが大きい
SATA SSDNAND Flash機械的動作がなく、HDDよりRandom I/Oが高速。SATA Interfaceの上限を受ける
NVMe SSDNAND FlashなどPCIeと多数のQueueを利用し、高IOPS・低Latency・高い並列性を実現する

同じSSDでも、SATA SSDとNVMe SSDではHostとの接続方式とCommand Protocolが異なります。Flash Memory自体の特性だけでなく、Interface、Controller、Queue構造が性能差に影響します。

接続インターフェースの違い

SATAは従来のStorage向けInterfaceとして広く利用され、AHCIと組み合わせる構成が一般的です。SASはEnterprise向けの接続性や冗長構成に適した機能を持ちます。

NVMeはNon-Volatile Memory向けに設計され、PCIe経由で接続します。複数のSubmission QueueとCompletion Queueを利用できるため、多数CPUからのI/Oを並列に処理しやすい構造です。

Device Driverの違い

SATA Deviceは一般にlibataとSCSI Subsystemを経由し、Diskとしてsd Driverから公開されます。SASではSCSI Mid-LayerとHBA固有Driverなどが連携します。NVMeではNVMe DriverがControllerとNamespaceを管理します。

Virtual Diskではvirtio_blk、virtio_scsi、Hypervisor固有のDriverなどが使用される場合があります。Cloud上で/dev/nvme0n1と見えても、物理的なNVMe Deviceを専有しているとは限らず、仮想化されたStorage ServiceへのInterfaceである場合があります。

現在Deviceへ関連付けられているDriverは、lspci -k、udevadm info、/sys/class/block配下のSymbolic Linkなどをたどって確認できます。Device名だけでDriverや物理構成を推測せず、sysfs情報を確認することが重要です。

Device DriverからStorageまでの流れを整理する

Block Layerとblk-mqがrequestをDispatchすると、Device DriverがReadやWriteなどの内容を確認し、Hardwareが理解できるCommandを作成します。DriverはDMA Mappingを準備し、Storage ControllerへCommandを通知します。

ControllerとStorage DeviceはCommandを処理し、DMAによってMemoryとの間でDataを転送します。転送とDevice側の処理が完了すると、一般にInterruptによってCPUへ通知します。

Device DriverはCompletion Statusを読み取り、対応するrequestを特定してBlock Layerへ完了を返します。Block Layerはbioを完了させ、FilesystemやPage Cacheへ結果を伝えます。同期I/Oで待っていたApplicationは、その後CPUへSchedulingされると処理を再開します。

図では理解しやすいようにStorage ControllerとStorage Deviceを分けていますが、両者の境界は構成によって異なります。また、高速I/OではInterruptの代わりにPollingを利用する場合があり、Virtual StorageではHost側に追加のDriverやBackend Layerが存在します。

まとめ

Device Driverは、Linux KernelとHardwareの間を結ぶSoftwareです。Block Layerから受け取った共通的なI/O RequestをSATA、SAS、NVMeなどのDeviceが理解できるCommandへ変換し、Commandの送信、DMA、Interrupt、Error処理を管理します。

/dev/sda、/dev/vda、/dev/nvme0n1はUser SpaceからBlock DeviceへアクセスするためのDevice Nodeであり、Driver本体ではありません。Device名はLinuxから見える分類を示しますが、物理的な接続やDriver Stackを正確に知るにはsysfs、udevadm、lspciなどによる確認が必要です。

データ転送では一般にDMAが使われるため、CPUは転送中に別の処理を実行できます。I/Oが完了するとInterruptまたはPollingでDriverが結果を検出し、blk-mq、Block Layer、Filesystem、Page Cacheへ完了を返します。

HDD、SATA SSD、NVMe SSDでは、MediaだけでなくInterface、Queue構造、Controller、Driver Stackが異なります。Device DriverからStorageまでの流れを理解することで、I/O Latency、Queue詰まり、Interrupt負荷、Driver Errorなどをレイヤーごとに切り分けやすくなります。

コメント