summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/hurd.texi8
-rw-r--r--ext2fs/pager.c147
-rw-r--r--libpager/Makefile2
-rw-r--r--libpager/data-return.c53
-rw-r--r--libpager/pager-bulk.c37
-rw-r--r--libpager/pager.h10
6 files changed, 249 insertions, 8 deletions
diff --git a/doc/hurd.texi b/doc/hurd.texi
index f4113da4..b07e46dd 100644
--- a/doc/hurd.texi
+++ b/doc/hurd.texi
@@ -1551,6 +1551,14 @@ references to @var{buf}. The only permissible error returns are
@code{EIO}, @code{EDQUOT}, and @code{ENOSPC}.
@end deftypefun
+@deftypefun error_t pager_write_pages (@w{struct user_pager_info *@var{pager}}, @w{vm_offset_t @var{offset}}, @w{vm_address_t @var{data}}, @w{vm_size_t @var{length}}, @w{vm_size_t *@var{written}})
+The user may define this function. For pager @var{pager}, synchronously write
+potentially multiple pages from @var{data} to offset @var{offset}. Do not
+deallocate @var{data}, and do not keep any references to @var{data}. The only
+permissible error returns are @code{EIO}, @code{EDQUOT}, @code{EOPNOTSUPP}, and
+@code{ENOSPC}.
+@end deftypefun
+
@deftypefun error_t pager_unlock_page (@w{struct user_pager_info *@var{pager}}, @w{vm_offset_t @var{address}})
A page should be made writable.
@end deftypefun
diff --git a/ext2fs/pager.c b/ext2fs/pager.c
index c55107a9..a7801bea 100644
--- a/ext2fs/pager.c
+++ b/ext2fs/pager.c
@@ -83,6 +83,10 @@ do { pthread_spin_lock (&ext2s_pager_stats.lock); \
#define STAT_INC(field) /* nop */0
#endif /* STATS */
+#ifndef EXT2_BULK_MAX_BLOCKS
+#define EXT2_BULK_MAX_BLOCKS 128
+#endif /* EXT2_BULK_MAX_BLOCKS */
+
static void
disk_cache_info_free_push (struct disk_cache_info *p);
@@ -378,6 +382,149 @@ pending_blocks_add (struct pending_blocks *pb, block_t block)
pb->num++;
return 0;
}
+
+/* Bulk write across [offset, offset + length). We coalesce strictly
+ consecutive blocks into a single device write. The run ends on the
+ first non-consecutive block or when we hit the configured cap.
+ We report partial progress via *written (page-aligned). */
+static error_t
+file_pager_write_pages (struct node *node,
+ vm_offset_t offset,
+ vm_address_t buf,
+ vm_size_t length,
+ vm_size_t *written)
+{
+ error_t err = 0;
+ vm_size_t done = 0; /* bytes successfully issued to the store */
+ pthread_rwlock_t *lock = &diskfs_node_disknode (node)->alloc_lock;
+ const unsigned max_blocks = EXT2_BULK_MAX_BLOCKS;
+
+ /* Persisted lookahead block across runs: if non-zero, it is the first
+ block of the next coalesced run and we won't re-find it. */
+ block_t blk_peek = 0;
+
+ if (written)
+ *written = 0;
+
+ while (done < length)
+ {
+ pthread_rwlock_rdlock (lock);
+
+ /* Recompute clipping against allocsize each iteration. */
+ vm_size_t left = length - done;
+ if (offset + done >= node->allocsize)
+ {
+ pthread_rwlock_unlock (lock);
+ break;
+ }
+ if (offset + done + left > node->allocsize)
+ left = node->allocsize - (offset + done);
+ if (left == 0)
+ {
+ pthread_rwlock_unlock (lock);
+ break;
+ }
+
+ /* Build one run of strictly consecutive on-disk blocks. */
+ struct pending_blocks pb;
+ vm_size_t built = 0;
+ vm_size_t blocks_built = 0;
+ block_t prev = 0;
+ block_t blk = 0;
+
+ pending_blocks_init (&pb, (void *) (buf + done));
+
+ while (blocks_built < max_blocks && built < left)
+ {
+ /* Use peeked block if we have one; otherwise look it up. */
+ if (blk_peek)
+ {
+ blk = blk_peek;
+ }
+ else
+ {
+ error_t ferr =
+ find_block (node, offset + done + built, &blk, &lock);
+ if (ferr)
+ {
+ err = ferr;
+ break;
+ }
+ }
+
+ assert_backtrace (blk);
+
+ if (prev != 0 && blk != prev + 1)
+ {
+ /* Non-consecutive: keep BLK for the next outer run. */
+ blk_peek = blk;
+ break;
+ }
+
+ /* Extend the run by one block. We are consuming BLK now. */
+ error_t ferr = pending_blocks_add (&pb, blk);
+ if (ferr)
+ {
+ err = ferr;
+ break;
+ }
+
+ prev = blk;
+ built += block_size;
+ blocks_built++;
+
+ /* We consumed the peeked block; clear it so we fetch the next. */
+ blk_peek = 0;
+ }
+
+ /* Flush exactly one coalesced run; even if the loop broke early,
+ we may have a valid prefix to push. */
+ error_t werr = pending_blocks_write (&pb);
+ if (!err)
+ err = werr;
+
+ pthread_rwlock_unlock (lock);
+
+ /* Advance only by what we actually enumerated and flushed. */
+ done += built;
+
+ /* Stop on error. */
+ if (err)
+ break;
+ }
+
+ /* We must not be "holding" a peeked block on return unless we stopped
+ due to an error. */
+ assert_backtrace (blk_peek == 0 || err);
+
+ if (written)
+ {
+ vm_size_t w = done;
+ if (w > length)
+ w = length;
+ /* libpager expects progress aligned to whole pages. */
+ w -= (w % vm_page_size);
+ *written = w;
+ }
+
+ return err;
+}
+
+/* Strong override: only FILE_DATA uses bulk; others keep per-page path. */
+error_t
+pager_write_pages (struct user_pager_info *pager,
+ vm_offset_t offset,
+ vm_address_t data,
+ vm_size_t length,
+ vm_size_t *written)
+{
+ /* libpager will just hand this off to the pager_write_page. */
+ if (pager->type != FILE_DATA)
+ return EOPNOTSUPP;
+
+ return file_pager_write_pages (pager->node, offset, data, length, written);
+}
+
/* Write one page for the pager backing NODE, at OFFSET, into BUF. This
may need to write several filesystem blocks to satisfy one page, and tries
diff --git a/libpager/Makefile b/libpager/Makefile
index 06fcb96b..169d5ab1 100644
--- a/libpager/Makefile
+++ b/libpager/Makefile
@@ -24,7 +24,7 @@ SRCS = data-request.c data-return.c data-unlock.c pager-port.c \
pager-create.c pager-flush.c pager-shutdown.c pager-sync.c \
stubs.c demuxer.c chg-compl.c pager-attr.c clean.c \
dropweak.c get-upi.c pager-memcpy.c pager-return.c \
- offer-page.c pager-ro-port.c
+ offer-page.c pager-ro-port.c pager-bulk.c
installhdrs = pager.h
HURDLIBS= ports
diff --git a/libpager/data-return.c b/libpager/data-return.c
index a69a2c5c..1416ae41 100644
--- a/libpager/data-return.c
+++ b/libpager/data-return.c
@@ -158,14 +158,53 @@ _pager_do_write_request (struct pager *p,
/* Let someone else in. */
pthread_mutex_unlock (&p->interlock);
- /* This is inefficient; we should send all the pages to the device at once
- but until the pager library interface is changed, this will have to do. */
+ int i_page = 0;
+ while (i_page < npages)
+ {
+ if (omitdata & (1U << i_page))
+ {
+ i_page++;
+ continue;
+ }
+
+ /* Find maximal contiguous run [i_page, j_page) with no omitdata. */
+ int j_page = i_page + 1;
+ while (j_page < npages && ! (omitdata & (1U << j_page)))
+ j_page++;
+
+ vm_offset_t run_off = offset + (vm_page_size * i_page);
+ vm_address_t run_ptr = data + (vm_page_size * i_page);
+ vm_size_t run_len = vm_page_size * (j_page - i_page);
+
+ vm_size_t wrote = 0;
+
+ /* Attempt bulk write. */
+ error_t berr = pager_write_pages (p->upi, run_off, run_ptr,
+ run_len, &wrote);
+
+ /* How many pages did bulk actually complete? (only if not EOPNOTSUPP) */
+ int pages_done = 0;
+ if (berr != EOPNOTSUPP)
+ {
+ if (wrote > run_len)
+ wrote = run_len;
+ wrote -= (wrote % vm_page_size);
+ pages_done = wrote / vm_page_size;
+ }
+
+ /* Mark successful prefix (if any). */
+ for (int k = 0; k < pages_done; k++)
+ pagerrs[i_page + k] = 0;
+
+ /* Per-page the remaining suffix of the run, or the whole run if unsupported. */
+ for (int k = i_page + pages_done; k < j_page; k++)
+ pagerrs[k] = pager_write_page (p->upi,
+ offset + (vm_page_size * k),
+ data + (vm_page_size * k));
+
+ i_page = j_page;
+ }
- for (i = 0; i < npages; i++)
- if (!(omitdata & (1U << i)))
- pagerrs[i] = pager_write_page (p->upi,
- offset + (vm_page_size * i),
- data + (vm_page_size * i));
/* Acquire the right to meddle with the pagemap */
pthread_mutex_lock (&p->interlock);
diff --git a/libpager/pager-bulk.c b/libpager/pager-bulk.c
new file mode 100644
index 00000000..8dba01b5
--- /dev/null
+++ b/libpager/pager-bulk.c
@@ -0,0 +1,37 @@
+/* pager-bulk.c Default (dummy) implementation of bulk page write.
+
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ Written by Milos Nikic.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with the GNU Hurd; if not, see <https://www.gnu.org/licenses/>. */
+
+#include <libpager/pager.h>
+#include "priv.h"
+
+/* Default dummy implementation of pager_write_pages. */
+__attribute__((weak)) error_t
+pager_write_pages (struct user_pager_info *upi,
+ vm_offset_t offset,
+ vm_address_t data, vm_size_t length, vm_size_t *written)
+{
+ (void) upi;
+ (void) offset;
+ (void) data;
+ (void) length;
+ if (written)
+ *written = 0;
+ return EOPNOTSUPP;
+}
diff --git a/libpager/pager.h b/libpager/pager.h
index 3b1c7251..8c43ad0e 100644
--- a/libpager/pager.h
+++ b/libpager/pager.h
@@ -203,6 +203,16 @@ pager_write_page (struct user_pager_info *pager,
vm_offset_t page,
vm_address_t buf);
+/* The user may define this function. For pager PAGER, synchronously
+ write potentially multiple pages from DATA to offset.
+ Do not deallocate DATA, and do not keep any references to DATA.
+ The only permissible error returns are EIO, EDQUOT, EOPNOTSUPP, and ENOSPC. */
+error_t pager_write_pages(struct user_pager_info *upi,
+ vm_offset_t offset,
+ vm_address_t data,
+ vm_size_t length,
+ vm_size_t *written);
+
/* The user must define this function. A page should be made writable. */
error_t
pager_unlock_page (struct user_pager_info *pager,