root / block / qcow2-cache.c @ 737e150e
History | View | Annotate | Download (7.8 kB)
1 |
/*
|
---|---|
2 |
* L2/refcount table cache for the QCOW2 format
|
3 |
*
|
4 |
* Copyright (c) 2010 Kevin Wolf <kwolf@redhat.com>
|
5 |
*
|
6 |
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
7 |
* of this software and associated documentation files (the "Software"), to deal
|
8 |
* in the Software without restriction, including without limitation the rights
|
9 |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10 |
* copies of the Software, and to permit persons to whom the Software is
|
11 |
* furnished to do so, subject to the following conditions:
|
12 |
*
|
13 |
* The above copyright notice and this permission notice shall be included in
|
14 |
* all copies or substantial portions of the Software.
|
15 |
*
|
16 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
19 |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22 |
* THE SOFTWARE.
|
23 |
*/
|
24 |
|
25 |
#include "block/block_int.h" |
26 |
#include "qemu-common.h" |
27 |
#include "qcow2.h" |
28 |
#include "trace.h" |
29 |
|
30 |
typedef struct Qcow2CachedTable { |
31 |
void* table;
|
32 |
int64_t offset; |
33 |
bool dirty;
|
34 |
int cache_hits;
|
35 |
int ref;
|
36 |
} Qcow2CachedTable; |
37 |
|
38 |
struct Qcow2Cache {
|
39 |
Qcow2CachedTable* entries; |
40 |
struct Qcow2Cache* depends;
|
41 |
int size;
|
42 |
bool depends_on_flush;
|
43 |
}; |
44 |
|
45 |
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables)
|
46 |
{ |
47 |
BDRVQcowState *s = bs->opaque; |
48 |
Qcow2Cache *c; |
49 |
int i;
|
50 |
|
51 |
c = g_malloc0(sizeof(*c));
|
52 |
c->size = num_tables; |
53 |
c->entries = g_malloc0(sizeof(*c->entries) * num_tables);
|
54 |
|
55 |
for (i = 0; i < c->size; i++) { |
56 |
c->entries[i].table = qemu_blockalign(bs, s->cluster_size); |
57 |
} |
58 |
|
59 |
return c;
|
60 |
} |
61 |
|
62 |
int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c)
|
63 |
{ |
64 |
int i;
|
65 |
|
66 |
for (i = 0; i < c->size; i++) { |
67 |
assert(c->entries[i].ref == 0);
|
68 |
qemu_vfree(c->entries[i].table); |
69 |
} |
70 |
|
71 |
g_free(c->entries); |
72 |
g_free(c); |
73 |
|
74 |
return 0; |
75 |
} |
76 |
|
77 |
static int qcow2_cache_flush_dependency(BlockDriverState *bs, Qcow2Cache *c) |
78 |
{ |
79 |
int ret;
|
80 |
|
81 |
ret = qcow2_cache_flush(bs, c->depends); |
82 |
if (ret < 0) { |
83 |
return ret;
|
84 |
} |
85 |
|
86 |
c->depends = NULL;
|
87 |
c->depends_on_flush = false;
|
88 |
|
89 |
return 0; |
90 |
} |
91 |
|
92 |
static int qcow2_cache_entry_flush(BlockDriverState *bs, Qcow2Cache *c, int i) |
93 |
{ |
94 |
BDRVQcowState *s = bs->opaque; |
95 |
int ret = 0; |
96 |
|
97 |
if (!c->entries[i].dirty || !c->entries[i].offset) {
|
98 |
return 0; |
99 |
} |
100 |
|
101 |
trace_qcow2_cache_entry_flush(qemu_coroutine_self(), |
102 |
c == s->l2_table_cache, i); |
103 |
|
104 |
if (c->depends) {
|
105 |
ret = qcow2_cache_flush_dependency(bs, c); |
106 |
} else if (c->depends_on_flush) { |
107 |
ret = bdrv_flush(bs->file); |
108 |
if (ret >= 0) { |
109 |
c->depends_on_flush = false;
|
110 |
} |
111 |
} |
112 |
|
113 |
if (ret < 0) { |
114 |
return ret;
|
115 |
} |
116 |
|
117 |
if (c == s->refcount_block_cache) {
|
118 |
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_UPDATE_PART); |
119 |
} else if (c == s->l2_table_cache) { |
120 |
BLKDBG_EVENT(bs->file, BLKDBG_L2_UPDATE); |
121 |
} |
122 |
|
123 |
ret = bdrv_pwrite(bs->file, c->entries[i].offset, c->entries[i].table, |
124 |
s->cluster_size); |
125 |
if (ret < 0) { |
126 |
return ret;
|
127 |
} |
128 |
|
129 |
c->entries[i].dirty = false;
|
130 |
|
131 |
return 0; |
132 |
} |
133 |
|
134 |
int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c)
|
135 |
{ |
136 |
BDRVQcowState *s = bs->opaque; |
137 |
int result = 0; |
138 |
int ret;
|
139 |
int i;
|
140 |
|
141 |
trace_qcow2_cache_flush(qemu_coroutine_self(), c == s->l2_table_cache); |
142 |
|
143 |
for (i = 0; i < c->size; i++) { |
144 |
ret = qcow2_cache_entry_flush(bs, c, i); |
145 |
if (ret < 0 && result != -ENOSPC) { |
146 |
result = ret; |
147 |
} |
148 |
} |
149 |
|
150 |
if (result == 0) { |
151 |
ret = bdrv_flush(bs->file); |
152 |
if (ret < 0) { |
153 |
result = ret; |
154 |
} |
155 |
} |
156 |
|
157 |
return result;
|
158 |
} |
159 |
|
160 |
int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c,
|
161 |
Qcow2Cache *dependency) |
162 |
{ |
163 |
int ret;
|
164 |
|
165 |
if (dependency->depends) {
|
166 |
ret = qcow2_cache_flush_dependency(bs, dependency); |
167 |
if (ret < 0) { |
168 |
return ret;
|
169 |
} |
170 |
} |
171 |
|
172 |
if (c->depends && (c->depends != dependency)) {
|
173 |
ret = qcow2_cache_flush_dependency(bs, c); |
174 |
if (ret < 0) { |
175 |
return ret;
|
176 |
} |
177 |
} |
178 |
|
179 |
c->depends = dependency; |
180 |
return 0; |
181 |
} |
182 |
|
183 |
void qcow2_cache_depends_on_flush(Qcow2Cache *c)
|
184 |
{ |
185 |
c->depends_on_flush = true;
|
186 |
} |
187 |
|
188 |
static int qcow2_cache_find_entry_to_replace(Qcow2Cache *c) |
189 |
{ |
190 |
int i;
|
191 |
int min_count = INT_MAX;
|
192 |
int min_index = -1; |
193 |
|
194 |
|
195 |
for (i = 0; i < c->size; i++) { |
196 |
if (c->entries[i].ref) {
|
197 |
continue;
|
198 |
} |
199 |
|
200 |
if (c->entries[i].cache_hits < min_count) {
|
201 |
min_index = i; |
202 |
min_count = c->entries[i].cache_hits; |
203 |
} |
204 |
|
205 |
/* Give newer hits priority */
|
206 |
/* TODO Check how to optimize the replacement strategy */
|
207 |
c->entries[i].cache_hits /= 2;
|
208 |
} |
209 |
|
210 |
if (min_index == -1) { |
211 |
/* This can't happen in current synchronous code, but leave the check
|
212 |
* here as a reminder for whoever starts using AIO with the cache */
|
213 |
abort(); |
214 |
} |
215 |
return min_index;
|
216 |
} |
217 |
|
218 |
static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c, |
219 |
uint64_t offset, void **table, bool read_from_disk) |
220 |
{ |
221 |
BDRVQcowState *s = bs->opaque; |
222 |
int i;
|
223 |
int ret;
|
224 |
|
225 |
trace_qcow2_cache_get(qemu_coroutine_self(), c == s->l2_table_cache, |
226 |
offset, read_from_disk); |
227 |
|
228 |
/* Check if the table is already cached */
|
229 |
for (i = 0; i < c->size; i++) { |
230 |
if (c->entries[i].offset == offset) {
|
231 |
goto found;
|
232 |
} |
233 |
} |
234 |
|
235 |
/* If not, write a table back and replace it */
|
236 |
i = qcow2_cache_find_entry_to_replace(c); |
237 |
trace_qcow2_cache_get_replace_entry(qemu_coroutine_self(), |
238 |
c == s->l2_table_cache, i); |
239 |
if (i < 0) { |
240 |
return i;
|
241 |
} |
242 |
|
243 |
ret = qcow2_cache_entry_flush(bs, c, i); |
244 |
if (ret < 0) { |
245 |
return ret;
|
246 |
} |
247 |
|
248 |
trace_qcow2_cache_get_read(qemu_coroutine_self(), |
249 |
c == s->l2_table_cache, i); |
250 |
c->entries[i].offset = 0;
|
251 |
if (read_from_disk) {
|
252 |
if (c == s->l2_table_cache) {
|
253 |
BLKDBG_EVENT(bs->file, BLKDBG_L2_LOAD); |
254 |
} |
255 |
|
256 |
ret = bdrv_pread(bs->file, offset, c->entries[i].table, s->cluster_size); |
257 |
if (ret < 0) { |
258 |
return ret;
|
259 |
} |
260 |
} |
261 |
|
262 |
/* Give the table some hits for the start so that it won't be replaced
|
263 |
* immediately. The number 32 is completely arbitrary. */
|
264 |
c->entries[i].cache_hits = 32;
|
265 |
c->entries[i].offset = offset; |
266 |
|
267 |
/* And return the right table */
|
268 |
found:
|
269 |
c->entries[i].cache_hits++; |
270 |
c->entries[i].ref++; |
271 |
*table = c->entries[i].table; |
272 |
|
273 |
trace_qcow2_cache_get_done(qemu_coroutine_self(), |
274 |
c == s->l2_table_cache, i); |
275 |
|
276 |
return 0; |
277 |
} |
278 |
|
279 |
int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
280 |
void **table)
|
281 |
{ |
282 |
return qcow2_cache_do_get(bs, c, offset, table, true); |
283 |
} |
284 |
|
285 |
int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
286 |
void **table)
|
287 |
{ |
288 |
return qcow2_cache_do_get(bs, c, offset, table, false); |
289 |
} |
290 |
|
291 |
int qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table) |
292 |
{ |
293 |
int i;
|
294 |
|
295 |
for (i = 0; i < c->size; i++) { |
296 |
if (c->entries[i].table == *table) {
|
297 |
goto found;
|
298 |
} |
299 |
} |
300 |
return -ENOENT;
|
301 |
|
302 |
found:
|
303 |
c->entries[i].ref--; |
304 |
*table = NULL;
|
305 |
|
306 |
assert(c->entries[i].ref >= 0);
|
307 |
return 0; |
308 |
} |
309 |
|
310 |
void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table) |
311 |
{ |
312 |
int i;
|
313 |
|
314 |
for (i = 0; i < c->size; i++) { |
315 |
if (c->entries[i].table == table) {
|
316 |
goto found;
|
317 |
} |
318 |
} |
319 |
abort(); |
320 |
|
321 |
found:
|
322 |
c->entries[i].dirty = true;
|
323 |
} |