diff options
Diffstat (limited to 'fs/xfs/xfs_filestream.c')
-rw-r--r-- | fs/xfs/xfs_filestream.c | 455 |
1 files changed, 235 insertions, 220 deletions
diff --git a/fs/xfs/xfs_filestream.c b/fs/xfs/xfs_filestream.c index 34b21a29c39b..22c13933c8f8 100644 --- a/fs/xfs/xfs_filestream.c +++ b/fs/xfs/xfs_filestream.c @@ -12,6 +12,7 @@ #include "xfs_mount.h" #include "xfs_inode.h" #include "xfs_bmap.h" +#include "xfs_bmap_util.h" #include "xfs_alloc.h" #include "xfs_mru_cache.h" #include "xfs_trace.h" @@ -22,7 +23,7 @@ struct xfs_fstrm_item { struct xfs_mru_cache_elem mru; - xfs_agnumber_t ag; /* AG in use for this directory */ + struct xfs_perag *pag; /* AG in use for this directory */ }; enum xfs_fstrm_alloc { @@ -30,117 +31,68 @@ enum xfs_fstrm_alloc { XFS_PICK_LOWSPACE = 2, }; -/* - * Allocation group filestream associations are tracked with per-ag atomic - * counters. These counters allow xfs_filestream_pick_ag() to tell whether a - * particular AG already has active filestreams associated with it. - */ -int -xfs_filestream_peek_ag( - xfs_mount_t *mp, - xfs_agnumber_t agno) -{ - struct xfs_perag *pag; - int ret; - - pag = xfs_perag_get(mp, agno); - ret = atomic_read(&pag->pagf_fstrms); - xfs_perag_put(pag); - return ret; -} - -static int -xfs_filestream_get_ag( - xfs_mount_t *mp, - xfs_agnumber_t agno) -{ - struct xfs_perag *pag; - int ret; - - pag = xfs_perag_get(mp, agno); - ret = atomic_inc_return(&pag->pagf_fstrms); - xfs_perag_put(pag); - return ret; -} - -static void -xfs_filestream_put_ag( - xfs_mount_t *mp, - xfs_agnumber_t agno) -{ - struct xfs_perag *pag; - - pag = xfs_perag_get(mp, agno); - atomic_dec(&pag->pagf_fstrms); - xfs_perag_put(pag); -} - static void xfs_fstrm_free_func( void *data, struct xfs_mru_cache_elem *mru) { - struct xfs_mount *mp = data; struct xfs_fstrm_item *item = container_of(mru, struct xfs_fstrm_item, mru); + struct xfs_perag *pag = item->pag; - xfs_filestream_put_ag(mp, item->ag); - trace_xfs_filestream_free(mp, mru->key, item->ag); + trace_xfs_filestream_free(pag, mru->key); + atomic_dec(&pag->pagf_fstrms); + xfs_perag_rele(pag); kmem_free(item); } /* - * Scan the AGs starting at startag looking for an AG that isn't in use and has - * at least minlen blocks free. + * Scan the AGs starting at start_agno looking for an AG that isn't in use and + * has at least minlen blocks free. If no AG is found to match the allocation + * requirements, pick the AG with the most free space in it. */ static int xfs_filestream_pick_ag( - struct xfs_inode *ip, - xfs_agnumber_t startag, - xfs_agnumber_t *agp, + struct xfs_alloc_arg *args, + xfs_ino_t pino, + xfs_agnumber_t start_agno, int flags, - xfs_extlen_t minlen) + xfs_extlen_t *longest) { - struct xfs_mount *mp = ip->i_mount; - struct xfs_fstrm_item *item; + struct xfs_mount *mp = args->mp; struct xfs_perag *pag; - xfs_extlen_t longest, free = 0, minfree, maxfree = 0; - xfs_agnumber_t ag, max_ag = NULLAGNUMBER; - int err, trylock, nscan; - - ASSERT(S_ISDIR(VFS_I(ip)->i_mode)); + struct xfs_perag *max_pag = NULL; + xfs_extlen_t minlen = *longest; + xfs_extlen_t free = 0, minfree, maxfree = 0; + xfs_agnumber_t agno; + bool first_pass = true; + int err; /* 2% of an AG's blocks must be free for it to be chosen. */ minfree = mp->m_sb.sb_agblocks / 50; - ag = startag; - *agp = NULLAGNUMBER; - - /* For the first pass, don't sleep trying to init the per-AG. */ - trylock = XFS_ALLOC_FLAG_TRYLOCK; - - for (nscan = 0; 1; nscan++) { - trace_xfs_filestream_scan(mp, ip->i_ino, ag); - - pag = xfs_perag_get(mp, ag); - - if (!pag->pagf_init) { - err = xfs_alloc_read_agf(pag, NULL, trylock, NULL); - if (err) { - if (err != -EAGAIN) { - xfs_perag_put(pag); - return err; - } - /* Couldn't lock the AGF, skip this AG. */ - goto next_ag; - } +restart: + for_each_perag_wrap(mp, start_agno, agno, pag) { + trace_xfs_filestream_scan(pag, pino); + *longest = 0; + err = xfs_bmap_longest_free_extent(pag, NULL, longest); + if (err) { + xfs_perag_rele(pag); + if (err != -EAGAIN) + break; + /* Couldn't lock the AGF, skip this AG. */ + err = 0; + continue; } /* Keep track of the AG with the most free blocks. */ if (pag->pagf_freeblks > maxfree) { maxfree = pag->pagf_freeblks; - max_ag = ag; + if (max_pag) + xfs_perag_rele(max_pag); + atomic_inc(&pag->pag_active_ref); + max_pag = pag; } /* @@ -149,93 +101,73 @@ xfs_filestream_pick_ag( * loop, and it guards against two filestreams being established * in the same AG as each other. */ - if (xfs_filestream_get_ag(mp, ag) > 1) { - xfs_filestream_put_ag(mp, ag); - goto next_ag; - } - - longest = xfs_alloc_longest_free_extent(pag, - xfs_alloc_min_freelist(mp, pag), - xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); - if (((minlen && longest >= minlen) || - (!minlen && pag->pagf_freeblks >= minfree)) && - (!pag->pagf_metadata || !(flags & XFS_PICK_USERDATA) || - (flags & XFS_PICK_LOWSPACE))) { - - /* Break out, retaining the reference on the AG. */ - free = pag->pagf_freeblks; - xfs_perag_put(pag); - *agp = ag; - break; + if (atomic_inc_return(&pag->pagf_fstrms) <= 1) { + if (((minlen && *longest >= minlen) || + (!minlen && pag->pagf_freeblks >= minfree)) && + (!xfs_perag_prefers_metadata(pag) || + !(flags & XFS_PICK_USERDATA) || + (flags & XFS_PICK_LOWSPACE))) { + /* Break out, retaining the reference on the AG. */ + free = pag->pagf_freeblks; + break; + } } /* Drop the reference on this AG, it's not usable. */ - xfs_filestream_put_ag(mp, ag); -next_ag: - xfs_perag_put(pag); - /* Move to the next AG, wrapping to AG 0 if necessary. */ - if (++ag >= mp->m_sb.sb_agcount) - ag = 0; - - /* If a full pass of the AGs hasn't been done yet, continue. */ - if (ag != startag) - continue; + atomic_dec(&pag->pagf_fstrms); + } - /* Allow sleeping in xfs_alloc_read_agf() on the 2nd pass. */ - if (trylock != 0) { - trylock = 0; - continue; + if (err) { + xfs_perag_rele(pag); + if (max_pag) + xfs_perag_rele(max_pag); + return err; + } + + if (!pag) { + /* + * Allow a second pass to give xfs_bmap_longest_free_extent() + * another attempt at locking AGFs that it might have skipped + * over before we fail. + */ + if (first_pass) { + first_pass = false; + goto restart; } - /* Finally, if lowspace wasn't set, set it for the 3rd pass. */ + /* + * We must be low on data space, so run a final lowspace + * optimised selection pass if we haven't already. + */ if (!(flags & XFS_PICK_LOWSPACE)) { flags |= XFS_PICK_LOWSPACE; - continue; + goto restart; } /* - * Take the AG with the most free space, regardless of whether - * it's already in use by another filestream. + * No unassociated AGs are available, so select the AG with the + * most free space, regardless of whether it's already in use by + * another filestream. It none suit, just use whatever AG we can + * grab. */ - if (max_ag != NULLAGNUMBER) { - xfs_filestream_get_ag(mp, max_ag); + if (!max_pag) { + for_each_perag_wrap(args->mp, 0, start_agno, args->pag) + break; + atomic_inc(&args->pag->pagf_fstrms); + *longest = 0; + } else { + pag = max_pag; free = maxfree; - *agp = max_ag; - break; + atomic_inc(&pag->pagf_fstrms); } - - /* take AG 0 if none matched */ - trace_xfs_filestream_pick(ip, *agp, free, nscan); - *agp = 0; - return 0; - } - - trace_xfs_filestream_pick(ip, *agp, free, nscan); - - if (*agp == NULLAGNUMBER) - return 0; - - err = -ENOMEM; - item = kmem_alloc(sizeof(*item), KM_MAYFAIL); - if (!item) - goto out_put_ag; - - item->ag = *agp; - - err = xfs_mru_cache_insert(mp->m_filestream, ip->i_ino, &item->mru); - if (err) { - if (err == -EEXIST) - err = 0; - goto out_free_item; + } else if (max_pag) { + xfs_perag_rele(max_pag); } + trace_xfs_filestream_pick(pag, pino, free); + args->pag = pag; return 0; -out_free_item: - kmem_free(item); -out_put_ag: - xfs_filestream_put_ag(mp, *agp); - return err; } static struct xfs_inode * @@ -263,104 +195,187 @@ out: } /* - * Find the right allocation group for a file, either by finding an - * existing file stream or creating a new one. + * Lookup the mru cache for an existing association. If one exists and we can + * use it, return with an active perag reference indicating that the allocation + * will proceed with that association. * - * Returns NULLAGNUMBER in case of an error. + * If we have no association, or we cannot use the current one and have to + * destroy it, return with longest = 0 to tell the caller to create a new + * association. */ -xfs_agnumber_t -xfs_filestream_lookup_ag( - struct xfs_inode *ip) +static int +xfs_filestream_lookup_association( + struct xfs_bmalloca *ap, + struct xfs_alloc_arg *args, + xfs_ino_t pino, + xfs_extlen_t *longest) { - struct xfs_mount *mp = ip->i_mount; - struct xfs_inode *pip = NULL; - xfs_agnumber_t startag, ag = NULLAGNUMBER; + struct xfs_mount *mp = args->mp; + struct xfs_perag *pag; struct xfs_mru_cache_elem *mru; + int error = 0; - ASSERT(S_ISREG(VFS_I(ip)->i_mode)); - - pip = xfs_filestream_get_parent(ip); - if (!pip) - return NULLAGNUMBER; + *longest = 0; + mru = xfs_mru_cache_lookup(mp->m_filestream, pino); + if (!mru) + return 0; + /* + * Grab the pag and take an extra active reference for the caller whilst + * the mru item cannot go away. This means we'll pin the perag with + * the reference we get here even if the filestreams association is torn + * down immediately after we mark the lookup as done. + */ + pag = container_of(mru, struct xfs_fstrm_item, mru)->pag; + atomic_inc(&pag->pag_active_ref); + xfs_mru_cache_done(mp->m_filestream); - mru = xfs_mru_cache_lookup(mp->m_filestream, pip->i_ino); - if (mru) { - ag = container_of(mru, struct xfs_fstrm_item, mru)->ag; - xfs_mru_cache_done(mp->m_filestream); + trace_xfs_filestream_lookup(pag, ap->ip->i_ino); - trace_xfs_filestream_lookup(mp, ip->i_ino, ag); - goto out; - } + ap->blkno = XFS_AGB_TO_FSB(args->mp, pag->pag_agno, 0); + xfs_bmap_adjacent(ap); /* - * Set the starting AG using the rotor for inode32, otherwise - * use the directory inode's AG. + * If there is very little free space before we start a filestreams + * allocation, we're almost guaranteed to fail to find a large enough + * free space available so just use the cached AG. */ - if (xfs_is_inode32(mp)) { - xfs_agnumber_t rotorstep = xfs_rotorstep; - startag = (mp->m_agfrotor / rotorstep) % mp->m_sb.sb_agcount; - mp->m_agfrotor = (mp->m_agfrotor + 1) % - (mp->m_sb.sb_agcount * rotorstep); - } else - startag = XFS_INO_TO_AGNO(mp, pip->i_ino); + if (ap->tp->t_flags & XFS_TRANS_LOWMODE) { + *longest = 1; + goto out_done; + } - if (xfs_filestream_pick_ag(pip, startag, &ag, 0, 0)) - ag = NULLAGNUMBER; -out: - xfs_irele(pip); - return ag; + error = xfs_bmap_longest_free_extent(pag, args->tp, longest); + if (error == -EAGAIN) + error = 0; + if (error || *longest < args->maxlen) { + /* We aren't going to use this perag */ + *longest = 0; + xfs_perag_rele(pag); + return error; + } + +out_done: + args->pag = pag; + return 0; } -/* - * Pick a new allocation group for the current file and its file stream. - * - * This is called when the allocator can't find a suitable extent in the - * current AG, and we have to move the stream into a new AG with more space. - */ -int -xfs_filestream_new_ag( +static int +xfs_filestream_create_association( struct xfs_bmalloca *ap, - xfs_agnumber_t *agp) + struct xfs_alloc_arg *args, + xfs_ino_t pino, + xfs_extlen_t *longest) { - struct xfs_inode *ip = ap->ip, *pip; - struct xfs_mount *mp = ip->i_mount; - xfs_extlen_t minlen = ap->length; - xfs_agnumber_t startag = 0; - int flags = 0; - int err = 0; + struct xfs_mount *mp = args->mp; struct xfs_mru_cache_elem *mru; + struct xfs_fstrm_item *item; + xfs_agnumber_t agno = XFS_INO_TO_AGNO(mp, pino); + int flags = 0; + int error; - *agp = NULLAGNUMBER; - - pip = xfs_filestream_get_parent(ip); - if (!pip) - goto exit; - - mru = xfs_mru_cache_remove(mp->m_filestream, pip->i_ino); + /* Changing parent AG association now, so remove the existing one. */ + mru = xfs_mru_cache_remove(mp->m_filestream, pino); if (mru) { struct xfs_fstrm_item *item = container_of(mru, struct xfs_fstrm_item, mru); - startag = (item->ag + 1) % mp->m_sb.sb_agcount; + + agno = (item->pag->pag_agno + 1) % mp->m_sb.sb_agcount; + xfs_fstrm_free_func(mp, mru); + } else if (xfs_is_inode32(mp)) { + xfs_agnumber_t rotorstep = xfs_rotorstep; + + agno = (mp->m_agfrotor / rotorstep) % mp->m_sb.sb_agcount; + mp->m_agfrotor = (mp->m_agfrotor + 1) % + (mp->m_sb.sb_agcount * rotorstep); } + ap->blkno = XFS_AGB_TO_FSB(args->mp, agno, 0); + xfs_bmap_adjacent(ap); + if (ap->datatype & XFS_ALLOC_USERDATA) flags |= XFS_PICK_USERDATA; if (ap->tp->t_flags & XFS_TRANS_LOWMODE) flags |= XFS_PICK_LOWSPACE; - err = xfs_filestream_pick_ag(pip, startag, agp, flags, minlen); + *longest = ap->length; + error = xfs_filestream_pick_ag(args, pino, agno, flags, longest); + if (error) + return error; /* - * Only free the item here so we skip over the old AG earlier. + * We are going to use this perag now, so create an assoication for it. + * xfs_filestream_pick_ag() has already bumped the perag fstrms counter + * for us, so all we need to do here is take another active reference to + * the perag for the cached association. + * + * If we fail to store the association, we need to drop the fstrms + * counter as well as drop the perag reference we take here for the + * item. We do not need to return an error for this failure - as long as + * we return a referenced AG, the allocation can still go ahead just + * fine. */ - if (mru) - xfs_fstrm_free_func(mp, mru); + item = kmem_alloc(sizeof(*item), KM_MAYFAIL); + if (!item) + goto out_put_fstrms; + + atomic_inc(&args->pag->pag_active_ref); + item->pag = args->pag; + error = xfs_mru_cache_insert(mp->m_filestream, pino, &item->mru); + if (error) + goto out_free_item; + return 0; + +out_free_item: + xfs_perag_rele(item->pag); + kmem_free(item); +out_put_fstrms: + atomic_dec(&args->pag->pagf_fstrms); + return 0; +} + +/* + * Search for an allocation group with a single extent large enough for + * the request. First we look for an existing association and use that if it + * is found. Otherwise, we create a new association by selecting an AG that fits + * the allocation criteria. + * + * We return with a referenced perag in args->pag to indicate which AG we are + * allocating into or an error with no references held. + */ +int +xfs_filestream_select_ag( + struct xfs_bmalloca *ap, + struct xfs_alloc_arg *args, + xfs_extlen_t *longest) +{ + struct xfs_mount *mp = args->mp; + struct xfs_inode *pip; + xfs_ino_t ino = 0; + int error = 0; + + *longest = 0; + args->total = ap->total; + pip = xfs_filestream_get_parent(ap->ip); + if (pip) { + ino = pip->i_ino; + error = xfs_filestream_lookup_association(ap, args, ino, + longest); + xfs_irele(pip); + if (error) + return error; + if (*longest >= args->maxlen) + goto out_select; + if (ap->tp->t_flags & XFS_TRANS_LOWMODE) + goto out_select; + } + + error = xfs_filestream_create_association(ap, args, ino, longest); + if (error) + return error; - xfs_irele(pip); -exit: - if (*agp == NULLAGNUMBER) - *agp = 0; - return err; +out_select: + ap->blkno = XFS_AGB_TO_FSB(mp, args->pag->pag_agno, 0); + return 0; } void |