mirror of https://github.com/ARMmbed/mbed-os.git
littlefs: Fixed issue with lookahead trusting old lookahead blocks
One of the big simplifications in littlefs's implementation is the complete lack of tracking free blocks, allowing operations to simply drop blocks that are no longer in use. However, this means the lookahead buffer can easily contain outdated blocks that were previously deleted. This is usually fine, as littlefs will rescan the storage if it can't find a free block in the lookahead buffer, but after changes that caused littlefs to more conservatively respect the alloc acks (e611cf5), any scanned blocks after an ack would be incorrectly trusted. The fix is to eagerly scan ahead in the lookahead when we allocate so that alloc acks are better able to discredit old lookahead blocks. Since usually alloc acks are tightly coupled to allocations of one or two blocks, this allows littlefs to properly rescan every set of allocations. This may still be a concern if there is a long series of worn out blocks, but in the worst case littlefs will conservatively avoid using blocks it's not sure about. Found by davideferpull/6604/head
parent
3bc2d2e1db
commit
47bee2343c
|
|
@ -290,6 +290,15 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
||||||
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
|
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
|
||||||
// found a free block
|
// found a free block
|
||||||
*block = (lfs->free.begin + off) % lfs->cfg->block_count;
|
*block = (lfs->free.begin + off) % lfs->cfg->block_count;
|
||||||
|
|
||||||
|
// eagerly find next off so an alloc ack can
|
||||||
|
// discredit old lookahead blocks
|
||||||
|
while (lfs->free.off != lfs->free.size &&
|
||||||
|
(lfs->free.buffer[lfs->free.off / 32] &
|
||||||
|
(1U << (lfs->free.off % 32)))) {
|
||||||
|
lfs->free.off += 1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +324,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lfs_alloc_ack(lfs_t *lfs) {
|
static void lfs_alloc_ack(lfs_t *lfs) {
|
||||||
lfs->free.ack = lfs->free.off-1 + lfs->free.begin + lfs->cfg->block_count;
|
lfs->free.ack = lfs->free.begin+lfs->free.off + lfs->cfg->block_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ tests/test.py << TEST
|
||||||
}
|
}
|
||||||
lfs_file_close(&lfs, &file[0]) => 0;
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
// open whole
|
// open hole
|
||||||
lfs_remove(&lfs, "bump") => 0;
|
lfs_remove(&lfs, "bump") => 0;
|
||||||
|
|
||||||
lfs_mkdir(&lfs, "splitdir") => 0;
|
lfs_mkdir(&lfs, "splitdir") => 0;
|
||||||
|
|
@ -301,5 +301,128 @@ tests/test.py << TEST
|
||||||
lfs_unmount(&lfs) => 0;
|
lfs_unmount(&lfs) => 0;
|
||||||
TEST
|
TEST
|
||||||
|
|
||||||
|
echo "--- Outdated lookahead test ---"
|
||||||
|
rm -rf blocks
|
||||||
|
tests/test.py << TEST
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
// fill completely with two files
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion1",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion2",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
// remount to force reset of lookahead
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
// rewrite one file
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion1",
|
||||||
|
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
||||||
|
lfs_file_sync(&lfs, &file[0]) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
// rewrite second file, this requires lookahead does not
|
||||||
|
// use old population
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion2",
|
||||||
|
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
||||||
|
lfs_file_sync(&lfs, &file[0]) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
TEST
|
||||||
|
|
||||||
|
echo "--- Outdated lookahead and split dir test ---"
|
||||||
|
rm -rf blocks
|
||||||
|
tests/test.py << TEST
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
// fill completely with two files
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion1",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion2",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
// remount to force reset of lookahead
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
// rewrite one file with a hole of one block
|
||||||
|
lfs_file_open(&lfs, &file[0], "exhaustion1",
|
||||||
|
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
||||||
|
lfs_file_sync(&lfs, &file[0]) => 0;
|
||||||
|
size = strlen("blahblahblahblah");
|
||||||
|
memcpy(buffer, "blahblahblahblah", size);
|
||||||
|
for (lfs_size_t i = 0;
|
||||||
|
i < ((cfg.block_count-4)/2 - 1)*(cfg.block_size-8);
|
||||||
|
i += size) {
|
||||||
|
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
// try to allocate a directory, should fail!
|
||||||
|
lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
|
||||||
|
|
||||||
|
// file should not fail
|
||||||
|
lfs_file_open(&lfs, &file[0], "notasplit",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||||
|
lfs_file_write(&lfs, &file[0], "hi", 2) => 2;
|
||||||
|
lfs_file_close(&lfs, &file[0]) => 0;
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
TEST
|
||||||
|
|
||||||
echo "--- Results ---"
|
echo "--- Results ---"
|
||||||
tests/stats.py
|
tests/stats.py
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue