]> git.saurik.com Git - apple/xnu.git/blame_incremental - osfmk/ipc/ipc_hash.c
xnu-344.21.73.tar.gz
[apple/xnu.git] / osfmk / ipc / ipc_hash.c
... / ...
CommitLineData
1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25/*
26 * @OSF_COPYRIGHT@
27 */
28/*
29 * Mach Operating System
30 * Copyright (c) 1991,1990,1989 Carnegie Mellon University
31 * All Rights Reserved.
32 *
33 * Permission to use, copy, modify and distribute this software and its
34 * documentation is hereby granted, provided that both the copyright
35 * notice and this permission notice appear in all copies of the
36 * software, derivative works or modified versions, and any portions
37 * thereof, and that both notices appear in supporting documentation.
38 *
39 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
40 * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
41 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
42 *
43 * Carnegie Mellon requests users of this software to return to
44 *
45 * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
46 * School of Computer Science
47 * Carnegie Mellon University
48 * Pittsburgh PA 15213-3890
49 *
50 * any improvements or extensions that they make and grant Carnegie Mellon
51 * the rights to redistribute these changes.
52 */
53/*
54 */
55/*
56 * File: ipc/ipc_hash.c
57 * Author: Rich Draves
58 * Date: 1989
59 *
60 * Entry hash table operations.
61 */
62
63#include <mach/boolean.h>
64#include <mach/port.h>
65#include <kern/lock.h>
66#include <kern/kalloc.h>
67#include <ipc/port.h>
68#include <ipc/ipc_space.h>
69#include <ipc/ipc_object.h>
70#include <ipc/ipc_entry.h>
71#include <ipc/ipc_hash.h>
72#include <ipc/ipc_init.h>
73
74#include <mach_ipc_debug.h>
75
76#if MACH_IPC_DEBUG
77#include <mach/kern_return.h>
78#include <mach_debug/hash_info.h>
79#include <vm/vm_map.h>
80#include <vm/vm_kern.h>
81#endif /* MACH_IPC_DEBUG */
82
83/*
84 * Forward declarations
85 */
86
87/* Lookup (space, obj) in global hash table */
88boolean_t ipc_hash_global_lookup(
89 ipc_space_t space,
90 ipc_object_t obj,
91 mach_port_name_t *namep,
92 ipc_tree_entry_t *entryp);
93
94/* Insert an entry into the global reverse hash table */
95void ipc_hash_global_insert(
96 ipc_space_t space,
97 ipc_object_t obj,
98 mach_port_name_t name,
99 ipc_tree_entry_t entry);
100
101/* Delete an entry from the local reverse hash table */
102void ipc_hash_local_delete(
103 ipc_space_t space,
104 ipc_object_t obj,
105 mach_port_index_t index,
106 ipc_entry_t entry);
107
108/*
109 * Routine: ipc_hash_lookup
110 * Purpose:
111 * Converts (space, obj) -> (name, entry).
112 * Returns TRUE if an entry was found.
113 * Conditions:
114 * The space must be locked (read or write) throughout.
115 */
116
117boolean_t
118ipc_hash_lookup(
119 ipc_space_t space,
120 ipc_object_t obj,
121 mach_port_name_t *namep,
122 ipc_entry_t *entryp)
123{
124 boolean_t rv;
125
126 rv = ipc_hash_local_lookup(space, obj, namep, entryp);
127 if (!rv) {
128 assert(!is_fast_space(space) || space->is_tree_hash == 0);
129 if (space->is_tree_hash > 0)
130 rv = ipc_hash_global_lookup(space, obj, namep,
131 (ipc_tree_entry_t *) entryp);
132 }
133 return (rv);
134}
135
136/*
137 * Routine: ipc_hash_insert
138 * Purpose:
139 * Inserts an entry into the appropriate reverse hash table,
140 * so that ipc_hash_lookup will find it.
141 * Conditions:
142 * The space must be write-locked.
143 */
144
145void
146ipc_hash_insert(
147 ipc_space_t space,
148 ipc_object_t obj,
149 mach_port_name_t name,
150 ipc_entry_t entry)
151{
152 mach_port_index_t index;
153
154 index = MACH_PORT_INDEX(name);
155 if ((index < space->is_table_size) &&
156 (entry == &space->is_table[index]))
157 ipc_hash_local_insert(space, obj, index, entry);
158 else {
159 assert(!is_fast_space(space));
160 ipc_hash_global_insert(space, obj, name,
161 (ipc_tree_entry_t) entry);
162 }
163}
164
165/*
166 * Routine: ipc_hash_delete
167 * Purpose:
168 * Deletes an entry from the appropriate reverse hash table.
169 * Conditions:
170 * The space must be write-locked.
171 */
172
173void
174ipc_hash_delete(
175 ipc_space_t space,
176 ipc_object_t obj,
177 mach_port_name_t name,
178 ipc_entry_t entry)
179{
180 mach_port_index_t index;
181
182 index = MACH_PORT_INDEX(name);
183 if ((index < space->is_table_size) &&
184 (entry == &space->is_table[index]))
185 ipc_hash_local_delete(space, obj, index, entry);
186 else {
187 assert(!is_fast_space(space));
188 ipc_hash_global_delete(space, obj, name,
189 (ipc_tree_entry_t) entry);
190 }
191}
192
193/*
194 * The global reverse hash table holds splay tree entries.
195 * It is a simple open-chaining hash table with singly-linked buckets.
196 * Each bucket is locked separately, with an exclusive lock.
197 * Within each bucket, move-to-front is used.
198 */
199
200typedef natural_t ipc_hash_index_t;
201
202ipc_hash_index_t ipc_hash_global_size;
203ipc_hash_index_t ipc_hash_global_mask;
204
205#define IH_GLOBAL_HASH(space, obj) \
206 (((((ipc_hash_index_t) ((vm_offset_t)space)) >> 4) + \
207 (((ipc_hash_index_t) ((vm_offset_t)obj)) >> 6)) & \
208 ipc_hash_global_mask)
209
210typedef struct ipc_hash_global_bucket {
211 decl_mutex_data(, ihgb_lock_data)
212 ipc_tree_entry_t ihgb_head;
213} *ipc_hash_global_bucket_t;
214
215#define IHGB_NULL ((ipc_hash_global_bucket_t) 0)
216
217#define ihgb_lock_init(ihgb) mutex_init(&(ihgb)->ihgb_lock_data, \
218 ETAP_IPC_IHGB)
219#define ihgb_lock(ihgb) mutex_lock(&(ihgb)->ihgb_lock_data)
220#define ihgb_unlock(ihgb) mutex_unlock(&(ihgb)->ihgb_lock_data)
221
222ipc_hash_global_bucket_t ipc_hash_global_table;
223
224/*
225 * Routine: ipc_hash_global_lookup
226 * Purpose:
227 * Converts (space, obj) -> (name, entry).
228 * Looks in the global table, for splay tree entries.
229 * Returns TRUE if an entry was found.
230 * Conditions:
231 * The space must be locked (read or write) throughout.
232 */
233
234boolean_t
235ipc_hash_global_lookup(
236 ipc_space_t space,
237 ipc_object_t obj,
238 mach_port_name_t *namep,
239 ipc_tree_entry_t *entryp)
240{
241 ipc_hash_global_bucket_t bucket;
242 ipc_tree_entry_t this, *last;
243
244 assert(space != IS_NULL);
245 assert(obj != IO_NULL);
246
247 assert(!is_fast_space(space));
248 bucket = &ipc_hash_global_table[IH_GLOBAL_HASH(space, obj)];
249 ihgb_lock(bucket);
250
251 if ((this = bucket->ihgb_head) != ITE_NULL) {
252 if ((this->ite_object == obj) &&
253 (this->ite_space == space)) {
254 /* found it at front; no need to move */
255
256 *namep = this->ite_name;
257 *entryp = this;
258 } else for (last = &this->ite_next;
259 (this = *last) != ITE_NULL;
260 last = &this->ite_next) {
261 if ((this->ite_object == obj) &&
262 (this->ite_space == space)) {
263 /* found it; move to front */
264
265 *last = this->ite_next;
266 this->ite_next = bucket->ihgb_head;
267 bucket->ihgb_head = this;
268
269 *namep = this->ite_name;
270 *entryp = this;
271 break;
272 }
273 }
274 }
275
276 ihgb_unlock(bucket);
277 return this != ITE_NULL;
278}
279
280/*
281 * Routine: ipc_hash_global_insert
282 * Purpose:
283 * Inserts an entry into the global reverse hash table.
284 * Conditions:
285 * The space must be write-locked.
286 */
287
288void
289ipc_hash_global_insert(
290 ipc_space_t space,
291 ipc_object_t obj,
292 mach_port_name_t name,
293 ipc_tree_entry_t entry)
294{
295 ipc_hash_global_bucket_t bucket;
296
297
298 assert(!is_fast_space(space));
299
300
301 assert(entry->ite_name == name);
302 assert(space != IS_NULL);
303 assert(entry->ite_space == space);
304 assert(obj != IO_NULL);
305 assert(entry->ite_object == obj);
306
307 space->is_tree_hash++;
308 assert(space->is_tree_hash <= space->is_tree_total);
309
310 bucket = &ipc_hash_global_table[IH_GLOBAL_HASH(space, obj)];
311 ihgb_lock(bucket);
312
313 /* insert at front of bucket */
314
315 entry->ite_next = bucket->ihgb_head;
316 bucket->ihgb_head = entry;
317
318 ihgb_unlock(bucket);
319}
320
321/*
322 * Routine: ipc_hash_global_delete
323 * Purpose:
324 * Deletes an entry from the global reverse hash table.
325 * Conditions:
326 * The space must be write-locked.
327 */
328
329void
330ipc_hash_global_delete(
331 ipc_space_t space,
332 ipc_object_t obj,
333 mach_port_name_t name,
334 ipc_tree_entry_t entry)
335{
336 ipc_hash_global_bucket_t bucket;
337 ipc_tree_entry_t this, *last;
338
339 assert(!is_fast_space(space));
340
341 assert(entry->ite_name == name);
342 assert(space != IS_NULL);
343 assert(entry->ite_space == space);
344 assert(obj != IO_NULL);
345 assert(entry->ite_object == obj);
346
347 assert(space->is_tree_hash > 0);
348 space->is_tree_hash--;
349
350 bucket = &ipc_hash_global_table[IH_GLOBAL_HASH(space, obj)];
351 ihgb_lock(bucket);
352
353 for (last = &bucket->ihgb_head;
354 (this = *last) != ITE_NULL;
355 last = &this->ite_next) {
356 if (this == entry) {
357 /* found it; remove from bucket */
358
359 *last = this->ite_next;
360 break;
361 }
362 }
363 assert(this != ITE_NULL);
364
365 ihgb_unlock(bucket);
366}
367
368/*
369 * Each space has a local reverse hash table, which holds
370 * entries from the space's table. In fact, the hash table
371 * just uses a field (ie_index) in the table itself.
372 *
373 * The local hash table is an open-addressing hash table,
374 * which means that when a collision occurs, instead of
375 * throwing the entry into a bucket, the entry is rehashed
376 * to another position in the table. In this case the rehash
377 * is very simple: linear probing (ie, just increment the position).
378 * This simple rehash makes deletions tractable (they're still a pain),
379 * but it means that collisions tend to build up into clumps.
380 *
381 * Because at least one entry in the table (index 0) is always unused,
382 * there will always be room in the reverse hash table. If a table
383 * with n slots gets completely full, the reverse hash table will
384 * have one giant clump of n-1 slots and one free slot somewhere.
385 * Because entries are only entered into the reverse table if they
386 * are pure send rights (not receive, send-once, port-set,
387 * or dead-name rights), and free entries of course aren't entered,
388 * I expect the reverse hash table won't get unreasonably full.
389 *
390 * Ordered hash tables (Amble & Knuth, Computer Journal, v. 17, no. 2,
391 * pp. 135-142.) may be desirable here. They can dramatically help
392 * unsuccessful lookups. But unsuccessful lookups are almost always
393 * followed by insertions, and those slow down somewhat. They
394 * also can help deletions somewhat. Successful lookups aren't affected.
395 * So possibly a small win; probably nothing significant.
396 */
397
398#define IH_LOCAL_HASH(obj, size) \
399 ((((mach_port_index_t) (obj)) >> 6) % (size))
400
401/*
402 * Routine: ipc_hash_local_lookup
403 * Purpose:
404 * Converts (space, obj) -> (name, entry).
405 * Looks in the space's local table, for table entries.
406 * Returns TRUE if an entry was found.
407 * Conditions:
408 * The space must be locked (read or write) throughout.
409 */
410
411boolean_t
412ipc_hash_local_lookup(
413 ipc_space_t space,
414 ipc_object_t obj,
415 mach_port_name_t *namep,
416 ipc_entry_t *entryp)
417{
418 ipc_entry_t table;
419 ipc_entry_num_t size;
420 mach_port_index_t hindex, index;
421
422 assert(space != IS_NULL);
423 assert(obj != IO_NULL);
424
425 table = space->is_table;
426 size = space->is_table_size;
427 hindex = IH_LOCAL_HASH(obj, size);
428
429 /*
430 * Ideally, table[hindex].ie_index is the name we want.
431 * However, must check ie_object to verify this,
432 * because collisions can happen. In case of a collision,
433 * search farther along in the clump.
434 */
435
436 while ((index = table[hindex].ie_index) != 0) {
437 ipc_entry_t entry = &table[index];
438
439 if (entry->ie_object == obj) {
440 *entryp = entry;
441 *namep = MACH_PORT_MAKE(index,
442 IE_BITS_GEN(entry->ie_bits));
443 return TRUE;
444 }
445
446 if (++hindex == size)
447 hindex = 0;
448 }
449
450 return FALSE;
451}
452
453/*
454 * Routine: ipc_hash_local_insert
455 * Purpose:
456 * Inserts an entry into the space's reverse hash table.
457 * Conditions:
458 * The space must be write-locked.
459 */
460
461void
462ipc_hash_local_insert(
463 ipc_space_t space,
464 ipc_object_t obj,
465 mach_port_index_t index,
466 ipc_entry_t entry)
467{
468 ipc_entry_t table;
469 ipc_entry_num_t size;
470 mach_port_index_t hindex;
471
472 assert(index != 0);
473 assert(space != IS_NULL);
474 assert(obj != IO_NULL);
475
476 table = space->is_table;
477 size = space->is_table_size;
478 hindex = IH_LOCAL_HASH(obj, size);
479
480 assert(entry == &table[index]);
481 assert(entry->ie_object == obj);
482
483 /*
484 * We want to insert at hindex, but there may be collisions.
485 * If a collision occurs, search for the end of the clump
486 * and insert there.
487 */
488
489 while (table[hindex].ie_index != 0) {
490 if (++hindex == size)
491 hindex = 0;
492 }
493
494 table[hindex].ie_index = index;
495}
496
497/*
498 * Routine: ipc_hash_local_delete
499 * Purpose:
500 * Deletes an entry from the space's reverse hash table.
501 * Conditions:
502 * The space must be write-locked.
503 */
504
505void
506ipc_hash_local_delete(
507 ipc_space_t space,
508 ipc_object_t obj,
509 mach_port_index_t index,
510 ipc_entry_t entry)
511{
512 ipc_entry_t table;
513 ipc_entry_num_t size;
514 mach_port_index_t hindex, dindex;
515
516 assert(index != MACH_PORT_NULL);
517 assert(space != IS_NULL);
518 assert(obj != IO_NULL);
519
520 table = space->is_table;
521 size = space->is_table_size;
522 hindex = IH_LOCAL_HASH(obj, size);
523
524 assert(entry == &table[index]);
525 assert(entry->ie_object == obj);
526
527 /*
528 * First check we have the right hindex for this index.
529 * In case of collision, we have to search farther
530 * along in this clump.
531 */
532
533 while (table[hindex].ie_index != index) {
534 if (++hindex == size)
535 hindex = 0;
536 }
537
538 /*
539 * Now we want to set table[hindex].ie_index = 0.
540 * But if we aren't the last index in a clump,
541 * this might cause problems for lookups of objects
542 * farther along in the clump that are displaced
543 * due to collisions. Searches for them would fail
544 * at hindex instead of succeeding.
545 *
546 * So we must check the clump after hindex for objects
547 * that are so displaced, and move one up to the new hole.
548 *
549 * hindex - index of new hole in the clump
550 * dindex - index we are checking for a displaced object
551 *
552 * When we move a displaced object up into the hole,
553 * it creates a new hole, and we have to repeat the process
554 * until we get to the end of the clump.
555 */
556
557 for (dindex = hindex; index != 0; hindex = dindex) {
558 for (;;) {
559 mach_port_index_t tindex;
560 ipc_object_t tobj;
561
562 if (++dindex == size)
563 dindex = 0;
564 assert(dindex != hindex);
565
566 /* are we at the end of the clump? */
567
568 index = table[dindex].ie_index;
569 if (index == 0)
570 break;
571
572 /* is this a displaced object? */
573
574 tobj = table[index].ie_object;
575 assert(tobj != IO_NULL);
576 tindex = IH_LOCAL_HASH(tobj, size);
577
578 if ((dindex < hindex) ?
579 ((dindex < tindex) && (tindex <= hindex)) :
580 ((dindex < tindex) || (tindex <= hindex)))
581 break;
582 }
583
584 table[hindex].ie_index = index;
585 }
586}
587
588/*
589 * Routine: ipc_hash_init
590 * Purpose:
591 * Initialize the reverse hash table implementation.
592 */
593
594void
595ipc_hash_init(void)
596{
597 ipc_hash_index_t i;
598
599 /* if not configured, initialize ipc_hash_global_size */
600
601 if (ipc_hash_global_size == 0) {
602 ipc_hash_global_size = ipc_tree_entry_max >> 8;
603 if (ipc_hash_global_size < 32)
604 ipc_hash_global_size = 32;
605 }
606
607 /* make sure it is a power of two */
608
609 ipc_hash_global_mask = ipc_hash_global_size - 1;
610 if ((ipc_hash_global_size & ipc_hash_global_mask) != 0) {
611 natural_t bit;
612
613 /* round up to closest power of two */
614
615 for (bit = 1;; bit <<= 1) {
616 ipc_hash_global_mask |= bit;
617 ipc_hash_global_size = ipc_hash_global_mask + 1;
618
619 if ((ipc_hash_global_size & ipc_hash_global_mask) == 0)
620 break;
621 }
622 }
623
624 /* allocate ipc_hash_global_table */
625
626 ipc_hash_global_table = (ipc_hash_global_bucket_t)
627 kalloc((vm_size_t) (ipc_hash_global_size *
628 sizeof(struct ipc_hash_global_bucket)));
629 assert(ipc_hash_global_table != IHGB_NULL);
630
631 /* and initialize it */
632
633 for (i = 0; i < ipc_hash_global_size; i++) {
634 ipc_hash_global_bucket_t bucket;
635
636 bucket = &ipc_hash_global_table[i];
637 ihgb_lock_init(bucket);
638 bucket->ihgb_head = ITE_NULL;
639 }
640}
641
642#if MACH_IPC_DEBUG
643
644/*
645 * Routine: ipc_hash_info
646 * Purpose:
647 * Return information about the global reverse hash table.
648 * Fills the buffer with as much information as possible
649 * and returns the desired size of the buffer.
650 * Conditions:
651 * Nothing locked. The caller should provide
652 * possibly-pageable memory.
653 */
654
655
656ipc_hash_index_t
657ipc_hash_info(
658 hash_info_bucket_t *info,
659 mach_msg_type_number_t count)
660{
661 ipc_hash_index_t i;
662
663 if (ipc_hash_global_size < count)
664 count = ipc_hash_global_size;
665
666 for (i = 0; i < count; i++) {
667 ipc_hash_global_bucket_t bucket = &ipc_hash_global_table[i];
668 unsigned int bucket_count = 0;
669 ipc_tree_entry_t entry;
670
671 ihgb_lock(bucket);
672 for (entry = bucket->ihgb_head;
673 entry != ITE_NULL;
674 entry = entry->ite_next)
675 bucket_count++;
676 ihgb_unlock(bucket);
677
678 /* don't touch pageable memory while holding locks */
679 info[i].hib_count = bucket_count;
680 }
681
682 return ipc_hash_global_size;
683}
684
685#endif /* MACH_IPC_DEBUG */