📄 mp_fget.c
字号:
/*- * See the file LICENSE for redistribution information. * * Copyright (c) 1996-2002 * Sleepycat Software. All rights reserved. */#include "db_config.h"#ifndef lintstatic const char revid[] = "$Id: mp_fget.c,v 11.68 2002/08/06 04:58:09 bostic Exp $";#endif /* not lint */#ifndef NO_SYSTEM_INCLUDES#include <sys/types.h>#include <string.h>#endif#include "db_int.h"#include "dbinc/db_shash.h"#include "dbinc/mp.h"#ifdef HAVE_FILESYSTEM_NOTZEROstatic int __memp_fs_notzero __P((DB_ENV *, DB_MPOOLFILE *, MPOOLFILE *, db_pgno_t *));#endif/* * __memp_fget -- * Get a page from the file. * * PUBLIC: int __memp_fget * PUBLIC: __P((DB_MPOOLFILE *, db_pgno_t *, u_int32_t, void *)); */int__memp_fget(dbmfp, pgnoaddr, flags, addrp) DB_MPOOLFILE *dbmfp; db_pgno_t *pgnoaddr; u_int32_t flags; void *addrp;{ enum { FIRST_FOUND, FIRST_MISS, SECOND_FOUND, SECOND_MISS } state; BH *alloc_bhp, *bhp; DB_ENV *dbenv; DB_MPOOL *dbmp; DB_MPOOL_HASH *hp; MPOOL *c_mp, *mp; MPOOLFILE *mfp; roff_t mf_offset; u_int32_t n_cache, st_hsearch; int b_incr, extending, first, ret; *(void **)addrp = NULL; dbmp = dbmfp->dbmp; dbenv = dbmp->dbenv; PANIC_CHECK(dbenv); mp = dbmp->reginfo[0].primary; mfp = dbmfp->mfp; mf_offset = R_OFFSET(dbmp->reginfo, mfp); alloc_bhp = bhp = NULL; hp = NULL; b_incr = extending = ret = 0; /* * Validate arguments. * * !!! * Don't test for DB_MPOOL_CREATE and DB_MPOOL_NEW flags for readonly * files here, and create non-existent pages in readonly files if the * flags are set, later. The reason is that the hash access method * wants to get empty pages that don't really exist in readonly files. * The only alternative is for hash to write the last "bucket" all the * time, which we don't want to do because one of our big goals in life * is to keep database files small. It's sleazy as hell, but we catch * any attempt to actually write the file in memp_fput(). */#define OKFLAGS (DB_MPOOL_CREATE | DB_MPOOL_LAST | DB_MPOOL_NEW) if (flags != 0) { if ((ret = __db_fchk(dbenv, "memp_fget", flags, OKFLAGS)) != 0) return (ret); switch (flags) { case DB_MPOOL_CREATE: break; case DB_MPOOL_LAST: /* Get the last page number in the file. */ if (flags == DB_MPOOL_LAST) { R_LOCK(dbenv, dbmp->reginfo); *pgnoaddr = mfp->last_pgno; R_UNLOCK(dbenv, dbmp->reginfo); } break; case DB_MPOOL_NEW: /* * If always creating a page, skip the first search * of the hash bucket. */ if (flags == DB_MPOOL_NEW) goto alloc; break; default: return (__db_ferr(dbenv, "memp_fget", 1)); } } /* * If mmap'ing the file and the page is not past the end of the file, * just return a pointer. * * The page may be past the end of the file, so check the page number * argument against the original length of the file. If we previously * returned pages past the original end of the file, last_pgno will * have been updated to match the "new" end of the file, and checking * against it would return pointers past the end of the mmap'd region. * * If another process has opened the file for writing since we mmap'd * it, we will start playing the game by their rules, i.e. everything * goes through the cache. All pages previously returned will be safe, * as long as the correct locking protocol was observed. * * We don't discard the map because we don't know when all of the * pages will have been discarded from the process' address space. * It would be possible to do so by reference counting the open * pages from the mmap, but it's unclear to me that it's worth it. */ if (dbmfp->addr != NULL && F_ISSET(mfp, MP_CAN_MMAP) && *pgnoaddr <= mfp->orig_last_pgno) { *(void **)addrp = R_ADDR(dbmfp, *pgnoaddr * mfp->stat.st_pagesize); ++mfp->stat.st_map; return (0); }hb_search: /* * Determine the cache and hash bucket where this page lives and get * local pointers to them. Reset on each pass through this code, the * page number can change. */ n_cache = NCACHE(mp, mf_offset, *pgnoaddr); c_mp = dbmp->reginfo[n_cache].primary; hp = R_ADDR(&dbmp->reginfo[n_cache], c_mp->htab); hp = &hp[NBUCKET(c_mp, mf_offset, *pgnoaddr)]; /* Search the hash chain for the page. */retry: st_hsearch = 0; MUTEX_LOCK(dbenv, &hp->hash_mutex); for (bhp = SH_TAILQ_FIRST(&hp->hash_bucket, __bh); bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, hq, __bh)) { ++st_hsearch; if (bhp->pgno != *pgnoaddr || bhp->mf_offset != mf_offset) continue; /* * Increment the reference count. We may discard the hash * bucket lock as we evaluate and/or read the buffer, so we * need to ensure it doesn't move and its contents remain * unchanged. */ if (bhp->ref == UINT16_T_MAX) { __db_err(dbenv, "%s: page %lu: reference count overflow", __memp_fn(dbmfp), (u_long)bhp->pgno); ret = EINVAL; MUTEX_UNLOCK(dbenv, &hp->hash_mutex); goto err; } ++bhp->ref; b_incr = 1; /* * BH_LOCKED -- * I/O is in progress or sync is waiting on the buffer to write * it. Because we've incremented the buffer reference count, * we know the buffer can't move. Unlock the bucket lock, wait * for the buffer to become available, reacquire the bucket. */ for (first = 1; F_ISSET(bhp, BH_LOCKED) && !F_ISSET(dbenv, DB_ENV_NOLOCKING); first = 0) { /* * If someone is trying to sync this buffer and the * buffer is hot, they may never get in. Give up * and try again. */ if (!first && bhp->ref_sync != 0) { --bhp->ref; b_incr = 0; MUTEX_UNLOCK(dbenv, &hp->hash_mutex); __os_yield(dbenv, 1); goto retry; } MUTEX_UNLOCK(dbenv, &hp->hash_mutex); /* * Explicitly yield the processor if not the first pass * through this loop -- if we don't, we might run to the * end of our CPU quantum as we will simply be swapping * between the two locks. */ if (!first) __os_yield(dbenv, 1); MUTEX_LOCK(dbenv, &bhp->mutex); /* Wait for I/O to finish... */ MUTEX_UNLOCK(dbenv, &bhp->mutex); MUTEX_LOCK(dbenv, &hp->hash_mutex); } ++mfp->stat.st_cache_hit; break; } /* * Update the hash bucket search statistics -- do now because our next * search may be for a different bucket. */ ++c_mp->stat.st_hash_searches; if (st_hsearch > c_mp->stat.st_hash_longest) c_mp->stat.st_hash_longest = st_hsearch; c_mp->stat.st_hash_examined += st_hsearch; /* * There are 4 possible paths to this location: * * FIRST_MISS: * Didn't find the page in the hash bucket on our first pass: * bhp == NULL, alloc_bhp == NULL * * FIRST_FOUND: * Found the page in the hash bucket on our first pass: * bhp != NULL, alloc_bhp == NULL * * SECOND_FOUND: * Didn't find the page in the hash bucket on the first pass, * allocated space, and found the page in the hash bucket on * our second pass: * bhp != NULL, alloc_bhp != NULL * * SECOND_MISS: * Didn't find the page in the hash bucket on the first pass, * allocated space, and didn't find the page in the hash bucket * on our second pass: * bhp == NULL, alloc_bhp != NULL */ state = bhp == NULL ? (alloc_bhp == NULL ? FIRST_MISS : SECOND_MISS) : (alloc_bhp == NULL ? FIRST_FOUND : SECOND_FOUND); switch (state) { case FIRST_FOUND: /* We found the buffer in our first check -- we're done. */ break; case FIRST_MISS: /* * We didn't find the buffer in our first check. Figure out * if the page exists, and allocate structures so we can add * the page to the buffer pool. */ MUTEX_UNLOCK(dbenv, &hp->hash_mutex);alloc: /* * If DB_MPOOL_NEW is set, we have to allocate a page number. * If neither DB_MPOOL_CREATE or DB_MPOOL_CREATE is set, then * it's an error to try and get a page past the end of file. */ COMPQUIET(n_cache, 0); extending = ret = 0; R_LOCK(dbenv, dbmp->reginfo); switch (flags) { case DB_MPOOL_NEW: extending = 1; *pgnoaddr = mfp->last_pgno + 1; break; case DB_MPOOL_CREATE: extending = *pgnoaddr > mfp->last_pgno; break; default: ret = *pgnoaddr > mfp->last_pgno ? DB_PAGE_NOTFOUND : 0; break; } R_UNLOCK(dbenv, dbmp->reginfo); if (ret != 0) goto err; /* * !!! * In the DB_MPOOL_NEW code path, mf_offset and n_cache have * not yet been initialized. */ mf_offset = R_OFFSET(dbmp->reginfo, mfp); n_cache = NCACHE(mp, mf_offset, *pgnoaddr); /* Allocate a new buffer header and data space. */ if ((ret = __memp_alloc(dbmp, &dbmp->reginfo[n_cache], mfp, 0, NULL, &alloc_bhp)) != 0) goto err;#ifdef DIAGNOSTIC if ((db_alignp_t)alloc_bhp->buf & (sizeof(size_t) - 1)) { __db_err(dbenv, "Error: buffer data is NOT size_t aligned"); ret = EINVAL; goto err; }#endif /* * If we are extending the file, we'll need the region lock * again. */ if (extending) R_LOCK(dbenv, dbmp->reginfo); /* * DB_MPOOL_NEW does not guarantee you a page unreferenced by * any other thread of control. (That guarantee is interesting * for DB_MPOOL_NEW, unlike DB_MPOOL_CREATE, because the caller * did not specify the page number, and so, may reasonably not * have any way to lock the page outside of mpool.) Regardless, * if we allocate the page, and some other thread of control * requests the page by number, we will not detect that and the * thread of control that allocated using DB_MPOOL_NEW may not * have a chance to initialize the page. (Note: we *could* * detect this case if we set a flag in the buffer header which * guaranteed that no gets of the page would succeed until the * reference count went to 0, that is, until the creating page * put the page.) What we do guarantee is that if two threads
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -