Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | /* * linux/fs/dcache.c * * (C) Copyright 1994 Linus Torvalds */ /* * The directory cache is a "two-level" cache, each level doing LRU on * its entries. Adding new entries puts them at the end of the LRU * queue on the first-level cache, while the second-level cache is * fed by any cache hits. * * The idea is that new additions (from readdir(), for example) will not * flush the cache of entries that have really been used. * * There is a global hash-table over both caches that hashes the entries * based on the directory inode number and device as well as on a * string-hash computed over the name. */ #include <stddef.h> #include <linux/fs.h> #include <linux/string.h> /* * Don't bother caching long names.. They just take up space in the cache, and * for a name cache you just want to cache the "normal" names anyway which tend * to be short. */ #define DCACHE_NAME_LEN 15 #define DCACHE_SIZE 128 struct hash_list { struct dir_cache_entry * next; struct dir_cache_entry * prev; }; /* * The dir_cache_entry must be in this order: we do ugly things with the pointers */ struct dir_cache_entry { struct hash_list h; unsigned long dev; unsigned long dir; unsigned long version; unsigned long ino; unsigned char name_len; char name[DCACHE_NAME_LEN]; struct dir_cache_entry ** lru_head; struct dir_cache_entry * next_lru, * prev_lru; }; #define COPYDATA(de, newde) \ memcpy((void *) &newde->dev, (void *) &de->dev, \ 4*sizeof(unsigned long) + 1 + DCACHE_NAME_LEN) static struct dir_cache_entry level1_cache[DCACHE_SIZE]; static struct dir_cache_entry level2_cache[DCACHE_SIZE]; /* * The LRU-lists are doubly-linked circular lists, and do not change in size * so these pointers always have something to point to (after _init) */ static struct dir_cache_entry * level1_head; static struct dir_cache_entry * level2_head; /* * The hash-queues are also doubly-linked circular lists, but the head is * itself on the doubly-linked list, not just a pointer to the first entry. */ #define DCACHE_HASH_QUEUES 19 #define hash_fn(dev,dir,namehash) (((dev) ^ (dir) ^ (namehash)) % DCACHE_HASH_QUEUES) static struct hash_list hash_table[DCACHE_HASH_QUEUES]; static inline void remove_lru(struct dir_cache_entry * de) { de->next_lru->prev_lru = de->prev_lru; de->prev_lru->next_lru = de->next_lru; } static inline void add_lru(struct dir_cache_entry * de, struct dir_cache_entry *head) { de->next_lru = head; de->prev_lru = head->prev_lru; de->prev_lru->next_lru = de; head->prev_lru = de; } static inline void update_lru(struct dir_cache_entry * de) { if (de == *de->lru_head) *de->lru_head = de->next_lru; else { remove_lru(de); add_lru(de,*de->lru_head); } } /* * Stupid name"hash" algorithm. Write something better if you want to, * but I doubt it matters that much */ static inline unsigned long namehash(const char * name, int len) { return len * *(unsigned char *) name; } /* * Hash queue manipulation. Look out for the casts.. */ static inline void remove_hash(struct dir_cache_entry * de) { if (de->h.next) { de->h.next->h.prev = de->h.prev; de->h.prev->h.next = de->h.next; de->h.next = NULL; } } static inline void add_hash(struct dir_cache_entry * de, struct hash_list * hash) { de->h.next = hash->next; de->h.prev = (struct dir_cache_entry *) hash; hash->next->h.prev = de; hash->next = de; } /* * Find a directory cache entry given all the necessary info. */ static struct dir_cache_entry * find_entry(struct inode * dir, const char * name, int len, struct hash_list * hash) { struct dir_cache_entry * de = hash->next; for (de = hash->next ; de != (struct dir_cache_entry *) hash ; de = de->h.next) { if (de->dev != dir->i_dev) continue; if (de->dir != dir->i_ino) continue; if (de->version != dir->i_version) continue; if (de->name_len != len) continue; if (memcmp(de->name, name, len)) continue; return de; } return NULL; } /* * Move a successfully used entry to level2. If already at level2, * move it to the end of the LRU queue.. */ static inline void move_to_level2(struct dir_cache_entry * old_de, struct hash_list * hash) { struct dir_cache_entry * de; if (old_de->lru_head == &level2_head) { update_lru(old_de); return; } de = level2_head; level2_head = de->next_lru; remove_hash(de); COPYDATA(old_de, de); add_hash(de, hash); } int dcache_lookup(struct inode * dir, const char * name, int len, unsigned long * ino) { struct hash_list * hash; struct dir_cache_entry *de; if (len > DCACHE_NAME_LEN) return 0; hash = hash_table + hash_fn(dir->i_dev, dir->i_ino, namehash(name,len)); de = find_entry(dir, name, len, hash); if (!de) return 0; *ino = de->ino; move_to_level2(de, hash); return 1; } void dcache_add(struct inode * dir, const char * name, int len, unsigned long ino) { struct hash_list * hash; struct dir_cache_entry *de; if (len > DCACHE_NAME_LEN) return; hash = hash_table + hash_fn(dir->i_dev, dir->i_ino, namehash(name,len)); if ((de = find_entry(dir, name, len, hash)) != NULL) { de->ino = ino; update_lru(de); return; } de = level1_head; level1_head = de->next_lru; remove_hash(de); de->dev = dir->i_dev; de->dir = dir->i_ino; de->version = dir->i_version; de->ino = ino; de->name_len = len; memcpy(de->name, name, len); add_hash(de, hash); } unsigned long name_cache_init(unsigned long mem_start, unsigned long mem_end) { int i; struct dir_cache_entry * p; /* * Init level1 LRU lists.. */ p = level1_cache; do { p[1].prev_lru = p; p[0].next_lru = p+1; p[0].lru_head = &level1_head; } while (++p < level1_cache + DCACHE_SIZE-1); level1_cache[0].prev_lru = p; p[0].next_lru = &level1_cache[0]; p[0].lru_head = &level1_head; level1_head = level1_cache; /* * Init level2 LRU lists.. */ p = level2_cache; do { p[1].prev_lru = p; p[0].next_lru = p+1; p[0].lru_head = &level2_head; } while (++p < level2_cache + DCACHE_SIZE-1); level2_cache[0].prev_lru = p; p[0].next_lru = &level2_cache[0]; p[0].lru_head = &level2_head; level2_head = level2_cache; /* * Empty hash queues.. */ for (i = 0 ; i < DCACHE_HASH_QUEUES ; i++) hash_table[i].next = hash_table[i].next = (struct dir_cache_entry *) &hash_table[i]; return mem_start; } |