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