">

ほそぼそストレージ研究所

LinuxNVMe操作アプリケーションを作ってみた-1-LinuxSystemInfo

投稿日 2026.04.09 カテゴリ nvme レベル 中級者
nvme linux implementation

はじめに

前回の記事では、NVMe 学習用 CLI の v0 全体像をまとめました。
今回はその中でも、最初に作った listshow に絞って書いてみます。

この2つは、後で作った identifysmarterrinfo とはかなり性質が違います。
identify 以降は NVMe command を自分で投げて情報を取りに行きますが、listshow はそうではありません。

listshow がやっているのは、Linux がすでに持っている NVMe デバイス情報を拾って整形することです。

実装を進める中で、入口としての list/show は他の command とはかなり別物として扱う必要があると分かってきました。

なぜ最初に list/show から作ったのか

今回の v0 は、最初から identifysmart に入ったわけではなく、まず listshow から作りました。

理由はかなり単純で、

  • 対象NVMeを見つけるために、まずは Linux から見えているデバイスを把握しておく必要がある
  • 対象の serial や model を確認する入口があると便利
  • いきなり低レイヤーの NVMe command に入る前に、CLI の流れを作りたかった
  • boot / system デバイスを触るので、最初はできるだけ安全側に寄せたかった

からです。

今回の環境ではM.2 SSDをシステム用以外に挿すことができなかったので、boot / system deviceを触らざるを得ないです。
そのため、NVMeを直接操作するpassthroughやioctlに進むのはリスクが高いと考えました。
また、複数台用意できる環境でも、どのデバイスが存在して自分が触るデバイスがなにかを確認するのは大事なことです。

list/show は NVMe command を投げていない

listshow は、NVMe の IdentifyGet Log Page などのコマンドを投げているわけではありません。
Linux がすでに認識している block device と、その周辺情報を集めて表示しています。

つまり、ここで見ているのは「NVMe device が何を返すか」ではなく、Linux が NVMe device をどう見ているか です。

Linux system上のNVMeデバイスの仕組み

今回の list/show 実装では、主に次の情報源を見ています。

  • /sys/class/block/
  • /sys/class/block/<device>/device/
  • /proc/self/mounts
  • /dev/nvme*

流れとしてはかなり素直で、まず /sys/class/block/ を走査し、nvme で始まる block device を探しています。実際に見てみましょう

$ ls /sys/class/block | grep nvme
nvme0n1
nvme0n1p1
nvme0n1p2
$ ls /sys/class/block/nvme0n1
alignment_offset  capability  dev     discard_alignment  eui     events_async       ext_range  holders   integrity       mq     nsid  nvme0n1p1  partscan                  power  range      ro    slaves  subsystem  uevent  wwid
bdi               csi         device  diskseq            events  events_poll_msecs  hidden     inflight  metadata_bytes  nguid  nuse  nvme0n1p2  passthru_err_log_enabled  queue  removable  size  stat    trace      uuid
$ ls /sys/class/block/nvme0n1/device
address  cntlid  cntrltype  dctype  dev  device  firmware_rev  hwmon1  kato  model  ng0n1  numa_node  nvme0n1  passthru_err_log_enabled  power  queue_count  rescan_controller  reset_controller  serial  sqsize  state  subsysnqn  subsystem  transport  uevent
  • size
  • device/model
  • device/serial
  • device/firmware_rev

のような情報を読んで、DeviceInfo に詰めています。
ファイルは以下のような情報が入っています。情報自体はシンプルですね。

$ cat /sys/class/block/nvme0n1/size 
1000215216
$ cat /sys/class/block/nvme0n1/device/model 
WPBSNM8-512GTP

*) 補足
このmodelはWodposit社製の512GB PCIe Gen3.0 x 4 NVMeデバイスです。
Gen3.0 x 4としては性能は優秀な方のようです。
Wodpositなんて会社初めて聞きました。中華製のデバイスですね。Geekomと提携していて、今回のMiniPCももれなくそれに該当します。

実際のコードでは
以下のように実装しています。

std::string block_dir="/sys/class/block/";
for (const auto& entry : std::filesystem::directory_iterator(block_dir)) 
    if (std::filesystem::exists(entry.path() / "partition")) continue;
    const auto name = entry.path().filename().string();
    if (name.rfind("nvme", 0) != 0) continue;
    DeviceInfo info;
    std::string tgt_dir = block_dir + name + "/";
    info.char_dev_path = "/dev/"+NormalizeNvmeController(name);
    info.block_dev_path = "/dev/"+name;
    info.capacity_bytes = std::stoull(ReadTextFile(tgt_dir + "size")) * 512;
    tgt_dir += "device/";
    info.model = TrimRightAsciiWhitespace(ReadTextFile(tgt_dir + "model"));
    info.serial = TrimRightAsciiWhitespace(ReadTextFile(tgt_dir + "serial"));
    info.firmware = TrimRightAsciiWhitespace(ReadTextFile(tgt_dir + "firmware_rev"));
    info.bus_type = "NVMe";
}

ここでは、C++のSTLのfilesystemを使って、/sys/class/block下のディレクトリを順々に見ています。
いろいろなデバイスがあるので、以下の要素で絞り込みます。

  • ディレクトリ名にnvmeがある
  • 該当ディレクトリ下にpartitionファイルが存在しない。

後述しますが、partitionファイルがあるblockは今回の操作の対象外です。

さらに /proc/self/mounts を見て、

  • そのデバイスが / を支えているか
  • /boot/boot/efi に使われているか

を確認し、is_system_diskis_boot_disk の判定に使っています。
私の環境では以下のようになっています。
nvme0n1p1がboot向け、nvme0n1p2がシステム向けの領域です。

$ cat /proc/self/mounts | grep nvme
/dev/nvme0n1p2 / ext4 rw,relatime 0 0
/dev/nvme0n1p1 /boot/efi vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 0

私の環境では1枚しかnvmeを挿せないので、何も考えずに同じデバイスを使っていますが、当初の予定ではここで使われているデバイスはis_allowed = falseというフラグを立てて、アクセス禁止にする、という予定でした。

今回の実装ではこのように書いています。

std::ifstream ifs("/proc/self/mounts");
std::string line;
while(std::getline(ifs, line)) {
    std::istringstream iss(line);
    std::string source, mount_point, fstype;
    if (!(iss >> source >> mount_point >> fstype)) {
        continue;
    }
    if (mount_point == "/") {
        if (NormalizeNvmeParent(source) == info.block_dev_path) {
            info.is_system_disk = true;
            info.flags.push_back("system");
        }
    }
    if (mount_point == "/boot" || mount_point == "/boot/efi") {
        if (NormalizeNvmeParent(source) == info.block_dev_path) {
            info.is_boot_disk = true;
            info.flags.push_back("boot");
        }
    }
}

/proc/self/mountsの中身はたくさんあってややこしいのですが、/, /boot/efiを探して、その時のデバイスを特定するだけであれば、さほど難しくないです。

nvme0nvme0n1nvme0n1p1

Linux 上では、似たような名前のデバイスがいくつも見えます。

  • nvme0
  • nvme0n1
  • nvme0n1p1

今回の整理としては、ざっくり次のように考えています。

  • nvme0
  • controller 側の char device
  • 今回のコードでは char_dev_path
  • nvme0n1
  • namespace に対応する block device
  • 今回のコードでは block_dev_path
  • nvme0n1p1
  • その block device 上の partition

list/show で主に見たいのは、「評価対象としてのストレージ本体はどれか」です。
そのため、partition そのものを一覧に混ぜず、走査時には partition ファイルがあるものをスキップしています。

これにより、

  • 何を評価対象にすべきか
  • /boot/ がどの NVMe block device にぶら下がっているか

を見やすくしています。

partition を親デバイスへ戻す

/proc/self/mounts を見ると、//boot にマウントされている実体は nvme0n1p1 のような partition 名で出てくることがあります。

ただ、show で知りたいのは partition 単位ではなく、「その partition が属している NVMe block device は何か」です。

そのため、実装では

  • nvme0n1p1nvme0n1

のように親 block device へ正規化する処理を入れています。

これによって、

  • /bootnvme0n1p1
  • /nvme0n1p2

のようなケースでも、

  • 元の NVMe block device は nvme0n1

と判断できるようになります。

list と show の役割の違い

listshow は、同じ情報源を見ていても役割が少し違います。

list

list は候補デバイスをざっと眺める command です。

主に見るのは、

  • device path
  • model
  • serial

だけで、あまり情報を出しすぎないようにしています。

ここでやりたいのは、
どの NVMe device を次に詳しく見るべきか決めること
です。

show

show は、list で見つけた serial を頼りに 1 台を詳しく見る command です。

こちらではさらに、

  • firmware
  • capacity
  • bus type
  • is system disk
  • is boot disk
  • is allowed
  • flags
  • reason denied

まで表示します。

ここでの is_allowedflags は、単なる飾りではなく「このデバイスを次にどう扱うか」を判断するための補助情報です。

  • is_system_disk
  • is_boot_disk
  • flags

のような情報を先に見ておくことで、対象が boot / system に関わるデバイスかどうかを確認できます。

今回の v0 では write 系 command を入れていないので、is_allowed は厳密な実行制御というより、今後の安全判定に向けたフックの意味合いが強いです。
それでも、show の時点で安全に関わる情報をまとめて見られるようにしておくことには意味があると考えています。

つまり show は、

  • 本当に意図したデバイスか
  • 触ってよさそうか
  • 次に identify へ進んでよいか

を確認するための command です。

list/show の次に、NVMe command の世界へ進む

list/show を実装していると、この層で扱っているのは Linux がすでに知っている情報を整理して見せること だと分かってきます。

たとえば、

  • SMART Log の中身
  • Error Information Log
  • Identify Controller の各 field

のような情報は、Linux がそのまま見せてくれるわけではありません。

そこから先は、こちらが NVMe command を直接投げて、自分で buffer を読み、spec に従って解釈する段階に入っていきます。

つまり、

  • list/show では Linux が持っている情報を確認する
  • identify 以降では NVMe device に直接 command を打って情報を取りに行く

という流れになります。

この境界が見えるようになると、

  • list/show は Linux の観察
  • identify/smart/errinfo は NVMe protocol の観察

という整理がしやすくなります。

まとめ

listshow は、NVMe 学習用 CLI の入口としてかなり良い command でした。

理由は、

  • まず Linux が見ているデバイス情報を整理できる
  • 安全に read-only で確認できる
  • char device / block device / partition の違いを理解できる
  • 次に identifysmart へ進む前の足場になる

からです。

この段階を踏んだからこそ、後で IdentifyGet Log Page に進んだ時も、

  • 何を Linux から拾っているのか
  • 何を NVMe command で取りにいっているのか

を混同せずに済んでいます。

次はこの続きとして、identify 以降でついに NVMe device に直接 command を打ち、何を受け取っているのかを別記事でまとめたいです。