Commit Diff


commit - 881c6377d4b550f742935f30340d6a2fc92426f0
commit + e7410e29eb092d28a616a7e1bf805d39c154d987
blob - d2f8c1fdb899a833e10af3bccfc4d275b6f63e82
blob + bc69f8c2d60c1508f5e3361e85b1d6eb3b6baad9
--- changelog.md
+++ changelog.md
@@ -2,6 +2,12 @@
 
 # 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
@@ -521,10 +521,9 @@ ext2_write_bytes(uint64_t offset, const void *buf, siz
 			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;
@@ -533,15 +532,13 @@ ext2_write_bytes(uint64_t offset, const void *buf, siz
 
 			/* 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;
@@ -1907,66 +1904,68 @@ ext2_inode_block(const struct ext2_inode *inode, uint3
 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
@@ -17,6 +17,7 @@
 #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 */
@@ -29,6 +30,77 @@
 
 #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)
 {
@@ -51,8 +123,8 @@ 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",
@@ -65,6 +137,8 @@ main(void)
 		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
@@ -157,8 +231,13 @@ main(void)
 	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
@@ -9,7 +9,7 @@
 #include <lenix/stdio.h>
 #include <stdint.h>
 
-#define ITERATIONS 100000
+#define ITERATIONS 1000000
 #define WARMUP_ITERATIONS 1000
 
 /* Minimal syscall wrapper - prevents compiler optimization */
@@ -55,6 +55,10 @@ main(void)
 	/* 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);