]>
Commit | Line | Data |
---|---|---|
1c79356b | 1 | /* |
c910b4d9 | 2 | * Copyright (c) 1993-1995, 1999-2008 Apple Inc. All rights reserved. |
1c79356b | 3 | * |
2d21ac55 | 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
1c79356b | 5 | * |
2d21ac55 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
8f6c56a5 | 14 | * |
2d21ac55 A |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
8f6c56a5 A |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
2d21ac55 A |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
8f6c56a5 | 25 | * |
2d21ac55 | 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
1c79356b | 27 | */ |
1c79356b A |
28 | |
29 | #include <mach/mach_types.h> | |
91447636 | 30 | #include <mach/thread_act.h> |
1c79356b | 31 | |
91447636 | 32 | #include <kern/kern_types.h> |
c910b4d9 | 33 | #include <kern/zalloc.h> |
1c79356b A |
34 | #include <kern/sched_prim.h> |
35 | #include <kern/clock.h> | |
36 | #include <kern/task.h> | |
37 | #include <kern/thread.h> | |
91447636 | 38 | #include <kern/wait_queue.h> |
39236c6e | 39 | #include <kern/ledger.h> |
91447636 A |
40 | |
41 | #include <vm/vm_pageout.h> | |
1c79356b A |
42 | |
43 | #include <kern/thread_call.h> | |
44 | #include <kern/call_entry.h> | |
1c79356b A |
45 | #include <kern/timer_call.h> |
46 | ||
316670eb | 47 | #include <libkern/OSAtomic.h> |
39236c6e | 48 | #include <kern/timer_queue.h> |
316670eb | 49 | |
55e303ae | 50 | #include <sys/kdebug.h> |
4b17d6b6 A |
51 | #if CONFIG_DTRACE |
52 | #include <mach/sdt.h> | |
53 | #endif | |
39236c6e | 54 | #include <machine/machine_routines.h> |
1c79356b | 55 | |
316670eb A |
56 | static zone_t thread_call_zone; |
57 | static struct wait_queue daemon_wqueue; | |
1c79356b | 58 | |
c910b4d9 A |
59 | struct thread_call_group { |
60 | queue_head_t pending_queue; | |
6d2010ae | 61 | uint32_t pending_count; |
1c79356b | 62 | |
c910b4d9 | 63 | queue_head_t delayed_queue; |
316670eb | 64 | uint32_t delayed_count; |
1c79356b | 65 | |
c910b4d9 | 66 | timer_call_data_t delayed_timer; |
316670eb | 67 | timer_call_data_t dealloc_timer; |
1c79356b | 68 | |
c910b4d9 | 69 | struct wait_queue idle_wqueue; |
6d2010ae | 70 | uint32_t idle_count, active_count; |
1c79356b | 71 | |
316670eb A |
72 | integer_t pri; |
73 | uint32_t target_thread_count; | |
74 | uint64_t idle_timestamp; | |
c910b4d9 | 75 | |
316670eb A |
76 | uint32_t flags; |
77 | sched_call_t sched_call; | |
78 | }; | |
c910b4d9 | 79 | |
316670eb | 80 | typedef struct thread_call_group *thread_call_group_t; |
c910b4d9 | 81 | |
316670eb A |
82 | #define TCG_PARALLEL 0x01 |
83 | #define TCG_DEALLOC_ACTIVE 0x02 | |
84 | ||
85 | #define THREAD_CALL_GROUP_COUNT 4 | |
86 | #define THREAD_CALL_THREAD_MIN 4 | |
87 | #define INTERNAL_CALL_COUNT 768 | |
88 | #define THREAD_CALL_DEALLOC_INTERVAL_NS (5 * 1000 * 1000) /* 5 ms */ | |
89 | #define THREAD_CALL_ADD_RATIO 4 | |
90 | #define THREAD_CALL_MACH_FACTOR_CAP 3 | |
91 | ||
92 | static struct thread_call_group thread_call_groups[THREAD_CALL_GROUP_COUNT]; | |
93 | static boolean_t thread_call_daemon_awake; | |
94 | static thread_call_data_t internal_call_storage[INTERNAL_CALL_COUNT]; | |
95 | static queue_head_t thread_call_internal_queue; | |
39236c6e | 96 | int thread_call_internal_queue_count = 0; |
316670eb A |
97 | static uint64_t thread_call_dealloc_interval_abs; |
98 | ||
39236c6e | 99 | static __inline__ thread_call_t _internal_call_allocate(thread_call_func_t func, thread_call_param_t param0); |
316670eb A |
100 | static __inline__ void _internal_call_release(thread_call_t call); |
101 | static __inline__ boolean_t _pending_call_enqueue(thread_call_t call, thread_call_group_t group); | |
102 | static __inline__ boolean_t _delayed_call_enqueue(thread_call_t call, thread_call_group_t group, uint64_t deadline); | |
103 | static __inline__ boolean_t _call_dequeue(thread_call_t call, thread_call_group_t group); | |
104 | static __inline__ void thread_call_wake(thread_call_group_t group); | |
105 | static __inline__ void _set_delayed_call_timer(thread_call_t call, thread_call_group_t group); | |
106 | static boolean_t _remove_from_pending_queue(thread_call_func_t func, thread_call_param_t param0, boolean_t remove_all); | |
107 | static boolean_t _remove_from_delayed_queue(thread_call_func_t func, thread_call_param_t param0, boolean_t remove_all); | |
108 | static void thread_call_daemon(void *arg); | |
109 | static void thread_call_thread(thread_call_group_t group, wait_result_t wres); | |
110 | extern void thread_call_delayed_timer(timer_call_param_t p0, timer_call_param_t p1); | |
111 | static void thread_call_dealloc_timer(timer_call_param_t p0, timer_call_param_t p1); | |
112 | static void thread_call_group_setup(thread_call_group_t group, thread_call_priority_t pri, uint32_t target_thread_count, boolean_t parallel); | |
113 | static void sched_call_thread(int type, thread_t thread); | |
114 | static void thread_call_start_deallocate_timer(thread_call_group_t group); | |
115 | static void thread_call_wait_locked(thread_call_t call); | |
39236c6e A |
116 | static boolean_t thread_call_enter_delayed_internal(thread_call_t call, |
117 | thread_call_func_t alt_func, thread_call_param_t alt_param0, | |
118 | thread_call_param_t param1, uint64_t deadline, | |
119 | uint64_t leeway, unsigned int flags); | |
1c79356b A |
120 | |
121 | #define qe(x) ((queue_entry_t)(x)) | |
122 | #define TC(x) ((thread_call_t)(x)) | |
123 | ||
6d2010ae A |
124 | |
125 | lck_grp_t thread_call_queues_lck_grp; | |
126 | lck_grp_t thread_call_lck_grp; | |
127 | lck_attr_t thread_call_lck_attr; | |
128 | lck_grp_attr_t thread_call_lck_grp_attr; | |
129 | ||
130 | #if defined(__i386__) || defined(__x86_64__) | |
131 | lck_mtx_t thread_call_lock_data; | |
132 | #else | |
133 | lck_spin_t thread_call_lock_data; | |
134 | #endif | |
135 | ||
316670eb | 136 | |
6d2010ae A |
137 | #define thread_call_lock_spin() \ |
138 | lck_mtx_lock_spin_always(&thread_call_lock_data) | |
139 | ||
140 | #define thread_call_unlock() \ | |
141 | lck_mtx_unlock_always(&thread_call_lock_data) | |
142 | ||
39236c6e | 143 | extern boolean_t mach_timer_coalescing_enabled; |
6d2010ae | 144 | |
316670eb A |
145 | static inline spl_t |
146 | disable_ints_and_lock(void) | |
147 | { | |
148 | spl_t s; | |
149 | ||
150 | s = splsched(); | |
151 | thread_call_lock_spin(); | |
152 | ||
153 | return s; | |
154 | } | |
155 | ||
156 | static inline void | |
fe8ab488 | 157 | enable_ints_and_unlock(spl_t s) |
316670eb A |
158 | { |
159 | thread_call_unlock(); | |
fe8ab488 | 160 | splx(s); |
316670eb A |
161 | } |
162 | ||
163 | ||
164 | static inline boolean_t | |
165 | group_isparallel(thread_call_group_t group) | |
166 | { | |
167 | return ((group->flags & TCG_PARALLEL) != 0); | |
168 | } | |
169 | ||
170 | static boolean_t | |
171 | thread_call_group_should_add_thread(thread_call_group_t group) | |
172 | { | |
173 | uint32_t thread_count; | |
174 | ||
175 | if (!group_isparallel(group)) { | |
176 | if (group->pending_count > 0 && group->active_count == 0) { | |
177 | return TRUE; | |
178 | } | |
179 | ||
180 | return FALSE; | |
181 | } | |
182 | ||
183 | if (group->pending_count > 0) { | |
184 | if (group->idle_count > 0) { | |
185 | panic("Pending work, but threads are idle?"); | |
186 | } | |
187 | ||
188 | thread_count = group->active_count; | |
189 | ||
190 | /* | |
191 | * Add a thread if either there are no threads, | |
192 | * the group has fewer than its target number of | |
193 | * threads, or the amount of work is large relative | |
194 | * to the number of threads. In the last case, pay attention | |
195 | * to the total load on the system, and back off if | |
196 | * it's high. | |
197 | */ | |
198 | if ((thread_count == 0) || | |
199 | (thread_count < group->target_thread_count) || | |
200 | ((group->pending_count > THREAD_CALL_ADD_RATIO * thread_count) && | |
201 | (sched_mach_factor < THREAD_CALL_MACH_FACTOR_CAP))) { | |
202 | return TRUE; | |
203 | } | |
204 | } | |
205 | ||
206 | return FALSE; | |
207 | } | |
208 | ||
209 | static inline integer_t | |
210 | thread_call_priority_to_sched_pri(thread_call_priority_t pri) | |
211 | { | |
212 | switch (pri) { | |
213 | case THREAD_CALL_PRIORITY_HIGH: | |
214 | return BASEPRI_PREEMPT; | |
215 | case THREAD_CALL_PRIORITY_KERNEL: | |
216 | return BASEPRI_KERNEL; | |
217 | case THREAD_CALL_PRIORITY_USER: | |
218 | return BASEPRI_DEFAULT; | |
219 | case THREAD_CALL_PRIORITY_LOW: | |
39236c6e | 220 | return MAXPRI_THROTTLE; |
316670eb A |
221 | default: |
222 | panic("Invalid priority."); | |
223 | } | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | /* Lock held */ | |
229 | static inline thread_call_group_t | |
230 | thread_call_get_group( | |
231 | thread_call_t call) | |
232 | { | |
233 | thread_call_priority_t pri = call->tc_pri; | |
234 | ||
235 | assert(pri == THREAD_CALL_PRIORITY_LOW || | |
236 | pri == THREAD_CALL_PRIORITY_USER || | |
237 | pri == THREAD_CALL_PRIORITY_KERNEL || | |
238 | pri == THREAD_CALL_PRIORITY_HIGH); | |
239 | ||
240 | return &thread_call_groups[pri]; | |
241 | } | |
242 | ||
243 | static void | |
244 | thread_call_group_setup( | |
245 | thread_call_group_t group, | |
246 | thread_call_priority_t pri, | |
247 | uint32_t target_thread_count, | |
248 | boolean_t parallel) | |
249 | { | |
250 | queue_init(&group->pending_queue); | |
251 | queue_init(&group->delayed_queue); | |
252 | ||
253 | timer_call_setup(&group->delayed_timer, thread_call_delayed_timer, group); | |
254 | timer_call_setup(&group->dealloc_timer, thread_call_dealloc_timer, group); | |
255 | ||
256 | wait_queue_init(&group->idle_wqueue, SYNC_POLICY_FIFO); | |
257 | ||
258 | group->target_thread_count = target_thread_count; | |
259 | group->pri = thread_call_priority_to_sched_pri(pri); | |
260 | ||
261 | group->sched_call = sched_call_thread; | |
262 | if (parallel) { | |
263 | group->flags |= TCG_PARALLEL; | |
264 | group->sched_call = NULL; | |
265 | } | |
266 | } | |
267 | ||
268 | /* | |
269 | * Simple wrapper for creating threads bound to | |
270 | * thread call groups. | |
271 | */ | |
272 | static kern_return_t | |
273 | thread_call_thread_create( | |
274 | thread_call_group_t group) | |
275 | { | |
276 | thread_t thread; | |
277 | kern_return_t result; | |
278 | ||
279 | result = kernel_thread_start_priority((thread_continue_t)thread_call_thread, group, group->pri, &thread); | |
280 | if (result != KERN_SUCCESS) { | |
281 | return result; | |
282 | } | |
283 | ||
284 | if (group->pri < BASEPRI_PREEMPT) { | |
285 | /* | |
286 | * New style doesn't get to run to completion in | |
287 | * kernel if there are higher priority threads | |
288 | * available. | |
289 | */ | |
290 | thread_set_eager_preempt(thread); | |
291 | } | |
292 | ||
293 | thread_deallocate(thread); | |
294 | return KERN_SUCCESS; | |
295 | } | |
296 | ||
1c79356b | 297 | /* |
c910b4d9 | 298 | * thread_call_initialize: |
1c79356b | 299 | * |
c910b4d9 A |
300 | * Initialize this module, called |
301 | * early during system initialization. | |
1c79356b | 302 | */ |
1c79356b A |
303 | void |
304 | thread_call_initialize(void) | |
305 | { | |
6d2010ae | 306 | thread_call_t call; |
c910b4d9 | 307 | kern_return_t result; |
316670eb A |
308 | thread_t thread; |
309 | int i; | |
fe8ab488 | 310 | spl_t s; |
c910b4d9 A |
311 | |
312 | i = sizeof (thread_call_data_t); | |
313 | thread_call_zone = zinit(i, 4096 * i, 16 * i, "thread_call"); | |
6d2010ae | 314 | zone_change(thread_call_zone, Z_CALLERACCT, FALSE); |
0b4c1975 | 315 | zone_change(thread_call_zone, Z_NOENCRYPT, TRUE); |
1c79356b | 316 | |
6d2010ae A |
317 | lck_attr_setdefault(&thread_call_lck_attr); |
318 | lck_grp_attr_setdefault(&thread_call_lck_grp_attr); | |
319 | lck_grp_init(&thread_call_queues_lck_grp, "thread_call_queues", &thread_call_lck_grp_attr); | |
320 | lck_grp_init(&thread_call_lck_grp, "thread_call", &thread_call_lck_grp_attr); | |
1c79356b | 321 | |
6d2010ae A |
322 | #if defined(__i386__) || defined(__x86_64__) |
323 | lck_mtx_init(&thread_call_lock_data, &thread_call_lck_grp, &thread_call_lck_attr); | |
324 | #else | |
325 | lck_spin_init(&thread_call_lock_data, &thread_call_lck_grp, &thread_call_lck_attr); | |
326 | #endif | |
1c79356b | 327 | |
316670eb A |
328 | nanotime_to_absolutetime(0, THREAD_CALL_DEALLOC_INTERVAL_NS, &thread_call_dealloc_interval_abs); |
329 | wait_queue_init(&daemon_wqueue, SYNC_POLICY_FIFO); | |
c910b4d9 | 330 | |
316670eb A |
331 | thread_call_group_setup(&thread_call_groups[THREAD_CALL_PRIORITY_LOW], THREAD_CALL_PRIORITY_LOW, 0, TRUE); |
332 | thread_call_group_setup(&thread_call_groups[THREAD_CALL_PRIORITY_USER], THREAD_CALL_PRIORITY_USER, 0, TRUE); | |
333 | thread_call_group_setup(&thread_call_groups[THREAD_CALL_PRIORITY_KERNEL], THREAD_CALL_PRIORITY_KERNEL, 1, TRUE); | |
334 | thread_call_group_setup(&thread_call_groups[THREAD_CALL_PRIORITY_HIGH], THREAD_CALL_PRIORITY_HIGH, THREAD_CALL_THREAD_MIN, FALSE); | |
1c79356b | 335 | |
fe8ab488 | 336 | s = disable_ints_and_lock(); |
c910b4d9 | 337 | |
6d2010ae A |
338 | queue_init(&thread_call_internal_queue); |
339 | for ( | |
316670eb A |
340 | call = internal_call_storage; |
341 | call < &internal_call_storage[INTERNAL_CALL_COUNT]; | |
1c79356b A |
342 | call++) { |
343 | ||
c910b4d9 | 344 | enqueue_tail(&thread_call_internal_queue, qe(call)); |
39236c6e | 345 | thread_call_internal_queue_count++; |
6d2010ae | 346 | } |
1c79356b | 347 | |
c910b4d9 | 348 | thread_call_daemon_awake = TRUE; |
1c79356b | 349 | |
fe8ab488 | 350 | enable_ints_and_unlock(s); |
1c79356b | 351 | |
316670eb | 352 | result = kernel_thread_start_priority((thread_continue_t)thread_call_daemon, NULL, BASEPRI_PREEMPT + 1, &thread); |
91447636 A |
353 | if (result != KERN_SUCCESS) |
354 | panic("thread_call_initialize"); | |
355 | ||
356 | thread_deallocate(thread); | |
1c79356b A |
357 | } |
358 | ||
359 | void | |
360 | thread_call_setup( | |
361 | thread_call_t call, | |
362 | thread_call_func_t func, | |
c910b4d9 | 363 | thread_call_param_t param0) |
1c79356b | 364 | { |
316670eb A |
365 | bzero(call, sizeof(*call)); |
366 | call_entry_setup((call_entry_t)call, func, param0); | |
367 | call->tc_pri = THREAD_CALL_PRIORITY_HIGH; /* Default priority */ | |
1c79356b A |
368 | } |
369 | ||
370 | /* | |
c910b4d9 | 371 | * _internal_call_allocate: |
1c79356b | 372 | * |
c910b4d9 | 373 | * Allocate an internal callout entry. |
1c79356b | 374 | * |
c910b4d9 | 375 | * Called with thread_call_lock held. |
1c79356b | 376 | */ |
1c79356b | 377 | static __inline__ thread_call_t |
39236c6e | 378 | _internal_call_allocate(thread_call_func_t func, thread_call_param_t param0) |
1c79356b A |
379 | { |
380 | thread_call_t call; | |
381 | ||
c910b4d9 | 382 | if (queue_empty(&thread_call_internal_queue)) |
1c79356b A |
383 | panic("_internal_call_allocate"); |
384 | ||
c910b4d9 | 385 | call = TC(dequeue_head(&thread_call_internal_queue)); |
39236c6e A |
386 | thread_call_internal_queue_count--; |
387 | ||
388 | thread_call_setup(call, func, param0); | |
389 | call->tc_refs = 0; | |
390 | call->tc_flags = 0; /* THREAD_CALL_ALLOC not set, do not free back to zone */ | |
391 | ||
1c79356b A |
392 | return (call); |
393 | } | |
394 | ||
395 | /* | |
c910b4d9 | 396 | * _internal_call_release: |
1c79356b | 397 | * |
c910b4d9 | 398 | * Release an internal callout entry which |
39236c6e A |
399 | * is no longer pending (or delayed). This is |
400 | * safe to call on a non-internal entry, in which | |
401 | * case nothing happens. | |
1c79356b | 402 | * |
c910b4d9 | 403 | * Called with thread_call_lock held. |
1c79356b | 404 | */ |
c910b4d9 | 405 | static __inline__ void |
1c79356b | 406 | _internal_call_release( |
c910b4d9 | 407 | thread_call_t call) |
1c79356b A |
408 | { |
409 | if ( call >= internal_call_storage && | |
39236c6e A |
410 | call < &internal_call_storage[INTERNAL_CALL_COUNT] ) { |
411 | assert((call->tc_flags & THREAD_CALL_ALLOC) == 0); | |
c910b4d9 | 412 | enqueue_head(&thread_call_internal_queue, qe(call)); |
39236c6e A |
413 | thread_call_internal_queue_count++; |
414 | } | |
1c79356b A |
415 | } |
416 | ||
417 | /* | |
c910b4d9 | 418 | * _pending_call_enqueue: |
1c79356b | 419 | * |
c910b4d9 A |
420 | * Place an entry at the end of the |
421 | * pending queue, to be executed soon. | |
1c79356b | 422 | * |
c910b4d9 A |
423 | * Returns TRUE if the entry was already |
424 | * on a queue. | |
1c79356b | 425 | * |
c910b4d9 | 426 | * Called with thread_call_lock held. |
1c79356b | 427 | */ |
c910b4d9 | 428 | static __inline__ boolean_t |
1c79356b | 429 | _pending_call_enqueue( |
c910b4d9 A |
430 | thread_call_t call, |
431 | thread_call_group_t group) | |
1c79356b | 432 | { |
6d2010ae | 433 | queue_head_t *old_queue; |
1c79356b | 434 | |
316670eb A |
435 | old_queue = call_entry_enqueue_tail(CE(call), &group->pending_queue); |
436 | ||
437 | if (old_queue == NULL) { | |
438 | call->tc_submit_count++; | |
439 | } | |
1c79356b | 440 | |
c910b4d9 | 441 | group->pending_count++; |
1c79356b | 442 | |
316670eb A |
443 | thread_call_wake(group); |
444 | ||
c910b4d9 | 445 | return (old_queue != NULL); |
1c79356b A |
446 | } |
447 | ||
448 | /* | |
c910b4d9 | 449 | * _delayed_call_enqueue: |
1c79356b | 450 | * |
c910b4d9 A |
451 | * Place an entry on the delayed queue, |
452 | * after existing entries with an earlier | |
453 | * (or identical) deadline. | |
1c79356b | 454 | * |
c910b4d9 A |
455 | * Returns TRUE if the entry was already |
456 | * on a queue. | |
1c79356b | 457 | * |
c910b4d9 | 458 | * Called with thread_call_lock held. |
1c79356b | 459 | */ |
c910b4d9 | 460 | static __inline__ boolean_t |
1c79356b | 461 | _delayed_call_enqueue( |
316670eb | 462 | thread_call_t call, |
c910b4d9 | 463 | thread_call_group_t group, |
6d2010ae | 464 | uint64_t deadline) |
1c79356b | 465 | { |
6d2010ae | 466 | queue_head_t *old_queue; |
1c79356b | 467 | |
316670eb | 468 | old_queue = call_entry_enqueue_deadline(CE(call), &group->delayed_queue, deadline); |
c910b4d9 A |
469 | |
470 | if (old_queue == &group->pending_queue) | |
471 | group->pending_count--; | |
316670eb A |
472 | else if (old_queue == NULL) |
473 | call->tc_submit_count++; | |
c910b4d9 A |
474 | |
475 | return (old_queue != NULL); | |
1c79356b A |
476 | } |
477 | ||
478 | /* | |
c910b4d9 | 479 | * _call_dequeue: |
1c79356b | 480 | * |
c910b4d9 | 481 | * Remove an entry from a queue. |
1c79356b | 482 | * |
c910b4d9 | 483 | * Returns TRUE if the entry was on a queue. |
1c79356b | 484 | * |
c910b4d9 | 485 | * Called with thread_call_lock held. |
1c79356b | 486 | */ |
c910b4d9 A |
487 | static __inline__ boolean_t |
488 | _call_dequeue( | |
489 | thread_call_t call, | |
490 | thread_call_group_t group) | |
1c79356b | 491 | { |
6d2010ae | 492 | queue_head_t *old_queue; |
c910b4d9 | 493 | |
316670eb | 494 | old_queue = call_entry_dequeue(CE(call)); |
c910b4d9 | 495 | |
316670eb A |
496 | if (old_queue != NULL) { |
497 | call->tc_finish_count++; | |
498 | if (old_queue == &group->pending_queue) | |
499 | group->pending_count--; | |
500 | } | |
c910b4d9 A |
501 | |
502 | return (old_queue != NULL); | |
1c79356b A |
503 | } |
504 | ||
505 | /* | |
c910b4d9 | 506 | * _set_delayed_call_timer: |
1c79356b | 507 | * |
c910b4d9 A |
508 | * Reset the timer so that it |
509 | * next expires when the entry is due. | |
1c79356b | 510 | * |
c910b4d9 | 511 | * Called with thread_call_lock held. |
1c79356b | 512 | */ |
1c79356b A |
513 | static __inline__ void |
514 | _set_delayed_call_timer( | |
c910b4d9 A |
515 | thread_call_t call, |
516 | thread_call_group_t group) | |
1c79356b | 517 | { |
39236c6e A |
518 | uint64_t leeway; |
519 | ||
520 | assert((call->tc_soft_deadline != 0) && ((call->tc_soft_deadline <= call->tc_call.deadline))); | |
521 | ||
522 | leeway = call->tc_call.deadline - call->tc_soft_deadline; | |
523 | timer_call_enter_with_leeway(&group->delayed_timer, NULL, | |
524 | call->tc_soft_deadline, leeway, | |
525 | TIMER_CALL_SYS_CRITICAL|TIMER_CALL_LEEWAY, | |
fe8ab488 | 526 | ((call->tc_flags & THREAD_CALL_RATELIMITED) == THREAD_CALL_RATELIMITED)); |
1c79356b A |
527 | } |
528 | ||
529 | /* | |
c910b4d9 | 530 | * _remove_from_pending_queue: |
1c79356b | 531 | * |
c910b4d9 A |
532 | * Remove the first (or all) matching |
533 | * entries from the pending queue. | |
1c79356b | 534 | * |
c910b4d9 A |
535 | * Returns TRUE if any matching entries |
536 | * were found. | |
1c79356b | 537 | * |
c910b4d9 | 538 | * Called with thread_call_lock held. |
1c79356b | 539 | */ |
c910b4d9 | 540 | static boolean_t |
1c79356b A |
541 | _remove_from_pending_queue( |
542 | thread_call_func_t func, | |
543 | thread_call_param_t param0, | |
c910b4d9 | 544 | boolean_t remove_all) |
1c79356b | 545 | { |
316670eb | 546 | boolean_t call_removed = FALSE; |
c910b4d9 | 547 | thread_call_t call; |
316670eb A |
548 | thread_call_group_t group = &thread_call_groups[THREAD_CALL_PRIORITY_HIGH]; |
549 | ||
550 | call = TC(queue_first(&group->pending_queue)); | |
551 | ||
552 | while (!queue_end(&group->pending_queue, qe(call))) { | |
553 | if (call->tc_call.func == func && | |
554 | call->tc_call.param0 == param0) { | |
1c79356b | 555 | thread_call_t next = TC(queue_next(qe(call))); |
316670eb | 556 | |
c910b4d9 | 557 | _call_dequeue(call, group); |
1c79356b A |
558 | |
559 | _internal_call_release(call); | |
316670eb | 560 | |
1c79356b A |
561 | call_removed = TRUE; |
562 | if (!remove_all) | |
563 | break; | |
316670eb | 564 | |
1c79356b A |
565 | call = next; |
566 | } | |
567 | else | |
568 | call = TC(queue_next(qe(call))); | |
316670eb A |
569 | } |
570 | ||
571 | return (call_removed); | |
1c79356b A |
572 | } |
573 | ||
574 | /* | |
c910b4d9 | 575 | * _remove_from_delayed_queue: |
1c79356b | 576 | * |
c910b4d9 A |
577 | * Remove the first (or all) matching |
578 | * entries from the delayed queue. | |
1c79356b | 579 | * |
c910b4d9 A |
580 | * Returns TRUE if any matching entries |
581 | * were found. | |
1c79356b | 582 | * |
c910b4d9 | 583 | * Called with thread_call_lock held. |
1c79356b | 584 | */ |
c910b4d9 | 585 | static boolean_t |
1c79356b A |
586 | _remove_from_delayed_queue( |
587 | thread_call_func_t func, | |
588 | thread_call_param_t param0, | |
c910b4d9 | 589 | boolean_t remove_all) |
1c79356b | 590 | { |
316670eb A |
591 | boolean_t call_removed = FALSE; |
592 | thread_call_t call; | |
593 | thread_call_group_t group = &thread_call_groups[THREAD_CALL_PRIORITY_HIGH]; | |
594 | ||
595 | call = TC(queue_first(&group->delayed_queue)); | |
596 | ||
597 | while (!queue_end(&group->delayed_queue, qe(call))) { | |
598 | if (call->tc_call.func == func && | |
599 | call->tc_call.param0 == param0) { | |
1c79356b | 600 | thread_call_t next = TC(queue_next(qe(call))); |
316670eb | 601 | |
c910b4d9 | 602 | _call_dequeue(call, group); |
316670eb | 603 | |
1c79356b | 604 | _internal_call_release(call); |
316670eb | 605 | |
1c79356b A |
606 | call_removed = TRUE; |
607 | if (!remove_all) | |
608 | break; | |
316670eb | 609 | |
1c79356b A |
610 | call = next; |
611 | } | |
612 | else | |
613 | call = TC(queue_next(qe(call))); | |
316670eb A |
614 | } |
615 | ||
616 | return (call_removed); | |
1c79356b A |
617 | } |
618 | ||
1c79356b | 619 | /* |
c910b4d9 | 620 | * thread_call_func_delayed: |
1c79356b | 621 | * |
c910b4d9 A |
622 | * Enqueue a function callout to |
623 | * occur at the stated time. | |
1c79356b | 624 | */ |
1c79356b A |
625 | void |
626 | thread_call_func_delayed( | |
316670eb A |
627 | thread_call_func_t func, |
628 | thread_call_param_t param, | |
629 | uint64_t deadline) | |
1c79356b | 630 | { |
39236c6e A |
631 | (void)thread_call_enter_delayed_internal(NULL, func, param, 0, deadline, 0, 0); |
632 | } | |
316670eb | 633 | |
39236c6e A |
634 | /* |
635 | * thread_call_func_delayed_with_leeway: | |
636 | * | |
637 | * Same as thread_call_func_delayed(), but with | |
638 | * leeway/flags threaded through. | |
639 | */ | |
316670eb | 640 | |
39236c6e A |
641 | void |
642 | thread_call_func_delayed_with_leeway( | |
643 | thread_call_func_t func, | |
644 | thread_call_param_t param, | |
645 | uint64_t deadline, | |
646 | uint64_t leeway, | |
647 | uint32_t flags) | |
648 | { | |
649 | (void)thread_call_enter_delayed_internal(NULL, func, param, 0, deadline, leeway, flags); | |
1c79356b A |
650 | } |
651 | ||
652 | /* | |
c910b4d9 | 653 | * thread_call_func_cancel: |
1c79356b | 654 | * |
c910b4d9 | 655 | * Dequeue a function callout. |
1c79356b | 656 | * |
c910b4d9 A |
657 | * Removes one (or all) { function, argument } |
658 | * instance(s) from either (or both) | |
659 | * the pending and the delayed queue, | |
660 | * in that order. | |
1c79356b | 661 | * |
c910b4d9 | 662 | * Returns TRUE if any calls were cancelled. |
1c79356b | 663 | */ |
1c79356b A |
664 | boolean_t |
665 | thread_call_func_cancel( | |
316670eb A |
666 | thread_call_func_t func, |
667 | thread_call_param_t param, | |
668 | boolean_t cancel_all) | |
1c79356b | 669 | { |
316670eb A |
670 | boolean_t result; |
671 | spl_t s; | |
1c79356b | 672 | |
316670eb A |
673 | s = splsched(); |
674 | thread_call_lock_spin(); | |
675 | ||
676 | if (cancel_all) | |
1c79356b | 677 | result = _remove_from_pending_queue(func, param, cancel_all) | |
316670eb | 678 | _remove_from_delayed_queue(func, param, cancel_all); |
1c79356b A |
679 | else |
680 | result = _remove_from_pending_queue(func, param, cancel_all) || | |
316670eb A |
681 | _remove_from_delayed_queue(func, param, cancel_all); |
682 | ||
683 | thread_call_unlock(); | |
684 | splx(s); | |
1c79356b A |
685 | |
686 | return (result); | |
687 | } | |
688 | ||
316670eb A |
689 | /* |
690 | * Allocate a thread call with a given priority. Importances | |
691 | * other than THREAD_CALL_PRIORITY_HIGH will be run in threads | |
692 | * with eager preemption enabled (i.e. may be aggressively preempted | |
693 | * by higher-priority threads which are not in the normal "urgent" bands). | |
694 | */ | |
695 | thread_call_t | |
696 | thread_call_allocate_with_priority( | |
697 | thread_call_func_t func, | |
698 | thread_call_param_t param0, | |
699 | thread_call_priority_t pri) | |
700 | { | |
701 | thread_call_t call; | |
702 | ||
703 | if (pri > THREAD_CALL_PRIORITY_LOW) { | |
704 | panic("Invalid pri: %d\n", pri); | |
705 | } | |
706 | ||
707 | call = thread_call_allocate(func, param0); | |
708 | call->tc_pri = pri; | |
709 | ||
710 | return call; | |
711 | } | |
712 | ||
1c79356b | 713 | /* |
c910b4d9 | 714 | * thread_call_allocate: |
1c79356b | 715 | * |
c910b4d9 | 716 | * Allocate a callout entry. |
1c79356b | 717 | */ |
1c79356b A |
718 | thread_call_t |
719 | thread_call_allocate( | |
316670eb A |
720 | thread_call_func_t func, |
721 | thread_call_param_t param0) | |
1c79356b | 722 | { |
316670eb | 723 | thread_call_t call = zalloc(thread_call_zone); |
c910b4d9 | 724 | |
316670eb A |
725 | thread_call_setup(call, func, param0); |
726 | call->tc_refs = 1; | |
727 | call->tc_flags = THREAD_CALL_ALLOC; | |
c910b4d9 | 728 | |
316670eb | 729 | return (call); |
1c79356b A |
730 | } |
731 | ||
732 | /* | |
c910b4d9 | 733 | * thread_call_free: |
1c79356b | 734 | * |
316670eb A |
735 | * Release a callout. If the callout is currently |
736 | * executing, it will be freed when all invocations | |
737 | * finish. | |
1c79356b | 738 | */ |
1c79356b A |
739 | boolean_t |
740 | thread_call_free( | |
316670eb | 741 | thread_call_t call) |
1c79356b | 742 | { |
316670eb A |
743 | spl_t s; |
744 | int32_t refs; | |
1c79356b | 745 | |
316670eb A |
746 | s = splsched(); |
747 | thread_call_lock_spin(); | |
748 | ||
749 | if (call->tc_call.queue != NULL) { | |
750 | thread_call_unlock(); | |
751 | splx(s); | |
752 | ||
753 | return (FALSE); | |
754 | } | |
755 | ||
756 | refs = --call->tc_refs; | |
757 | if (refs < 0) { | |
758 | panic("Refcount negative: %d\n", refs); | |
759 | } | |
760 | ||
761 | thread_call_unlock(); | |
762 | splx(s); | |
763 | ||
764 | if (refs == 0) { | |
765 | zfree(thread_call_zone, call); | |
766 | } | |
1c79356b A |
767 | |
768 | return (TRUE); | |
769 | } | |
770 | ||
771 | /* | |
c910b4d9 | 772 | * thread_call_enter: |
1c79356b | 773 | * |
c910b4d9 | 774 | * Enqueue a callout entry to occur "soon". |
1c79356b | 775 | * |
c910b4d9 A |
776 | * Returns TRUE if the call was |
777 | * already on a queue. | |
1c79356b | 778 | */ |
1c79356b A |
779 | boolean_t |
780 | thread_call_enter( | |
316670eb | 781 | thread_call_t call) |
1c79356b | 782 | { |
316670eb A |
783 | boolean_t result = TRUE; |
784 | thread_call_group_t group; | |
785 | spl_t s; | |
786 | ||
787 | group = thread_call_get_group(call); | |
788 | ||
6d2010ae A |
789 | s = splsched(); |
790 | thread_call_lock_spin(); | |
316670eb A |
791 | |
792 | if (call->tc_call.queue != &group->pending_queue) { | |
793 | result = _pending_call_enqueue(call, group); | |
1c79356b A |
794 | } |
795 | ||
316670eb | 796 | call->tc_call.param1 = 0; |
1c79356b | 797 | |
6d2010ae A |
798 | thread_call_unlock(); |
799 | splx(s); | |
1c79356b A |
800 | |
801 | return (result); | |
802 | } | |
803 | ||
804 | boolean_t | |
805 | thread_call_enter1( | |
316670eb A |
806 | thread_call_t call, |
807 | thread_call_param_t param1) | |
1c79356b | 808 | { |
316670eb A |
809 | boolean_t result = TRUE; |
810 | thread_call_group_t group; | |
811 | spl_t s; | |
812 | ||
813 | group = thread_call_get_group(call); | |
814 | ||
6d2010ae A |
815 | s = splsched(); |
816 | thread_call_lock_spin(); | |
316670eb A |
817 | |
818 | if (call->tc_call.queue != &group->pending_queue) { | |
819 | result = _pending_call_enqueue(call, group); | |
c910b4d9 | 820 | } |
1c79356b | 821 | |
316670eb | 822 | call->tc_call.param1 = param1; |
1c79356b | 823 | |
6d2010ae A |
824 | thread_call_unlock(); |
825 | splx(s); | |
1c79356b A |
826 | |
827 | return (result); | |
828 | } | |
829 | ||
830 | /* | |
c910b4d9 | 831 | * thread_call_enter_delayed: |
1c79356b | 832 | * |
c910b4d9 A |
833 | * Enqueue a callout entry to occur |
834 | * at the stated time. | |
1c79356b | 835 | * |
c910b4d9 A |
836 | * Returns TRUE if the call was |
837 | * already on a queue. | |
1c79356b | 838 | */ |
1c79356b A |
839 | boolean_t |
840 | thread_call_enter_delayed( | |
316670eb | 841 | thread_call_t call, |
39236c6e | 842 | uint64_t deadline) |
1c79356b | 843 | { |
39236c6e A |
844 | assert(call); |
845 | return thread_call_enter_delayed_internal(call, NULL, 0, 0, deadline, 0, 0); | |
1c79356b A |
846 | } |
847 | ||
848 | boolean_t | |
849 | thread_call_enter1_delayed( | |
316670eb A |
850 | thread_call_t call, |
851 | thread_call_param_t param1, | |
852 | uint64_t deadline) | |
39236c6e A |
853 | { |
854 | assert(call); | |
855 | return thread_call_enter_delayed_internal(call, NULL, 0, param1, deadline, 0, 0); | |
856 | } | |
857 | ||
858 | boolean_t | |
859 | thread_call_enter_delayed_with_leeway( | |
860 | thread_call_t call, | |
861 | thread_call_param_t param1, | |
862 | uint64_t deadline, | |
863 | uint64_t leeway, | |
864 | unsigned int flags) | |
865 | { | |
866 | assert(call); | |
867 | return thread_call_enter_delayed_internal(call, NULL, 0, param1, deadline, leeway, flags); | |
868 | } | |
869 | ||
870 | ||
871 | /* | |
872 | * thread_call_enter_delayed_internal: | |
873 | * enqueue a callout entry to occur at the stated time | |
874 | * | |
875 | * Returns True if the call was already on a queue | |
876 | * params: | |
877 | * call - structure encapsulating state of the callout | |
878 | * alt_func/alt_param0 - if call is NULL, allocate temporary storage using these parameters | |
879 | * deadline - time deadline in nanoseconds | |
880 | * leeway - timer slack represented as delta of deadline. | |
881 | * flags - THREAD_CALL_DELAY_XXX : classification of caller's desires wrt timer coalescing. | |
882 | * THREAD_CALL_DELAY_LEEWAY : value in leeway is used for timer coalescing. | |
883 | */ | |
884 | boolean_t | |
885 | thread_call_enter_delayed_internal( | |
886 | thread_call_t call, | |
887 | thread_call_func_t alt_func, | |
888 | thread_call_param_t alt_param0, | |
889 | thread_call_param_t param1, | |
890 | uint64_t deadline, | |
891 | uint64_t leeway, | |
892 | unsigned int flags) | |
1c79356b | 893 | { |
316670eb A |
894 | boolean_t result = TRUE; |
895 | thread_call_group_t group; | |
896 | spl_t s; | |
39236c6e A |
897 | uint64_t abstime, sdeadline, slop; |
898 | uint32_t urgency; | |
316670eb | 899 | |
39236c6e A |
900 | /* direct mapping between thread_call, timer_call, and timeout_urgency values */ |
901 | urgency = (flags & TIMEOUT_URGENCY_MASK); | |
1c79356b | 902 | |
6d2010ae A |
903 | s = splsched(); |
904 | thread_call_lock_spin(); | |
39236c6e A |
905 | |
906 | if (call == NULL) { | |
907 | /* allocate a structure out of internal storage, as a convenience for BSD callers */ | |
908 | call = _internal_call_allocate(alt_func, alt_param0); | |
909 | } | |
910 | ||
911 | group = thread_call_get_group(call); | |
4b17d6b6 | 912 | abstime = mach_absolute_time(); |
39236c6e A |
913 | |
914 | call->tc_flags |= THREAD_CALL_DELAYED; | |
915 | ||
916 | call->tc_soft_deadline = sdeadline = deadline; | |
917 | ||
918 | boolean_t ratelimited = FALSE; | |
919 | slop = timer_call_slop(deadline, abstime, urgency, current_thread(), &ratelimited); | |
920 | ||
921 | if ((flags & THREAD_CALL_DELAY_LEEWAY) != 0 && leeway > slop) | |
922 | slop = leeway; | |
923 | ||
924 | if (UINT64_MAX - deadline <= slop) | |
925 | deadline = UINT64_MAX; | |
926 | else | |
927 | deadline += slop; | |
928 | ||
39236c6e | 929 | if (ratelimited) { |
fe8ab488 | 930 | call->tc_flags |= TIMER_CALL_RATELIMITED; |
39236c6e | 931 | } else { |
fe8ab488 | 932 | call->tc_flags &= ~TIMER_CALL_RATELIMITED; |
39236c6e A |
933 | } |
934 | ||
fe8ab488 | 935 | |
39236c6e A |
936 | call->tc_call.param1 = param1; |
937 | call->ttd = (sdeadline > abstime) ? (sdeadline - abstime) : 0; | |
1c79356b | 938 | |
c910b4d9 | 939 | result = _delayed_call_enqueue(call, group, deadline); |
1c79356b | 940 | |
c910b4d9 A |
941 | if (queue_first(&group->delayed_queue) == qe(call)) |
942 | _set_delayed_call_timer(call, group); | |
1c79356b | 943 | |
4b17d6b6 | 944 | #if CONFIG_DTRACE |
39236c6e | 945 | DTRACE_TMR5(thread_callout__create, thread_call_func_t, call->tc_call.func, uint64_t, (deadline - sdeadline), uint64_t, (call->ttd >> 32), (unsigned) (call->ttd & 0xFFFFFFFF), call); |
4b17d6b6 | 946 | #endif |
6d2010ae A |
947 | thread_call_unlock(); |
948 | splx(s); | |
1c79356b A |
949 | |
950 | return (result); | |
951 | } | |
952 | ||
953 | /* | |
c910b4d9 | 954 | * thread_call_cancel: |
1c79356b | 955 | * |
c910b4d9 | 956 | * Dequeue a callout entry. |
1c79356b | 957 | * |
c910b4d9 A |
958 | * Returns TRUE if the call was |
959 | * on a queue. | |
1c79356b | 960 | */ |
1c79356b A |
961 | boolean_t |
962 | thread_call_cancel( | |
316670eb | 963 | thread_call_t call) |
1c79356b | 964 | { |
39236c6e | 965 | boolean_t result, do_cancel_callout = FALSE; |
316670eb A |
966 | thread_call_group_t group; |
967 | spl_t s; | |
968 | ||
969 | group = thread_call_get_group(call); | |
970 | ||
6d2010ae A |
971 | s = splsched(); |
972 | thread_call_lock_spin(); | |
c910b4d9 | 973 | |
39236c6e A |
974 | if ((call->tc_call.deadline != 0) && |
975 | (queue_first(&group->delayed_queue) == qe(call))) { | |
976 | assert (call->tc_call.queue == &group->delayed_queue); | |
977 | do_cancel_callout = TRUE; | |
978 | } | |
979 | ||
c910b4d9 | 980 | result = _call_dequeue(call, group); |
316670eb | 981 | |
39236c6e A |
982 | if (do_cancel_callout) { |
983 | timer_call_cancel(&group->delayed_timer); | |
984 | if (!queue_empty(&group->delayed_queue)) { | |
985 | _set_delayed_call_timer(TC(queue_first(&group->delayed_queue)), group); | |
986 | } | |
987 | } | |
988 | ||
6d2010ae A |
989 | thread_call_unlock(); |
990 | splx(s); | |
4b17d6b6 A |
991 | #if CONFIG_DTRACE |
992 | DTRACE_TMR4(thread_callout__cancel, thread_call_func_t, call->tc_call.func, 0, (call->ttd >> 32), (unsigned) (call->ttd & 0xFFFFFFFF)); | |
993 | #endif | |
1c79356b A |
994 | |
995 | return (result); | |
996 | } | |
997 | ||
316670eb A |
998 | /* |
999 | * Cancel a thread call. If it cannot be cancelled (i.e. | |
1000 | * is already in flight), waits for the most recent invocation | |
1001 | * to finish. Note that if clients re-submit this thread call, | |
1002 | * it may still be pending or in flight when thread_call_cancel_wait | |
1003 | * returns, but all requests to execute this work item prior | |
1004 | * to the call to thread_call_cancel_wait will have finished. | |
1005 | */ | |
1006 | boolean_t | |
1007 | thread_call_cancel_wait( | |
1008 | thread_call_t call) | |
1009 | { | |
1010 | boolean_t result; | |
1011 | thread_call_group_t group; | |
1012 | ||
1013 | if ((call->tc_flags & THREAD_CALL_ALLOC) == 0) { | |
1014 | panic("%s: Can't wait on thread call whose storage I don't own.", __FUNCTION__); | |
1015 | } | |
1016 | ||
1017 | group = thread_call_get_group(call); | |
1018 | ||
1019 | (void) splsched(); | |
1020 | thread_call_lock_spin(); | |
1021 | ||
1022 | result = _call_dequeue(call, group); | |
1023 | if (result == FALSE) { | |
1024 | thread_call_wait_locked(call); | |
1025 | } | |
1026 | ||
1027 | thread_call_unlock(); | |
1028 | (void) spllo(); | |
1029 | ||
1030 | return result; | |
1031 | } | |
1032 | ||
1033 | ||
1c79356b | 1034 | /* |
c910b4d9 | 1035 | * thread_call_wake: |
1c79356b | 1036 | * |
c910b4d9 A |
1037 | * Wake a call thread to service |
1038 | * pending call entries. May wake | |
1039 | * the daemon thread in order to | |
1040 | * create additional call threads. | |
1c79356b | 1041 | * |
c910b4d9 | 1042 | * Called with thread_call_lock held. |
316670eb A |
1043 | * |
1044 | * For high-priority group, only does wakeup/creation if there are no threads | |
1045 | * running. | |
1c79356b | 1046 | */ |
c910b4d9 A |
1047 | static __inline__ void |
1048 | thread_call_wake( | |
1049 | thread_call_group_t group) | |
1c79356b | 1050 | { |
316670eb A |
1051 | /* |
1052 | * New behavior: use threads if you've got 'em. | |
1053 | * Traditional behavior: wake only if no threads running. | |
1054 | */ | |
1055 | if (group_isparallel(group) || group->active_count == 0) { | |
1056 | if (wait_queue_wakeup_one(&group->idle_wqueue, NO_EVENT, THREAD_AWAKENED, -1) == KERN_SUCCESS) { | |
1057 | group->idle_count--; group->active_count++; | |
1058 | ||
1059 | if (group->idle_count == 0) { | |
1060 | timer_call_cancel(&group->dealloc_timer); | |
1061 | group->flags &= TCG_DEALLOC_ACTIVE; | |
1062 | } | |
1063 | } else { | |
1064 | if (!thread_call_daemon_awake && thread_call_group_should_add_thread(group)) { | |
1065 | thread_call_daemon_awake = TRUE; | |
1066 | wait_queue_wakeup_one(&daemon_wqueue, NO_EVENT, THREAD_AWAKENED, -1); | |
1067 | } | |
1068 | } | |
1c79356b A |
1069 | } |
1070 | } | |
1071 | ||
9bccf70c | 1072 | /* |
2d21ac55 | 1073 | * sched_call_thread: |
9bccf70c | 1074 | * |
316670eb A |
1075 | * Call out invoked by the scheduler. Used only for high-priority |
1076 | * thread call group. | |
9bccf70c | 1077 | */ |
2d21ac55 A |
1078 | static void |
1079 | sched_call_thread( | |
316670eb A |
1080 | int type, |
1081 | __unused thread_t thread) | |
9bccf70c | 1082 | { |
316670eb A |
1083 | thread_call_group_t group; |
1084 | ||
1085 | group = &thread_call_groups[THREAD_CALL_PRIORITY_HIGH]; /* XXX */ | |
c910b4d9 | 1086 | |
6d2010ae | 1087 | thread_call_lock_spin(); |
9bccf70c | 1088 | |
2d21ac55 | 1089 | switch (type) { |
9bccf70c | 1090 | |
316670eb A |
1091 | case SCHED_CALL_BLOCK: |
1092 | --group->active_count; | |
1093 | if (group->pending_count > 0) | |
1094 | thread_call_wake(group); | |
1095 | break; | |
9bccf70c | 1096 | |
316670eb A |
1097 | case SCHED_CALL_UNBLOCK: |
1098 | group->active_count++; | |
1099 | break; | |
2d21ac55 | 1100 | } |
9bccf70c | 1101 | |
6d2010ae | 1102 | thread_call_unlock(); |
9bccf70c | 1103 | } |
1c79356b | 1104 | |
316670eb A |
1105 | /* |
1106 | * Interrupts disabled, lock held; returns the same way. | |
1107 | * Only called on thread calls whose storage we own. Wakes up | |
1108 | * anyone who might be waiting on this work item and frees it | |
1109 | * if the client has so requested. | |
1110 | */ | |
1111 | static void | |
fe8ab488 | 1112 | thread_call_finish(thread_call_t call, spl_t *s) |
316670eb A |
1113 | { |
1114 | boolean_t dowake = FALSE; | |
1115 | ||
1116 | call->tc_finish_count++; | |
1117 | call->tc_refs--; | |
1118 | ||
1119 | if ((call->tc_flags & THREAD_CALL_WAIT) != 0) { | |
1120 | dowake = TRUE; | |
1121 | call->tc_flags &= ~THREAD_CALL_WAIT; | |
1122 | ||
1123 | /* | |
1124 | * Dropping lock here because the sched call for the | |
1125 | * high-pri group can take the big lock from under | |
1126 | * a thread lock. | |
1127 | */ | |
1128 | thread_call_unlock(); | |
1129 | thread_wakeup((event_t)call); | |
1130 | thread_call_lock_spin(); | |
1131 | } | |
1132 | ||
1133 | if (call->tc_refs == 0) { | |
1134 | if (dowake) { | |
1135 | panic("Someone waiting on a thread call that is scheduled for free: %p\n", call->tc_call.func); | |
1136 | } | |
1137 | ||
fe8ab488 | 1138 | enable_ints_and_unlock(*s); |
316670eb A |
1139 | |
1140 | zfree(thread_call_zone, call); | |
1141 | ||
fe8ab488 | 1142 | *s = disable_ints_and_lock(); |
316670eb A |
1143 | } |
1144 | ||
1145 | } | |
1146 | ||
1c79356b | 1147 | /* |
c910b4d9 | 1148 | * thread_call_thread: |
1c79356b | 1149 | */ |
c910b4d9 A |
1150 | static void |
1151 | thread_call_thread( | |
316670eb A |
1152 | thread_call_group_t group, |
1153 | wait_result_t wres) | |
1c79356b | 1154 | { |
316670eb A |
1155 | thread_t self = current_thread(); |
1156 | boolean_t canwait; | |
fe8ab488 | 1157 | spl_t s; |
1c79356b | 1158 | |
4b17d6b6 A |
1159 | if ((thread_get_tag_internal(self) & THREAD_TAG_CALLOUT) == 0) |
1160 | (void)thread_set_tag_internal(self, THREAD_TAG_CALLOUT); | |
1161 | ||
316670eb A |
1162 | /* |
1163 | * A wakeup with THREAD_INTERRUPTED indicates that | |
1164 | * we should terminate. | |
1165 | */ | |
1166 | if (wres == THREAD_INTERRUPTED) { | |
1167 | thread_terminate(self); | |
1168 | ||
1169 | /* NOTREACHED */ | |
1170 | panic("thread_terminate() returned?"); | |
1171 | } | |
1172 | ||
fe8ab488 | 1173 | s = disable_ints_and_lock(); |
1c79356b | 1174 | |
316670eb | 1175 | thread_sched_call(self, group->sched_call); |
9bccf70c | 1176 | |
316670eb | 1177 | while (group->pending_count > 0) { |
1c79356b A |
1178 | thread_call_t call; |
1179 | thread_call_func_t func; | |
1180 | thread_call_param_t param0, param1; | |
1181 | ||
c910b4d9 A |
1182 | call = TC(dequeue_head(&group->pending_queue)); |
1183 | group->pending_count--; | |
1c79356b | 1184 | |
316670eb A |
1185 | func = call->tc_call.func; |
1186 | param0 = call->tc_call.param0; | |
1187 | param1 = call->tc_call.param1; | |
1188 | ||
1189 | call->tc_call.queue = NULL; | |
1c79356b A |
1190 | |
1191 | _internal_call_release(call); | |
1192 | ||
316670eb A |
1193 | /* |
1194 | * Can only do wakeups for thread calls whose storage | |
1195 | * we control. | |
1196 | */ | |
1197 | if ((call->tc_flags & THREAD_CALL_ALLOC) != 0) { | |
1198 | canwait = TRUE; | |
1199 | call->tc_refs++; /* Delay free until we're done */ | |
1200 | } else | |
1201 | canwait = FALSE; | |
1202 | ||
fe8ab488 | 1203 | enable_ints_and_unlock(s); |
1c79356b | 1204 | |
55e303ae | 1205 | KERNEL_DEBUG_CONSTANT( |
316670eb A |
1206 | MACHDBG_CODE(DBG_MACH_SCHED,MACH_CALLOUT) | DBG_FUNC_NONE, |
1207 | VM_KERNEL_UNSLIDE(func), param0, param1, 0, 0); | |
55e303ae | 1208 | |
39236c6e A |
1209 | #if CONFIG_DTRACE |
1210 | DTRACE_TMR6(thread_callout__start, thread_call_func_t, func, int, 0, int, (call->ttd >> 32), (unsigned) (call->ttd & 0xFFFFFFFF), (call->tc_flags & THREAD_CALL_DELAYED), call); | |
1211 | #endif | |
1212 | ||
1c79356b A |
1213 | (*func)(param0, param1); |
1214 | ||
39236c6e A |
1215 | #if CONFIG_DTRACE |
1216 | DTRACE_TMR6(thread_callout__end, thread_call_func_t, func, int, 0, int, (call->ttd >> 32), (unsigned) (call->ttd & 0xFFFFFFFF), (call->tc_flags & THREAD_CALL_DELAYED), call); | |
1217 | #endif | |
1218 | ||
6d2010ae A |
1219 | if (get_preemption_level() != 0) { |
1220 | int pl = get_preemption_level(); | |
1221 | panic("thread_call_thread: preemption_level %d, last callout %p(%p, %p)", | |
316670eb | 1222 | pl, (void *)VM_KERNEL_UNSLIDE(func), param0, param1); |
6d2010ae | 1223 | } |
316670eb | 1224 | |
fe8ab488 | 1225 | s = disable_ints_and_lock(); |
316670eb A |
1226 | |
1227 | if (canwait) { | |
1228 | /* Frees if so desired */ | |
fe8ab488 | 1229 | thread_call_finish(call, &s); |
316670eb A |
1230 | } |
1231 | } | |
9bccf70c | 1232 | |
2d21ac55 | 1233 | thread_sched_call(self, NULL); |
c910b4d9 | 1234 | group->active_count--; |
39236c6e A |
1235 | |
1236 | if (self->callout_woken_from_icontext && !self->callout_woke_thread) { | |
1237 | ledger_credit(self->t_ledger, task_ledgers.interrupt_wakeups, 1); | |
1238 | if (self->callout_woken_from_platform_idle) | |
1239 | ledger_credit(self->t_ledger, task_ledgers.platform_idle_wakeups, 1); | |
1240 | } | |
1241 | ||
1242 | self->callout_woken_from_icontext = FALSE; | |
1243 | self->callout_woken_from_platform_idle = FALSE; | |
1244 | self->callout_woke_thread = FALSE; | |
9bccf70c | 1245 | |
316670eb A |
1246 | if (group_isparallel(group)) { |
1247 | /* | |
1248 | * For new style of thread group, thread always blocks. | |
1249 | * If we have more than the target number of threads, | |
1250 | * and this is the first to block, and it isn't active | |
1251 | * already, set a timer for deallocating a thread if we | |
1252 | * continue to have a surplus. | |
1253 | */ | |
c910b4d9 | 1254 | group->idle_count++; |
1c79356b | 1255 | |
316670eb A |
1256 | if (group->idle_count == 1) { |
1257 | group->idle_timestamp = mach_absolute_time(); | |
1258 | } | |
1259 | ||
1260 | if (((group->flags & TCG_DEALLOC_ACTIVE) == 0) && | |
1261 | ((group->active_count + group->idle_count) > group->target_thread_count)) { | |
1262 | group->flags |= TCG_DEALLOC_ACTIVE; | |
1263 | thread_call_start_deallocate_timer(group); | |
1264 | } | |
1265 | ||
1266 | /* Wait for more work (or termination) */ | |
1267 | wres = wait_queue_assert_wait(&group->idle_wqueue, NO_EVENT, THREAD_INTERRUPTIBLE, 0); | |
1268 | if (wres != THREAD_WAITING) { | |
1269 | panic("kcall worker unable to assert wait?"); | |
1270 | } | |
1271 | ||
fe8ab488 | 1272 | enable_ints_and_unlock(s); |
1c79356b | 1273 | |
c910b4d9 | 1274 | thread_block_parameter((thread_continue_t)thread_call_thread, group); |
316670eb A |
1275 | } else { |
1276 | if (group->idle_count < group->target_thread_count) { | |
1277 | group->idle_count++; | |
c910b4d9 | 1278 | |
316670eb A |
1279 | wait_queue_assert_wait(&group->idle_wqueue, NO_EVENT, THREAD_UNINT, 0); /* Interrupted means to exit */ |
1280 | ||
fe8ab488 | 1281 | enable_ints_and_unlock(s); |
316670eb A |
1282 | |
1283 | thread_block_parameter((thread_continue_t)thread_call_thread, group); | |
1284 | /* NOTREACHED */ | |
1285 | } | |
1286 | } | |
1287 | ||
fe8ab488 | 1288 | enable_ints_and_unlock(s); |
316670eb A |
1289 | |
1290 | thread_terminate(self); | |
1c79356b A |
1291 | /* NOTREACHED */ |
1292 | } | |
1293 | ||
1c79356b | 1294 | /* |
316670eb A |
1295 | * thread_call_daemon: walk list of groups, allocating |
1296 | * threads if appropriate (as determined by | |
1297 | * thread_call_group_should_add_thread()). | |
1c79356b | 1298 | */ |
c910b4d9 | 1299 | static void |
316670eb | 1300 | thread_call_daemon_continue(__unused void *arg) |
1c79356b | 1301 | { |
316670eb A |
1302 | int i; |
1303 | kern_return_t kr; | |
1304 | thread_call_group_t group; | |
fe8ab488 | 1305 | spl_t s; |
316670eb | 1306 | |
fe8ab488 | 1307 | s = disable_ints_and_lock(); |
316670eb A |
1308 | |
1309 | /* Starting at zero happens to be high-priority first. */ | |
1310 | for (i = 0; i < THREAD_CALL_GROUP_COUNT; i++) { | |
1311 | group = &thread_call_groups[i]; | |
1312 | while (thread_call_group_should_add_thread(group)) { | |
1313 | group->active_count++; | |
1314 | ||
fe8ab488 | 1315 | enable_ints_and_unlock(s); |
316670eb A |
1316 | |
1317 | kr = thread_call_thread_create(group); | |
1318 | if (kr != KERN_SUCCESS) { | |
1319 | /* | |
1320 | * On failure, just pause for a moment and give up. | |
1321 | * We can try again later. | |
1322 | */ | |
1323 | delay(10000); /* 10 ms */ | |
fe8ab488 | 1324 | s = disable_ints_and_lock(); |
316670eb A |
1325 | goto out; |
1326 | } | |
1327 | ||
fe8ab488 | 1328 | s = disable_ints_and_lock(); |
316670eb A |
1329 | } |
1330 | } | |
91447636 | 1331 | |
316670eb A |
1332 | out: |
1333 | thread_call_daemon_awake = FALSE; | |
1334 | wait_queue_assert_wait(&daemon_wqueue, NO_EVENT, THREAD_UNINT, 0); | |
55e303ae | 1335 | |
fe8ab488 | 1336 | enable_ints_and_unlock(s); |
c910b4d9 | 1337 | |
316670eb | 1338 | thread_block_parameter((thread_continue_t)thread_call_daemon_continue, NULL); |
1c79356b A |
1339 | /* NOTREACHED */ |
1340 | } | |
1341 | ||
c910b4d9 A |
1342 | static void |
1343 | thread_call_daemon( | |
316670eb | 1344 | __unused void *arg) |
1c79356b | 1345 | { |
55e303ae | 1346 | thread_t self = current_thread(); |
1c79356b | 1347 | |
91447636 | 1348 | self->options |= TH_OPT_VMPRIV; |
1c79356b | 1349 | vm_page_free_reserve(2); /* XXX */ |
316670eb A |
1350 | |
1351 | thread_call_daemon_continue(NULL); | |
1352 | /* NOTREACHED */ | |
1353 | } | |
1354 | ||
1355 | /* | |
1356 | * Schedule timer to deallocate a worker thread if we have a surplus | |
1357 | * of threads (in excess of the group's target) and at least one thread | |
1358 | * is idle the whole time. | |
1359 | */ | |
1360 | static void | |
1361 | thread_call_start_deallocate_timer( | |
1362 | thread_call_group_t group) | |
1363 | { | |
1364 | uint64_t deadline; | |
1365 | boolean_t onqueue; | |
1366 | ||
1367 | assert(group->idle_count > 0); | |
1368 | ||
1369 | group->flags |= TCG_DEALLOC_ACTIVE; | |
1370 | deadline = group->idle_timestamp + thread_call_dealloc_interval_abs; | |
1371 | onqueue = timer_call_enter(&group->dealloc_timer, deadline, 0); | |
1372 | ||
1373 | if (onqueue) { | |
1374 | panic("Deallocate timer already active?"); | |
1375 | } | |
1c79356b A |
1376 | } |
1377 | ||
6d2010ae | 1378 | void |
c910b4d9 | 1379 | thread_call_delayed_timer( |
316670eb A |
1380 | timer_call_param_t p0, |
1381 | __unused timer_call_param_t p1 | |
1c79356b A |
1382 | ) |
1383 | { | |
316670eb | 1384 | thread_call_t call; |
c910b4d9 | 1385 | thread_call_group_t group = p0; |
39236c6e | 1386 | uint64_t timestamp; |
1c79356b | 1387 | |
6d2010ae | 1388 | thread_call_lock_spin(); |
1c79356b | 1389 | |
c910b4d9 | 1390 | timestamp = mach_absolute_time(); |
316670eb A |
1391 | |
1392 | call = TC(queue_first(&group->delayed_queue)); | |
1393 | ||
1394 | while (!queue_end(&group->delayed_queue, qe(call))) { | |
39236c6e | 1395 | if (call->tc_soft_deadline <= timestamp) { |
fe8ab488 | 1396 | if ((call->tc_flags & THREAD_CALL_RATELIMITED) && |
39236c6e A |
1397 | (CE(call)->deadline > timestamp) && |
1398 | (ml_timer_forced_evaluation() == FALSE)) { | |
1399 | break; | |
1400 | } | |
c910b4d9 | 1401 | _pending_call_enqueue(call, group); |
39236c6e | 1402 | } /* TODO, identify differentially coalesced timers */ |
1c79356b A |
1403 | else |
1404 | break; | |
316670eb | 1405 | |
c910b4d9 | 1406 | call = TC(queue_first(&group->delayed_queue)); |
316670eb | 1407 | } |
1c79356b | 1408 | |
c910b4d9 A |
1409 | if (!queue_end(&group->delayed_queue, qe(call))) |
1410 | _set_delayed_call_timer(call, group); | |
1c79356b | 1411 | |
316670eb A |
1412 | thread_call_unlock(); |
1413 | } | |
1414 | ||
39236c6e A |
1415 | static void |
1416 | thread_call_delayed_timer_rescan(timer_call_param_t p0, __unused timer_call_param_t p1) | |
1417 | { | |
1418 | thread_call_t call; | |
1419 | thread_call_group_t group = p0; | |
1420 | uint64_t timestamp; | |
1421 | boolean_t istate; | |
1422 | ||
1423 | istate = ml_set_interrupts_enabled(FALSE); | |
1424 | thread_call_lock_spin(); | |
1425 | ||
1426 | assert(ml_timer_forced_evaluation() == TRUE); | |
1427 | timestamp = mach_absolute_time(); | |
1428 | ||
1429 | call = TC(queue_first(&group->delayed_queue)); | |
1430 | ||
1431 | while (!queue_end(&group->delayed_queue, qe(call))) { | |
1432 | if (call->tc_soft_deadline <= timestamp) { | |
1433 | _pending_call_enqueue(call, group); | |
1434 | call = TC(queue_first(&group->delayed_queue)); | |
1435 | } | |
1436 | else { | |
1437 | uint64_t skew = call->tc_call.deadline - call->tc_soft_deadline; | |
1438 | assert (call->tc_call.deadline >= call->tc_soft_deadline); | |
1439 | /* On a latency quality-of-service level change, | |
1440 | * re-sort potentially rate-limited callout. The platform | |
1441 | * layer determines which timers require this. | |
1442 | */ | |
1443 | if (timer_resort_threshold(skew)) { | |
1444 | _call_dequeue(call, group); | |
1445 | _delayed_call_enqueue(call, group, call->tc_soft_deadline); | |
1446 | } | |
1447 | call = TC(queue_next(qe(call))); | |
1448 | } | |
1449 | } | |
1450 | ||
1451 | if (!queue_empty(&group->delayed_queue)) | |
1452 | _set_delayed_call_timer(TC(queue_first(&group->delayed_queue)), group); | |
1453 | thread_call_unlock(); | |
1454 | ml_set_interrupts_enabled(istate); | |
1455 | } | |
1456 | ||
1457 | void | |
1458 | thread_call_delayed_timer_rescan_all(void) { | |
1459 | thread_call_delayed_timer_rescan((timer_call_param_t)&thread_call_groups[THREAD_CALL_PRIORITY_LOW], NULL); | |
1460 | thread_call_delayed_timer_rescan((timer_call_param_t)&thread_call_groups[THREAD_CALL_PRIORITY_USER], NULL); | |
1461 | thread_call_delayed_timer_rescan((timer_call_param_t)&thread_call_groups[THREAD_CALL_PRIORITY_KERNEL], NULL); | |
1462 | thread_call_delayed_timer_rescan((timer_call_param_t)&thread_call_groups[THREAD_CALL_PRIORITY_HIGH], NULL); | |
1463 | } | |
1464 | ||
316670eb A |
1465 | /* |
1466 | * Timer callback to tell a thread to terminate if | |
1467 | * we have an excess of threads and at least one has been | |
1468 | * idle for a long time. | |
1469 | */ | |
1470 | static void | |
1471 | thread_call_dealloc_timer( | |
1472 | timer_call_param_t p0, | |
1473 | __unused timer_call_param_t p1) | |
1474 | { | |
1475 | thread_call_group_t group = (thread_call_group_t)p0; | |
1476 | uint64_t now; | |
1477 | kern_return_t res; | |
1478 | boolean_t terminated = FALSE; | |
1479 | ||
1480 | thread_call_lock_spin(); | |
1481 | ||
1482 | now = mach_absolute_time(); | |
1483 | if (group->idle_count > 0) { | |
1484 | if (now > group->idle_timestamp + thread_call_dealloc_interval_abs) { | |
1485 | terminated = TRUE; | |
1486 | group->idle_count--; | |
1487 | res = wait_queue_wakeup_one(&group->idle_wqueue, NO_EVENT, THREAD_INTERRUPTED, -1); | |
1488 | if (res != KERN_SUCCESS) { | |
1489 | panic("Unable to wake up idle thread for termination?"); | |
1490 | } | |
1491 | } | |
1492 | ||
1493 | } | |
1494 | ||
1495 | /* | |
1496 | * If we still have an excess of threads, schedule another | |
1497 | * invocation of this function. | |
1498 | */ | |
1499 | if (group->idle_count > 0 && (group->idle_count + group->active_count > group->target_thread_count)) { | |
1500 | /* | |
1501 | * If we killed someone just now, push out the | |
1502 | * next deadline. | |
1503 | */ | |
1504 | if (terminated) { | |
1505 | group->idle_timestamp = now; | |
1506 | } | |
1c79356b | 1507 | |
316670eb A |
1508 | thread_call_start_deallocate_timer(group); |
1509 | } else { | |
1510 | group->flags &= ~TCG_DEALLOC_ACTIVE; | |
1511 | } | |
1512 | ||
1513 | thread_call_unlock(); | |
1c79356b | 1514 | } |
316670eb A |
1515 | |
1516 | /* | |
1517 | * Wait for all requested invocations of a thread call prior to now | |
1518 | * to finish. Can only be invoked on thread calls whose storage we manage. | |
1519 | * Just waits for the finish count to catch up to the submit count we find | |
1520 | * at the beginning of our wait. | |
1521 | */ | |
1522 | static void | |
1523 | thread_call_wait_locked(thread_call_t call) | |
1524 | { | |
1525 | uint64_t submit_count; | |
1526 | wait_result_t res; | |
1527 | ||
1528 | assert(call->tc_flags & THREAD_CALL_ALLOC); | |
1529 | ||
1530 | submit_count = call->tc_submit_count; | |
1531 | ||
1532 | while (call->tc_finish_count < submit_count) { | |
1533 | call->tc_flags |= THREAD_CALL_WAIT; | |
1534 | ||
1535 | res = assert_wait(call, THREAD_UNINT); | |
1536 | if (res != THREAD_WAITING) { | |
1537 | panic("Unable to assert wait?"); | |
1538 | } | |
1539 | ||
1540 | thread_call_unlock(); | |
1541 | (void) spllo(); | |
1542 | ||
1543 | res = thread_block(NULL); | |
1544 | if (res != THREAD_AWAKENED) { | |
1545 | panic("Awoken with %d?", res); | |
1546 | } | |
1547 | ||
1548 | (void) splsched(); | |
1549 | thread_call_lock_spin(); | |
1550 | } | |
1551 | } | |
1552 | ||
1553 | /* | |
1554 | * Determine whether a thread call is either on a queue or | |
1555 | * currently being executed. | |
1556 | */ | |
1557 | boolean_t | |
1558 | thread_call_isactive(thread_call_t call) | |
1559 | { | |
1560 | boolean_t active; | |
fe8ab488 | 1561 | spl_t s; |
316670eb | 1562 | |
fe8ab488 | 1563 | s = disable_ints_and_lock(); |
316670eb | 1564 | active = (call->tc_submit_count > call->tc_finish_count); |
fe8ab488 | 1565 | enable_ints_and_unlock(s); |
316670eb A |
1566 | |
1567 | return active; | |
1568 | } |