]> git.saurik.com Git - apple/xnu.git/blob - tests/memorystatus_freeze_test.c
0e1e51ad6a19e457619d76780ac2deceb591e849
[apple/xnu.git] / tests / memorystatus_freeze_test.c
1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <sys/kern_memorystatus_freeze.h>
6 #include <time.h>
7 #include <mach-o/dyld.h>
8 #include <mach/mach_vm.h>
9 #include <mach/vm_page_size.h> /* Needed for vm_region info */
10 #include <mach/shared_region.h>
11 #include <mach/mach.h>
12
13 #ifdef T_NAMESPACE
14 #undef T_NAMESPACE
15 #endif
16 #include <darwintest.h>
17 #include <darwintest_utils.h>
18
19 #include "memorystatus_assertion_helpers.h"
20
21 T_GLOBAL_META(
22 T_META_NAMESPACE("xnu.vm"),
23 T_META_CHECK_LEAKS(false)
24 );
25
26 #define MEM_SIZE_MB 10
27 #define NUM_ITERATIONS 5
28 #define FREEZE_PAGES_MAX 256
29
30 #define CREATE_LIST(X) \
31 X(SUCCESS) \
32 X(TOO_FEW_ARGUMENTS) \
33 X(SYSCTL_VM_PAGESIZE_FAILED) \
34 X(VM_PAGESIZE_IS_ZERO) \
35 X(DISPATCH_SOURCE_CREATE_FAILED) \
36 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
37 X(SIGNAL_TO_PARENT_FAILED) \
38 X(MEMORYSTATUS_CONTROL_FAILED) \
39 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
40 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
41 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
42 X(FROZEN_BIT_SET) \
43 X(FROZEN_BIT_NOT_SET) \
44 X(MEMORYSTATUS_CONTROL_ERROR) \
45 X(EXIT_CODE_MAX) \
46
47 #define EXIT_CODES_ENUM(VAR) VAR,
48 enum exit_codes_num {
49 CREATE_LIST(EXIT_CODES_ENUM)
50 };
51
52 #define EXIT_CODES_STRING(VAR) #VAR,
53 static const char *exit_codes_str[] = {
54 CREATE_LIST(EXIT_CODES_STRING)
55 };
56
57 static int
58 get_vmpage_size()
59 {
60 int vmpage_size;
61 size_t size = sizeof(vmpage_size);
62 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
63 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
64 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
65 return vmpage_size;
66 }
67
68 static pid_t child_pid = -1;
69 static int freeze_count = 0;
70
71 void move_to_idle_band(pid_t);
72 void run_freezer_test(int);
73 void freeze_helper_process(void);
74 /* Gets and optionally sets the freeze pages max threshold */
75 int sysctl_freeze_pages_max(int* new_value);
76
77 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
78 * We're moving away from those unit tests, so they're copied here.
79 */
80
81 /* Cribbed from 'top'... */
82 static int
83 in_shared_region(mach_vm_address_t addr, cpu_type_t type)
84 {
85 mach_vm_address_t base = 0, size = 0;
86
87 switch (type) {
88 case CPU_TYPE_ARM:
89 base = SHARED_REGION_BASE_ARM;
90 size = SHARED_REGION_SIZE_ARM;
91 break;
92
93 case CPU_TYPE_ARM64:
94 base = SHARED_REGION_BASE_ARM64;
95 size = SHARED_REGION_SIZE_ARM64;
96 break;
97
98
99 case CPU_TYPE_X86_64:
100 base = SHARED_REGION_BASE_X86_64;
101 size = SHARED_REGION_SIZE_X86_64;
102 break;
103
104 case CPU_TYPE_I386:
105 base = SHARED_REGION_BASE_I386;
106 size = SHARED_REGION_SIZE_I386;
107 break;
108
109 case CPU_TYPE_POWERPC:
110 base = SHARED_REGION_BASE_PPC;
111 size = SHARED_REGION_SIZE_PPC;
112 break;
113
114 case CPU_TYPE_POWERPC64:
115 base = SHARED_REGION_BASE_PPC64;
116 size = SHARED_REGION_SIZE_PPC64;
117 break;
118
119 default: {
120 int t = type;
121
122 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
123 abort();
124 }
125 }
126
127 return addr >= base && addr < (base + size);
128 }
129
130 /* Get the resident private memory of the given pid */
131 static unsigned long long
132 get_rprvt(pid_t pid)
133 {
134 mach_port_name_t task;
135 kern_return_t kr;
136
137 mach_vm_size_t rprvt = 0;
138 mach_vm_size_t empty = 0;
139 mach_vm_size_t fw_private = 0;
140 mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported
141 // in terms of vm_kernel_page_size.
142 mach_vm_size_t regs = 0;
143
144 mach_vm_address_t addr;
145 mach_vm_size_t size;
146
147 int split = 0;
148
149 kr = task_for_pid(mach_task_self(), pid, &task);
150 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
151
152 for (addr = 0;; addr += size) {
153 vm_region_top_info_data_t info;
154 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
155 mach_port_t object_name;
156
157 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
158 if (kr != KERN_SUCCESS) {
159 break;
160 }
161
162 #if defined (__arm64__)
163 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
164 #else
165 if (in_shared_region(addr, CPU_TYPE_ARM)) {
166 #endif
167 // Private Shared
168 fw_private += info.private_pages_resident * pagesize;
169
170 /*
171 * Check if this process has the globally shared
172 * text and data regions mapped in. If so, set
173 * split to TRUE and avoid checking
174 * again.
175 */
176 if (split == FALSE && info.share_mode == SM_EMPTY) {
177 vm_region_basic_info_data_64_t b_info;
178 mach_vm_address_t b_addr = addr;
179 mach_vm_size_t b_size = size;
180 count = VM_REGION_BASIC_INFO_COUNT_64;
181
182 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
183 if (kr != KERN_SUCCESS) {
184 break;
185 }
186
187 if (b_info.reserved) {
188 split = TRUE;
189 }
190 }
191
192 /*
193 * Short circuit the loop if this isn't a shared
194 * private region, since that's the only region
195 * type we care about within the current address
196 * range.
197 */
198 if (info.share_mode != SM_PRIVATE) {
199 continue;
200 }
201 }
202
203 regs++;
204
205 /*
206 * Update counters according to the region type.
207 */
208
209 if (info.share_mode == SM_COW && info.ref_count == 1) {
210 // Treat single reference SM_COW as SM_PRIVATE
211 info.share_mode = SM_PRIVATE;
212 }
213
214 switch (info.share_mode) {
215 case SM_LARGE_PAGE:
216 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
217 // since they are not shareable and are wired.
218 case SM_PRIVATE:
219 rprvt += info.private_pages_resident * pagesize;
220 rprvt += info.shared_pages_resident * pagesize;
221 break;
222
223 case SM_EMPTY:
224 empty += size;
225 break;
226
227 case SM_COW:
228 case SM_SHARED:
229 if (pid == 0) {
230 // Treat kernel_task specially
231 if (info.share_mode == SM_COW) {
232 rprvt += info.private_pages_resident * pagesize;
233 }
234 break;
235 }
236
237 if (info.share_mode == SM_COW) {
238 rprvt += info.private_pages_resident * pagesize;
239 }
240 break;
241
242 default:
243 assert(0);
244 break;
245 }
246 }
247
248 return rprvt;
249 }
250
251 void
252 move_to_idle_band(pid_t pid)
253 {
254 memorystatus_priority_properties_t props;
255 /*
256 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
257 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
258 */
259 props.priority = JETSAM_PRIORITY_IDLE;
260 props.user_data = 0;
261
262 /*
263 * This requires us to run as root (in the absence of entitlement).
264 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
265 */
266 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) {
267 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
268 }
269 }
270
271 void
272 freeze_helper_process(void)
273 {
274 size_t length;
275 int ret, freeze_enabled, errno_freeze_sysctl;
276 uint64_t resident_memory_before, resident_memory_after, vmpage_size;
277 vmpage_size = (uint64_t) get_vmpage_size();
278 resident_memory_before = get_rprvt(child_pid) / vmpage_size;
279
280 T_LOG("Freezing child pid %d", child_pid);
281 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
282 errno_freeze_sysctl = errno;
283 sleep(1);
284
285 /*
286 * The child process toggles its freezable state on each iteration.
287 * So a failure for every alternate freeze is expected.
288 */
289 if (freeze_count % 2) {
290 length = sizeof(freeze_enabled);
291 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
292 "failed to query vm.freeze_enabled");
293 if (freeze_enabled) {
294 errno = errno_freeze_sysctl;
295 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
296 } else {
297 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
298 T_LOG("Freeze has been disabled. Terminating early.");
299 T_END;
300 }
301 resident_memory_after = get_rprvt(child_pid) / vmpage_size;
302 uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
303 T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
304 if (resident_memory_before > freeze_pages_max) {
305 T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
306 }
307 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
308 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
309 } else {
310 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
311 T_LOG("Freeze failed as expected");
312 }
313
314 freeze_count++;
315
316 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
317 }
318
319 static void
320 skip_if_freezer_is_disabled()
321 {
322 int freeze_enabled;
323 size_t length = sizeof(freeze_enabled);
324
325 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
326 "failed to query vm.freeze_enabled");
327 if (!freeze_enabled) {
328 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
329 T_SKIP("Freeze has been disabled. Skipping test.");
330 }
331 }
332
333 void
334 run_freezer_test(int num_pages)
335 {
336 int ret;
337 char sz_str[50];
338 char **launch_tool_args;
339 char testpath[PATH_MAX];
340 uint32_t testpath_buf_size;
341 dispatch_source_t ds_freeze, ds_proc;
342
343 skip_if_freezer_is_disabled();
344
345 signal(SIGUSR1, SIG_IGN);
346 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
347 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
348
349 dispatch_source_set_event_handler(ds_freeze, ^{
350 if (freeze_count < NUM_ITERATIONS) {
351 freeze_helper_process();
352 } else {
353 kill(child_pid, SIGKILL);
354 dispatch_source_cancel(ds_freeze);
355 }
356 });
357 dispatch_activate(ds_freeze);
358
359 testpath_buf_size = sizeof(testpath);
360 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
361 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
362 T_LOG("Executable path: %s", testpath);
363
364 sprintf(sz_str, "%d", num_pages);
365 launch_tool_args = (char *[]){
366 testpath,
367 "-n",
368 "allocate_pages",
369 "--",
370 sz_str,
371 NULL
372 };
373
374 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
375 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
376 if (ret != 0) {
377 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
378 }
379 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
380
381 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
382 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
383
384 dispatch_source_set_event_handler(ds_proc, ^{
385 int status = 0, code = 0;
386 pid_t rc = waitpid(child_pid, &status, 0);
387 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
388 code = WEXITSTATUS(status);
389
390 if (code == 0) {
391 T_END;
392 } else if (code > 0 && code < EXIT_CODE_MAX) {
393 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
394 } else {
395 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
396 }
397 });
398 dispatch_activate(ds_proc);
399
400 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
401 dispatch_main();
402 }
403
404 static void
405 allocate_pages(int num_pages)
406 {
407 int i, j, vmpgsize;
408 char val;
409 __block int num_iter = 0;
410 __block char **buf;
411 dispatch_source_t ds_signal;
412 vmpgsize = get_vmpage_size();
413 if (num_pages < 1) {
414 printf("Invalid number of pages to allocate: %d\n", num_pages);
415 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
416 }
417
418 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
419
420 /* Gives us the compression ratio we see in the typical case (~2.7) */
421 for (j = 0; j < num_pages; j++) {
422 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
423 val = 0;
424 for (i = 0; i < vmpgsize; i += 16) {
425 memset(&buf[j][i], val, 16);
426 if (i < 3400 * (vmpgsize / 4096)) {
427 val++;
428 }
429 }
430 }
431
432 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
433 /* Signal to the parent that we're done allocating and it's ok to freeze us */
434 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
435 if (kill(getppid(), SIGUSR1) != 0) {
436 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
437 }
438 });
439
440 signal(SIGUSR1, SIG_IGN);
441 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
442 if (ds_signal == NULL) {
443 exit(DISPATCH_SOURCE_CREATE_FAILED);
444 }
445
446 dispatch_source_set_event_handler(ds_signal, ^{
447 int current_state, new_state;
448 volatile int tmp;
449
450 /* Make sure all the pages are accessed before trying to freeze again */
451 for (int x = 0; x < num_pages; x++) {
452 tmp = buf[x][0];
453 }
454
455 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
456 /* Sysprocs start off as unfreezable. Verify that first. */
457 if (num_iter == 0 && current_state != 0) {
458 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
459 }
460
461 /* Toggle freezable state */
462 new_state = (current_state) ? 0: 1;
463 printf("[%d] Changing state from %s to %s\n", getpid(),
464 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
465 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
466 exit(MEMORYSTATUS_CONTROL_FAILED);
467 }
468
469 /* Verify that the state has been set correctly */
470 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
471 if (new_state != current_state) {
472 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
473 }
474 num_iter++;
475
476 if (kill(getppid(), SIGUSR1) != 0) {
477 exit(SIGNAL_TO_PARENT_FAILED);
478 }
479 });
480 dispatch_activate(ds_signal);
481 move_to_idle_band(getpid());
482
483 dispatch_main();
484 }
485
486 T_HELPER_DECL(allocate_pages,
487 "allocates pages to freeze",
488 T_META_ASROOT(true)) {
489 if (argc < 1) {
490 exit(TOO_FEW_ARGUMENTS);
491 }
492
493 int num_pages = atoi(argv[0]);
494 allocate_pages(num_pages);
495 }
496
497 T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
498 run_freezer_test(
499 (MEM_SIZE_MB << 20) / get_vmpage_size());
500 }
501
502 static int old_freeze_pages_max = 0;
503 static void
504 reset_freeze_pages_max()
505 {
506 if (old_freeze_pages_max != 0) {
507 sysctl_freeze_pages_max(&old_freeze_pages_max);
508 }
509 }
510
511 int
512 sysctl_freeze_pages_max(int* new_value)
513 {
514 static int set_end_handler = false;
515 int freeze_pages_max, ret;
516 size_t size = sizeof(freeze_pages_max);
517 ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
518 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
519 if (!set_end_handler) {
520 // Save the original value and instruct darwintest to restore it after the test completes
521 old_freeze_pages_max = freeze_pages_max;
522 T_ATEND(reset_freeze_pages_max);
523 set_end_handler = true;
524 }
525 return old_freeze_pages_max;
526 }
527
528 T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
529 int freeze_pages_max = FREEZE_PAGES_MAX;
530 sysctl_freeze_pages_max(&freeze_pages_max);
531 run_freezer_test(FREEZE_PAGES_MAX * 2);
532 }
533
534 T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) {
535 kern_return_t kern_ret;
536 /* Set the process to freezable */
537 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
538 T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
539 /* Signal to our parent that we can be frozen */
540 if (kill(getppid(), SIGUSR1) != 0) {
541 T_LOG("Unable to signal to parent process!");
542 exit(1);
543 }
544 while (1) {
545 ;
546 }
547 }
548
549 /* Launches the frozen_background helper as a managed process. */
550 static pid_t
551 launch_background_helper(const char* variant)
552 {
553 pid_t pid;
554 char **launch_tool_args;
555 char testpath[PATH_MAX];
556 uint32_t testpath_buf_size;
557 int ret;
558
559 testpath_buf_size = sizeof(testpath);
560 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
561 printf("Launching %s\n", testpath);
562 launch_tool_args = (char *[]){
563 testpath,
564 "-n",
565 variant,
566 NULL
567 };
568 ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
569 if (ret != 0) {
570 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
571 }
572 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool");
573 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
574 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0);
575 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
576 return pid;
577 }
578
579 static void
580 freeze_process(pid_t pid)
581 {
582 int ret, freeze_enabled, errno_freeze_sysctl;
583 size_t length;
584 T_LOG("Freezing pid %d", pid);
585
586 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
587 errno_freeze_sysctl = errno;
588 length = sizeof(freeze_enabled);
589 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
590 "failed to query vm.freeze_enabled");
591 if (freeze_enabled) {
592 errno = errno_freeze_sysctl;
593 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
594 } else {
595 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
596 T_LOG("Freeze has been disabled. Terminating early.");
597 T_END;
598 }
599 }
600
601 static void
602 memorystatus_assertion_test_demote_frozen()
603 {
604 /*
605 * Test that if we assert a priority on a process, freeze it, and then demote all frozen processes, it does not get demoted below the asserted priority.
606 * Then remove thee assertion, and ensure it gets demoted properly.
607 */
608 /* these values will remain fixed during testing */
609 int active_limit_mb = 15; /* arbitrary */
610 int inactive_limit_mb = 7; /* arbitrary */
611 int demote_value = 1;
612 /* Launch the child process, and elevate its priority */
613 int requestedpriority;
614 dispatch_source_t ds_signal, ds_exit;
615 requestedpriority = JETSAM_PRIORITY_UI_SUPPORT;
616
617 /* Wait for the child process to tell us that it's ready, and then freeze it */
618 signal(SIGUSR1, SIG_IGN);
619 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
620 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
621 dispatch_source_set_event_handler(ds_signal, ^{
622 int sysctl_ret;
623 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
624 freeze_process(child_pid);
625 /* Agressive demotion */
626 sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value));
627 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
628 /* Check */
629 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
630 T_LOG("Relinquishing our assertion.");
631 /* Relinquish our assertion, and check that it gets demoted. */
632 relinquish_assertion_priority(child_pid, 0x0);
633 (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
634 /* Kill the child */
635 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
636 T_END;
637 });
638
639 /* Launch the child process and set the initial properties on it. */
640 child_pid = launch_background_helper("frozen_background");
641 set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
642 set_assertion_priority(child_pid, requestedpriority, 0x0);
643 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
644 /* Listen for exit. */
645 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
646 dispatch_source_set_event_handler(ds_exit, ^{
647 int status = 0, code = 0;
648 pid_t rc = waitpid(child_pid, &status, 0);
649 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
650 code = WEXITSTATUS(status);
651 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
652 T_END;
653 });
654
655 dispatch_activate(ds_exit);
656 dispatch_activate(ds_signal);
657 dispatch_main();
658 }
659
660 T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
661 memorystatus_assertion_test_demote_frozen();
662 }
663
664 T_DECL(budget_replenishment, "budget replenishes properly") {
665 size_t length;
666 int ret;
667 static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours
668 unsigned int memorystatus_freeze_daily_mb_max, memorystatus_freeze_daily_pages_max;
669 static unsigned int kFixedPointFactor = 100;
670 static unsigned int kNumSecondsInDay = 60 * 60 * 24;
671 unsigned int new_budget, expected_new_budget_pages;
672 size_t new_budget_ln;
673 unsigned int page_size = (unsigned int) get_vmpage_size();
674
675 /*
676 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
677 * ago and we used up its entire budget.
678 */
679 length = sizeof(kTestIntervalSecs);
680 new_budget_ln = sizeof(new_budget);
681 ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length);
682 T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
683
684 // Grab the daily budget.
685 length = sizeof(memorystatus_freeze_daily_mb_max);
686 ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0);
687 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
688
689 memorystatus_freeze_daily_pages_max = memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / page_size;
690 T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max);
691 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max);
692 T_LOG("page_size %u", page_size);
693
694 /*
695 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
696 * seconds of budget.
697 */
698 expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
699 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages);
700 T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change");
701 expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
702 * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
703 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages);
704 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max);
705
706 T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
707 }
708
709
710 static bool
711 is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len)
712 {
713 int bytes_written;
714 bool found = false;
715 global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t));
716 T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc");
717
718 bytes_written = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_GET_PROCS, frozen_procs, sizeof(global_frozen_procs_t));
719 T_QUIET; T_ASSERT_LE((size_t) bytes_written, sizeof(global_frozen_procs_t), "Didn't overflow buffer");
720 T_QUIET; T_ASSERT_GT(bytes_written, 0, "Wrote someting");
721
722 for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) {
723 if (frozen_procs->gfp_procs[i].fp_pid == pid) {
724 found = true;
725 strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len);
726 }
727 }
728 return found;
729 }
730
731 static void
732 drop_jetsam_snapshot_ownership(void)
733 {
734 int ret;
735 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_SNAPSHOT_OWNERSHIP, 0, MEMORYSTATUS_FLAGS_SNAPSHOT_DROP_OWNERSHIP, NULL, 0);
736 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot");
737 }
738
739 static void
740 take_jetsam_snapshot_ownership(void)
741 {
742 int ret;
743 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_SNAPSHOT_OWNERSHIP, 0, MEMORYSTATUS_FLAGS_SNAPSHOT_TAKE_OWNERSHIP, NULL, 0);
744 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot");
745 T_ATEND(drop_jetsam_snapshot_ownership);
746 }
747
748 /*
749 * Retrieve a jetsam snapshot.
750 *
751 * return:
752 * pointer to snapshot.
753 *
754 * Caller is responsible for freeing snapshot.
755 */
756 static
757 memorystatus_jetsam_snapshot_t *
758 get_jetsam_snapshot(uint32_t flags, bool empty_allowed)
759 {
760 memorystatus_jetsam_snapshot_t * snapshot = NULL;
761 int ret;
762 uint32_t size;
763
764 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, NULL, 0);
765 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Get jetsam snapshot size");
766 size = (uint32_t) ret;
767 if (size == 0 && empty_allowed) {
768 return snapshot;
769 }
770
771 snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
772 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size);
773
774 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size);
775 T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot");
776
777 if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) {
778 T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size,
779 sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t),
780 sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t)));
781 if (snapshot) {
782 free(snapshot);
783 }
784 }
785
786 return snapshot;
787 }
788
789 /*
790 * Look for the given pid in the snapshot.
791 *
792 * return:
793 * pointer to pid's entry or NULL if pid is not found.
794 *
795 * Caller has ownership of snapshot before and after call.
796 */
797 static
798 memorystatus_jetsam_snapshot_entry_t *
799 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid)
800 {
801 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Got snapshot");
802 for (size_t i = 0; i < snapshot->entry_count; i++) {
803 memorystatus_jetsam_snapshot_entry_t *curr = &(snapshot->entries[i]);
804 if (curr->pid == pid) {
805 return curr;
806 }
807 }
808
809 return NULL;
810 }
811
812 /*
813 * Launches the child & runs the given block after the child signals.
814 * If exit_with_child is true, the test will exit when the child exits.
815 */
816 static void
817 test_after_background_helper_launches(bool exit_with_child, const char* variant, dispatch_block_t test_block)
818 {
819 dispatch_source_t ds_signal, ds_exit;
820
821 /* Run the test block after the child launches & signals it's ready. */
822 signal(SIGUSR1, SIG_IGN);
823 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
824 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
825 dispatch_source_set_event_handler(ds_signal, test_block);
826 /* Launch the child process. */
827 child_pid = launch_background_helper(variant);
828 /* Listen for exit. */
829 if (exit_with_child) {
830 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
831 dispatch_source_set_event_handler(ds_exit, ^{
832 int status = 0, code = 0;
833 pid_t rc = waitpid(child_pid, &status, 0);
834 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
835 code = WEXITSTATUS(status);
836 if (code != 0) {
837 T_LOG("Child exited with error: %s", exit_codes_str[code]);
838 }
839 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
840 T_END;
841 });
842
843 dispatch_activate(ds_exit);
844 }
845 dispatch_activate(ds_signal);
846 dispatch_main();
847 }
848
849 T_DECL(get_frozen_procs, "List processes in the freezer") {
850 skip_if_freezer_is_disabled();
851
852 test_after_background_helper_launches(true, "frozen_background", ^{
853 proc_name_t name;
854 /* Place the child in the idle band so that it gets elevated like a typical app. */
855 move_to_idle_band(child_pid);
856 /* Freeze the process, and check that it's in the list of frozen processes. */
857 freeze_process(child_pid);
858 /* Check */
859 T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
860 T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
861 /* Kill the child */
862 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
863 T_END;
864 });
865 }
866
867 T_DECL(frozen_to_swap_accounting, "jetsam snapshot has frozen_to_swap accounting") {
868 static const size_t kSnapshotSleepDelay = 5;
869 static const size_t kFreezeToDiskMaxDelay = 60;
870
871 skip_if_freezer_is_disabled();
872
873 test_after_background_helper_launches(true, "frozen_background", ^{
874 memorystatus_jetsam_snapshot_t *snapshot = NULL;
875 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
876 /* Place the child in the idle band so that it gets elevated like a typical app. */
877 move_to_idle_band(child_pid);
878 freeze_process(child_pid);
879 /*
880 * Wait until the child's pages get paged out to disk.
881 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
882 * something is either wrong with the compactor or the accounting.
883 */
884 for (size_t i = 0; i < kFreezeToDiskMaxDelay / kSnapshotSleepDelay; i++) {
885 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
886 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
887 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
888 if (child_entry->jse_frozen_to_swap_pages > 0) {
889 break;
890 }
891 free(snapshot);
892 sleep(kSnapshotSleepDelay);
893 }
894 T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap");
895 free(snapshot);
896 /* Kill the child */
897 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
898 T_END;
899 });
900 }
901
902 T_DECL(freezer_snapshot, "App kills are recorded in the freezer snapshot") {
903 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
904 take_jetsam_snapshot_ownership();
905
906 test_after_background_helper_launches(false, "frozen_background", ^{
907 int ret;
908 memorystatus_jetsam_snapshot_t *snapshot = NULL;
909 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
910
911 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
912 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
913
914 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
915 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
916 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
917 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
918 T_QUIET; T_ASSERT_EQ(child_entry->killed, (unsigned long long) JETSAM_REASON_GENERIC, "Child entry was killed");
919
920 free(snapshot);
921 T_END;
922 });
923 }
924
925 T_DECL(freezer_snapshot_consume, "Freezer snapshot is consumed on read") {
926 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
927 take_jetsam_snapshot_ownership();
928
929 test_after_background_helper_launches(false, "frozen_background", ^{
930 int ret;
931 memorystatus_jetsam_snapshot_t *snapshot = NULL;
932 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
933
934 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
935 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
936
937 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
938 T_ASSERT_NOTNULL(snapshot, "Got first freezer snapshot");
939 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
940 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in first freezer snapshot");
941
942 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, true);
943 if (snapshot != NULL) {
944 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
945 T_QUIET; T_ASSERT_NULL(child_entry, "Child is not in second freezer snapshot");
946 }
947
948 free(snapshot);
949 T_END;
950 });
951 }
952
953 T_DECL(freezer_snapshot_frozen_state, "Frozen state is recorded in freezer snapshot") {
954 skip_if_freezer_is_disabled();
955 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
956 take_jetsam_snapshot_ownership();
957
958 test_after_background_helper_launches(false, "frozen_background", ^{
959 int ret;
960 memorystatus_jetsam_snapshot_t *snapshot = NULL;
961 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
962
963 move_to_idle_band(child_pid);
964 freeze_process(child_pid);
965
966 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
967 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
968
969 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
970 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
971 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
972 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
973 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is set");
974
975 free(snapshot);
976 T_END;
977 });
978 }
979
980 T_DECL(freezer_snapshot_thaw_state, "Thaw count is recorded in freezer snapshot") {
981 skip_if_freezer_is_disabled();
982 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
983 take_jetsam_snapshot_ownership();
984
985 test_after_background_helper_launches(false, "frozen_background", ^{
986 int ret;
987 memorystatus_jetsam_snapshot_t *snapshot = NULL;
988 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
989
990 move_to_idle_band(child_pid);
991 ret = pid_suspend(child_pid);
992 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
993 freeze_process(child_pid);
994 ret = pid_resume(child_pid);
995 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
996
997 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
998 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
999
1000 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1001 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1002 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1003 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1004 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is still set after thaw");
1005 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusWasThawed, "Child entry was thawed");
1006 T_QUIET; T_ASSERT_EQ(child_entry->jse_thaw_count, 1ULL, "Child entry's thaw count was incremented");
1007
1008 free(snapshot);
1009 T_END;
1010 });
1011 }
1012
1013 T_HELPER_DECL(check_frozen, "Check frozen state", T_META_ASROOT(true)) {
1014 int kern_ret;
1015 dispatch_source_t ds_signal;
1016 __block int is_frozen;
1017 /* Set the process to freezable */
1018 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
1019 T_QUIET; T_ASSERT_POSIX_SUCCESS(kern_ret, "set process is freezable");
1020 /* Signal to our parent that we can be frozen */
1021 if (kill(getppid(), SIGUSR1) != 0) {
1022 T_LOG("Unable to signal to parent process!");
1023 exit(SIGNAL_TO_PARENT_FAILED);
1024 }
1025
1026 /* We should not be frozen yet. */
1027 is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1028 if (is_frozen == -1) {
1029 T_LOG("memorystatus_control error: %s", strerror(errno));
1030 exit(MEMORYSTATUS_CONTROL_ERROR);
1031 }
1032 if (is_frozen) {
1033 exit(FROZEN_BIT_SET);
1034 }
1035
1036
1037 sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1038 T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1039 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1040 if (ds_signal == NULL) {
1041 exit(DISPATCH_SOURCE_CREATE_FAILED);
1042 }
1043
1044 dispatch_source_set_event_handler(ds_signal, ^{
1045 /* We should now be frozen. */
1046 is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1047 if (is_frozen == -1) {
1048 T_LOG("memorystatus_control error: %s", strerror(errno));
1049 exit(MEMORYSTATUS_CONTROL_ERROR);
1050 }
1051 if (!is_frozen) {
1052 exit(FROZEN_BIT_NOT_SET);
1053 }
1054 exit(SUCCESS);
1055 });
1056 dispatch_activate(ds_signal);
1057
1058 dispatch_main();
1059 }
1060
1061 T_DECL(memorystatus_get_process_is_frozen, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") {
1062 skip_if_freezer_is_disabled();
1063
1064 test_after_background_helper_launches(true, "check_frozen", ^{
1065 int ret;
1066 /* Freeze the child, resume it, and signal it to check its state */
1067 move_to_idle_band(child_pid);
1068 ret = pid_suspend(child_pid);
1069 T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1070 freeze_process(child_pid);
1071 ret = pid_resume(child_pid);
1072 T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1073
1074 kill(child_pid, SIGUSR1);
1075 /* The child will checks its own frozen state & exit. */
1076 });
1077 }