xref: /illumos-gate/usr/src/uts/common/vm/hat_refmod.c (revision 76716eaced8d7659d4594350eb3f343c31fe2806)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * The following routines implement the hat layer's
31  * recording of the referenced and modified bits.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/debug.h>
38 #include <sys/kmem.h>
39 
40 /*
41  * Note, usage of cmn_err requires you not hold any hat layer locks.
42  */
43 #include <sys/cmn_err.h>
44 
45 #include <vm/as.h>
46 #include <vm/hat.h>
47 
48 kmutex_t hat_statlock;		/* protects all hat statistics data */
49 struct hrmstat *hrm_memlist;	/* tracks memory alloced for hrm_blist blocks */
50 struct hrmstat **hrm_hashtab;	/* hash table for finding blocks quickly */
51 struct hrmstat *hrm_blist;
52 int hrm_blist_incr = HRM_BLIST_INCR;
53 int hrm_blist_lowater = HRM_BLIST_INCR/2;
54 int hrm_blist_num = 0;
55 int hrm_blist_total = 0;
56 int hrm_mlockinited = 0;
57 int hrm_allocfailmsg = 0;	/* print a message when allocations fail */
58 int hrm_allocfail = 0;
59 
60 static struct hrmstat	*hrm_balloc(void);
61 static void	hrm_init(void);
62 static void	hrm_link(struct hrmstat *);
63 static void	hrm_setbits(struct hrmstat *, caddr_t, uint_t);
64 static void	hrm_hashout(struct hrmstat *);
65 static void	hrm_getblk(int);
66 
67 #define	hrm_hash(as, addr) \
68 	(HRM_HASHMASK & \
69 	(((uintptr_t)(addr) >> HRM_BASESHIFT) ^ ((uintptr_t)(as) >> 2)))
70 
71 #define	hrm_match(hrm, as, addr) \
72 	(((hrm)->hrm_as == (as) && \
73 	((hrm)->hrm_base == ((uintptr_t)(addr) & HRM_BASEMASK))) ? 1 : 0)
74 
75 /*
76  * reserve enough statistic blocks for
77  * chunk of bytes (pages) in a given as.
78  */
79 /* ARGSUSED */
80 void
81 hat_resvstat(size_t chunk, struct as *as, caddr_t addr)
82 {
83 	int nhrm = btop(chunk)/HRM_PAGES;
84 
85 	if (nhrm < HRM_BLIST_INCR)
86 		nhrm = 0;	/* preallocate at least HRM_BLIST_INCR */
87 	hrm_getblk(nhrm);
88 }
89 
90 /*
91  * Start the statistics gathering for an address space.
92  * Return -1 if we can't do it, otherwise return an opaque
93  * identifier to be used when querying for the gathered statistics.
94  * The identifier is an unused bit in a_vbits.
95  * Bit 0 is reserved for swsmon.
96  */
97 int
98 hat_startstat(struct as *as)
99 {
100 	uint_t nbits;		/* number of bits */
101 	uint_t bn;		/* bit number */
102 	uint_t id;		/* new vbit, identifier */
103 	uint_t vbits;		/* used vbits of address space */
104 	size_t chunk;		/* mapped size for stats */
105 	/*
106 	 * Initialize global data, if needed.
107 	 */
108 	hrm_init();
109 
110 	/*
111 	 * If the refmod saving memory allocator runs out, print
112 	 * a warning message about how to fix it, see comment at
113 	 * the beginning of hat_setstat.
114 	 */
115 	if (hrm_allocfailmsg) {
116 		cmn_err(CE_WARN,
117 		    "hrm_balloc failures occured, increase hrm_blist_incr");
118 		hrm_allocfailmsg = 0;
119 	}
120 
121 	/*
122 	 * Verify that a buffer of statistics blocks exists
123 	 * and allocate more, if needed.
124 	 */
125 
126 	chunk = hat_get_mapped_size(as->a_hat);
127 	chunk = (btop(chunk)/HRM_PAGES);
128 	if (chunk < HRM_BLIST_INCR)
129 		chunk = 0;
130 
131 	hrm_getblk((int)chunk);
132 
133 	/*
134 	 * Find a unused id in the given address space.
135 	 */
136 	hat_enter(as->a_hat);
137 	vbits = as->a_vbits;
138 	nbits = sizeof (as->a_vbits) * NBBY;
139 	for (bn = 1, id = 2; bn < (nbits - 1); bn++, id <<= 1)
140 		if ((id & vbits) == 0)
141 			break;
142 	if (bn >= (nbits - 1)) {
143 		hat_exit(as->a_hat);
144 		return (-1);
145 	}
146 	as->a_vbits |= id;
147 	hat_exit(as->a_hat);
148 	(void) hat_stats_enable(as->a_hat);
149 	return (id);
150 }
151 
152 /*
153  * Record referenced and modified information for an address space.
154  * Rmbits is a word containing the referenced bit in bit position 1
155  * and the modified bit in bit position 0.
156  *
157  * For current informational uses, one can rerun any program using
158  * this facility after modifying the hrm_blist_incr to be a larger
159  * amount so that a larger buffer of blocks will be maintained.
160  */
161 void
162 hat_setstat(struct as *as, caddr_t addr, size_t len, uint_t rmbits)
163 {
164 	struct hrmstat	*hrm;
165 	uint_t		vbits, newbits, nb;
166 	int		h;
167 
168 	ASSERT(len == PAGESIZE);
169 	ASSERT((rmbits & ~(P_MOD|P_REF)) == 0);
170 
171 	if (rmbits == 0)
172 		return;
173 
174 	/*
175 	 * Initialize global data, if needed.
176 	 */
177 	hrm_init();
178 
179 	mutex_enter(&hat_statlock);
180 
181 	/*
182 	 * The previous owner of hat_statlock could have been
183 	 * hat_freestat(). Check whether hrm_hashtab is NULL, if it is,
184 	 * we bail out.
185 	 */
186 	if (hrm_hashtab == NULL) {
187 		mutex_exit(&hat_statlock);
188 		return;
189 	}
190 
191 	/*
192 	 * Search the hash list for the as and addr we are looking for
193 	 * and set the ref and mod bits in every block that matches.
194 	 */
195 	vbits = 0;
196 	h = hrm_hash(as, addr);
197 	for (hrm = hrm_hashtab[h]; hrm; hrm = hrm->hrm_hnext) {
198 		if (hrm_match(hrm, as, addr)) {
199 			hrm_setbits(hrm, addr, rmbits);
200 			vbits |= hrm->hrm_id;
201 		}
202 	}
203 
204 	/*
205 	 * If we didn't find a block for all of the enabled
206 	 * vpages bits, then allocate and initialize a block
207 	 * for each bit that was not found.
208 	 */
209 	if (vbits != as->a_vbits) {
210 		newbits = vbits ^ as->a_vbits;
211 		while (newbits) {
212 			if (ffs(newbits))
213 				nb = 1 << (ffs(newbits)-1);
214 			hrm = (struct hrmstat *)hrm_balloc();
215 			if (hrm == NULL) {
216 				hrm_allocfailmsg = 1;
217 				hrm_allocfail++;
218 				mutex_exit(&hat_statlock);
219 				return;
220 			}
221 			hrm->hrm_as = as;
222 			hrm->hrm_base = (uintptr_t)addr & HRM_BASEMASK;
223 			hrm->hrm_id = nb;
224 			hrm_link(hrm);
225 			hrm_setbits(hrm, addr, rmbits);
226 			newbits &= ~nb;
227 		}
228 	}
229 	mutex_exit(&hat_statlock);
230 }
231 
232 /*
233  * Free the resources used to maintain the referenced and modified
234  * statistics for the virtual page view of an address space
235  * identified by id.
236  */
237 void
238 hat_freestat(struct as *as, int id)
239 {
240 	struct hrmstat *hrm, *prev_ahrm;
241 
242 	hat_stats_disable(as->a_hat);	/* tell the hat layer to stop */
243 	hat_enter(as->a_hat);
244 	if (id == 0)
245 		as->a_vbits = 0;
246 	else
247 		as->a_vbits &= ~id;
248 
249 	if ((hrm = as->a_hrm) == NULL) {
250 		hat_exit(as->a_hat);
251 		return;
252 	}
253 	hat_exit(as->a_hat);
254 
255 	mutex_enter(&hat_statlock);
256 	if (hrm_hashtab == NULL) {
257 		/* can't happen? */
258 		mutex_exit(&hat_statlock);
259 		return;
260 	}
261 	for (prev_ahrm = NULL; hrm; hrm = hrm->hrm_anext) {
262 		if ((id == hrm->hrm_id) || (id == NULL)) {
263 
264 			hrm_hashout(hrm);
265 			hrm->hrm_hnext = hrm_blist;
266 			hrm_blist = hrm;
267 			hrm_blist_num++;
268 
269 			if (prev_ahrm == NULL)
270 				as->a_hrm = hrm->hrm_anext;
271 			else
272 				prev_ahrm->hrm_anext = hrm->hrm_anext;
273 
274 		} else
275 			prev_ahrm = hrm;
276 	}
277 
278 	/*
279 	 * If all statistics blocks are free,
280 	 * return the memory to the system.
281 	 */
282 	if (hrm_blist_num == hrm_blist_total) {
283 		/* zero the block list since we are giving back its memory */
284 		hrm_blist = NULL;
285 		hrm_blist_num = 0;
286 		hrm_blist_total = 0;
287 		while (hrm_memlist) {
288 			hrm = hrm_memlist;
289 			hrm_memlist = hrm->hrm_hnext;
290 			kmem_free(hrm, hrm->hrm_base);
291 		}
292 		ASSERT(hrm_memlist == NULL);
293 		kmem_free(hrm_hashtab, HRM_HASHSIZE * sizeof (char *));
294 		hrm_hashtab = NULL;
295 	}
296 	mutex_exit(&hat_statlock);
297 }
298 
299 /*
300  * Initialize any global state for the statistics handling.
301  * Hrm_lock protects the globally allocted memory:
302  *	hrm_memlist and hrm_hashtab.
303  */
304 static void
305 hrm_init(void)
306 {
307 	/*
308 	 * Alloacte the hashtable if it doesn't exist yet.
309 	 */
310 	mutex_enter(&hat_statlock);
311 	if (hrm_hashtab == NULL)
312 		hrm_hashtab =
313 			kmem_zalloc(HRM_HASHSIZE * sizeof (char *), KM_SLEEP);
314 	mutex_exit(&hat_statlock);
315 }
316 
317 /*
318  * Grab memory for statistics gathering of the hat layer.
319  */
320 static void
321 hrm_getblk(int chunk)
322 {
323 	struct hrmstat *hrm, *l;
324 	int i;
325 	int hrm_incr;
326 
327 	mutex_enter(&hat_statlock);
328 	if ((hrm_blist == NULL) ||
329 	    (hrm_blist_num <= hrm_blist_lowater) ||
330 	    (chunk && (hrm_blist_num < chunk))) {
331 
332 		mutex_exit(&hat_statlock);
333 
334 		hrm_incr = chunk? chunk : hrm_blist_incr;
335 		hrm = kmem_zalloc(sizeof (struct hrmstat) * hrm_incr, KM_SLEEP);
336 		hrm->hrm_base = sizeof (struct hrmstat) * hrm_incr;
337 
338 		/*
339 		 * thread the allocated blocks onto a freelist
340 		 * using the first block to hold information for
341 		 * freeing them all later
342 		 */
343 		mutex_enter(&hat_statlock);
344 		hrm->hrm_hnext = hrm_memlist;
345 		hrm_memlist = hrm;
346 
347 		hrm_blist_total += (hrm_incr - 1);
348 		for (i = 1; i < hrm_incr; i++) {
349 			l = &hrm[i];
350 			l->hrm_hnext = hrm_blist;
351 			hrm_blist = l;
352 			hrm_blist_num++;
353 		}
354 	}
355 	mutex_exit(&hat_statlock);
356 }
357 
358 static void
359 hrm_hashin(struct hrmstat *hrm)
360 {
361 	int 		h;
362 
363 	ASSERT(MUTEX_HELD(&hat_statlock));
364 	h = hrm_hash(hrm->hrm_as, hrm->hrm_base);
365 
366 	hrm->hrm_hnext = hrm_hashtab[h];
367 	hrm_hashtab[h] = hrm;
368 }
369 
370 static void
371 hrm_hashout(struct hrmstat *hrm)
372 {
373 	struct hrmstat	*list, **prev_hrm;
374 	int		h;
375 
376 	ASSERT(MUTEX_HELD(&hat_statlock));
377 	h = hrm_hash(hrm->hrm_as, hrm->hrm_base);
378 	list = hrm_hashtab[h];
379 	prev_hrm = &hrm_hashtab[h];
380 
381 	while (list) {
382 		if (list == hrm) {
383 			*prev_hrm = list->hrm_hnext;
384 			return;
385 		}
386 		prev_hrm = &list->hrm_hnext;
387 		list = list->hrm_hnext;
388 	}
389 }
390 
391 
392 /*
393  * Link a statistic block into an address space and also put it
394  * on the hash list for future references.
395  */
396 static void
397 hrm_link(struct hrmstat *hrm)
398 {
399 	struct as *as = hrm->hrm_as;
400 
401 	ASSERT(MUTEX_HELD(&hat_statlock));
402 	hrm->hrm_anext = as->a_hrm;
403 	as->a_hrm = hrm;
404 	hrm_hashin(hrm);
405 }
406 
407 /*
408  * Allocate a block for statistics keeping.
409  * Returns NULL if blocks are unavailable.
410  */
411 static struct hrmstat *
412 hrm_balloc(void)
413 {
414 	struct hrmstat *hrm;
415 
416 	ASSERT(MUTEX_HELD(&hat_statlock));
417 
418 	hrm = hrm_blist;
419 	if (hrm != NULL) {
420 		hrm_blist = hrm->hrm_hnext;
421 		hrm_blist_num--;
422 		hrm->hrm_hnext = NULL;
423 	}
424 	return (hrm);
425 }
426 
427 /*
428  * Set the ref and mod bits for addr within statistics block hrm.
429  */
430 static void
431 hrm_setbits(struct hrmstat *hrm, caddr_t addr, uint_t bits)
432 {
433 	uint_t po, bo, spb;
434 	uint_t nbits;
435 
436 	po = ((uintptr_t)addr & HRM_BASEOFFSET) >> MMU_PAGESHIFT; /* pg off */
437 	bo = po / (NBBY / 2);			/* which byte in bit array */
438 	spb = (3 - (po & 3)) * 2;		/* shift position within byte */
439 	nbits = bits << spb;			/* bit mask */
440 	hrm->hrm_bits[bo] |= nbits;
441 }
442 
443 /*
444  * Return collected statistics about an address space.
445  * If clearflag is set, atomically read and zero the bits.
446  *
447  * Fill in the data array supplied with the referenced and
448  * modified bits collected for address range [addr ... addr + len]
449  * in address space, as, uniquely identified by id.
450  * The destination is a byte array.  We fill in three bits per byte:
451  * referenced, modified, and hwmapped bits.
452  * Kernel only interface, can't fault on destination data array.
453  *
454  */
455 void
456 hat_getstat(struct as *as, caddr_t addr, size_t len, uint_t id,
457     caddr_t datap, int clearflag)
458 {
459 	size_t	np;		/* number of pages */
460 	caddr_t	a;
461 	char 	*dp;
462 
463 	np = btop(len);
464 	bzero(datap, np);
465 
466 	hat_sync(as->a_hat, addr, len, clearflag);
467 
468 	/* allocate more statistics blocks if needed */
469 	hrm_getblk(0);
470 
471 	mutex_enter(&hat_statlock);
472 	if (hrm_hashtab == NULL) {
473 		/* can happen when victim process exits */
474 		mutex_exit(&hat_statlock);
475 		return;
476 	}
477 	dp = datap;
478 	a = (caddr_t)((uintptr_t)addr & (uintptr_t)PAGEMASK);
479 	while (a < addr + len) {
480 		struct hrmstat	*hrm;
481 		size_t	n;		/* number of pages, temp */
482 		int	h;		/* hash index */
483 		uint_t	po;
484 
485 		h = hrm_hash(as, a);
486 		n = (HRM_PAGES -
487 			(((uintptr_t)a & HRM_PAGEMASK) >> MMU_PAGESHIFT));
488 		if (n > np)
489 			n = np;
490 		po = ((uintptr_t)a & HRM_BASEOFFSET) >> MMU_PAGESHIFT;
491 
492 		for (hrm = hrm_hashtab[h]; hrm; hrm = hrm->hrm_hnext) {
493 			if (hrm->hrm_as == as &&
494 			    hrm->hrm_base == ((uintptr_t)a & HRM_BASEMASK) &&
495 			    id == hrm->hrm_id) {
496 				int i, nr;
497 				uint_t bo, spb;
498 
499 				/*
500 				 * Extract leading unaligned bits.
501 				 */
502 				i = 0;
503 				while (i < n && (po & 3)) {
504 					bo = po / (NBBY / 2);
505 					spb = (3 - (po & 3)) * 2;
506 					*dp++ |= (hrm->hrm_bits[bo] >> spb) & 3;
507 					if (clearflag)
508 						hrm->hrm_bits[bo] &= ~(3<<spb);
509 					po++;
510 					i++;
511 				}
512 				/*
513 				 * Extract aligned bits.
514 				 */
515 				nr = n/4*4;
516 				bo = po / (NBBY / 2);
517 				while (i < nr) {
518 					int bits = hrm->hrm_bits[bo];
519 					*dp++ |= (bits >> 6) & 3;
520 					*dp++ |= (bits >> 4) & 3;
521 					*dp++ |= (bits >> 2) & 3;
522 					*dp++ |= (bits >> 0) & 3;
523 					if (clearflag)
524 						hrm->hrm_bits[bo] = 0;
525 					bo++;
526 					po += 4;
527 					i += 4;
528 				}
529 				/*
530 				 * Extract trailing unaligned bits.
531 				 */
532 				while (i < n) {
533 					bo = po / (NBBY / 2);
534 					spb = (3 - (po & 3)) * 2;
535 					*dp++ |= (hrm->hrm_bits[bo] >> spb) & 3;
536 					if (clearflag)
537 						hrm->hrm_bits[bo] &= ~(3<<spb);
538 					po++;
539 					i++;
540 				}
541 
542 				break;
543 			}
544 		}
545 		if (hrm == NULL)
546 			dp += n;
547 		np -= n;
548 		a += n * MMU_PAGESIZE;
549 	}
550 	mutex_exit(&hat_statlock);
551 }
552