littlefs-project / littlefs

A little fail-safe filesystem designed for microcontrollers
BSD 3-Clause "New" or "Revised" License
4.9k stars 770 forks source link

running into RAM limitations for freertos tasks #971

Open brettdaman opened 2 months ago

brettdaman commented 2 months ago

First of all, thank you for the excellent project. It's a great library with tons of options, well done.

We have a large project that runs on a stm32 with Freertos. We use littlefs to store persistent variables and files. This has worked well for a couple of years until recently we ran into very slow lfs_renames.

Together, with removing lfs_rename we also updated littleFS to the latest version.

This is throwing some issues because some operations require a lot more stack than before. We need to bump each task that stores some variable in littlefs.

How would you structure the littlefs integration if you have for example 30 Freertos tasks that all require storing a unique single value in littlefs? The current solution to store the variable directly from the tasks is not workable, because each task requires a stack increase of +2000 bytes to be able to open the files.

Is there a solution in littlefs that we are missing? Our solution might be to write a caching layer in a task that contains littlefs to a single task.

Does anyone with a similar problem already fixed this?

geky commented 2 months ago

Hi @brettdaman, thanks for creating an issue.

Out of curiosity, what version did you upgrade from? I may be forgetting something, but I don't remember a recent version bump with a significant stack increase. Do you think this was a sort of death-by-a-thousand-cuts situation?

How would you structure the littlefs integration if you have for example 30 Freertos tasks that all require storing a unique single value in littlefs? The current solution to store the variable directly from the tasks is not workable, because each task requires a stack increase of +2000 bytes to be able to open the files.

This does seem challenging, and an unfortunate downside of littlefs's stack-heavy implementation. The tradeoff being better RAM utilization in simpler single-stack systems.

It does sounds like the best option involves a single storage task that other tasks communicate with to get work done. If you block the other tasks, this could work by passing references to buffers to write from/read into. But it may be easier to implement the cross-task communication with application-specific queries.


You could imagine a struct-heavy implementation of littlefs that moves more of the on-stack state into the lfs_t struct. But you would still be blocking tasks because littlefs isn't really multi-threaded internally (and ultimately IO would force serialization), and it would be more difficult to make sure RAM is reused effectively across littlefs's internal functions.

That being said, an implementation that synchronizes tasks via the internal lfs_dir_commit sync point could be very interesting...

brettdaman commented 1 week ago

Hi @geky, sorry for the slow response.

We upgraded from 2.4.0 -> 2.9.1

In the meantime, we optimized the configs to ensure the small variables are stored as metadata. This already decreases the amount of erased data.

We also freed the scheduler on some of the hal calls this unblocks the MCU enough for the high prior tasks.

In the long term, we are looking for a different solution to store small variables to solve the high stack usage in each task.

In the meantime, we are trying to force the LFS formats into a low-priority task to decrease the blocking time for high-priority tasks. The idea was to call the garbage collection function from time to time. but we observe that writing small variables directly after the garbage collection still triggers a format. It looks that garbage collection doesn't work for the metadata blocks.

geky commented 1 week ago

We upgraded from 2.4.0 -> 2.9.1

Ah, v2.5 was when we finally removed all remaining recursion. Prior to this it was still possible for specific situations to end up with a theoretically unbounded stack.

I'm curious if what you're seeing is a result of previously unmeasurable stack usage becoming measurable, or an actual increase in real stack usage (via stack canary, watermarking, etc).

We also freed the scheduler on some of the hal calls this unblocks the MCU enough for the high prior tasks.

This is smart when not filesystem-bound.

In the meantime, we are trying to force the LFS formats into a low-priority task to decrease the blocking time for high-priority tasks. The idea was to call the garbage collection function from time to time. but we observe that writing small variables directly after the garbage collection still triggers a format. It looks that garbage collection doesn't work for the metadata blocks.

I'm sorry, I'm having difficulty understanding this. Do you mean block erases? lfs_format does not really make sense here, unless I'm misunderstanding something.

Right now lfs_fs_gc does not pre-erase blocks. This is a wishlist item, but unfortunately it requires quite a bit of extra infrastructure to implement (global block map).

Though if the files are small enough, lfs_fs_gc with a sufficiently low compact_thresh configuration should usually be able to avoid erases. Though this currently requires the files fit in cache_size, maybe cache_size is too small?

geky commented 1 week ago

In the long term, we are looking for a different solution to store small variables to solve the high stack usage in each task.

If your RTOS provides a thread-safe queue/fifo, and the files/variables can fit in the queue/fifo, you could implement a single "storage" task that reads from the queue and writes the variables to the filesystem. This is the best solution I can think of short of reimplementing littlefs...