-
Notifications
You must be signed in to change notification settings - Fork 808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Top root block(s) wear leveling #1020
Comments
As far as I can tell, the root block points to the root directory. Every time we change the root dir N times, we have to move it, which would require rewriting the root block. The spec IIRC says the superblock is only rarely written, when the root dir needs to be moved. But N is usually like 1000, so it usually works just fine? Seems like if you regularly changed stuff in the root dir, you could have an issue after hundreds of millions of changes, so perhaps the documentation should tell us to keep frequently changed stuff in a subdir? |
For me, you always have to know where "end of" linkedlist is, otherwise how do you know where to start? I may not fully understand this part of littlefs. And if you modify subdir file, then you also need to inform parent to point to the new end of file, which needs to know to point to the new one. Then you have to modify root again, no? |
I think they are modifying the parent pointer in place, so the pointer to
it remains valid, it's the same exact physical block.
They only move it every 1000 updates or so, so each level up in the
heirarchy gets 1000 times fewer write cycles than the one before it.
…On Wed, Sep 25, 2024, 8:17 AM Tilen Majerle ***@***.***> wrote:
For me, you always have to know where "end of" linkedlist is, otherwise
how do you know where to start? I may not fully understand this part of
littlefs.
And if you modify subdir file, then you also need to inform parent to
point to the new end of file, which needs to know to point to the new one.
Then you have to modify root again, no?
—
Reply to this email directly, view it on GitHub
<#1020 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFZCH6FZIGSLQAMZS4EU2DZYLAWBAVCNFSM6AAAAABNBYTKBCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGNZUGIZDEMJRGI>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
I'm kinda confused. If littlefs performs update "in place", then this destroys the whole point of wear leveling in my view. Or am I wrong? |
If I understand it right, it only does the in place update 1000 times, then
it moves on to another pair of blocks, the only thing that permanently
stays in the same place is the superblock which is only very rarely written
to
…On Wed, Sep 25, 2024, 1:30 PM Tilen Majerle ***@***.***> wrote:
I'm kinda confused. If littlefs performs update "in place", then this
destroys the whole point of wear leveling in my view.
Or am I wrong?
—
Reply to this email directly, view it on GitHub
<#1020 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFZCHYNKC2N4U2BQQOXTY3ZYMFMTAVCNFSM6AAAAABNBYTKBCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGNZVGA3TKMBRHA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Hi @MaJerle, @EternityForest, thanks for creating an issue, sorry about the late response. Forgetting to include superblock expansion in DESIGN.md was an oversight. There's a terse description in SPEC.md, but that's sort of the wrong place for it. I thought for sure I wrote more on the feature, but I think it was only this commit: 7c70068, and never made it made its way into DESIGN.md... Which is a shame because I think it's quite neat. TLDRTo keep writes to the superblock anchor as low as possible, littlefs increases the length of the superblock chain every High-levelThe problem is interesting: How do you find the superblock/root of the filesystem? You can scan the disk for it, but this gets expensive. You can store it at a fixed location, but then it wouldn't participate in wear-leveling and risk an early death of the filesystem. The solution in littlefs is sort of a form of exponential backoff. We store a superblock at a fixed location (blocks {0,1}), but write to it exponentially less often. The way this works is that we actually have a linked-list of superblocks, with the head being the "anchor" superblock, and the tail being the "active" superblock. The anchor superblock is always at blocks {0,1}, which makes finding it easy, while the other superblocks in the chain participate in wear-leveling and move around the disk:
For the most part, we only write to the active superblock. However, moving the superblock as a part of wear-leveling requires updating the pointer in its parent. This causes writes to slowly propagate up the superblock chain, and eventually reach the superblock anchor. Here's the main trick: Everytime we accumulate enough writes to want to move the superblock anchor, we instead increase the length of the superblock chain by 1. ExampleThis is all controlled by the At first, after formatting, the filesystem contains a single superblock:
After
This new superblock doesn't need to move until another
This continues until the superblock anchor reaches
This goes on forever:
MathThe funny thing about exponential growth is that it's exponential. With this scheme, after Where And since the each superblock requires writing to the anchor Putting these two together we can solve for how many writes, So lets say we have configured So But each write is also wearing out the other blocks on the disk. So to even get to this point, you'd need NotesSorry if I'm repeating some of what @EternityForest commented, I started writing this yesterday but putting it all together was a bit complicated. Some other things to note:
Related: #727 (comment), 7c70068, SPEC.md#0x0ff-lfs_type_superblock |
Some other TLDR because the above turned into a text wall
No, the superblock anchor is always at blocks {0,1}.
The superblock expansion scheme should adapt to your directory layout. After one expansion ( |
Thanks for extensive explanation, few things are more clear. So we use the "normal" linked list (not backwards one) for superblocks. What I struggle to understand for some reason (please point me where I can read more) is how do we achieve logarithmic part and not linear. Why? If you have, let's say, 1k erase cycle of the underlying flash, and we put
The log mathematics, in my view, explicitly says that when we modify one block Don't we update the superblock every time we write the file? If an answer is NO, where do we store the pointer to the last block allocated for the file that we just wrote to? Because how otherwise root dir knows where the file is to search for with backwards linkedlist? There must be a chain somewhere, somehow? |
Sorry if I'm misunderstanding the question.
I think this might be the confusion.
Most NOR flash I've seen supports ~100K, NAND flash usually ~10K, so You're right that
You're correct. If you store a file in the root, every write to the file adds a commit to the root mdir/superblock. |
I do think Current ideas are |
What I'm missing in the story, how, even if you store the file in the subdirectory, can littlefs find last file information? If it starts from root search that has forward linked list, then there must be an update to this list everytime when you update any file in the system. ROOT -> SUBDIR1 -> FILE1 File1 gets updated, new entry is added to the end of backward list (which now becomes entry 1st). ROOT -> (must point to new SUBDIR1 end) SUBDIR1 -> (must point to new end of FILE1) FILE1
|
Ah, so directories in littlefs are not represented the same as files (DESIGN.md#directories). Files are stored as backwards skip-lists, but directories are stored as "forward" linked-lists similar to the superblock chain. This is possible because the directory blocks are also metadata logs, and can be updated in-place (up to The tradeoff is filename lookup is |
OK thanks - that clue was helpful. From flash standpoint, if I cite your comment you linked above: From here:
To here
and later here:
If we compare this logic to the counter in a bit, let's say increasing length of number by byte every time we want to increase size:
Numbers are growing fast. We need log2 bits to represent them.
As you can see to apply the logic of As you can see, to achieve the Essentially, I do not understand why writes grow log(x) in the root, and not linearly as we see above in my bit example? When first block performs Don't know, maybe I just need to sleep more. |
Viewing block cycles as digits in a number is a good way to think about.
Ah, but after the first expansion, bit 1 is now free to move around the disk. Every time it "flips" we move it to a new block as a part of wear-leveling. If this wears out to the point of failure, which is possible, most of the disk should be reaching its end of life. So we're really only worried about the blocks that are not participating in wear-leveling, which is only the superblock anchor. This is where the log comes from:
This is why my initial response took so long, it can get confusing :) |
Reading the design guide, specs and some of the topics, it is not fully clear how littlefs manages the root block on a physical disk with wear leveling. My understanding of the COW and backward linked-list + CTZ-lists is that if we append data to the file, we are effectively increasing number of blocks, therefore the root must also be updated to point to the latest entry in the list.
Some questions I'm unable to answer:
The text was updated successfully, but these errors were encountered: