commit - 881c6377d4b550f742935f30340d6a2fc92426f0
commit + e7410e29eb092d28a616a7e1bf805d39c154d987
blob - d2f8c1fdb899a833e10af3bccfc4d275b6f63e82
blob + bc69f8c2d60c1508f5e3361e85b1d6eb3b6baad9
--- changelog.md
+++ changelog.md
# 2026-08-31
+## Ext2 indirect write performance
+
+- Restored 4 KiB ext2 write batching for aligned and partial-block operations, replacing eight 512-byte service requests with one batched request.
+- Removed redundant initialization and rereading when adding the first singly indirect block pointer.
+- Added filesystem benchmark validation across the direct-to-indirect boundary and increased the syscall benchmark sample to one million calls.
+
## Ubuntu 26.04 toolchain setup and x86_64 boot reliability
- Added Ubuntu 26.04 build and QEMU environment setup, plus a root-level helper for building and installing the Mercuron LLVM toolchain from `third-party/ports/llvm`.
blob - 924031f1060d6c236bf3817eb6f7ba70fce9f0dd
blob + 42cc40383a9fb3a8cefc75783f0b6323379f73ea
--- servers/fs/ext2/main.c
+++ servers/fs/ext2/main.c
if (block_off == 0 && chunk == ext2_ctx.block_size) {
/* Aligned full block write - no RMW needed */
uint64_t sector = block_start / EXT2_SECTOR_SIZE;
- for (uint32_t s = 0; s < ext2_ctx.sectors_per_block; s++) {
- if (block_write_sectors(sector + s, 1, in + (s * EXT2_SECTOR_SIZE)) != 0)
+ if (block_write_sectors(sector,
+ ext2_ctx.sectors_per_block, in) != 0)
return -1;
- }
in += ext2_ctx.block_size;
offset += ext2_ctx.block_size;
len -= ext2_ctx.block_size;
/* Partial block write - RMW at edge */
uint64_t sector = block_start / EXT2_SECTOR_SIZE;
- for (uint32_t s = 0; s < ext2_ctx.sectors_per_block; s++) {
- if (block_read_sectors(sector + s, 1, temp + (s * EXT2_SECTOR_SIZE)) != 0)
+ if (block_read_sectors(sector,
+ ext2_ctx.sectors_per_block, temp) != 0)
return -1;
- }
memcpy(temp + block_off, in, chunk);
- for (uint32_t s = 0; s < ext2_ctx.sectors_per_block; s++) {
- if (block_write_sectors(sector + s, 1, temp + (s * EXT2_SECTOR_SIZE)) != 0)
+ if (block_write_sectors(sector,
+ ext2_ctx.sectors_per_block, temp) != 0)
return -1;
- }
in += chunk;
offset += chunk;
len -= chunk;
static int
ext2_inode_set_block(struct ext2_inode *inode, uint32_t idx, uint32_t block)
{
- static uint8_t indir_buf[EXT2_MAX_BLOCK_SIZE];
+ static uint8_t indir_buf[EXT2_MAX_BLOCK_SIZE];
+ uint32_t *entries;
+ uint32_t indirect_idx;
+ uint32_t indir_block_no;
+ uint32_t per_block;
- /* Direct blocks (0-11) */
- if (idx < 12) {
- inode->i_block[idx] = block;
- return 0;
- }
-
- /* Single indirect blocks (12 to 12 + per_block - 1) */
- uint32_t per_block = ext2_ctx.block_size / sizeof(uint32_t);
- if (idx < 12 + per_block) {
- uint32_t indirect_idx = idx - 12;
-
- /* Check if we need to allocate the indirect block itself */
- if (inode->i_block[12] == 0) {
- /* Allocate the indirect block */
- uint32_t indir_block_no;
- if (ext2_allocate_block(&indir_block_no) != 0) {
- printf("[ext2] ext2_inode_set_block: failed to allocate indirect block\n");
- return -1;
- }
- #ifdef LENIX_DEBUG
- printf("[ext2] ext2_inode_set_block: allocated indirect block %u\n", indir_block_no);
- #endif
- inode->i_block[12] = indir_block_no;
-
- /* Initialize the indirect block to all zeros */
- memset(indir_buf, 0, ext2_ctx.block_size);
- if (ext2_write_block(indir_block_no, indir_buf) != 0) {
- printf("[ext2] ext2_inode_set_block: failed to initialize indirect block\n");
- return -1;
- }
- }
-
- /* Read the indirect block */
- if (ext2_read_block(inode->i_block[12], indir_buf) != 0) {
- printf("[ext2] ext2_inode_set_block: failed to read indirect block %u\n", inode->i_block[12]);
- return -1;
- }
-
- /* Update the block pointer in the indirect block */
- uint32_t *entries = (uint32_t *)indir_buf;
- entries[indirect_idx] = block;
-
- /* Write the indirect block back */
- if (ext2_write_block(inode->i_block[12], indir_buf) != 0) {
- printf("[ext2] ext2_inode_set_block: failed to write indirect block %u\n", inode->i_block[12]);
- return -1;
- }
-
- #ifdef LENIX_DEBUG
- printf("[ext2] ext2_inode_set_block: set block[%u] = %u in indirect block\n", idx, block);
- #endif
+ /* Direct blocks (0-11). */
+ if (idx < 12) {
+ inode->i_block[idx] = block;
return 0;
- }
+ }
- /* Double and triple indirect blocks not yet supported */
- printf("[ext2] ext2_inode_set_block: ERROR - block index %u not supported (max %u for single indirect)\n",
- idx, 12 + per_block - 1);
- return -1;
+ /* Single indirect blocks (12 to 12 + per_block - 1). */
+ per_block = ext2_ctx.block_size / sizeof(uint32_t);
+ if (idx >= 12 + per_block) {
+ printf("[ext2] ext2_inode_set_block: ERROR - block index %u "
+ "not supported (max %u for single indirect)\n",
+ idx, 12 + per_block - 1);
+ return -1;
+ }
+
+ indirect_idx = idx - 12;
+ indir_block_no = inode->i_block[12];
+ if (indir_block_no == 0) {
+ if (ext2_allocate_block(&indir_block_no) != 0) {
+ puts("[ext2] ext2_inode_set_block: failed to allocate "
+ "indirect block");
+ return -1;
+ }
+#ifdef LENIX_DEBUG
+ printf("[ext2] ext2_inode_set_block: allocated indirect "
+ "block %u\n", indir_block_no);
+#endif
+ memset(indir_buf, 0, ext2_ctx.block_size);
+ entries = (uint32_t *)indir_buf;
+ entries[indirect_idx] = block;
+ if (ext2_write_block(indir_block_no, indir_buf) != 0) {
+ puts("[ext2] ext2_inode_set_block: failed to write new "
+ "indirect block");
+ return -1;
+ }
+ inode->i_block[12] = indir_block_no;
+ } else {
+ if (ext2_read_block(indir_block_no, indir_buf) != 0) {
+ printf("[ext2] ext2_inode_set_block: failed to read "
+ "indirect block %u\n", indir_block_no);
+ return -1;
+ }
+ entries = (uint32_t *)indir_buf;
+ entries[indirect_idx] = block;
+ if (ext2_write_block(indir_block_no, indir_buf) != 0) {
+ printf("[ext2] ext2_inode_set_block: failed to write "
+ "indirect block %u\n", indir_block_no);
+ return -1;
+ }
+ }
+
+#ifdef LENIX_DEBUG
+ printf("[ext2] ext2_inode_set_block: set block[%u] = %u in "
+ "indirect block\n", idx, block);
+#endif
+ return 0;
}
static int ext2_dir_lookup(const struct ext2_inode *dir_inode,
blob - 744fd4f61aab484bb5ff14dc0eb38561b5264922
blob + 6a0a754703653f6c7041f7affb80153972eb6cc4
--- user/bench/bench_fs_read.c
+++ user/bench/bench_fs_read.c
#define FS_ITERATIONS 10
#define FS_BUFSIZE 4096
#define FS_WRITE_BLOCKS 16
+#define FS_FILE_SIZE (FS_BUFSIZE * FS_WRITE_BLOCKS)
#define FS_TEST_PATH "/fstest"
#define FS_BENCH_VERBOSE 0
#define FS_BENCH_DIAG 1 /* Diagnostic timing breakdowns */
#define FS_SUMMARY(...) printf(__VA_ARGS__)
+static void
+fill_test_block(char *buf, int block_index)
+{
+ memset(buf, block_index + 1, FS_BUFSIZE);
+}
+
+static int
+verify_test_file(void)
+{
+ char buf[FS_BUFSIZE];
+ ssize_t n;
+ int fd;
+
+ fd = open(FS_TEST_PATH, O_RDONLY, 0);
+ if (fd < 0) {
+ FS_SUMMARY("[bench_fs_read] ERROR: validation open failed "
+ "(errno=%d)\n", errno);
+ return -1;
+ }
+
+ for (int block = 0; block < FS_WRITE_BLOCKS; block++) {
+ size_t block_bytes = 0;
+
+ while (block_bytes < sizeof(buf)) {
+ n = read(fd, buf + block_bytes,
+ sizeof(buf) - block_bytes);
+ if (n < 0) {
+ FS_SUMMARY("[bench_fs_read] ERROR: validation read "
+ "failed at block %d (errno=%d)\n", block,
+ errno);
+ close(fd);
+ return -1;
+ }
+ if (n == 0) {
+ FS_SUMMARY("[bench_fs_read] ERROR: validation "
+ "reached EOF at block %d\n", block);
+ close(fd);
+ return -1;
+ }
+ block_bytes += (size_t)n;
+ }
+
+ for (size_t offset = 0; offset < sizeof(buf); offset++) {
+ if ((uint8_t)buf[offset] != (uint8_t)(block + 1)) {
+ FS_SUMMARY("[bench_fs_read] ERROR: validation "
+ "mismatch at block %d offset %zu\n", block,
+ offset);
+ close(fd);
+ return -1;
+ }
+ }
+ }
+
+ n = read(fd, buf, 1);
+ if (n != 0) {
+ FS_SUMMARY("[bench_fs_read] ERROR: validation file size "
+ "mismatch\n");
+ close(fd);
+ return -1;
+ }
+ if (close(fd) < 0) {
+ FS_SUMMARY("[bench_fs_read] ERROR: validation close failed "
+ "(errno=%d)\n", errno);
+ return -1;
+ }
+
+ FS_SUMMARY("[bench_fs_read] Validation passed: %d bytes\n",
+ FS_FILE_SIZE);
+ return 0;
+}
+
int
main(void)
{
}
char buf[FS_BUFSIZE];
- memset(buf, 0x42, sizeof(buf));
for (int i = 0; i < FS_WRITE_BLOCKS; i++) {
+ fill_test_block(buf, i);
ssize_t wrote = write(fd, buf, sizeof(buf));
if (wrote != (ssize_t)sizeof(buf)) {
FS_SUMMARY("[bench_fs_read] ERROR: write failed at block %d (errno=%d)\n",
FS_SUMMARY("[bench_fs_read] ERROR: close failed after write (errno=%d)\n", errno);
return 1;
}
+ if (verify_test_file() != 0)
+ return 1;
/* Phase 2: timed read benchmark */
#if FS_BENCH_DIAG
bench_ns = (uint64_t)(bench_end.tv_sec - bench_start.tv_sec) * 1000000000ULL +
(uint64_t)(bench_end.tv_nsec - bench_start.tv_nsec);
- if (iterations_done == 0)
- iterations_done = 1; /* avoid div-by-zero if everything failed */
+ if (iterations_done != FS_ITERATIONS ||
+ total_bytes != (uint64_t)FS_FILE_SIZE * FS_ITERATIONS) {
+ FS_SUMMARY("[bench_fs_read] ERROR: incomplete benchmark "
+ "(%d iterations, %llu bytes)\n", iterations_done,
+ (unsigned long long)total_bytes);
+ return 1;
+ }
avg_iter_ns = bench_ns / (uint64_t)iterations_done;
throughput = (bench_ns > 0) ? (total_bytes * 1000000000ULL) / bench_ns : 0;
blob - ef3da32a0f2ff18210e491d8f217e921fa98ecac
blob + 109184356b181a1709846caa73074c995200add3
--- user/bench/bench_syscall.c
+++ user/bench/bench_syscall.c
#include <lenix/stdio.h>
#include <stdint.h>
-#define ITERATIONS 100000
+#define ITERATIONS 1000000
#define WARMUP_ITERATIONS 1000
/* Minimal syscall wrapper - prevents compiler optimization */
/* Calculate elapsed time in nanoseconds */
elapsed_ns = (end.tv_sec - start.tv_sec) * 1000000000ULL +
(end.tv_nsec - start.tv_nsec);
+ if (elapsed_ns == 0) {
+ puts("[bench_syscall] ERROR: elapsed time is zero");
+ return 1;
+ }
ns_per_call = (long)(elapsed_ns / ITERATIONS);