xref: /xnu-12377.81.4/osfmk/ipc/ipc_hash.c (revision 043036a2b3718f7f0be807e2870f8f47d3fa0796)
1 /*
2  * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
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.
14  *
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
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
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.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 /*
29  * @OSF_COPYRIGHT@
30  */
31 /*
32  * Mach Operating System
33  * Copyright (c) 1991,1990,1989 Carnegie Mellon University
34  * All Rights Reserved.
35  *
36  * Permission to use, copy, modify and distribute this software and its
37  * documentation is hereby granted, provided that both the copyright
38  * notice and this permission notice appear in all copies of the
39  * software, derivative works or modified versions, and any portions
40  * thereof, and that both notices appear in supporting documentation.
41  *
42  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
43  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
44  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
45  *
46  * Carnegie Mellon requests users of this software to return to
47  *
48  *  Software Distribution Coordinator  or  [email protected]
49  *  School of Computer Science
50  *  Carnegie Mellon University
51  *  Pittsburgh PA 15213-3890
52  *
53  * any improvements or extensions that they make and grant Carnegie Mellon
54  * the rights to redistribute these changes.
55  */
56 /*
57  */
58 /*
59  *	File:	ipc/ipc_hash.c
60  *	Author:	Rich Draves
61  *	Date:	1989
62  *
63  *	Entry hash table operations.
64  */
65 
66 #include <mach/boolean.h>
67 #include <mach/port.h>
68 #include <ipc/port.h>
69 #include <ipc/ipc_space.h>
70 #include <ipc/ipc_object.h>
71 #include <ipc/ipc_entry.h>
72 #include <ipc/ipc_hash.h>
73 #include <os/hash.h>
74 
75 #include <mach/kern_return.h>
76 #include <mach_debug/hash_info.h>
77 #include <vm/vm_map.h>
78 #include <vm/vm_kern.h>
79 
80 /*
81  * Forward declarations
82  */
83 
84 /* Delete an entry from the local reverse hash table */
85 void ipc_hash_local_delete(
86 	ipc_space_t             space,
87 	ipc_object_t            obj,
88 	mach_port_index_t       index,
89 	ipc_entry_t             entry);
90 
91 /*
92  *	Routine:	ipc_hash_lookup
93  *	Purpose:
94  *		Converts (space, obj) -> (name, entry).
95  *		Returns TRUE if an entry was found.
96  *	Conditions:
97  *		The space must be locked (read or write) throughout.
98  */
99 
100 boolean_t
ipc_hash_lookup(ipc_space_t space,ipc_object_t obj,mach_port_name_t * namep,ipc_entry_t * entryp)101 ipc_hash_lookup(
102 	ipc_space_t             space,
103 	ipc_object_t            obj,
104 	mach_port_name_t        *namep,
105 	ipc_entry_t             *entryp)
106 {
107 	return ipc_hash_table_lookup(is_active_table(space), obj, namep, entryp);
108 }
109 
110 /*
111  *	Routine:	ipc_hash_insert
112  *	Purpose:
113  *		Inserts an entry into the appropriate reverse hash table,
114  *		so that ipc_hash_lookup will find it.
115  *	Conditions:
116  *		The space must be write-locked.
117  */
118 
119 void
ipc_hash_insert(ipc_space_t space,ipc_object_t obj,mach_port_name_t name,ipc_entry_t entry)120 ipc_hash_insert(
121 	ipc_space_t             space,
122 	ipc_object_t            obj,
123 	mach_port_name_t        name,
124 	ipc_entry_t             entry)
125 {
126 	mach_port_index_t index;
127 
128 	index = MACH_PORT_INDEX(name);
129 	space->is_table_hashed++;
130 	ipc_hash_table_insert(is_active_table(space), obj, index, entry);
131 }
132 
133 /*
134  *	Routine:	ipc_hash_delete
135  *	Purpose:
136  *		Deletes an entry from the appropriate reverse hash table.
137  *	Conditions:
138  *		The space must be write-locked.
139  */
140 
141 void
ipc_hash_delete(ipc_space_t space,ipc_object_t obj,mach_port_name_t name,ipc_entry_t entry)142 ipc_hash_delete(
143 	ipc_space_t             space,
144 	ipc_object_t            obj,
145 	mach_port_name_t        name,
146 	ipc_entry_t             entry)
147 {
148 	mach_port_index_t index;
149 
150 	index = MACH_PORT_INDEX(name);
151 	space->is_table_hashed--;
152 	ipc_hash_table_delete(is_active_table(space), obj, index, entry);
153 }
154 
155 /*
156  *	Each space has a local reverse hash table, which holds
157  *	entries from the space's table.  In fact, the hash table
158  *	just uses a field (ie_index) in the table itself.
159  *
160  *	The local hash table is an open-addressing hash table,
161  *	which means that when a collision occurs, instead of
162  *	throwing the entry into a bucket, the entry is rehashed
163  *	to another position in the table.  In this case the rehash
164  *	is very simple: linear probing (ie, just increment the position).
165  *	This simple rehash makes deletions tractable (they're still a pain),
166  *	but it means that collisions tend to build up into clumps.
167  *
168  *	Because at least one entry in the table (index 0) is always unused,
169  *	there will always be room in the reverse hash table.  If a table
170  *	with n slots gets completely full, the reverse hash table will
171  *	have one giant clump of n-1 slots and one free slot somewhere.
172  *	Because entries are only entered into the reverse table if they
173  *	are pure send rights (not receive, send-once, port-set,
174  *	or dead-name rights), and free entries of course aren't entered,
175  *	I expect the reverse hash table won't get unreasonably full.
176  *
177  *	Ordered hash tables (Amble & Knuth, Computer Journal, v. 17, no. 2,
178  *	pp. 135-142.) may be desirable here.  They can dramatically help
179  *	unsuccessful lookups.  But unsuccessful lookups are almost always
180  *	followed by insertions, and those slow down somewhat.  They
181  *	also can help deletions somewhat.  Successful lookups aren't affected.
182  *	So possibly a small win; probably nothing significant.
183  */
184 
185 #define IH_TABLE_HASH(obj, size)                                \
186 	        ((mach_port_index_t)(os_hash_kernel_pointer(obj) % (size)))
187 
188 /*
189  *	Routine:	ipc_hash_table_lookup
190  *	Purpose:
191  *		Converts (table, obj) -> (name, entry).
192  *	Conditions:
193  *		Must have read consistency on the table.
194  */
195 
196 boolean_t
ipc_hash_table_lookup(ipc_entry_table_t array,ipc_object_t obj,mach_port_name_t * namep,ipc_entry_t * entryp)197 ipc_hash_table_lookup(
198 	ipc_entry_table_t       array,
199 	ipc_object_t            obj,
200 	mach_port_name_t        *namep,
201 	ipc_entry_t             *entryp)
202 {
203 	mach_port_index_t hindex, index, hdist;
204 	ipc_entry_t       table = ipc_entry_table_base(array);
205 	ipc_entry_num_t   size  = ipc_entry_table_count(array);
206 
207 	if (obj == IPC_OBJECT_NULL) {
208 		return FALSE;
209 	}
210 
211 	hindex = IH_TABLE_HASH(obj, size);
212 	hdist  = 0;
213 
214 	/*
215 	 *	Ideally, table[hindex].ie_index is the name we want.
216 	 *	However, must check ie_object to verify this,
217 	 *	because collisions can happen.  In case of a collision,
218 	 *	search farther along in the clump.
219 	 */
220 
221 	while ((index = table[hindex].ie_index) != 0) {
222 		ipc_entry_t entry = index < size ? &table[index] : IE_NULL;
223 
224 		/*
225 		 * if our current displacement is strictly larger
226 		 * than the current slot one, then insertion would
227 		 * have stolen his place so we can't possibly exist.
228 		 */
229 		if (hdist > table[hindex].ie_dist) {
230 			return FALSE;
231 		}
232 
233 		/*
234 		 * If our current displacement is exactly the current
235 		 * slot displacement, then it can be a match, let's check.
236 		 */
237 		if (hdist == table[hindex].ie_dist) {
238 			if (entry->ie_object == obj) {
239 				*entryp = entry;
240 				*namep = MACH_PORT_MAKE(index,
241 				    IE_BITS_GEN(entry->ie_bits));
242 				return TRUE;
243 			}
244 		} else {
245 			assert(entry->ie_object != obj);
246 		}
247 
248 		if (hdist < IPC_ENTRY_DIST_MAX) {
249 			/* peg the displacement distance at IPC_ENTRY_DIST_MAX */
250 			++hdist;
251 		}
252 		if (++hindex == size) {
253 			hindex = 0;
254 		}
255 	}
256 
257 	return FALSE;
258 }
259 
260 /*
261  *	Routine:	ipc_hash_table_insert
262  *	Purpose:
263  *		Inserts an entry into the space's reverse hash table.
264  *	Conditions:
265  *		The space must be write-locked.
266  */
267 
268 void
ipc_hash_table_insert(ipc_entry_table_t array,ipc_object_t obj,mach_port_index_t index,__assert_only ipc_entry_t entry)269 ipc_hash_table_insert(
270 	ipc_entry_table_t               array,
271 	ipc_object_t                    obj,
272 	mach_port_index_t               index,
273 	__assert_only ipc_entry_t       entry)
274 {
275 	mach_port_index_t hindex, hdist;
276 	ipc_entry_t       table = ipc_entry_table_base(array);
277 	ipc_entry_num_t   size  = ipc_entry_table_count(array);
278 
279 	assert(index != 0);
280 	assert(obj != IPC_OBJECT_NULL);
281 
282 	hindex = IH_TABLE_HASH(obj, size);
283 	hdist  = 0;
284 
285 	assert(entry == &table[index]);
286 	assert(entry->ie_object == obj);
287 
288 	/*
289 	 *	We want to insert at hindex, but there may be collisions.
290 	 *	If a collision occurs, search for the end of the clump
291 	 *	and insert there.
292 	 *
293 	 *	However, Robin Hood steals from the rich, and as we go
294 	 *	through the clump, if we go over an item that is less
295 	 *	displaced than we'd be, we steal his slot and
296 	 *	keep inserting him in our stead.
297 	 */
298 	while (table[hindex].ie_index != 0) {
299 		if (table[hindex].ie_dist < hdist) {
300 #define swap(a, b)  ({ typeof(a) _tmp = (b); (b) = (a); (a) = _tmp; })
301 			swap(hdist, table[hindex].ie_dist);
302 			swap(index, table[hindex].ie_index);
303 #undef swap
304 		}
305 		if (hdist < IPC_ENTRY_DIST_MAX) {
306 			/* peg the displacement distance at IPC_ENTRY_DIST_MAX */
307 			++hdist;
308 		}
309 		if (++hindex == size) {
310 			hindex = 0;
311 		}
312 	}
313 
314 	table[hindex].ie_index = index;
315 	table[hindex].ie_dist = hdist;
316 }
317 
318 /*
319  *	Routine:	ipc_hash_table_delete
320  *	Purpose:
321  *		Deletes an entry from the table's reverse hash.
322  *	Conditions:
323  *		Exclusive access to the table.
324  */
325 
326 void
ipc_hash_table_delete(ipc_entry_table_t array,ipc_object_t obj,mach_port_index_t index,__assert_only ipc_entry_t entry)327 ipc_hash_table_delete(
328 	ipc_entry_table_t               array,
329 	ipc_object_t                    obj,
330 	mach_port_index_t               index,
331 	__assert_only ipc_entry_t       entry)
332 {
333 	mach_port_index_t hindex, dindex, dist;
334 	ipc_entry_t       table = ipc_entry_table_base(array);
335 	ipc_entry_num_t   size  = ipc_entry_table_count(array);
336 
337 	assert(index != MACH_PORT_NULL);
338 	assert(obj != IPC_OBJECT_NULL);
339 
340 	hindex = IH_TABLE_HASH(obj, size);
341 
342 	assert(entry == &table[index]);
343 	assert(entry->ie_object == obj);
344 
345 	/*
346 	 *	First check we have the right hindex for this index.
347 	 *	In case of collision, we have to search farther
348 	 *	along in this clump.
349 	 */
350 
351 	while (table[hindex].ie_index != index) {
352 		if (++hindex == size) {
353 			hindex = 0;
354 		}
355 	}
356 
357 	/*
358 	 *	Now we want to set table[hindex].ie_index = 0.
359 	 *	But if we aren't the last index in a clump,
360 	 *	this might cause problems for lookups of objects
361 	 *	farther along in the clump that are displaced
362 	 *	due to collisions.  Searches for them would fail
363 	 *	at hindex instead of succeeding.
364 	 *
365 	 *	So we must check the clump after hindex for objects
366 	 *	that are so displaced, and move one up to the new hole.
367 	 *
368 	 *		hindex - index of new hole in the clump
369 	 *		dindex - index we are checking for a displaced object
370 	 *
371 	 *	When we move a displaced object up into the hole,
372 	 *	it creates a new hole, and we have to repeat the process
373 	 *	until we get to the end of the clump.
374 	 */
375 
376 	for (;;) {
377 		dindex = hindex + 1;
378 		if (dindex == size) {
379 			dindex = 0;
380 		}
381 
382 		/*
383 		 * If the next element is empty or isn't displaced,
384 		 * then lookup will end on the next element anyway,
385 		 * so we can leave the hole right here, we're done
386 		 */
387 		index = table[dindex].ie_index;
388 		dist  = table[dindex].ie_dist;
389 		if (index == 0 || dist == 0) {
390 			table[hindex].ie_index = 0;
391 			table[hindex].ie_dist = 0;
392 			return;
393 		}
394 
395 		/*
396 		 * Move this object closer to its own slot by occupying the hole.
397 		 * If its displacement was pegged, recompute it.
398 		 */
399 		if (dist-- == IPC_ENTRY_DIST_MAX) {
400 			uint32_t desired = IH_TABLE_HASH(table[index].ie_object, size);
401 			if (hindex >= desired) {
402 				dist = hindex - desired;
403 			} else {
404 				dist = hindex + size - desired;
405 			}
406 			if (dist > IPC_ENTRY_DIST_MAX) {
407 				dist = IPC_ENTRY_DIST_MAX;
408 			}
409 		}
410 
411 		/*
412 		 * Move the displaced element closer to its ideal bucket,
413 		 * and keep shifting elements back.
414 		 */
415 		table[hindex].ie_index = index;
416 		table[hindex].ie_dist = dist;
417 		hindex = dindex;
418 	}
419 }
420