How the repo is laid out.
A top-to-bottom tour of the v26.01 SPDK source tree. The directory
shape is a direct reflection of the stack diagram from
page 1.1: framework code in
lib/, public headers in include/spdk/,
optional backends in module/, runnable binaries in
app/, and a build system in mk/ that
glues it all together.
- The top level: what every directory is for
app/— the binaries you actually runlib/— the framework librariesmodule/— the plugin layer (especiallymodule/bdev/)include/spdk/— the public API surfacedoc/— the official documentationexamples/— small, focused programstest/,scripts/,mk/— the rest- The build system:
./configure+make - The Makefile structure: one Makefile per lib, one per module
- How an app links against the framework
- The include order convention (private vs public)
- Edge cases — what breaks when you break the conventions
- Why it matters for the rest of the curriculum
The top level: what every directory is for
From a fresh git clone (this is v26.01.0 — the
VERSION file is your source of truth), the top of the
tree looks like this:
flowchart TB classDef code fill:#f6f0e3,stroke:#a67c00,color:#1e2a3a classDef build fill:#faf0e8,stroke:#a04a2a,color:#1e2a3a classDef doc fill:#fdfbf6,stroke:#6b7790,color:#1e2a3a classDef sub fill:#1a2842,stroke:#2a6b6b,color:#e6e9f0 ROOT["spdk_v26_01_migration/"] ROOT --> A["app/"]:::code ROOT --> B["lib/"]:::code ROOT --> C["module/"]:::code ROOT --> D["include/spdk/"]:::code ROOT --> E["examples/"]:::code ROOT --> F["doc/"]:::doc ROOT --> G["test/"]:::doc ROOT --> H["scripts/"]:::doc ROOT --> I["mk/"]:::code ROOT --> J["Makefile + configure + CONFIG"]:::code ROOT --> K1["dpdk/"]:::sub ROOT --> K2["isa-l/ isa-l-crypto/"]:::sub ROOT --> K3["libvfio-user/"]:::sub ROOT --> K4["xnvme/ intel-ipsec-mb/"]:::sub ROOT --> K5["ocf/"]:::sub ROOT --> L1["dpdkbuild/ isalbuild/"]:::build ROOT --> L2["isalcryptobuild/ vfiouserbuild/"]:::build ROOT --> L3["xnvmebuild/ ipsecbuild/"]:::build ROOT --> M1["python/ go/ proto/"]:::doc ROOT --> M2["schema/ rpmbuild/"]:::doc ROOT --> M3["shared_lib/ docker/ patches/"]:::doc
fig. 1 Top-level SPDK tree. Source code (gold)
lives in app/, lib/, module/,
examples/, and include/. Build
helpers (rust) are in mk/,
configure, and the top-level Makefile.
Submodules (teal) and their build
outputs (also rust) are alongside. Everything else is docs
and packaging.
The shape to internalize: code lives in
lib/, module/, app/,
examples/, and include/. The other
directories are either build infrastructure, vendored submodules, or
documentation.
app/ — the binaries you actually run
app/ is the smallest "real" directory. Each
subdirectory builds exactly one binary; the binary lands in
build/bin/ after make. Here's what's in
it:
| App | What it does | When you'd run it |
|---|---|---|
nvmf_tgt | The NVMe-oF target — exposes local bdevs as NVMe namespaces over TCP, RDMA, FC, or VFIO-user. | Production deployments exposing remote storage; almost all of Layer 6 is about this binary. |
vhost | The virtio target — serves bdevs to QEMU / Cloud Hypervisor / Firecracker over vhost-user sockets. | Local VM deployments; covered in Layer 7.1. |
iscsi_tgt | The iSCSI target — exposes bdevs as iSCSI LUNs. | Legacy environments; rarely the right answer for new builds. |
spdk_tgt | A combined target that dynamically loads bdev modules and protocol plugins at runtime. | Convenience build when you don't want to pick a specific target at build time. |
bdevperf | The benchmark tool — a spdk bdev workload generator. | Capacity + IOPS testing; the official answer to "is my SPDK build actually fast?" |
spdk_top | A top-like tool for live reactor inspection. | Ops + debugging; see Layer 9.1. |
spdk_nvme_perf, spdk_nvme_identify, spdk_nvme_discover | NVMe-specific tools: throughput benchmark, controller info, NVMe-oF discovery. | Hardware bring-up, troubleshooting a misbehaving SSD. |
spdk_dd | Like dd, but for SPDK bdevs. | Quick copy/inflate of a bdev. |
spdk_lspci | List the PCI devices in the system, with the SPDK-relevant info (NUMA node, BAR addresses). | Pre-flight check before binding devices to DPDK. |
trace, trace_record | Userspace tracing tools (see Layer 9.2). | Production tracing and postmortem analysis. |
fio/ | The SPDK fio plugin — built only with --with-fio. | If you want fio to drive SPDK bdevs as a job source. |
Every entry above is one subdirectory of app/. For
example, app/nvmf_tgt/ contains just two files:
nvmf_main.c (the actual program) and a
Makefile that wires it up. That's it. There is no
app/nvmf_tgt/lib/, no app/nvmf_tgt/include/.
An SPDK app is intentionally tiny.
lib/ — the framework libraries
lib/ is the heart of SPDK. Each subdirectory is a
single C library — code, headers, a Makefile, and a linker version
script (spdk_*.map). Here is the full list, with the
one-line role of each:
| Library | Role | What you'll find inside |
|---|---|---|
lib/event | The reactor + spdk_app framework. | reactor.c, app.c, scheduler_static.c, app_rpc.c. See Layer 2.1. |
lib/thread | The threading abstraction (spdk_thread, spdk_io_channel, pollers). | thread.c, iobuf.c. See Layer 2.2. |
lib/bdev | The bdev framework — the plugin bus for block devices. | bdev.c, bdev_rpc.c, bdev_zone.c, part.c, scsi_nvme.c. See Layer 4. |
lib/nvme | The raw NVMe driver — talks to NVMe controllers, manages I/O queues. | Controller enumeration, queue pair management, namespace discovery, PRP/SGL handling. Public API in include/spdk/nvme.h. |
lib/nvmf | The NVMe-oF target library — the C API for building an NVMe-oF target. | nvmf.c, subsystem.c, ctrlr.c, ctrlr_bdev.c, discovery.c, tcp.c, rdma.c, fc.c, vfio_user.c, mdns_server.c, auth.c. See Layer 6. |
lib/vhost | The vhost target library — the C API for building a virtio target. | virtio-scsi, virtio-blk, vhost-scsi, vhost-blk. See Layer 7. |
lib/jsonrpc | The JSON-RPC client and server libraries. | jsonrpc_server.c, jsonrpc_client.c, plus *_tcp.c variants. See Layer 3. |
lib/rpc | Higher-level RPC helpers — registering handlers, defining the JSON schema. | The macro-and-function machinery that turns SPDK_RPC_REGISTER into JSON-RPC methods. |
lib/blob + lib/lvol | The blobstore (a key-value on top of bdevs) and lvol (the logical volume manager built on blobstore). | See Layer 5. |
lib/sock | The socket abstraction — POSIX, UDP, RDMA transports behind a single API. | Used by nvmf (TCP) and vhost (control plane). |
lib/iscsi | The iSCSI target library. | Build only with --with-iscsi-initiator for the initiator path. |
lib/log, lib/util, lib/conf | Logging, generic utilities, the configuration-file parser. | log.c, util/*.c, the INI-style spdk_conf reader. |
lib/init | The framework initializer — coordinates startup of all subsystems. | The thing that walks spdk_subsystem_* registrations and brings them up in order. |
lib/accel, lib/idxd, lib/ioat, lib/ae4dma | Copy / crypto offload abstractions and their Intel-specific backends. | Used by bdev modules that want to do CRC or crypto in hardware. |
lib/dma, lib/env_dpdk, lib/env_ocf | The "environment" abstractions — DMA memory, device access, primary execution environment. | Every other library depends on these. The default env_dpdk is the only one most users will ever touch. |
lib/trace, lib/trace_parser | Userspace tracing framework (ftrace-style) and the parser that reads trace files. | See Layer 9.2. |
lib/notify | Asynchronous change notification — for hotplug events, device changes, etc. | Used by the NVMe bdev module to react to device add/remove. |
lib/ftl, lib/scsi, lib/virtio, lib/nbd, lib/fuse_dispatcher, lib/keyring, lib/rdma_*, lib/vfio_user, lib/vfu_tgt, lib/vmd, lib/ublk, lib/fsdev | More framework libraries, each backing a specific subsystem. | You'll encounter these only when you go deep into the corresponding subsystem. |
lib/ut, lib/ut_mock | Unit-test scaffolding and mock helpers. | Used only by test/unit/. |
The naming convention is consistent: spdk_<libname>.map
is the linker version script that defines the ABI surface,
Makefile is the build script, and the public C API ends
up mirrored in include/spdk/. Internal helpers stay
private to the library.
module/ — the plugin layer (especially module/bdev/)
module/ is where the optional backends live. The
directory shape mirrors lib/: one subdirectory per
plugin family, and inside each, one subdirectory per plugin. The
largest is module/bdev/, with one directory per bdev
backend.
Listing out the bdev backends as of v26.01:
aio/ (Linux AIO to a file or block device),
crypto/ (ISA-L-crypto-backed encryption),
daos/ (--with-daos),
delay/ (artificial-latency for testing),
error/ (always-fails for fault-injection testing),
ftl/ (FTL over NAND),
gpt/ (GPT partition parser),
iscsi/ (iSCSI initiator as a bdev, --with-iscsi-initiator),
lvol/ (logical volumes over blobstore),
malloc/ (RAM-backed, volatile, fast, great for tests),
nvme/ (NVMe SSD — the canonical backend),
null/ (always succeeds, drops the data),
ocf/ (OCF cache, --with-ocf),
passthru/ (proxies I/O to another bdev),
raid/ (software RAID across bdevs),
rbd/ (Ceph RBD, --with-rbd),
split/ (multiplexes one bdev into many),
uring/ (io_uring backend, --with-uring),
virtio/ (virtio-pci, --with-virtio),
xnvme/ (--with-xnvme), and
zone_block/ (ZNS zoned block devices).
Each of these is a separate library with the same shape as
lib/<name>: a directory, a Makefile,
source files, and a public header for any module that wants to
depend on it. For example,
module/bdev/nvme/ is its own library
(libbdev_nvme.a) that links against libbdev.a
and libnvme.a.
The other module/ subdirectories follow the same
pattern, just for different kinds of plugins:
module/accel/— backends for the accel framework:dpdk_cryptodev,dsa,iaa,ioat,mlx5,cuda,ae4dma,error.module/sock/— socket plugins:posix(always built) anduring(io_uring-accelerated).module/event/— additional reactor subsystems:subsystemsis the standardspdk_subsystem_*registration set.module/scheduler/— alternative thread schedulers (e.g. dynamic, greedy).module/keyring/— keyring backends (for encrypted bdevs).module/blob/— alternative blob stores (in addition tolib/blob).module/vfu_device/— vfio-user device backends.module/env_dpdk/— the env layer plugin that wires DPDK intospdk_env. Most users never touch this; it builds automatically.module/fsdev/— filesystem-style device plugins (for FUSE-style exposure).
The single most important Makefile to read in this whole tree is
module/bdev/Makefile, because it shows the "subdirs"
pattern — how a parent Makefile delegates to its children:
include/spdk/ — the public API surface
include/spdk/ is the only directory in the repo that
downstream code is expected to #include from. It
contains the public C API for every framework library, plus a few
headers that aren't tied to a specific library
(stdinc.h, env.h, log.h).
As of v26.01, the public headers number around 100. The full list is too long to enumerate here, but the shape is:
| Header | Public surface of | Layer in the curriculum |
|---|---|---|
spdk/bdev.h | The bdev framework: spdk_bdev, spdk_bdev_io, spdk_bdev_open, module registration. | Layer 4 |
spdk/nvme.h | The raw NVMe driver: spdk_nvme_probe, spdk_nvme_ctrlr, spdk_nvme_ns, queue pair ops. | Layer 0.2 + Layer 6 |
spdk/nvmf.h | The NVMe-oF target: spdk_nvmf_tgt, spdk_nvmf_subsystem, spdk_nvmf_poll_group. | Layer 6 |
spdk/vhost.h | The vhost target: spdk_vhost_session, spdk_vhost_dev, virtio config space helpers. | Layer 7 |
spdk/jsonrpc.h | The JSON-RPC client and server. | Layer 3 |
spdk/rpc.h | The RPC handler-registration macros. | Layer 3 |
spdk/thread.h | spdk_thread, spdk_io_channel, pollers, spdk_poller. | Layer 2 |
spdk/event.h | spdk_app_start, spdk_app_stop, the reactor API. | Layer 2 |
spdk/blob.h, spdk/blob_bdev.h, spdk/lvol.h | The blobstore and lvol APIs. | Layer 5 |
spdk/env.h, spdk/stdinc.h, spdk/log.h, spdk/memory.h, spdk/mmio.h | Cross-cutting utilities — environment, stdint shim, logging, memory allocation, MMIO access. | Used everywhere |
spdk/nvme_spec.h, spdk/nvme_zns.h, spdk/nvme_ocssd.h, spdk/scsi_spec.h, spdk/opal_spec.h, spdk/nvmf_spec.h | NVMe / SCSI / NVMe-oF / Opal spec constants and structs — the spec-level, not the driver-level, API. | Layer 0.2 + 6 |
spdk/accel.h, spdk/idxd.h, spdk/ioat.h, spdk/dma.h, spdk/dif.h | Copy / crypto offload, DMA, Data Integrity Field. | Used by bdev modules + storage appliances |
spdk/init.h | The framework-init subsystem registration API. | Used by all apps |
spdk/scheduler.h, spdk/trace.h, spdk/notify.h, spdk/keyring.h, spdk/conf.h, spdk/sock.h, spdk/net.h, spdk/file.h, spdk/pipe.h, spdk/fd.h, spdk/fd_group.h | Smaller utilities and integrations. | Used as needed |
spdk/module/bdev/, spdk/module/keyring/ | Public headers for module implementations — your bdev module will include headers from here. | Layer 8 |
doc/ — the official documentation
doc/ is the Doxygen + Markdown source for the official
SPDK documentation. The most useful files for a new reader:
doc/about.md— the one-paragraph elevator pitch. Read this first.doc/overview.md— the structural overview, with the directory layout and the threading model explained.doc/concepts.md— the core concepts (channels, pollers, messages, subsystems).doc/bdev.md— the bdev framework reference.doc/nvme.md— the NVMe driver reference.doc/nvmf.md— the NVMe-oF target reference.doc/vhost.md— the vhost target reference.doc/jsonrpc.md.jinja2— the JSON-RPC method reference (auto-generated from the schemas inschema/).doc/getting_started.md— how to build and run SPDK end-to-end.doc/concurrency.md— the threading model explained in detail. (You'll read this again in Layer 2.4.)doc/Doxyfile— the Doxygen configuration that builds the API reference atspdk.io/doc/.
examples/ — small, focused programs
examples/ mirrors app/, but for
educational and reference programs. Each subdirectory is one
small program. The structure:
The list of subdirectories, by topic: bdev/ for bdev
examples (includes hello_world/ — the smallest
possible bdev program — and bdevperf/ — the bdevperf
workload generator), nvme/ for raw NVMe driver
examples (hello_world/, nvme_manage/,
hotplug/, and several more), nvmf/ for
NVMe-oF client (initiator) examples, sock/ for socket
abstraction examples, accel/ for copy + crypto offload
examples, blob/ for blobstore examples,
thread/ for threading examples, idxd/ for
IDXD offload examples, ioat/ for IOAT offload
examples, interrupt_tgt/ for an example target that
runs in interrupt mode, vmd/ for VMD examples,
fsdev/ for FUSE-style filesystem device examples,
go/ for Go client examples, and util/ for
miscellaneous utilities used by other examples.
The hello_world/ programs are the most useful: they
do the absolute minimum (open a bdev, submit one I/O, exit) and
are heavily commented. examples/bdev/hello_world/hello_bdev.c
is the canonical "first program" for the bdev API.
Examples are built with CONFIG_EXAMPLES=y (the
default), and their binaries land in build/examples/
— note the separate subdirectory from app/'s
build/bin/. The spdk.app.mk Makefile
fragment handles the path automatically based on where your
Makefile lives.
test/, scripts/, mk/ — the rest
The remaining three top-level directories matter, but in a more focused way.
test/
Functional and unit tests, organized by subsystem:
test/bdev/, test/nvme/,
test/nvmf/, test/lvol/,
test/event/, test/vhost/,
test/unit/, and so on. The functional tests are
shell scripts (driven by test/common/); the unit
tests are C programs built with
CONFIG_UNIT_TESTS=y and run via
test/unit/unittest.sh. If you change a library
behavior, the matching test/<name>/ directory
is where to look first.
scripts/
Operational and build helpers. The ones you'll touch:
scripts/pkgdep.sh— install all the system dependencies for a given distro. Run this on a fresh VM.scripts/setup.sh— bind NVMe devices to the kernel driver SPDK will replace (e.g.vfio-pcioruio_pci_generic), and allocate hugepages.scripts/rpc.py— the official Python CLI for SPDK's JSON-RPC. This is the Layer 3 equivalent ofcurl.scripts/gen_ftl.sh,scripts/gen_nvme.sh,scripts/genconfig.py,scripts/genrpc.py— code generators used at build time.scripts/check_format.sh— astyle-based style check. CI runs this; your PR will fail if you don't.scripts/spdkcli.py— an interactive CLI for SPDK. (Mostly superseded byrpc.pybut still shipped.)scripts/bpftrace.sh,scripts/bpf/— bpftrace-based live tracing.scripts/bash-completion/— bash completion for SPDK's tools.
mk/
The build "library" — small Makefile fragments that every
subdirectory's Makefile includes. The fragments:
mk/spdk.common.mk— the base fragment included by every Makefile in the tree. Defines the compiler flags, theCONFIG_variables, the path toinclude/spdk/config.h, and the helpers (Q,E,Mfor quiet builds).mk/spdk.lib.mk— the rules for building a library (libspdk_<name>.aand the matching.soifCONFIG_SHARED=y).mk/spdk.app.mk— the rules for building an app or example.mk/spdk.app_vars.mk— the variables thatspdk.app.mkexpects to be set.mk/spdk.modules.mk— the master list ofALL_MODULES_LIST, plus per-category lists (BLOCKDEV_MODULES_LIST,ACCEL_MODULES_LIST, etc.). Apps use this.mk/spdk.subdirs.mk— the recursive descent into subdirectories, used bymodule/bdev/Makefileand friends.mk/spdk.deps.mkandmk/spdk.lib_deps.mk— the library-dependency graph. Adding a new dependency between libraries is a one-line edit here.mk/spdk.unittest.mk,mk/spdk.mock.unittest.mk,mk/spdk.fio.mk,mk/nvme.libtest.mk— specialized fragments for unit tests, mocks, the fio plugin, and the NVMe test harness.
The single most useful thing you can do, on day one, is read
mk/spdk.common.mk in full. It's about 400 lines, and
it tells you everything about how SPDK thinks about flags,
includes, and the build environment.
The build system: ./configure + make
The build is the standard autoconf-shaped two-step:
./configuremake./configure is a 1450-line bash script. It checks
for system packages, asks the kernel for hugepage availability,
figures out the DPDK layout, and writes a resolved
CONFIG file. The relevant knobs you'll touch most
often (lifted from ./configure --help):
| Flag | Effect |
|---|---|
--prefix=path | Install destination (default /usr/local). |
--with-dpdk=DIR | Use a custom DPDK build instead of the bundled submodule. |
--with-rdma | Build the RDMA transport (requires libibverbs). |
--with-vhost | Build the vhost target (enabled by default). |
--with-virtio | Build the virtio-pci bdev and the vhost initiator. |
--with-vfio-user | Build the custom vfio-user transport for nvmf. |
--with-rbd | Build the Ceph RBD bdev module. |
--with-crypto | Build ISA-L-crypto and the vbdev crypto module. |
--with-shared | Build shared libraries (libspdk_*.so) in addition to the static archives. |
--disable-tests, --disable-unit-tests, --disable-examples, --disable-apps | Skip the corresponding subset of the build. |
--enable-debug | Turn on debug logging and disable optimizations. |
--enable-asan, --enable-ubsan, --enable-tsan | Enable the corresponding sanitizer. |
--enable-lto | Link-time optimization (slower build, faster binary). |
--enable-werror | Treat warnings as errors. Default n in SPDK; some downstream forks turn it on. |
--with-cuda, --with-daos, --with-xnvme, --with-uring, --with-iscsi-initiator, --with-ublk | Toggle the more specialized modules. |
What configure does not do: it does not invoke
make. You run them in sequence. A clean
./configure && make -j$(nproc) on a 16-core
box is on the order of 5–10 minutes.
The Makefile structure: one Makefile per lib, one per module
Every directory that produces a build artifact has its own Makefile. Every Makefile in SPDK looks roughly like one of three patterns. A library, an app, and a subdirs parent. (The full templates are in the next callout; reading them is the fastest way to internalize the pattern.)
The three top-level Makefile fragments (spdk.lib.mk,
spdk.app.mk, spdk.subdirs.mk) encode
almost all the "what does a build look like" knowledge. Once
you've seen one lib, one app, and one subdirs Makefile, you've
seen the pattern. The variations are all in the values —
SPDK_LIB_LIST, DIRS-y,
C_SRCS, SO_VER.
How an app links against the framework
Linking an app is the simplest part of the build, because the Makefile machinery has already done the hard work. Here is the sequence in detail:
Each
lib/<name>buildslibspdk_<name>.a(and.soif shared). The .a contains the .c files listed inC_SRCS, plus the public symbols declared inspdk_<name>.map.Each
module/<category>/<name>builds its own static archive, named afterLIBNAMEin its Makefile. Formodule/bdev/nvme/, that produceslibbdev_nvme.a.The app's Makefile lists
SPDK_LIB_LIST. This is the manifest of "what I want to link against."mk/spdk.app.mkresolves that list into the right-land-Larguments, ordered to satisfy dependencies. The order is computed frommk/spdk.lib_deps.mkandmk/spdk.modules.mk; you don't compute it.makeproduces the binary atbuild/bin/<appname>(forapp/) orbuild/examples/<appname>(forexamples/). Thespdk.app.mkMakefile fragment picks the destination based on the directory path.
The whole point of this design is that an SPDK app
Makefile is short, readable, and a pure manifest. You almost
never need to write a $(CC) invocation by hand.
The include order convention (private vs public)
Every C file in SPDK follows the same include order. The convention is not documented in one place — it's enforced by code review and the astyle-based check — but once you know it, you can read any C file in the tree:
The file's own private header first. If
lib/bdev/bdev.cincludes a private header, it's"bdev_internal.h"— relative, nospdk/prefix. This catches the case where you forgot to add the new private header to the include path.Public
spdk/headers in alphabetical order.spdk/bdev.h, thenspdk/env.h, thenspdk/json.h, etc. This makes the dependency graph visible at a glance.System headers last.
<stdio.h>,<string.h>, etc. The publicspdk/stdinc.hshim is included viaspdk/<name>.h, so most SPDK files don't includestdint.hdirectly.
Public headers in include/spdk/ only include other
public headers, plus standard C headers. They never include
lib/<name>/<name>_internal.h. That is
the single rule that keeps the public ABI clean.
Edge cases — what breaks when you break the conventions
The build system is forgiving, but there are a few specific ways to break it. These are the ones that cost you an afternoon if you don't know about them in advance.
Symptom: the build succeeds, but the new function is undefined at link time when something else references it.
Cause: the C_SRCS variable in lib/<name>/Makefile is the manifest. A new .c file is not picked up automatically.
Fix: add it to C_SRCS in the right Makefile. Run make clean && make on the affected library, then re-link.
Symptom: every .c file that includes the header fails to compile.
Cause: the header is the contract. Changing a function signature in include/spdk/foo.h is, definitionally, an ABI break.
Fix: update all callers (in lib/, module/, app/, examples/) in the same patch. For downstream consumers, bump SO_VER in the relevant library Makefile. The linker version script will then refuse to link the old and new .so together.
Symptom: an app that wants to link against your new library fails to link with an "undefined reference" to a symbol that should be in another library.
Cause: the linker order comes from mk/spdk.lib_deps.mk. New libraries aren't picked up automatically; their dependencies on other libraries need to be declared.
Fix: add the appropriate DEPS lines in mk/spdk.lib_deps.mk. The spdk.lib.mk rules are strict about ordering, so this is one of the most common "I added a new library" gotchas.
Symptom: your new module compiles (because you added it to module/bdev/Makefile DIRS), but apps that use $(ALL_MODULES_LIST) don't see it.
Cause: BLOCKDEV_MODULES_LIST in mk/spdk.modules.mk is the master list. Apps that want "all the bdev modules" link against that list, not against the directory tree.
Fix: add the LIBNAME to BLOCKDEV_MODULES_LIST in mk/spdk.modules.mk. (The directory tree in module/bdev/Makefile only determines whether the module builds; whether it's linked by an app is a separate question.)
Why it matters for the rest of the curriculum
The directory shape you just read is the map you'll carry through every later layer:
Layer 2 (threading) lives in
lib/event/+lib/thread/. When a page says "see the reactor," it meanslib/event/reactor.c.Layer 4 (bdev) lives in
lib/bdev/+module/bdev/*. The framework is inlib/; the backends are inmodule/.Layer 6 (nvmf) lives in
lib/nvmf/+app/nvmf_tgt/. The library is the framework; the app is one possible binary.Layer 8 (write a bdev) is the moment you create a new directory under
module/bdev/, add it tomodule/bdev/MakefileDIRS, add its LIBNAME toBLOCKDEV_MODULES_LISTinmk/spdk.modules.mk, and write a Makefile that usesspdk.lib.mk. Every step is exactly what you saw on this page.
The next layer — 2.1 — The reactor
poll loop — opens lib/event/reactor.c and walks
through what spdk_app_start actually does. The
directory structure from this page is the table of contents for
that walk.