kv: Implemented a simple key-value API
This adds a couple functions that treat files as simple key-value pairs:
- lfs3_get - Read a file
- lfs3_size - Get the size of a file
- lfs3_set - Write a file
- lfs3_remove - Remove a file (this one already exists!)
The idea is the only real difference between a filesystem and key-value
store in the microcontroller space is the API, and the key-value API
_is_ much easier to use.
It also opens the door to making the file API opt-out in the future to
trade code cost for feature set. littlefs will probably never be
competitive with other microcontroller-scale key-value stores, but it
may be interesting for systems already using littlefs for other storage.
And don't worry, these are still files, so they can always be opened
with the full file API when more advanced operations are needed.
These APIs also matches the custom attribute APIs, which makes sense
because they're both key-values. Any mismatch should be considered an
API bug, because the best user interface is a consistent one.
This new API is tested in tests/test_kv.toml.
---
At the moment the implementation is naive, just sitting on top of the
file API. This works remarkably well thanks to littlefs's cache
bypassing logic, but does have some downsides:
- lfs3_set always writes two commits: one for the stickynote and one for
the file sync.
Unfortunately this is a fundamental limitation of littlefs's file API.
One nice benefit of lfs3_set is in theory we can bypass this
limitation, but not if we just sit on top of the file API.
- There may be code savings from more tightly integrating the key-value
code.
This also highlighted an awkward corner case with per-file cache
configuration in which the buffer needs to be non-null even if zero. Not
the end of the world, but just a bit awkward. Maybe this deserves
revisiting in the config API rework?
---
Code changes were relatively minimal given that this is a whole new API,
unfortunately the stack took quite a hit:
code stack ctx
before: 37352 2280 636
after: 37644 (+0.8%) 2448 (+7.4%) 636 (+0.0%)
The stack surprised me, but in hindsight it makes sense. In sitting on
top of the reset of the codebase, the key-value API adds very little
code, but every stack allocation in these functions add to the stack
hot-path.
This isn't the end of the world, and it's actually probably a good thing
to have an lfs3_file_t allocated in the stack hot-path. lfs3_file_t's
size has been a bit difficult to track thanks to struct lfs3_info
dominating ctx measurements...
This commit is contained in:
@@ -619,7 +619,7 @@ struct lfs3_file_config {
|
||||
|
||||
// Size of the file cache in bytes. In addition to filesystem-wide
|
||||
// read/prog caches, each file gets its own cache to reduce disk
|
||||
// accesses. Defaults to file_cache_size.
|
||||
// accesses. Defaults to file_cache_size if cache_buffer is NULL.
|
||||
lfs3_size_t cache_size;
|
||||
|
||||
// Optional list of custom attributes attached to the file. If readable,
|
||||
@@ -916,6 +916,27 @@ int lfs3_unmount(lfs3_t *lfs3);
|
||||
|
||||
/// General operations ///
|
||||
|
||||
// Get the value of a file
|
||||
//
|
||||
// Returns the number of bytes read, or a negative error code on failure.
|
||||
// Note this may be less than the on-disk file size if the buffer is not
|
||||
// large enough.
|
||||
lfs3_ssize_t lfs3_get(lfs3_t *lfs3, const char *path,
|
||||
void *buffer, lfs3_size_t size);
|
||||
|
||||
// Get a file's size
|
||||
//
|
||||
// Returns the size of the file, or a negative error code on failure.
|
||||
lfs3_ssize_t lfs3_size(lfs3_t *lfs3, const char *path);
|
||||
|
||||
// Set the value of a file
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
#ifndef LFS3_RDONLY
|
||||
int lfs3_set(lfs3_t *lfs3, const char *path,
|
||||
const void *buffer, lfs3_size_t size);
|
||||
#endif
|
||||
|
||||
// Removes a file or directory
|
||||
//
|
||||
// If removing a directory, the directory must be empty.
|
||||
|
||||
Reference in New Issue
Block a user