]> mj.ucw.cz Git - libucw.git/blob - lib/pagecache.c
Use pread/pwrite whenever available. In all other cases, cache file positions.
[libucw.git] / lib / pagecache.c
1 /*
2  *      Sherlock Library -- File Page Cache
3  *
4  *      (c) 1999 Martin Mares <mj@atrey.karlin.mff.cuni.cz>
5  */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <fcntl.h>
11 #include <unistd.h>
12
13 #include "lib.h"
14 #include "pagecache.h"
15 #include "lfs.h"
16
17 struct page_cache {
18   list free_pages;                      /* LRU queue of free non-dirty pages */
19   list locked_pages;                    /* List of locked pages (starts with dirty ones) */
20   list dirty_pages;                     /* List of free dirty pages */
21   uns page_size;                        /* Bytes per page (must be a power of two) */
22   uns free_count;                       /* Number of free / dirty pages */
23   uns total_count;                      /* Total number of pages */
24   uns max_pages;                        /* Maximum number of free pages */
25   uns hash_size;                        /* Hash table size */
26   uns stat_hit;                         /* Number of cache hits */
27   uns stat_miss;                        /* Number of cache misses */
28   uns stat_write;                       /* Number of writes */
29   list *hash_table;                     /* List heads corresponding to hash buckets */
30 #ifndef SHERLOCK_HAVE_PREAD
31   sh_off_t pos;                         /* Current position in the file */
32 #endif
33 };
34
35 #define PAGE_NUMBER(pos) ((pos) & ~(sh_off_t)(c->page_size - 1))
36 #define PAGE_OFFSET(pos) ((pos) & (c->page_size - 1))
37
38 #define KEY_PAGE(key) PAGE_NUMBER(key)
39 #define KEY_FD(key) PAGE_OFFSET(key)
40
41 struct page_cache *
42 pgc_open(uns page_size, uns max_pages)
43 {
44   struct page_cache *c = xmalloc(sizeof(struct page_cache));
45   uns i;
46
47   bzero(c, sizeof(*c));
48   init_list(&c->free_pages);
49   init_list(&c->locked_pages);
50   init_list(&c->dirty_pages);
51   c->page_size = page_size;
52   c->max_pages = max_pages;
53   c->hash_size = nextprime(c->max_pages);
54   c->hash_table = xmalloc(sizeof(list) * c->hash_size);
55   for(i=0; i<c->hash_size; i++)
56     init_list(&c->hash_table[i]);
57   return c;
58 }
59
60 void
61 pgc_close(struct page_cache *c)
62 {
63   pgc_cleanup(c);
64   ASSERT(EMPTY_LIST(c->locked_pages));
65   ASSERT(EMPTY_LIST(c->dirty_pages));
66   ASSERT(EMPTY_LIST(c->free_pages));
67   free(c->hash_table);
68   free(c);
69 }
70
71 static void
72 pgc_debug_page(struct page *p)
73 {
74   printf("\tk=%08x f=%x c=%d\n", (uns) p->key, p->flags, p->lock_count);
75 }
76
77 void
78 pgc_debug(struct page_cache *c, int mode)
79 {
80   struct page *p;
81
82   printf(">> Page cache dump: pgsize=%d, pages=%d, freepages=%d of %d, hash=%d\n", c->page_size, c->total_count, c->free_count, c->max_pages, c->hash_size);
83   printf(">> stats: %d hits, %d misses, %d writes\n", c->stat_hit, c->stat_miss, c->stat_write);
84   if (mode)
85     {
86       puts("LRU list:");
87       WALK_LIST(p, c->free_pages)
88         pgc_debug_page(p);
89       puts("Locked list:");
90       WALK_LIST(p, c->locked_pages)
91         pgc_debug_page(p);
92       puts("Dirty list:");
93       WALK_LIST(p, c->dirty_pages)
94         pgc_debug_page(p);
95     }
96 }
97
98 static void
99 flush_page(struct page_cache *c, struct page *p)
100 {
101   int fd = KEY_FD(p->key);
102   sh_off_t pos = KEY_PAGE(p->key);
103   int s;
104
105   ASSERT(p->flags & PG_FLAG_DIRTY);
106 #ifdef SHERLOCK_HAVE_PREAD
107   s = pwrite(fd, p->data, c->page_size, pos);
108 #else
109   if (c->pos != pos)
110     sh_seek(fd, pos, SEEK_SET);
111   s = write(fd, p->data, c->page_size);
112   c->pos += s;
113 #endif
114   if (s < 0)
115     die("pgc_write(%d): %m", fd);
116   if (s != (int) c->page_size)
117     die("pgc_write(%d): incomplete page (only %d of %d)", s, c->page_size);
118   p->flags &= ~PG_FLAG_DIRTY;
119   c->stat_write++;
120 }
121
122 static inline uns
123 hash_page(struct page_cache *c, sh_off_t key)
124 {
125   return key % c->hash_size;
126 }
127
128 static struct page *
129 get_page(struct page_cache *c, sh_off_t key)
130 {
131   node *n;
132   struct page *p;
133   uns hash = hash_page(c, key);
134
135   /*
136    *  Return locked buffer for given page.
137    */
138
139   WALK_LIST(n, c->hash_table[hash])
140     {
141       p = SKIP_BACK(struct page, hn, n);
142       if (p->key == key)
143         {
144           /* Found in the cache */
145           rem_node(&p->n);
146           if (!p->lock_count)
147             c->free_count--;
148           return p;
149         }
150     }
151   if (c->total_count < c->max_pages || !c->free_count)
152     {
153       /* Enough free space, expand the cache */
154       p = xmalloc(sizeof(struct page) + c->page_size);
155       c->total_count++;
156     }
157   else
158     {
159       /* Discard the oldest unlocked page */
160       p = HEAD(c->free_pages);
161       if (!p->n.next)
162         {
163           /* There are only dirty pages here */
164           p = HEAD(c->dirty_pages);
165           flush_page(c, p);
166         }
167       ASSERT(!p->lock_count);
168       rem_node(&p->n);
169       rem_node(&p->hn);
170       c->free_count--;
171     }
172   p->key = key;
173   p->flags = 0;
174   p->lock_count = 0;
175   add_tail(&c->hash_table[hash], &p->hn);
176   return p;
177 }
178
179 void
180 pgc_flush(struct page_cache *c)
181 {
182   struct page *p;
183   node *n;
184
185   WALK_LIST_DELSAFE(p, n, c->dirty_pages)
186     {
187       flush_page(c, p);
188       rem_node(&p->n);
189       add_tail(&c->free_pages, &p->n);
190     }
191   WALK_LIST(p, c->locked_pages)
192     if (p->flags & PG_FLAG_DIRTY)
193       flush_page(c, p);
194     else
195       break;
196 }
197
198 void
199 pgc_cleanup(struct page_cache *c)
200 {
201   struct page *p;
202   node *n;
203
204   pgc_flush(c);
205   WALK_LIST_DELSAFE(p, n, c->free_pages)
206     {
207       ASSERT(!(p->flags & PG_FLAG_DIRTY) && !p->lock_count);
208       rem_node(&p->n);
209       c->free_count--;
210       c->total_count--;
211       free(p);
212     }
213   ASSERT(!c->free_count);
214 }
215
216 static inline struct page *
217 get_and_lock_page(struct page_cache *c, sh_off_t key)
218 {
219   struct page *p = get_page(c, key);
220
221   add_tail(&c->locked_pages, &p->n);
222   p->lock_count++;
223   return p;
224 }
225
226 struct page *
227 pgc_read(struct page_cache *c, int fd, sh_off_t pos)
228 {
229   sh_off_t key;
230   struct page *p;
231   int s;
232
233   ASSERT(!PAGE_OFFSET(pos));
234   ASSERT(!PAGE_NUMBER(fd));
235   key = pos | fd;
236   p = get_and_lock_page(c, key);
237   if (p->flags & PG_FLAG_VALID)
238     c->stat_hit++;
239   else
240     {
241       c->stat_miss++;
242 #ifdef SHERLOCK_HAVE_PREAD
243       s = pread(fd, p->data, c->page_size, pos);
244 #else
245       if (c->pos != pos)
246         sh_seek(fd, pos, SEEK_SET);
247       s = read(fd, p->data, c->page_size);
248       c->pos += s;
249 #endif
250       if (s < 0)
251         die("pgc_read(%d): %m", fd);
252       if (s != (int) c->page_size)
253         die("pgc_read(%d): incomplete page (only %d of %d)", s, c->page_size);
254       p->flags |= PG_FLAG_VALID;
255     }
256   return p;
257 }
258
259 struct page *
260 pgc_get(struct page_cache *c, int fd, sh_off_t pos)
261 {
262   sh_off_t key;
263   struct page *p;
264
265   ASSERT(!PAGE_OFFSET(pos));
266   ASSERT(!PAGE_NUMBER(fd));
267   key = pos | fd;
268   p = get_and_lock_page(c, key);
269   p->flags |= PG_FLAG_VALID | PG_FLAG_DIRTY;
270   return p;
271 }
272
273 void
274 pgc_put(struct page_cache *c, struct page *p)
275 {
276   ASSERT(p->lock_count);
277   if (--p->lock_count)
278     return;
279   rem_node(&p->n);
280   if (p->flags & PG_FLAG_DIRTY)
281     {
282       add_tail(&c->dirty_pages, &p->n);
283       c->free_count++;
284     }
285   else if (c->free_count < c->max_pages)
286     {
287       add_tail(&c->free_pages, &p->n);
288       c->free_count++;
289     }
290   else
291     {
292       free(p);
293       c->total_count--;
294     }
295 }
296
297 void
298 pgc_mark_dirty(struct page_cache *c, struct page *p)
299 {
300   ASSERT(p->lock_count);
301   if (!(p->flags & PG_FLAG_DIRTY))
302     {
303       p->flags |= PG_FLAG_DIRTY;
304       rem_node(&p->n);
305       add_head(&c->locked_pages, &p->n);
306     }
307 }
308
309 byte *
310 pgc_read_data(struct page_cache *c, int fd, sh_off_t pos, uns *len)
311 {
312   struct page *p;
313   sh_off_t page = PAGE_NUMBER(pos);
314   uns offset = PAGE_OFFSET(pos);
315
316   p = pgc_read(c, fd, page);
317   pgc_put(c, p);
318   *len = c->page_size - offset;
319   return p->data + offset;
320 }
321
322 #ifdef TEST
323
324 int main(int argc, char **argv)
325 {
326   struct page_cache *c = pgc_open(1024, 2);
327   struct page *p, *q, *r;
328   int fd = open("test", O_RDWR | O_CREAT | O_TRUNC, 0666);
329   if (fd < 0)
330     die("open: %m");
331   pgc_debug(c, 1);
332   p = pgc_get(c, fd, 0);
333   pgc_debug(c, 1);
334   strcpy(p->data, "one");
335   pgc_put(c, p);
336   pgc_debug(c, 1);
337   p = pgc_get(c, fd, 1024);
338   pgc_debug(c, 1);
339   strcpy(p->data, "two");
340   pgc_put(c, p);
341   pgc_debug(c, 1);
342   p = pgc_get(c, fd, 2048);
343   pgc_debug(c, 1);
344   strcpy(p->data, "three");
345   pgc_put(c, p);
346   pgc_debug(c, 1);
347   pgc_flush(c);
348   pgc_debug(c, 1);
349   p = pgc_read(c, fd, 0);
350   pgc_debug(c, 1);
351   strcpy(p->data, "odin");
352   pgc_mark_dirty(c, p);
353   pgc_debug(c, 1);
354   pgc_flush(c);
355   pgc_debug(c, 1);
356   q = pgc_read(c, fd, 1024);
357   pgc_debug(c, 1);
358   r = pgc_read(c, fd, 2048);
359   pgc_debug(c, 1);
360   pgc_put(c, p);
361   pgc_put(c, q);
362   pgc_put(c, r);
363   pgc_debug(c, 1);
364   p = pgc_get(c, fd, 3072);
365   pgc_debug(c, 1);
366   strcpy(p->data, "four");
367   pgc_put(c, p);
368   pgc_debug(c, 1);
369   pgc_cleanup(c);
370   pgc_debug(c, 1);
371   pgc_close(c);
372   return 0;
373 }
374
375 #endif