]>
Commit | Line | Data |
---|---|---|
41abca8c SH |
1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
2 | /* | |
3 | * Block I/O plugging | |
4 | * | |
5 | * Copyright Red Hat. | |
6 | * | |
7 | * This API defers a function call within a blk_io_plug()/blk_io_unplug() | |
8 | * section, allowing multiple calls to batch up. This is a performance | |
9 | * optimization that is used in the block layer to submit several I/O requests | |
10 | * at once instead of individually: | |
11 | * | |
12 | * blk_io_plug(); <-- start of plugged region | |
13 | * ... | |
14 | * blk_io_plug_call(my_func, my_obj); <-- deferred my_func(my_obj) call | |
15 | * blk_io_plug_call(my_func, my_obj); <-- another | |
16 | * blk_io_plug_call(my_func, my_obj); <-- another | |
17 | * ... | |
18 | * blk_io_unplug(); <-- end of plugged region, my_func(my_obj) is called once | |
19 | * | |
20 | * This code is actually generic and not tied to the block layer. If another | |
21 | * subsystem needs this functionality, it could be renamed. | |
22 | */ | |
23 | ||
24 | #include "qemu/osdep.h" | |
25 | #include "qemu/coroutine-tls.h" | |
26 | #include "qemu/notify.h" | |
27 | #include "qemu/thread.h" | |
28 | #include "sysemu/block-backend.h" | |
29 | ||
30 | /* A function call that has been deferred until unplug() */ | |
31 | typedef struct { | |
32 | void (*fn)(void *); | |
33 | void *opaque; | |
34 | } UnplugFn; | |
35 | ||
36 | /* Per-thread state */ | |
37 | typedef struct { | |
38 | unsigned count; /* how many times has plug() been called? */ | |
39 | GArray *unplug_fns; /* functions to call at unplug time */ | |
40 | } Plug; | |
41 | ||
42 | /* Use get_ptr_plug() to fetch this thread-local value */ | |
43 | QEMU_DEFINE_STATIC_CO_TLS(Plug, plug); | |
44 | ||
45 | /* Called at thread cleanup time */ | |
46 | static void blk_io_plug_atexit(Notifier *n, void *value) | |
47 | { | |
48 | Plug *plug = get_ptr_plug(); | |
49 | g_array_free(plug->unplug_fns, TRUE); | |
50 | } | |
51 | ||
52 | /* This won't involve coroutines, so use __thread */ | |
53 | static __thread Notifier blk_io_plug_atexit_notifier; | |
54 | ||
55 | /** | |
56 | * blk_io_plug_call: | |
57 | * @fn: a function pointer to be invoked | |
58 | * @opaque: a user-defined argument to @fn() | |
59 | * | |
60 | * Call @fn(@opaque) immediately if not within a blk_io_plug()/blk_io_unplug() | |
61 | * section. | |
62 | * | |
63 | * Otherwise defer the call until the end of the outermost | |
64 | * blk_io_plug()/blk_io_unplug() section in this thread. If the same | |
65 | * @fn/@opaque pair has already been deferred, it will only be called once upon | |
66 | * blk_io_unplug() so that accumulated calls are batched into a single call. | |
67 | * | |
68 | * The caller must ensure that @opaque is not freed before @fn() is invoked. | |
69 | */ | |
70 | void blk_io_plug_call(void (*fn)(void *), void *opaque) | |
71 | { | |
72 | Plug *plug = get_ptr_plug(); | |
73 | ||
74 | /* Call immediately if we're not plugged */ | |
75 | if (plug->count == 0) { | |
76 | fn(opaque); | |
77 | return; | |
78 | } | |
79 | ||
80 | GArray *array = plug->unplug_fns; | |
81 | if (!array) { | |
82 | array = g_array_new(FALSE, FALSE, sizeof(UnplugFn)); | |
83 | plug->unplug_fns = array; | |
84 | blk_io_plug_atexit_notifier.notify = blk_io_plug_atexit; | |
85 | qemu_thread_atexit_add(&blk_io_plug_atexit_notifier); | |
86 | } | |
87 | ||
88 | UnplugFn *fns = (UnplugFn *)array->data; | |
89 | UnplugFn new_fn = { | |
90 | .fn = fn, | |
91 | .opaque = opaque, | |
92 | }; | |
93 | ||
94 | /* | |
95 | * There won't be many, so do a linear search. If this becomes a bottleneck | |
96 | * then a binary search (glib 2.62+) or different data structure could be | |
97 | * used. | |
98 | */ | |
99 | for (guint i = 0; i < array->len; i++) { | |
100 | if (memcmp(&fns[i], &new_fn, sizeof(new_fn)) == 0) { | |
101 | return; /* already exists */ | |
102 | } | |
103 | } | |
104 | ||
105 | g_array_append_val(array, new_fn); | |
106 | } | |
107 | ||
108 | /** | |
109 | * blk_io_plug: Defer blk_io_plug_call() functions until blk_io_unplug() | |
110 | * | |
111 | * blk_io_plug/unplug are thread-local operations. This means that multiple | |
112 | * threads can simultaneously call plug/unplug, but the caller must ensure that | |
113 | * each unplug() is called in the same thread of the matching plug(). | |
114 | * | |
115 | * Nesting is supported. blk_io_plug_call() functions are only called at the | |
116 | * outermost blk_io_unplug(). | |
117 | */ | |
118 | void blk_io_plug(void) | |
119 | { | |
120 | Plug *plug = get_ptr_plug(); | |
121 | ||
122 | assert(plug->count < UINT32_MAX); | |
123 | ||
124 | plug->count++; | |
125 | } | |
126 | ||
127 | /** | |
128 | * blk_io_unplug: Run any pending blk_io_plug_call() functions | |
129 | * | |
130 | * There must have been a matching blk_io_plug() call in the same thread prior | |
131 | * to this blk_io_unplug() call. | |
132 | */ | |
133 | void blk_io_unplug(void) | |
134 | { | |
135 | Plug *plug = get_ptr_plug(); | |
136 | ||
137 | assert(plug->count > 0); | |
138 | ||
139 | if (--plug->count > 0) { | |
140 | return; | |
141 | } | |
142 | ||
143 | GArray *array = plug->unplug_fns; | |
144 | if (!array) { | |
145 | return; | |
146 | } | |
147 | ||
148 | UnplugFn *fns = (UnplugFn *)array->data; | |
149 | ||
150 | for (guint i = 0; i < array->len; i++) { | |
151 | fns[i].fn(fns[i].opaque); | |
152 | } | |
153 | ||
154 | /* | |
155 | * This resets the array without freeing memory so that appending is cheap | |
156 | * in the future. | |
157 | */ | |
158 | g_array_set_size(array, 0); | |
159 | } |