bf0c49d0ba7253abf9d7b6a9d0b1cd94853c0060
[openafs.git] / src / afs / afs_buffer.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include "afs/param.h"
12
13
14 #include "afs/sysincludes.h"
15 #include "afsincludes.h"
16 #if !defined(UKERNEL)
17 #include "h/param.h"
18 #include "h/types.h"
19 #include "h/time.h"
20 #if     defined(AFS_AIX31_ENV) 
21 #include "h/limits.h"
22 #endif
23 #if     !defined(AFS_AIX_ENV) && !defined(AFS_SUN5_ENV) && !defined(AFS_SGI_ENV) && !defined(AFS_LINUX20_ENV)
24 #include "h/kernel.h"           /* Doesn't needed, so it should go */
25 #endif
26 #endif /* !defined(UKERNEL) */
27
28 #include "afs/afs_osi.h"
29 #include "afsint.h"
30 #include "afs/lock.h"
31
32 #if !defined(UKERNEL) && !defined(AFS_LINUX20_ENV)
33 #include "h/buf.h"
34 #endif /* !defined(UKERNEL) */
35
36 #include "afs/stds.h"
37 #include "afs/volerrors.h"
38 #include "afs/exporter.h"
39 #include "afs/prs_fs.h"
40 #include "afs/afs_chunkops.h"
41 #include "afs/dir.h"
42
43 #include "afs/afs_stats.h"
44 #include "afs/afs.h"
45
46 #ifndef BUF_TIME_MAX
47 #define BUF_TIME_MAX    0x7fffffff
48 #endif
49 /* number of pages per Unix buffer, when we're using Unix buffer pool */
50 #define NPB 4
51 /* page size */
52 #define AFS_BUFFER_PAGESIZE 2048
53 /* log page size */
54 #define LOGPS 11
55 /* If you change any of this PH stuff, make sure you don't break DZap() */
56 /* use last two bits for page */
57 #define PHPAGEMASK 3
58 /* use next five bits for fid */
59 #define PHFIDMASK 124
60 /* page hash table size - this is pretty intertwined with pHash */
61 #define PHSIZE (PHPAGEMASK + PHFIDMASK + 1)
62 /* the pHash macro */
63 #define pHash(fid,page) ((((afs_int32)(fid)) & PHFIDMASK) \
64                          | (page & PHPAGEMASK))
65
66 #ifdef  dirty
67 #undef dirty                    /* XXX */
68 #endif
69
70 static struct buffer *Buffers = 0;
71 static char *BufferData;
72
73 #ifdef  AFS_AIX_ENV
74 extern struct buf *geteblk();
75 #endif
76 #ifdef AFS_FBSD_ENV
77 #define timecounter afs_timecounter
78 #endif
79
80 /* A note on locking in 'struct buffer'
81  *
82  * afs_bufferLock protects the hash chain, and the 'lockers' field where that
83  * has a zero value. It must be held whenever lockers is incremented from zero.
84  *
85  * The individual buffer lock protects the contents of the structure, including
86  * the lockers field.
87  *
88  * For safety: afs_bufferLock and the individual buffer lock must be held
89  * when obtaining a reference on a structure. Only the individual buffer lock
90  * need be held when releasing a reference.
91  *
92  * The locking hierarchy is afs_bufferLock-> buffer.lock
93  *
94  */
95
96 static afs_lock_t afs_bufferLock;
97 static struct buffer *phTable[PHSIZE];  /* page hash table */
98 static int nbuffers;
99 static afs_int32 timecounter;
100
101 /* Prototypes for static routines */
102 static struct buffer *afs_newslot(struct dcache *adc, afs_int32 apage,
103                                   register struct buffer *lp);
104
105 static int dinit_flag = 0;
106 void
107 DInit(int abuffers)
108 {
109     /* Initialize the venus buffer system. */
110     register int i;
111     register struct buffer *tb;
112 #if defined(AFS_USEBUFFERS)
113     struct buf *tub;            /* unix buffer for allocation */
114 #endif
115
116     AFS_STATCNT(DInit);
117     if (dinit_flag)
118         return;
119     dinit_flag = 1;
120 #if defined(AFS_USEBUFFERS)
121     /* round up to next multiple of NPB, since we allocate multiple pages per chunk */
122     abuffers = ((abuffers - 1) | (NPB - 1)) + 1;
123 #endif
124     LOCK_INIT(&afs_bufferLock, "afs_bufferLock");
125     Buffers =
126         (struct buffer *)afs_osi_Alloc(abuffers * sizeof(struct buffer));
127 #if !defined(AFS_USEBUFFERS)
128     BufferData = (char *)afs_osi_Alloc(abuffers * AFS_BUFFER_PAGESIZE);
129 #endif
130     timecounter = 1;
131     afs_stats_cmperf.bufAlloced = nbuffers = abuffers;
132     for (i = 0; i < PHSIZE; i++)
133         phTable[i] = 0;
134     for (i = 0; i < abuffers; i++) {
135 #if defined(AFS_USEBUFFERS)
136         if ((i & (NPB - 1)) == 0) {
137             /* time to allocate a fresh buffer */
138             tub = geteblk(AFS_BUFFER_PAGESIZE * NPB);
139             BufferData = (char *)tub->b_un.b_addr;
140         }
141 #endif
142         /* Fill in each buffer with an empty indication. */
143         tb = &Buffers[i];
144         tb->fid = NULLIDX;
145         afs_reset_inode(&tb->inode);
146         tb->accesstime = 0;
147         tb->lockers = 0;
148 #if defined(AFS_USEBUFFERS)
149         if ((i & (NPB - 1)) == 0)
150             tb->bufp = tub;
151         else
152             tb->bufp = 0;
153         tb->data = &BufferData[AFS_BUFFER_PAGESIZE * (i & (NPB - 1))];
154 #else
155         tb->data = &BufferData[AFS_BUFFER_PAGESIZE * i];
156 #endif
157         tb->hashIndex = 0;
158         tb->dirty = 0;
159         AFS_RWLOCK_INIT(&tb->lock, "buffer lock");
160     }
161     return;
162 }
163
164 void *
165 DRead(register struct dcache *adc, register int page)
166 {
167     /* Read a page from the disk. */
168     register struct buffer *tb, *tb2;
169     struct osi_file *tfile;
170     int code;
171
172     AFS_STATCNT(DRead);
173     MObtainWriteLock(&afs_bufferLock, 256);
174
175 #define bufmatch(tb) (tb->page == page && tb->fid == adc->index)
176 #define buf_Front(head,parent,p) {(parent)->hashNext = (p)->hashNext; (p)->hashNext= *(head);*(head)=(p);}
177
178     /* this apparently-complicated-looking code is simply an example of
179      * a little bit of loop unrolling, and is a standard linked-list 
180      * traversal trick. It saves a few assignments at the the expense
181      * of larger code size.  This could be simplified by better use of
182      * macros. 
183      */
184     if ((tb = phTable[pHash(adc->index, page)])) {
185         if (bufmatch(tb)) {
186             MObtainWriteLock(&tb->lock, 257);
187             tb->lockers++;
188             ReleaseWriteLock(&afs_bufferLock);
189             tb->accesstime = timecounter++;
190             AFS_STATS(afs_stats_cmperf.bufHits++);
191             MReleaseWriteLock(&tb->lock);
192             return tb->data;
193         } else {
194             register struct buffer **bufhead;
195             bufhead = &(phTable[pHash(adc->index, page)]);
196             while ((tb2 = tb->hashNext)) {
197                 if (bufmatch(tb2)) {
198                     buf_Front(bufhead, tb, tb2);
199                     MObtainWriteLock(&tb2->lock, 258);
200                     tb2->lockers++;
201                     ReleaseWriteLock(&afs_bufferLock);
202                     tb2->accesstime = timecounter++;
203                     AFS_STATS(afs_stats_cmperf.bufHits++);
204                     MReleaseWriteLock(&tb2->lock);
205                     return tb2->data;
206                 }
207                 if ((tb = tb2->hashNext)) {
208                     if (bufmatch(tb)) {
209                         buf_Front(bufhead, tb2, tb);
210                         MObtainWriteLock(&tb->lock, 259);
211                         tb->lockers++;
212                         ReleaseWriteLock(&afs_bufferLock);
213                         tb->accesstime = timecounter++;
214                         AFS_STATS(afs_stats_cmperf.bufHits++);
215                         MReleaseWriteLock(&tb->lock);
216                         return tb->data;
217                     }
218                 } else
219                     break;
220             }
221         }
222     } else
223         tb2 = NULL;
224
225     AFS_STATS(afs_stats_cmperf.bufMisses++);
226     /* can't find it */
227     /* The last thing we looked at was either tb or tb2 (or nothing). That
228      * is at least the oldest buffer on one particular hash chain, so it's 
229      * a pretty good place to start looking for the truly oldest buffer.
230      */
231     tb = afs_newslot(adc, page, (tb ? tb : tb2));
232     if (!tb) {
233         MReleaseWriteLock(&afs_bufferLock);
234         return NULL;
235     }
236     MObtainWriteLock(&tb->lock, 260);
237     tb->lockers++;
238     MReleaseWriteLock(&afs_bufferLock);
239     if (page * AFS_BUFFER_PAGESIZE >= adc->f.chunkBytes) {
240         tb->fid = NULLIDX;
241         afs_reset_inode(&tb->inode);
242         tb->lockers--;
243         MReleaseWriteLock(&tb->lock);
244         return NULL;
245     }
246     tfile = afs_CFileOpen(&adc->f.inode);
247     code =
248         afs_CFileRead(tfile, tb->page * AFS_BUFFER_PAGESIZE, tb->data,
249                       AFS_BUFFER_PAGESIZE);
250     afs_CFileClose(tfile);
251     if (code < AFS_BUFFER_PAGESIZE) {
252         tb->fid = NULLIDX;
253         afs_reset_inode(&tb->inode);
254         tb->lockers--;
255         MReleaseWriteLock(&tb->lock);
256         return NULL;
257     }
258     /* Note that findslot sets the page field in the buffer equal to
259      * what it is searching for. */
260     MReleaseWriteLock(&tb->lock);
261     return tb->data;
262 }
263
264 static void
265 FixupBucket(register struct buffer *ap)
266 {
267     register struct buffer **lp, *tp;
268     register int i;
269     /* first try to get it out of its current hash bucket, in which it
270      * might not be */
271     AFS_STATCNT(FixupBucket);
272     i = ap->hashIndex;
273     lp = &phTable[i];
274     for (tp = *lp; tp; tp = tp->hashNext) {
275         if (tp == ap) {
276             *lp = tp->hashNext;
277             break;
278         }
279         lp = &tp->hashNext;
280     }
281     /* now figure the new hash bucket */
282     i = pHash(ap->fid, ap->page);
283     ap->hashIndex = i;          /* remember where we are for deletion */
284     ap->hashNext = phTable[i];  /* add us to the list */
285     phTable[i] = ap;            /* at the front, since it's LRU */
286 }
287
288 /* lp is pointer to a fairly-old buffer */
289 static struct buffer *
290 afs_newslot(struct dcache *adc, afs_int32 apage, register struct buffer *lp)
291 {
292     /* Find a usable buffer slot */
293     register afs_int32 i;
294     afs_int32 lt;
295     register struct buffer *tp;
296     struct osi_file *tfile;
297
298     AFS_STATCNT(afs_newslot);
299     /* we take a pointer here to a buffer which was at the end of an
300      * LRU hash chain.  Odds are, it's one of the older buffers, not
301      * one of the newer.  Having an older buffer to start with may
302      * permit us to avoid a few of the assignments in the "typical
303      * case" for loop below.
304      */
305     if (lp && (lp->lockers == 0)) {
306         lt = lp->accesstime;
307     } else {
308         lp = NULL;
309     }
310
311     /* timecounter might have wrapped, if machine is very very busy
312      * and stays up for a long time.  Timecounter mustn't wrap twice
313      * (positive->negative->positive) before calling newslot, but that
314      * would require 2 billion consecutive cache hits... Anyway, the
315      * penalty is only that the cache replacement policy will be
316      * almost MRU for the next ~2 billion DReads...  newslot doesn't
317      * get called nearly as often as DRead, so in order to avoid the
318      * performance penalty of using the hypers, it's worth doing the
319      * extra check here every time.  It's probably cheaper than doing
320      * hcmp, anyway.  There is a little performance hit resulting from
321      * resetting all the access times to 0, but it only happens once
322      * every month or so, and the access times will rapidly sort
323      * themselves back out after just a few more DReads.
324      */
325     if (timecounter < 0) {
326         timecounter = 1;
327         tp = Buffers;
328         for (i = 0; i < nbuffers; i++, tp++) {
329             tp->accesstime = 0;
330             if (!lp && !tp->lockers)    /* one is as good as the rest, I guess */
331                 lp = tp;
332         }
333     } else {
334         /* this is the typical case */
335         tp = Buffers;
336         for (i = 0; i < nbuffers; i++, tp++) {
337             if (tp->lockers == 0) {
338                 if (!lp || tp->accesstime < lt) {
339                     lp = tp;
340                     lt = tp->accesstime;
341                 }
342             }
343         }
344     }
345
346     if (lp == 0) {
347         /* There are no unlocked buffers -- this used to panic, but that
348          * seems extreme.  To the best of my knowledge, all the callers
349          * of DRead are prepared to handle a zero return.  Some of them
350          * just panic directly, but not all of them. */
351         afs_warn("afs: all buffers locked\n");
352         return 0;
353     }
354
355     if (lp->dirty) {
356         /* see DFlush for rationale for not getting and locking the dcache */
357         tfile = afs_CFileOpen(&lp->inode);
358         afs_CFileWrite(tfile, lp->page * AFS_BUFFER_PAGESIZE, lp->data,
359                        AFS_BUFFER_PAGESIZE);
360         lp->dirty = 0;
361         afs_CFileClose(tfile);
362         AFS_STATS(afs_stats_cmperf.bufFlushDirty++);
363     }
364
365     /* Now fill in the header. */
366     lp->fid = adc->index;
367     afs_copy_inode(&lp->inode, &adc->f.inode);
368     lp->page = apage;
369     lp->accesstime = timecounter++;
370     FixupBucket(lp);            /* move to the right hash bucket */
371
372     return lp;
373 }
374
375 void
376 DRelease(void *loc, int flag)
377 {
378     /* Release a buffer, specifying whether or not the buffer has been
379      * modified by the locker. */
380     register struct buffer *bp = (struct buffer *)loc;
381     register int index;
382 #if defined(AFS_USEBUFFERS)
383     register struct buffer *tp;
384 #endif
385
386     AFS_STATCNT(DRelease);
387     if (!bp)
388         return;
389 #if defined(AFS_USEBUFFERS)
390     /* look for buffer by scanning Unix buffers for appropriate address */
391     tp = Buffers;
392     for (index = 0; index < nbuffers; index += NPB, tp += NPB) {
393         if ((afs_int32) bp >= (afs_int32) tp->data
394             && (afs_int32) bp <
395             (afs_int32) tp->data + AFS_BUFFER_PAGESIZE * NPB) {
396             /* we found the right range */
397             index += ((afs_int32) bp - (afs_int32) tp->data) >> LOGPS;
398             break;
399         }
400     }
401 #else
402     index = (((char *)bp) - ((char *)BufferData)) >> LOGPS;
403 #endif
404     bp = &(Buffers[index]);
405     MObtainWriteLock(&bp->lock, 261);
406     bp->lockers--;
407     if (flag)
408         bp->dirty = 1;
409     MReleaseWriteLock(&bp->lock);
410 }
411
412 int
413 DVOffset(register void *ap)
414 {
415     /* Return the byte within a file represented by a buffer pointer. */
416     register struct buffer *bp;
417     register int index;
418 #if defined(AFS_USEBUFFERS)
419     register struct buffer *tp;
420 #endif
421     AFS_STATCNT(DVOffset);
422     bp = ap;
423 #if defined(AFS_USEBUFFERS)
424     /* look for buffer by scanning Unix buffers for appropriate address */
425     tp = Buffers;
426     for (index = 0; index < nbuffers; index += NPB, tp += NPB) {
427         if ((afs_int32) bp >= (afs_int32) tp->data
428             && (afs_int32) bp <
429             (afs_int32) tp->data + AFS_BUFFER_PAGESIZE * NPB) {
430             /* we found the right range */
431             index += ((afs_int32) bp - (afs_int32) tp->data) >> LOGPS;
432             break;
433         }
434     }
435 #else
436     index = (((char *)bp) - ((char *)BufferData)) >> LOGPS;
437 #endif
438     if (index < 0 || index >= nbuffers)
439         return -1;
440     bp = &(Buffers[index]);
441     return AFS_BUFFER_PAGESIZE * bp->page + (int)(((char *)ap) - bp->data);
442 }
443
444 /*! 
445  * Zap one dcache entry: destroy one FID's buffers.
446  *
447  * 1/1/91 - I've modified the hash function to take the page as well
448  * as the *fid, so that lookup will be a bit faster.  That presents some
449  * difficulties for Zap, which now has to have some knowledge of the nature
450  * of the hash function.  Oh well.  This should use the list traversal 
451  * method of DRead...
452  *
453  * \param adc The dcache entry to be zapped.
454  */
455 void
456 DZap(struct dcache *adc)
457 {
458     register int i;
459     /* Destroy all buffers pertaining to a particular fid. */
460     register struct buffer *tb;
461
462     AFS_STATCNT(DZap);
463     MObtainReadLock(&afs_bufferLock);
464
465     for (i = 0; i <= PHPAGEMASK; i++)
466         for (tb = phTable[pHash(adc->index, i)]; tb; tb = tb->hashNext)
467             if (tb->fid == adc->index) {
468                 MObtainWriteLock(&tb->lock, 262);
469                 tb->fid = NULLIDX;
470                 afs_reset_inode(&tb->inode);
471                 tb->dirty = 0;
472                 MReleaseWriteLock(&tb->lock);
473             }
474     MReleaseReadLock(&afs_bufferLock);
475 }
476
477 static void
478 DFlushBuffer(struct buffer *ab) {
479     struct osi_file *tfile;
480     
481     tfile = afs_CFileOpen(&ab->inode);
482     afs_CFileWrite(tfile, ab->page * AFS_BUFFER_PAGESIZE,
483                    ab->data, AFS_BUFFER_PAGESIZE);
484     ab->dirty = 0;      /* Clear the dirty flag */
485     afs_CFileClose(tfile);
486 }
487
488 void
489 DFlushDCache(struct dcache *adc) 
490 {
491     int i;
492     struct buffer *tb;
493
494     ObtainReadLock(&afs_bufferLock);
495
496     for (i = 0; i <= PHPAGEMASK; i++)
497         for (tb = phTable[pHash(adc->index, i)]; tb; tb = tb->hashNext)
498             if (tb->fid == adc->index) {
499                 ObtainWriteLock(&tb->lock, 701);
500                 tb->lockers++;
501                 ReleaseReadLock(&afs_bufferLock);
502                 if (tb->dirty) {
503                     DFlushBuffer(tb);
504                 }
505                 tb->lockers--;
506                 ReleaseWriteLock(&tb->lock);
507                 ObtainReadLock(&afs_bufferLock);
508             }
509
510     ReleaseReadLock(&afs_bufferLock);
511 }
512
513 void
514 DFlush(void)
515 {
516     /* Flush all the modified buffers. */
517     register int i;
518     register struct buffer *tb;
519
520     AFS_STATCNT(DFlush);
521     tb = Buffers;
522     MObtainReadLock(&afs_bufferLock);
523     for (i = 0; i < nbuffers; i++, tb++) {
524         if (tb->dirty) {
525             MObtainWriteLock(&tb->lock, 263);
526             tb->lockers++;
527             MReleaseReadLock(&afs_bufferLock);
528             if (tb->dirty) {
529                 /* it seems safe to do this I/O without having the dcache
530                  * locked, since the only things that will update the data in
531                  * a directory are the buffer package, which holds the relevant
532                  * tb->lock while doing the write, or afs_GetDCache, which 
533                  * DZap's the directory while holding the dcache lock.
534                  * It is not possible to lock the dcache or even call
535                  * afs_GetDSlot to map the index to the dcache since the dir
536                  * package's caller has some dcache object locked already (so
537                  * we cannot lock afs_xdcache). In addition, we cannot obtain
538                  * a dcache lock while holding the tb->lock of the same file
539                  * since that can deadlock with DRead/DNew */
540                 DFlushBuffer(tb);
541             }
542             tb->lockers--;
543             MReleaseWriteLock(&tb->lock);
544             MObtainReadLock(&afs_bufferLock);
545         }
546     }
547     MReleaseReadLock(&afs_bufferLock);
548 }
549
550 void *
551 DNew(register struct dcache *adc, register int page)
552 {
553     /* Same as read, only do *not* even try to read the page, since it probably doesn't exist. */
554     register struct buffer *tb;
555     AFS_STATCNT(DNew);
556     MObtainWriteLock(&afs_bufferLock, 264);
557     if ((tb = afs_newslot(adc, page, NULL)) == 0) {
558         MReleaseWriteLock(&afs_bufferLock);
559         return 0;
560     }
561     /* extend the chunk, if needed */
562     /* Do it now, not in DFlush or afs_newslot when the data is written out,
563      * since now our caller has adc->lock writelocked, and we can't acquire
564      * that lock (or even map from a fid to a dcache) in afs_newslot or
565      * DFlush due to lock hierarchy issues */
566     if ((page + 1) * AFS_BUFFER_PAGESIZE > adc->f.chunkBytes) {
567         afs_AdjustSize(adc, (page + 1) * AFS_BUFFER_PAGESIZE);
568         afs_WriteDCache(adc, 1);
569     }
570     MObtainWriteLock(&tb->lock, 265);
571     tb->lockers++;
572     MReleaseWriteLock(&afs_bufferLock);
573     MReleaseWriteLock(&tb->lock);
574     return tb->data;
575 }
576
577 void
578 shutdown_bufferpackage(void)
579 {
580 #if defined(AFS_USEBUFFERS)
581     register struct buffer *tp;
582 #endif
583     int i;
584
585     AFS_STATCNT(shutdown_bufferpackage);
586     /* Free all allocated Buffers and associated buffer pages */
587     DFlush();
588     if (afs_cold_shutdown) {
589         dinit_flag = 0;
590 #if !defined(AFS_USEBUFFERS)
591         afs_osi_Free(BufferData, nbuffers * AFS_BUFFER_PAGESIZE);
592 #else
593         tp = Buffers;
594         for (i = 0; i < nbuffers; i += NPB, tp += NPB) {
595             /* The following check shouldn't be necessary and it will be removed soon */
596             if (!tp->bufp)
597                 afs_warn
598                     ("afs: shutdown_bufferpackage: bufp == 0!! Shouldn't happen\n");
599             else {
600                 brelse(tp->bufp);
601                 tp->bufp = 0;
602             }
603         }
604 #endif
605         afs_osi_Free(Buffers, nbuffers * sizeof(struct buffer));
606         nbuffers = 0;
607         timecounter = 1;
608         for (i = 0; i < PHSIZE; i++)
609             phTable[i] = 0;
610         memset(&afs_bufferLock, 0, sizeof(afs_lock_t));
611     }
612 }