// SPDX-License-Identifier: GPL-2.0-only /* * linux/fs/nfs/pagelist.c * * A set of helper functions for managing NFS read and write requests. * The main purpose of these routines is to provide support for the * coalescing of several requests into a single RPC call. * * Copyright 2000, 2001 (c) Trond Myklebust <trond.myklebust@fys.uio.no> *
*/
/** * nfs_iocounter_wait - wait for i/o to complete * @l_ctx: nfs_lock_context with io_counter to use * * returns -ERESTARTSYS if interrupted by a fatal signal. * Otherwise returns 0 once the io_count hits 0.
*/ int
nfs_iocounter_wait(struct nfs_lock_context *l_ctx)
{ return wait_var_event_killable(&l_ctx->io_count,
!atomic_read(&l_ctx->io_count));
}
/** * nfs_async_iocounter_wait - wait on a rpc_waitqueue for I/O * to complete * @task: the rpc_task that should wait * @l_ctx: nfs_lock_context with io_counter to check * * Returns true if there is outstanding I/O to wait on and the * task has been put to sleep.
*/ bool
nfs_async_iocounter_wait(struct rpc_task *task, struct nfs_lock_context *l_ctx)
{ struct inode *inode = d_inode(l_ctx->open_context->dentry); bool ret = false;
if (atomic_read(&l_ctx->io_count) > 0) {
rpc_sleep_on(&NFS_SERVER(inode)->uoc_rpcwaitq, task, NULL);
ret = true;
}
if (atomic_read(&l_ctx->io_count) == 0) {
rpc_wake_up_queued_task(&NFS_SERVER(inode)->uoc_rpcwaitq, task);
ret = false;
}
/* * nfs_page_set_headlock - set the request PG_HEADLOCK * @req: request that is to be locked * * this lock must be held when modifying req->wb_head * * return 0 on success, < 0 on error
*/ int
nfs_page_set_headlock(struct nfs_page *req)
{ if (!test_and_set_bit(PG_HEADLOCK, &req->wb_flags)) return 0;
/* * nfs_page_clear_headlock - clear the request PG_HEADLOCK * @req: request that is to be locked
*/ void
nfs_page_clear_headlock(struct nfs_page *req)
{
clear_bit_unlock(PG_HEADLOCK, &req->wb_flags);
smp_mb__after_atomic(); if (!test_bit(PG_CONTENDED1, &req->wb_flags)) return;
wake_up_bit(&req->wb_flags, PG_HEADLOCK);
}
/* * nfs_page_group_lock - lock the head of the page group * @req: request in group that is to be locked * * this lock must be held when traversing or modifying the page * group list * * return 0 on success, < 0 on error
*/ int
nfs_page_group_lock(struct nfs_page *req)
{ int ret;
ret = nfs_page_set_headlock(req); if (ret || req->wb_head == req) return ret; return nfs_page_set_headlock(req->wb_head);
}
/* * nfs_page_group_unlock - unlock the head of the page group * @req: request in group that is to be unlocked
*/ void
nfs_page_group_unlock(struct nfs_page *req)
{ if (req != req->wb_head)
nfs_page_clear_headlock(req->wb_head);
nfs_page_clear_headlock(req);
}
/** * nfs_page_group_sync_on_bit_locked - Test if all requests have @bit set * @req: request in page group * @bit: PG_* bit that is used to sync page group * * must be called with page group lock held
*/ bool nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsignedint bit)
{ struct nfs_page *head = req->wb_head; struct nfs_page *tmp;
tmp = req->wb_this_page; while (tmp != req) { if (!test_bit(bit, &tmp->wb_flags)) returnfalse;
tmp = tmp->wb_this_page;
}
/* true! reset all bits */
tmp = req; do {
clear_bit(bit, &tmp->wb_flags);
tmp = tmp->wb_this_page;
} while (tmp != req);
returntrue;
}
/* * nfs_page_group_sync_on_bit - set bit on current request, but only * return true if the bit is set for all requests in page group * @req - request in page group * @bit - PG_* bit that is used to sync page group
*/ bool nfs_page_group_sync_on_bit(struct nfs_page *req, unsignedint bit)
{ bool ret;
nfs_page_group_lock(req);
ret = nfs_page_group_sync_on_bit_locked(req, bit);
nfs_page_group_unlock(req);
return ret;
}
/* * nfs_page_group_init - Initialize the page group linkage for @req * @req - a new nfs request * @prev - the previous request in page group, or NULL if @req is the first * or only request in the group (the head).
*/ staticinlinevoid
nfs_page_group_init(struct nfs_page *req, struct nfs_page *prev)
{ struct inode *inode;
WARN_ON_ONCE(prev == req);
if (!prev) { /* a head request */
req->wb_head = req;
req->wb_this_page = req;
} else { /* a subrequest */
WARN_ON_ONCE(prev->wb_this_page != prev->wb_head);
WARN_ON_ONCE(!test_bit(PG_HEADLOCK, &prev->wb_head->wb_flags));
req->wb_head = prev->wb_head;
req->wb_this_page = prev->wb_this_page;
prev->wb_this_page = req;
/* All subrequests take a ref on the head request until
* nfs_page_group_destroy is called */
kref_get(&req->wb_head->wb_kref);
/* grab extra ref and bump the request count if head request * has extra ref from the write/commit path to handle handoff
* between write and commit lists. */ if (test_bit(PG_INODE_REF, &prev->wb_head->wb_flags)) {
inode = nfs_page_to_inode(req);
set_bit(PG_INODE_REF, &req->wb_flags);
kref_get(&req->wb_kref);
atomic_long_inc(&NFS_I(inode)->nrequests);
}
}
}
/* * nfs_page_group_destroy - sync the destruction of page groups * @req - request that no longer needs the page group * * releases the page group reference from each member once all * members have called this function.
*/ staticvoid
nfs_page_group_destroy(struct kref *kref)
{ struct nfs_page *req = container_of(kref, struct nfs_page, wb_kref); struct nfs_page *head = req->wb_head; struct nfs_page *tmp, *next;
if (!nfs_page_group_sync_on_bit(req, PG_TEARDOWN)) goto out;
tmp = req; do {
next = tmp->wb_this_page; /* unlink and free */
tmp->wb_this_page = tmp;
tmp->wb_head = tmp;
nfs_free_request(tmp);
tmp = next;
} while (tmp != req);
out: /* subrequests must release the ref on the head request */ if (head != req)
nfs_release_request(head);
}
/* Initialize the request struct. Initially, we assume a * long write-back delay. This will be adjusted in
* update_nfs_request below if the region is not locked. */
req->wb_pgbase = pgbase;
req->wb_index = index;
req->wb_offset = offset;
req->wb_bytes = count;
kref_init(&req->wb_kref);
req->wb_nio = 0; return req;
}
/** * nfs_page_create_from_page - Create an NFS read/write request. * @ctx: open context to use * @page: page to write * @pgbase: starting offset within the page for the write * @offset: file offset for the write * @count: number of bytes to read/write * * The page must be locked by the caller. This makes sure we never * create two different requests for the same page. * User should ensure it is safe to sleep in this function.
*/ struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx, struct page *page, unsignedint pgbase, loff_t offset, unsignedint count)
{ struct nfs_lock_context *l_ctx = nfs_get_lock_context(ctx); struct nfs_page *ret;
if (IS_ERR(l_ctx)) return ERR_CAST(l_ctx);
ret = nfs_page_create(l_ctx, pgbase, offset >> PAGE_SHIFT,
offset_in_page(offset), count); if (!IS_ERR(ret)) {
nfs_page_assign_page(ret, page);
nfs_page_group_init(ret, NULL);
}
nfs_put_lock_context(l_ctx); return ret;
}
/** * nfs_page_create_from_folio - Create an NFS read/write request. * @ctx: open context to use * @folio: folio to write * @offset: starting offset within the folio for the write * @count: number of bytes to read/write * * The page must be locked by the caller. This makes sure we never * create two different requests for the same page. * User should ensure it is safe to sleep in this function.
*/ struct nfs_page *nfs_page_create_from_folio(struct nfs_open_context *ctx, struct folio *folio, unsignedint offset, unsignedint count)
{ struct nfs_lock_context *l_ctx = nfs_get_lock_context(ctx); struct nfs_page *ret;
if (IS_ERR(l_ctx)) return ERR_CAST(l_ctx);
ret = nfs_page_create(l_ctx, offset, folio->index, offset, count); if (!IS_ERR(ret)) {
nfs_page_assign_folio(ret, folio);
nfs_page_group_init(ret, NULL);
}
nfs_put_lock_context(l_ctx); return ret;
}
ret = nfs_page_create(req->wb_lock_context, pgbase, req->wb_index,
offset, count); if (!IS_ERR(ret)) { if (folio)
nfs_page_assign_folio(ret, folio); else
nfs_page_assign_page(ret, page); /* find the last request */ for (last = req->wb_head;
last->wb_this_page != req->wb_head;
last = last->wb_this_page)
;
/** * nfs_free_request - Release the count on an NFS read/write request * @req: request to release * * Note: Should never be called with the spinlock held!
*/ void nfs_free_request(struct nfs_page *req)
{
WARN_ON_ONCE(req->wb_this_page != req);
/* extra debug: make sure no sync bits are still set */
WARN_ON_ONCE(test_bit(PG_TEARDOWN, &req->wb_flags));
WARN_ON_ONCE(test_bit(PG_UNLOCKPAGE, &req->wb_flags));
WARN_ON_ONCE(test_bit(PG_UPTODATE, &req->wb_flags));
WARN_ON_ONCE(test_bit(PG_WB_END, &req->wb_flags));
WARN_ON_ONCE(test_bit(PG_REMOVE, &req->wb_flags));
/* Release struct file and open context */
nfs_clear_request(req);
nfs_page_free(req);
}
/* * nfs_generic_pg_test - determine if requests can be coalesced * @desc: pointer to descriptor * @prev: previous request in desc, or NULL * @req: this request * * Returns zero if @req cannot be coalesced into @desc, otherwise it returns * the size of the request.
*/
size_t nfs_generic_pg_test(struct nfs_pageio_descriptor *desc, struct nfs_page *prev, struct nfs_page *req)
{ struct nfs_pgio_mirror *mirror = nfs_pgio_current_mirror(desc);
if (mirror->pg_count > mirror->pg_bsize) { /* should never happen */
WARN_ON_ONCE(1); return 0;
}
/* * Limit the request size so that we can still allocate a page array * for it without upsetting the slab allocator.
*/ if (((mirror->pg_count + req->wb_bytes) >> PAGE_SHIFT) * sizeof(struct page *) > PAGE_SIZE) return 0;
/** * nfs_pgio_data_destroy - make @hdr suitable for reuse * * Frees memory and releases refs from nfs_generic_pgio, so that it may * be called again. * * @hdr: A header that has had nfs_generic_pgio called
*/ staticvoid nfs_pgio_data_destroy(struct nfs_pgio_header *hdr)
{ if (hdr->args.context)
put_nfs_open_context(hdr->args.context); if (hdr->page_array.pagevec != hdr->page_array.page_array)
kfree(hdr->page_array.pagevec);
}
/* * nfs_pgio_header_free - Free a read or write header * @hdr: The header to free
*/ void nfs_pgio_header_free(struct nfs_pgio_header *hdr)
{
nfs_pgio_data_destroy(hdr);
hdr->rw_ops->rw_free_header(hdr);
}
EXPORT_SYMBOL_GPL(nfs_pgio_header_free);
/** * nfs_pgio_rpcsetup - Set up arguments for a pageio call * @hdr: The pageio hdr * @pgbase: base * @count: Number of bytes to read * @how: How to commit data (writes only) * @cinfo: Commit information for the call (writes only)
*/ staticvoid nfs_pgio_rpcsetup(struct nfs_pgio_header *hdr, unsignedint pgbase, unsignedint count, int how, struct nfs_commit_info *cinfo)
{ struct nfs_page *req = hdr->req;
/* Set up the RPC argument and reply structs
* NB: take care not to mess about with hdr->commit et al. */
/** * nfs_pgio_result - Basic pageio error handling * @task: The task that ran * @calldata: Pageio header to check
*/ staticvoid nfs_pgio_result(struct rpc_task *task, void *calldata)
{ struct nfs_pgio_header *hdr = calldata; struct inode *inode = hdr->inode;
if (hdr->rw_ops->rw_done(task, hdr, inode) != 0) return; if (task->tk_status < 0)
nfs_set_pgio_error(hdr, task->tk_status, hdr->args.offset); else
hdr->rw_ops->rw_result(task, hdr);
}
/* * Create an RPC task for the given read or write request and kick it. * The page must have been locked by the caller. * * It may happen that the page we're passed is not marked dirty. * This is the case if nfs_updatepage detects a conflicting request * that has been written but not committed.
*/ int nfs_generic_pgio(struct nfs_pageio_descriptor *desc, struct nfs_pgio_header *hdr)
{ struct nfs_pgio_mirror *mirror = nfs_pgio_current_mirror(desc);
kfree(desc->pg_mirrors_dynamic);
desc->pg_mirrors_dynamic = NULL; if (mirror_count == 1) return desc->pg_mirrors_static;
ret = kmalloc_array(mirror_count, sizeof(*ret), nfs_io_gfp_mask()); if (ret != NULL) { for (i = 0; i < mirror_count; i++)
nfs_pageio_mirror_init(&ret[i], desc->pg_bsize);
desc->pg_mirrors_dynamic = ret;
} return ret;
}
/* * nfs_pageio_setup_mirroring - determine if mirroring is to be used * by calling the pg_get_mirror_count op
*/ staticvoid nfs_pageio_setup_mirroring(struct nfs_pageio_descriptor *pgio, struct nfs_page *req)
{ unsignedint mirror_count = 1;
if (pgio->pg_ops->pg_get_mirror_count)
mirror_count = pgio->pg_ops->pg_get_mirror_count(pgio, req); if (mirror_count == pgio->pg_mirror_count || pgio->pg_error < 0) return;
if (req_offset(req) != req_offset(prev) + prev->wb_bytes) returnfalse; if (req->wb_pgbase == 0) return prev_end == nfs_page_max_length(prev); if (req->wb_pgbase == prev_end) { struct folio *folio = nfs_page_to_folio(req); if (folio) return folio == nfs_page_to_folio(prev); return req->wb_page == prev->wb_page;
} returnfalse;
}
/** * nfs_coalesce_size - test two requests for compatibility * @prev: pointer to nfs_page * @req: pointer to nfs_page * @pgio: pointer to nfs_pagio_descriptor * * The nfs_page structures 'prev' and 'req' are compared to ensure that the * page data area they describe is contiguous, and that their RPC * credentials, NFSv4 open state, and lockowners are the same. * * Returns size of the request that can be coalesced
*/ staticunsignedint nfs_coalesce_size(struct nfs_page *prev, struct nfs_page *req, struct nfs_pageio_descriptor *pgio)
{ struct file_lock_context *flctx;
if (prev) { if (!nfs_match_open_context(nfs_req_openctx(req), nfs_req_openctx(prev))) return 0;
flctx = locks_inode_context(d_inode(nfs_req_openctx(req)->dentry)); if (flctx != NULL &&
!(list_empty_careful(&flctx->flc_posix) &&
list_empty_careful(&flctx->flc_flock)) &&
!nfs_match_lock_context(req->wb_lock_context,
prev->wb_lock_context)) return 0; if (!nfs_page_is_contiguous(prev, req)) return 0;
} return pgio->pg_ops->pg_test(pgio, prev, req);
}
/** * nfs_pageio_do_add_request - Attempt to coalesce a request into a page list. * @desc: destination io descriptor * @req: request * * If the request 'req' was successfully coalesced into the existing list * of pages 'desc', it returns the size of req.
*/ staticunsignedint
nfs_pageio_do_add_request(struct nfs_pageio_descriptor *desc, struct nfs_page *req)
{ struct nfs_pgio_mirror *mirror = nfs_pgio_current_mirror(desc); struct nfs_page *prev = NULL; unsignedint size;
if (list_empty(&mirror->pg_list)) { if (desc->pg_ops->pg_init)
desc->pg_ops->pg_init(desc, req); if (desc->pg_error < 0) return 0;
mirror->pg_base = req->wb_pgbase;
mirror->pg_count = 0;
mirror->pg_recoalesce = 0;
} else
prev = nfs_list_entry(mirror->pg_list.prev);
/** * __nfs_pageio_add_request - Attempt to coalesce a request into a page list. * @desc: destination io descriptor * @req: request * * This may split a request into subrequests which are all part of the * same page group. If so, it will submit @req as the last one, to ensure * the pointer to @req is still valid in case of failure. * * Returns true if the request 'req' was successfully coalesced into the * existing list of pages 'desc'.
*/ staticint __nfs_pageio_add_request(struct nfs_pageio_descriptor *desc, struct nfs_page *req)
{ struct nfs_pgio_mirror *mirror = nfs_pgio_current_mirror(desc); struct nfs_page *subreq; unsignedint size, subreq_size;
nfs_page_group_lock(req);
subreq = req;
subreq_size = subreq->wb_bytes; for(;;) {
size = nfs_pageio_do_add_request(desc, subreq); if (size == subreq_size) { /* We successfully submitted a request */ if (subreq == req) break;
req->wb_pgbase += size;
req->wb_bytes -= size;
req->wb_offset += size;
subreq_size = req->wb_bytes;
subreq = req; continue;
} if (WARN_ON_ONCE(subreq != req)) {
nfs_page_group_unlock(req);
nfs_pageio_cleanup_request(desc, subreq);
subreq = req;
subreq_size = req->wb_bytes;
nfs_page_group_lock(req);
} if (!size) { /* Can't coalesce any more, so do I/O */
nfs_page_group_unlock(req);
desc->pg_moreio = 1;
nfs_pageio_doio(desc); if (desc->pg_error < 0 || mirror->pg_recoalesce) return 0; /* retry add_request for this subreq */
nfs_page_group_lock(req); continue;
}
subreq = nfs_create_subreq(req, req->wb_pgbase,
req->wb_offset, size); if (IS_ERR(subreq)) goto err_ptr;
subreq_size = size;
}
/* * nfs_pageio_complete_mirror - Complete I/O on the current mirror of an * nfs_pageio_descriptor * @desc: pointer to io descriptor * @mirror_idx: pointer to mirror index
*/ staticvoid nfs_pageio_complete_mirror(struct nfs_pageio_descriptor *desc,
u32 mirror_idx)
{ struct nfs_pgio_mirror *mirror;
u32 restore_idx;
for (;;) {
nfs_pageio_doio(desc); if (desc->pg_error < 0 || !mirror->pg_recoalesce) break; if (!nfs_do_recoalesce(desc)) break;
}
nfs_pgio_set_current_mirror(desc, restore_idx);
}
/* * nfs_pageio_resend - Transfer requests to new descriptor and resend * @hdr - the pgio header to move request from * @desc - the pageio descriptor to add requests to * * Try to move each request (nfs_page) from @hdr to @desc then attempt * to send them. * * Returns 0 on success and < 0 on error.
*/ int nfs_pageio_resend(struct nfs_pageio_descriptor *desc, struct nfs_pgio_header *hdr)
{
LIST_HEAD(pages);
/** * nfs_pageio_complete - Complete I/O then cleanup an nfs_pageio_descriptor * @desc: pointer to io descriptor
*/ void nfs_pageio_complete(struct nfs_pageio_descriptor *desc)
{
u32 midx;
for (midx = 0; midx < desc->pg_mirror_count; midx++)
nfs_pageio_complete_mirror(desc, midx);
if (desc->pg_error < 0)
nfs_pageio_error_cleanup(desc); if (desc->pg_ops->pg_cleanup)
desc->pg_ops->pg_cleanup(desc);
nfs_pageio_cleanup_mirroring(desc);
}
/** * nfs_pageio_cond_complete - Conditional I/O completion * @desc: pointer to io descriptor * @index: page index * * It is important to ensure that processes don't try to take locks * on non-contiguous ranges of pages as that might deadlock. This * function should be called before attempting to wait on a locked * nfs_page. It will complete the I/O if the page index 'index' * is not contiguous with the existing list of pages in 'desc'.
*/ void nfs_pageio_cond_complete(struct nfs_pageio_descriptor *desc, pgoff_t index)
{ struct nfs_pgio_mirror *mirror; struct nfs_page *prev; struct folio *folio;
u32 midx;
for (midx = 0; midx < desc->pg_mirror_count; midx++) {
mirror = nfs_pgio_get_mirror(desc, midx); if (!list_empty(&mirror->pg_list)) {
prev = nfs_list_entry(mirror->pg_list.prev);
folio = nfs_page_to_folio(prev); if (folio) { if (index == folio_next_index(folio)) continue;
} elseif (index == prev->wb_index + 1) continue; /* * We will submit more requests after these. Indicate * this to the underlying layers.
*/
desc->pg_moreio = 1;
nfs_pageio_complete(desc); break;
}
}
}
/* * nfs_pageio_stop_mirroring - stop using mirroring (set mirror count to 1)
*/ void nfs_pageio_stop_mirroring(struct nfs_pageio_descriptor *pgio)
{
nfs_pageio_complete(pgio);
}
int __init nfs_init_nfspagecache(void)
{
nfs_page_cachep = kmem_cache_create("nfs_page", sizeof(struct nfs_page),
0, SLAB_HWCACHE_ALIGN,
NULL); if (nfs_page_cachep == NULL) return -ENOMEM;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.