Conventions#
A brief overview of conventions used for the xNVMe C codebase.
C: format#
clang-format is utilized to format the code. There are two clang-format-option
files, one for the headers (toolbox/clang-format-h
) and one for the source
toolbox/clang-format-c
.
You can setup your editor to use the format-files, or run clang-format via the
toolbox: make format-all
. This will format all files in the repository.
C: style#
Declare variables
At the top of code-blocks; before any logic, e.g.
at the top of a function-code-block / loop-nest / anonymous-block
for-loop “control/iterator” variables should be declared and initialized in the for-loop header.
In the code-block nearest to its utilization
in a function, the top-most scope is commonly used for argument-unpacking, and the idiomatic
int err
variable, along with auxilary variables needed by the function body in the outer-most scopevariable-declaration in nested-block-scope is encouraged; still at the top of that block-scope. For example, when a variable is only ever used in a nested-block, then declare it within that scope
Initialize variables
Using zero/constant-initialization;
struct foo = {0};
To unpack arguments;
int simple = arg->is->alot->simple;
Do not initialize using functions that require error-handling
Global variables
Prefix them with
g_
Compiler-specific features
For example, gnu/gcc-only is discouraged, as it hinders portability
Code should preferably translate using any of gcc/clang/icc/pgi
Exceptions to this would be code in backends utilizing features which are only available on the platform that the backend services
C: API#
The API and its backend implementations aims to be consistent with the following
Pass pointers to “objects” e.g.
xnvme_cmd_pass(*ctx, ...);
Pass double-pointers for “object-initialization” e.g.
xnvme_queue(..., **queue);
On success, return 0. On error, Return negative
errno
.Exceptions: API-calls mimicking legacy interfaces, such as the
xnvme_buf_alloc()
which is operating similar tomalloc()/free()
.
The API and its backend implementation cannot be assumed to be thread-safe
Be minimal with definitions in the public API
E.g. the ‘sys/queue.h’ was removed from the public API as it gave several head-aches when building xNVMe on multiple platforms. However, internally, specifically in backend implementations, assumptions can be made on the availability of certain headers and their general availability
Each function in the API must have a command-line tool exercising it
The source-code for the command-line tools serve as examples of utilizing the C API in general and the tools are used during testing
The command-line utilities must use the
libxnvme_cli.h
as this provides a common command-line interface, the common-cli is nice as for usability, such that all cli-tools use the same arguments, it is also essential for instrumentation of not just the logic of the tool but also the library backend
C: Backend Implementations#
The backend interface is declared in the internal header xnvme_be.h
. It
consists of function-pointers for different tasks grouped together in structs.
Those different set of tasks are referred to as the function-interfaces, they
are labeled / grouped by:
dev : device handles and device enumeration
mem : memory allocation for command-payloads aka buffers
admin : synchronous admin-command processing
sync : synchronous I/O command processing
async : asynchronous I/O command submission/completion via queues
Additionally, a backend-state is available for specialization to a given backend and its function-interface implementations.
Should have a header-file defining the backend and what it exposes
Located in
include/xnvme_be_<backend_name>.h
Have a state-struct named
xnvme_be_<backend_name>_state
Provide
extern struct xnvme_be_<interface> *
definitions for the interface implementations, these are used when “mixing-in” the implementations into the backend
Should be implemented in a file named
lib/xnvme_be_<backend_name>_<interface>_<ident>.c
The
<ident>
is a name uniquely identifying the interface-implementationExample: the Linux backend, named linux, has the async implementation of the async interface utilizing io_uring in a file named: *
xnvme_be_linux_async_liburing.c
Example: The SPDK backend, named spdk, has the async implementation using the SPDK NVMe driver in a file named: *
xnvme_be_spdk_async_nvme.c
Should use static function declarations to avoid symbol-leak
The
cmd_io()
backend interface function should be declaredstatic int cmd_io(...)
In case an interface-function should be shared by other backends, then provide the full “name”, backend/interface/ident/function
Example: for the Common Backend Implementations (CBI), the sync implementation using psync for the cmd_iov() interface function, then the function is named:
xnvme_be_cbi_sync_psync_cmd_io()