]> git.saurik.com Git - apple/system_cmds.git/blob - dynamic_pager.tproj/dynamic_pager.c
08a7d1a6a35af7a890127116b22238d2f667a71c
[apple/system_cmds.git] / dynamic_pager.tproj / dynamic_pager.c
1 /* File created by Chris Youngworth, Apple Computer 2/11/99 */
2
3
4 #define mig_external
5
6 #include <mach/port.h>
7 #include <mach/mach_error.h>
8 #include <mach/mach_traps.h>
9 #include <mach/mach.h>
10 #ifndef MACH_BSD
11 #define MACH_BSD
12 #endif
13 #include <mach/mach_syscalls.h>
14 #include <mach/mig_errors.h>
15 #include <sys/param.h>
16 #include <sys/mount.h>
17 #include <sys/file.h>
18 #include <sys/mman.h>
19 #include <sys/stat.h>
20 #include <sys/sysctl.h>
21 #include <sys/gmon.h>
22 #include <errno.h>
23 #include <kvm.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <nlist.h>
29 #include <ctype.h>
30 #include <unistd.h>
31 #include <paths.h>
32
33 #include <default_pager/default_pager_types.h>
34 #include <default_pager_alerts_server.h>
35 #include <backing_store_alerts.h>
36 #include <backing_store_triggers_server.h>
37
38
39 /*
40 * HI_WATER_DEFAULT set to this funny value to
41 * match the size that the low space application
42 * is asking for... need to keep MINIMUM_SIZE
43 * above this value.
44 */
45 #define HI_WATER_DEFAULT 40000000
46 #define MINIMUM_SIZE (1024 * 1024 * 64)
47 #define MAXIMUM_SIZE (1024 * 1024 * 1024)
48
49 #define MAX_LIMITS 8
50
51
52 struct limit {
53 unsigned int size;
54 unsigned int low_water;
55 } limits[MAX_LIMITS];
56
57
58 int debug = 0;
59 int max_valid = 0;
60 int file_count = 0;
61 unsigned int hi_water;
62 unsigned int local_hi_water;
63 int priority = 0;
64 int options = 0;
65 char fileroot[512];
66
67
68 /* global parameters for application notification option */
69 mach_port_t trigger_port = MACH_PORT_NULL;
70 mach_port_t notify_port = MACH_PORT_NULL;
71 unsigned int notify_high = 0;
72 unsigned int bs_recovery;
73
74 /*
75 void setprof __P((struct kvmvars *kvp, int state));
76 void dumpstate __P((struct kvmvars *kvp));
77 void reset __P((struct kvmvars *kvp));
78 */
79
80
81
82 mach_msg_return_t
83 server_alert_loop(
84 mach_msg_size_t max_size,
85 mach_port_t rcv_name,
86 mach_msg_options_t options)
87 {
88 mig_reply_error_t *bufRequest = 0, *bufReply = 0;
89 register mach_msg_return_t mr;
90 register kern_return_t kr;
91
92 if ((kr = vm_allocate(mach_task_self(),
93 (vm_address_t *)&bufRequest,
94 max_size + MAX_TRAILER_SIZE,
95 TRUE)) != KERN_SUCCESS)
96 return kr;
97 if ((kr = vm_protect(mach_task_self(),
98 (vm_address_t)bufRequest,
99 max_size + MAX_TRAILER_SIZE,
100 FALSE, VM_PROT_ALL)) != KERN_SUCCESS)
101 return kr;
102 mlock(bufRequest, max_size + MAX_TRAILER_SIZE);
103 if ((kr = vm_allocate(mach_task_self(),
104 (vm_address_t *)&bufReply,
105 max_size + MAX_TRAILER_SIZE,
106 TRUE)) != KERN_SUCCESS)
107 return kr;
108 if ((kr = vm_protect(mach_task_self(),
109 (vm_address_t)bufReply,
110 max_size + MAX_TRAILER_SIZE,
111 FALSE, VM_PROT_ALL)) != KERN_SUCCESS)
112 return kr;
113 mlock(bufReply, max_size + MAX_TRAILER_SIZE);
114 while(TRUE) {
115 mr = mach_msg(&bufRequest->Head, MACH_RCV_MSG|options,
116 0, max_size, rcv_name,
117 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
118 if (mr == MACH_MSG_SUCCESS) {
119 /* we have a request message */
120
121 if(!(default_pager_alerts_server(
122 &bufRequest->Head, &bufReply->Head)))
123 backing_store_triggers_server(
124 &bufRequest->Head, &bufReply->Head);
125
126 if (!(bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) &&
127 bufReply->RetCode != KERN_SUCCESS) {
128 if (bufReply->RetCode == MIG_NO_REPLY)
129 /*
130 * This return code is a little tricky--
131 * it appears that the demux routine found an
132 * error of some sort, but since that error
133 * would not normally get returned either to
134 * the local user or the remote one, we pretend it's
135 * ok.
136 */
137
138 bufRequest->Head.msgh_remote_port = MACH_PORT_NULL;
139 mach_msg_destroy(&bufRequest->Head);
140 continue;
141 }
142
143 if (bufReply->Head.msgh_remote_port == MACH_PORT_NULL) {
144 /* no reply port, so destroy the reply */
145 if (bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)
146 mach_msg_destroy(&bufReply->Head);
147 } else {
148 break;
149 }
150 } else {
151 break;
152 }
153 }
154
155 (void)vm_deallocate(mach_task_self(),
156 (vm_address_t) bufRequest,
157 max_size + MAX_TRAILER_SIZE);
158 (void)vm_deallocate(mach_task_self(),
159 (vm_address_t) bufReply,
160 max_size + MAX_TRAILER_SIZE);
161 return KERN_FAILURE;
162
163 }
164
165
166 kern_return_t
167 backing_store_triggers(dynamic_pager, hi_wat, flags, port)
168 mach_port_t dynamic_pager;
169 int hi_wat;
170 int flags;
171 mach_port_t port;
172 {
173 int cur_limits;
174
175 if (file_count > max_valid)
176 cur_limits = max_valid;
177 else
178 cur_limits = file_count;
179
180 if((hi_wat + limits[cur_limits].size) > limits[cur_limits].low_water)
181 return KERN_FAILURE; /* let ipc system clean up port */
182
183 /* If there was a previous registration, throw it away */
184 if (notify_port != MACH_PORT_NULL) {
185 mach_port_deallocate(mach_task_self(), notify_port);
186 notify_port = MACH_PORT_NULL;
187 }
188
189 notify_port = port;
190 notify_high = hi_wat;
191 if(hi_water < notify_high) {
192 local_hi_water = notify_high;
193 } else {
194 local_hi_water = hi_water;
195 }
196 if(notify_high > hi_water) {
197 default_pager_space_alert(trigger_port, HI_WAT_ALERT);
198 }
199 return KERN_SUCCESS;
200 }
201
202
203 kern_return_t
204 default_pager_space_alert(alert_port, flags)
205 mach_port_t alert_port;
206 int flags;
207 {
208 char subfile[512];
209 FILE *file_ptr;
210 off_t filesize;
211 int error;
212 kern_return_t ret;
213 int cur_limits;
214 unsigned int cur_size;
215 unsigned int notifications;
216
217
218 if(flags & HI_WAT_ALERT) {
219
220 file_count++;
221
222 if (file_count > max_valid)
223 cur_limits = max_valid;
224 else
225 cur_limits = file_count;
226
227 cur_size = limits[cur_limits].size;
228 filesize = cur_size;
229
230 /*
231 * because the LO_WAT threshold changes relative to
232 * the size of the swap file we're creating
233 * we need to reset the LO_WAT_ALERT threshold each
234 * time we create a new swap file
235 */
236 if (limits[cur_limits].low_water)
237 notifications = HI_WAT_ALERT | LO_WAT_ALERT;
238 else
239 notifications = HI_WAT_ALERT;
240
241 sprintf(subfile, "%s%d", fileroot, file_count);
242 file_ptr = fopen(subfile, "w+");
243 if (file_ptr == NULL) {
244 /* force error recovery below */
245 error = -1;
246 } else {
247 fchmod(fileno(file_ptr), (mode_t)01600);
248 error = fcntl(fileno(file_ptr), F_SETSIZE, &filesize);
249 if(error) {
250 error = ftruncate(fileno(file_ptr), filesize);
251 }
252 if(error)
253 unlink(subfile);
254 fclose(file_ptr);
255 }
256
257 if(error == -1) {
258 file_count--;
259
260 if (file_count > max_valid)
261 cur_limits = max_valid;
262 else
263 cur_limits = file_count;
264
265 if (limits[cur_limits].low_water)
266 notifications = HI_WAT_ALERT | LO_WAT_ALERT;
267 else
268 notifications = HI_WAT_ALERT;
269
270 local_hi_water = local_hi_water>>2;
271 if(notify_high >= (local_hi_water)) {
272 if(notify_port != MACH_PORT_NULL) {
273 /* notify monitoring app of */
274 /* backing store shortage */
275 backing_store_alert(notify_port,
276 HI_WAT_ALERT);
277 mach_port_deallocate(mach_task_self(),
278 notify_port);
279 notify_port = MACH_PORT_NULL;
280 notify_high = 0;
281 }
282 }
283 macx_triggers(local_hi_water, limits[cur_limits].low_water, notifications, alert_port);
284 } else {
285 if(hi_water < notify_high) {
286 if(local_hi_water < notify_high) {
287 bs_recovery = notify_high - local_hi_water;
288 }
289 local_hi_water = notify_high;
290 } else {
291 if(local_hi_water < hi_water) {
292 bs_recovery = hi_water - local_hi_water;
293 }
294 local_hi_water = hi_water;
295 }
296 ret = macx_swapon(subfile, flags, cur_size, priority);
297
298 if(ret) {
299 unlink(subfile);
300 file_count--;
301
302 if (file_count > max_valid)
303 cur_limits = max_valid;
304 else
305 cur_limits = file_count;
306
307 if (limits[cur_limits].low_water)
308 notifications = HI_WAT_ALERT | LO_WAT_ALERT;
309 else
310 notifications = HI_WAT_ALERT;
311
312 local_hi_water = local_hi_water>>2;
313 if(notify_high >= (local_hi_water)) {
314 if(notify_port != MACH_PORT_NULL) {
315 /* notify monitoring app of */
316 /* backing store shortage */
317 backing_store_alert(
318 notify_port,
319 HI_WAT_ALERT);
320 mach_port_deallocate(
321 mach_task_self(),
322 notify_port);
323 notify_port = MACH_PORT_NULL;
324 notify_high = 0;
325 }
326 }
327 macx_triggers(local_hi_water, limits[cur_limits].low_water, notifications, alert_port);
328 } else if(bs_recovery <= cur_size) {
329 if((bs_recovery != 0) && (notify_port)) {
330 backing_store_alert(notify_port,
331 LO_WAT_ALERT);
332 mach_port_deallocate(mach_task_self(),
333 notify_port);
334 notify_port = MACH_PORT_NULL;
335 notify_high = 0;
336 bs_recovery = 0;
337 }
338 } else
339 bs_recovery = bs_recovery-cur_size;
340 }
341 macx_triggers(local_hi_water, limits[cur_limits].low_water, notifications, alert_port);
342 }
343 if(flags & LO_WAT_ALERT) {
344 sprintf(subfile, "%s%d", fileroot, file_count);
345 if(hi_water < notify_high) {
346 local_hi_water = notify_high;
347 } else {
348 local_hi_water = hi_water;
349 }
350 if((bs_recovery != 0) && (notify_port != MACH_PORT_NULL)) {
351 backing_store_alert(notify_port, LO_WAT_ALERT);
352 mach_port_deallocate(mach_task_self(), notify_port);
353 notify_port = MACH_PORT_NULL;
354 notify_high = 0;
355 bs_recovery = 0;
356 }
357 if((error = macx_swapoff(subfile, flags)) == 0) {
358
359 unlink(subfile);
360 file_count--;
361
362 if (file_count > max_valid)
363 cur_limits = max_valid;
364 else
365 cur_limits = file_count;
366 } else {
367 if (file_count > max_valid)
368 cur_limits = max_valid;
369 else
370 cur_limits = file_count;
371 }
372 /*
373 * only need to reset the LO_WAT_ALERT... the HI_WAT size is fixed,
374 * it doesn't change even if the swap file size shrinks or grows
375 */
376 macx_triggers(local_hi_water, limits[cur_limits].low_water, LO_WAT_ALERT, alert_port);
377 }
378 return KERN_SUCCESS;
379 }
380
381 void
382 wait_on_paging_trigger(trigger_port)
383 mach_port_t trigger_port;
384 {
385 kern_return_t result;
386 result = server_alert_loop(4096, trigger_port, MACH_MSG_OPTION_NONE);
387 if (result != KERN_SUCCESS) {
388 fprintf(stderr, "dynamic_pager: default pager alert failed\n");
389 exit(EXIT_FAILURE);
390 }
391 exit(EXIT_SUCCESS);
392 }
393
394 void
395 paging_setup(flags, size, priority, low, high, encrypted)
396 int flags;
397 int size;
398 int priority;
399 int low;
400 int high;
401 boolean_t encrypted;
402 {
403 off_t filesize = size;
404 char subfile[512];
405 FILE *file_ptr;
406 int error;
407
408 file_count = 0;
409 sprintf(subfile, "%s%d", fileroot, file_count);
410 file_ptr = fopen(subfile, "w+");
411 if (file_ptr == NULL) {
412 fprintf(stderr, "dynamic_pager: cannot create paging file %s!\n",
413 subfile);
414 exit(EXIT_FAILURE);
415 }
416 fchmod(fileno(file_ptr), (mode_t)01600);
417
418 error = fcntl(fileno(file_ptr), F_SETSIZE, &filesize);
419 if(error) {
420 error = ftruncate(fileno(file_ptr), filesize);
421 }
422 fclose(file_ptr);
423
424 if (error == -1) {
425 fprintf(stderr, "dynamic_pager: cannot extend paging file size %s to %llu!\n",
426 subfile, filesize);
427 exit(EXIT_FAILURE);
428 }
429
430 if (macx_triggers(0, 0,
431 (encrypted
432 ? SWAP_ENCRYPT_ON
433 : SWAP_ENCRYPT_OFF),
434 MACH_PORT_NULL) != 0) {
435 fprintf(stderr,
436 "dynamic_pager: warning: "
437 "could not turn encrypted swap %s\n",
438 (encrypted ? "on" : "off"));
439 }
440
441 macx_swapon(subfile, flags, size, priority);
442
443 if(hi_water) {
444 mach_msg_type_name_t poly;
445
446 daemon(0,0);
447
448 if (mach_port_allocate(mach_task_self(),
449 MACH_PORT_RIGHT_RECEIVE,
450 &trigger_port) != KERN_SUCCESS) {
451 fprintf(stderr,"dynamic_pager: allocation of trigger port failed\n");
452 exit(EXIT_FAILURE);
453 }
454 /* create a send right on our local port */
455 mach_port_extract_right(mach_task_self(), trigger_port,
456 MACH_MSG_TYPE_MAKE_SEND, &trigger_port, &poly);
457 macx_triggers(high, low, HI_WAT_ALERT, trigger_port);
458
459 if(low) {
460 macx_triggers(high, low, LO_WAT_ALERT, trigger_port);
461 }
462 /* register control port for applications wishing to */
463 /* get backing store notifications or change dynamic */
464 /* pager settings. */
465 set_dp_control_port(mach_host_self(), trigger_port);
466 wait_on_paging_trigger(trigger_port);
467 }
468 exit(EXIT_SUCCESS);
469 }
470 int
471 main(int argc, char **argv)
472 {
473 extern char *optarg;
474 extern int optind;
475 char default_filename[] = "/private/var/vm/swapfile";
476 int ch;
477 int variable_sized = 1;
478 boolean_t encrypted_swap = FALSE;
479
480 seteuid(getuid());
481 strcpy(fileroot, default_filename);
482
483 limits[0].size = 20000000;
484 limits[0].low_water = 0;
485
486 hi_water = 0;
487 local_hi_water = 0;
488
489
490 while ((ch = getopt(argc, argv, "EF:L:H:S:P:O:")) != EOF) {
491 switch((char)ch) {
492
493 case 'E':
494 encrypted_swap = TRUE;
495 break;
496
497 case 'F':
498 strncpy(fileroot, optarg, 500);
499 break;
500
501 case 'L':
502 variable_sized = 0;
503 limits[0].low_water = atoi(optarg);
504 break;
505 case 'H':
506 variable_sized = 0;
507 hi_water = atoi(optarg);
508 break;
509 case 'S':
510 variable_sized = 0;
511 limits[0].size = atoi(optarg);
512 break;
513 case 'P':
514 priority = atoi(optarg);
515 break;
516
517 default:
518 (void)fprintf(stderr,
519 "usage: dynamic_pager [-F filename] [-L low water alert trigger] [-H high water alert trigger] [-S file size] [-P priority]\n");
520 exit(EXIT_FAILURE);
521 }
522 }
523
524 if (variable_sized) {
525 static char tmp[1024];
526 struct statfs sfs;
527 char *q;
528 int i;
529 int mib[4];
530 size_t len;
531 unsigned int size;
532 u_int64_t memsize;
533 u_int64_t fs_limit;
534
535 /*
536 * if we get here, then none of the following options were specified... -L, H, or -S
537 * drop into a new mode that scales the size of the swap file based on how much free
538 * space is left on the volume being used for swap and the amount of physical ram
539 * installed on the system...
540 * basically, we'll pick a maximum size that doesn't exceed the following limits...
541 * 1/8 the remaining free space of the swap volume
542 * the size of phsyical ram
543 * MAXIMUM_SIZE - currently set to 1 Gbyte...
544 * once we have the maximum, we'll create a list of sizes and low_water limits
545 * we'll start with 2 files of MINIMUM_SIZE - currently 64 Mbytes...
546 * subsequent entries will double in size up to the calculated maximum... the low_water
547 * limit will be the sum of the current file size and the previous file size for each entry...
548 * as we add or delete files, we'll use the current file_count as an index into this
549 * table... if it's beyond the table size, we'll use the last entry
550 * the table entry will determine the size of the file to be created and the new low_water mark...
551 * the high_water mark is set to HI_WATER_DEFAULT which must be smaller than MINIMUM_SIZE...
552 * currently it is set to 40,000,000 to match the size being requested by the application
553 * monitoring low space conditions... having it set to the same size keeps us from creating
554 * an additional swap file when it really isn't necessary
555 */
556
557 /*
558 * get rid of the filename at the end of the swap file specification
559 * we only want the portion of the pathname that should already exist
560 */
561 strcpy(tmp, fileroot);
562 if (q = strrchr(tmp, '/'))
563 *q = 0;
564
565 if (statfs(tmp, &sfs) != -1) {
566 /*
567 * Limit the maximum size of a swap file to 1/8 the free
568 * space available on the filesystem where the swap files
569 * are to reside. This will allow us to allocate and
570 * deallocate in finer increments on systems without much
571 * free space.
572 */
573 fs_limit = ((u_int64_t)sfs.f_bfree * (u_int64_t)sfs.f_bsize) / 8;
574
575 } else {
576 (void)fprintf(stderr, "dynamic_pager: swap directory must exist\n");
577 exit(EXIT_FAILURE);
578 }
579 mib[0] = CTL_HW;
580 mib[1] = HW_MEMSIZE;
581 len = sizeof(u_int64_t);
582
583 if (sysctl(mib, 2, &memsize, &len, NULL, 0) < 0) {
584 /*
585 * if the sysctl fails for some reason
586 * use the starting size as the default
587 */
588 memsize = MINIMUM_SIZE;
589 }
590 if (memsize > fs_limit)
591 /*
592 * clip based on filesystem space available
593 */
594 memsize = fs_limit;
595
596 /*
597 * further limit the maximum size of a swap file
598 */
599 if (memsize > MAXIMUM_SIZE)
600 memsize = MAXIMUM_SIZE;
601
602 size = MINIMUM_SIZE;
603
604 /*
605 * start small and work our way up to the maximum
606 * sized allowed... this way, we don't tie up too
607 * much disk space if we never do any real paging
608 */
609 for (max_valid = 0, i = 0; i < MAX_LIMITS; i++) {
610 limits[i].size = size;
611
612 if (i == 0)
613 limits[i].low_water = size * 2;
614 else {
615 if ((limits[i - 1].size / 2) > HI_WATER_DEFAULT)
616 limits[i].low_water = size + (limits[i - 1].size / 2);
617 else
618 limits[i].low_water = size + limits[i - 1].size;
619 }
620
621 if (i) {
622 /*
623 * make the first 2 files the same size
624 */
625 size = size * 2;
626 if (size > memsize)
627 break;
628 }
629 max_valid++;
630 }
631 if (max_valid >= MAX_LIMITS)
632 max_valid = MAX_LIMITS - 1;
633
634 hi_water = HI_WATER_DEFAULT;
635 }
636 local_hi_water = hi_water;
637
638 if((limits[0].low_water != 0) && (limits[0].low_water <= (limits[0].size + hi_water))) {
639 (void)fprintf(stderr, "dynamic_pager: low water trigger must be larger than size + hi_water\n");
640 exit(EXIT_FAILURE);
641 }
642 argc -= optind;
643 argv += optind;
644
645 paging_setup(0, limits[0].size, priority, limits[0].low_water, hi_water,
646 encrypted_swap);
647
648 return (0);
649 }