1 #include <time.h>
2 #include <errno.h>
3
4 #include <mach/mach.h>
5 #include <sys/kern_sysctl.h>
6 #include <sys/mman.h>
7
8 #include <darwintest.h>
9 #include <darwintest_utils.h>
10
11
12 static const char *g_sysctl_no_wire_name = "vm.global_no_user_wire_amount";
13 static const char *g_sysctl_wire_name = "vm.global_user_wire_limit";
14 static const char *g_sysctl_per_task_wire_name = "vm.user_wire_limit";
15 static const char *g_sysctl_current_wired_count_name = "vm.page_wire_count";
16 #if __x86_64__
17 static const char *g_sysctl_current_free_count_name = "vm.lopage_free_count";
18 #endif /* __x86_64__ */
19 static const char *g_sysctl_vm_page_size_name = "vm.pagesize";
20 static const char *g_sysctl_memsize_name = "hw.memsize";
21
22 static size_t
ptoa(size_t num_pages)23 ptoa(size_t num_pages)
24 {
25 static size_t page_size = 0;
26 int ret;
27 size_t page_size_size = sizeof(page_size);
28 if (page_size == 0) {
29 ret = sysctlbyname(g_sysctl_vm_page_size_name, &page_size, &page_size_size, NULL, 0);
30 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to get page size");
31 }
32 return num_pages * (size_t) page_size;
33 }
34
35
36 T_DECL(global_no_user_wire_amount, "no_user_wire_amount <= 32G", T_META_TAG_VM_PREFERRED) {
37 int ret;
38 vm_map_size_t no_wire;
39 size_t no_wire_size = sizeof(no_wire);
40 ret = sysctlbyname(g_sysctl_no_wire_name, &no_wire, &no_wire_size, NULL, 0);
41 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "no_user_wire sysctl failed");
42 T_QUIET; T_EXPECT_LE(no_wire, 32 * 2ULL << 30, "no_user_wire_amount is too big.");
43 }
44
45 T_DECL(user_wire_amount, "max_mem > user_wire_amount >= 0.7 * max_mem", T_META_TAG_VM_NOT_PREFERRED) {
46 int ret;
47 vm_map_size_t wire;
48 uint64_t max_mem;
49 size_t max_mem_size = sizeof(max_mem);
50 size_t wire_size = sizeof(wire);
51 ret = sysctlbyname(g_sysctl_memsize_name, &max_mem, &max_mem_size, NULL, 0);
52 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memsize sysctl failed");
53 ret = sysctlbyname(g_sysctl_wire_name, &wire, &wire_size, NULL, 0);
54 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl failed");
55 T_QUIET; T_ASSERT_LT(wire, max_mem, "wire limit is too big");
56 T_QUIET; T_ASSERT_GE(wire, max_mem * 70 / 100, "wire limit is too small.");
57 }
58
59 /*
60 * Sets the no wire limit, and ensures that the wire_limit
61 * changes correctly.
62 */
63 static void
set_no_wire_limit(vm_map_size_t value,uint64_t max_mem)64 set_no_wire_limit(vm_map_size_t value, uint64_t max_mem)
65 {
66 vm_map_size_t wire;
67 size_t wire_size = sizeof(wire);
68 int ret;
69 ret = sysctlbyname(g_sysctl_no_wire_name, NULL, 0, &value, sizeof(value));
70 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "no_user_wire sysctl set failed");
71 ret = sysctlbyname(g_sysctl_wire_name, &wire, &wire_size, NULL, 0);
72 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl failed");
73 T_QUIET; T_ASSERT_EQ(max_mem - wire, value, "no wire size is incorrect");
74 }
75
76 /*
77 * Sets the wire limit, and ensures that the no_wire_limit
78 * changes correctly.
79 */
80 static void
set_wire_limit(vm_map_size_t value,uint64_t max_mem)81 set_wire_limit(vm_map_size_t value, uint64_t max_mem)
82 {
83 vm_map_size_t no_wire;
84 size_t no_wire_size = sizeof(no_wire);
85 int ret;
86 ret = sysctlbyname(g_sysctl_wire_name, NULL, 0, &value, sizeof(value));
87 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl set failed");
88 ret = sysctlbyname(g_sysctl_no_wire_name, &no_wire, &no_wire_size, NULL, 0);
89 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "no_user_wire sysctl failed");
90 T_QUIET; T_ASSERT_EQ(max_mem - value, no_wire, "no wire size is incorrect");
91 }
92
93 T_DECL(set_global_no_user_wire_amount, "Setting no_user_wire_amount changes global_user_wire_amount", T_META_ASROOT(true), T_META_TAG_VM_PREFERRED) {
94 int ret;
95 vm_map_size_t no_wire, wire;
96 vm_map_size_t no_wire_delta = 16 * (1 << 10);
97 uint64_t max_mem;
98 size_t no_wire_size = sizeof(no_wire);
99 size_t wire_size = sizeof(wire);
100 size_t max_mem_size = sizeof(max_mem);
101 ret = sysctlbyname(g_sysctl_memsize_name, &max_mem, &max_mem_size, NULL, 0);
102 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "max_mem sysctl failed");
103 ret = sysctlbyname(g_sysctl_no_wire_name, &no_wire, &no_wire_size, NULL, 0);
104 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "no_user_wire sysctl failed");
105 ret = sysctlbyname(g_sysctl_wire_name, &wire, &wire_size, NULL, 0);
106 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl failed");
107 T_QUIET; T_ASSERT_EQ(max_mem - wire, no_wire, "no wire size is incorrect");
108
109 // Set the no_wire limit and ensure that the wire_size changed.
110 set_no_wire_limit(no_wire + no_wire_delta, max_mem);
111 set_no_wire_limit(no_wire, max_mem);
112 // Set the wire limit and ensure that the no_wire_limit has changed
113 set_wire_limit(wire - no_wire_delta, max_mem);
114 set_wire_limit(wire, max_mem);
115 }
116
117 T_DECL(set_user_wire_limit, "Set user_wire_limit", T_META_ASROOT(true), T_META_TAG_VM_PREFERRED) {
118 vm_map_size_t wire, original_wire;
119 size_t wire_size = sizeof(wire);
120 int ret;
121 vm_map_size_t wire_delta = 48 * (1 << 10);
122 ret = sysctlbyname(g_sysctl_per_task_wire_name, &original_wire, &wire_size, NULL, 0);
123 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl get failed");
124 wire = original_wire + wire_delta;
125 ret = sysctlbyname(g_sysctl_per_task_wire_name, NULL, 0, &wire, wire_size);
126 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl set failed");
127 ret = sysctlbyname(g_sysctl_per_task_wire_name, &wire, &wire_size, NULL, 0);
128 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl get failed");
129 T_QUIET; T_ASSERT_EQ(wire, original_wire + wire_delta, "user_wire sysctl didn't set the correct value.");
130
131 // Cleanup
132 ret = sysctlbyname(g_sysctl_per_task_wire_name, NULL, 0, &original_wire, wire_size);
133 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl set failed");
134 }
135
136 #if TARGET_OS_OSX
137 /*
138 * Test that wiring up to the limit doesn't hang the system.
139 * We only test this on OS X. On all other platforms, we'd expect
140 * to get jetsamm'ed for doing this.
141 */
142 static void *
wire_to_limit(size_t limit,size_t * size)143 wire_to_limit(size_t limit, size_t *size)
144 {
145 // Trying to wire directly to the limit is likely to fail
146 // repeatedly since other wired pages are probably coming and going
147 // so we just try to get close.
148 const unsigned int wiggle_room_pages = 1000;
149 int ret;
150 unsigned int current_wired, current_free;
151 size_t buffer_size, offset_from_limit;
152 void *buffer;
153 size_t current_wired_size = sizeof(current_wired);
154 #if __x86_64__
155 size_t current_free_size = sizeof(current_free);
156 #endif /* __x86_64__ */
157 while (true) {
158 ret = sysctlbyname(g_sysctl_current_wired_count_name, ¤t_wired, ¤t_wired_size, NULL, 0);
159 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "get current wired count failed");
160 #if __x86_64__
161 ret = sysctlbyname(g_sysctl_current_free_count_name, ¤t_free, ¤t_free_size, NULL, 0);
162 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "get current free count failed");
163 #else
164 current_free = 0;
165 #endif /* __x86_64__ */
166 offset_from_limit = ptoa(current_wired + current_free + wiggle_room_pages);
167 T_QUIET; T_ASSERT_GE(limit, offset_from_limit, "more pages are wired than the limit.");
168 buffer_size = limit - offset_from_limit;
169 buffer = malloc(buffer_size);
170 T_QUIET; T_ASSERT_NOTNULL(buffer, "Unable to allocate buffer");
171 ret = mlock(buffer, buffer_size);
172 if (ret == 0) {
173 break;
174 }
175 free(buffer);
176 }
177 *size = buffer_size;
178 return buffer;
179 }
180
181 T_DECL(wire_stress_test, "wire up to global_user_wire_limit and spin for 120 seconds.",
182 T_META_REQUIRES_SYSCTL_NE("kern.hv_vmm_present", 1), T_META_TAG_VM_NOT_ELIGIBLE)
183 {
184 static const int kNumSecondsToSpin = 120;
185 int ret;
186 struct timespec start, now;
187 size_t buffer_size;
188 size_t wire_limit;
189 size_t wire_limit_size = sizeof(wire_limit);
190 void *buffer;
191
192 ret = sysctlbyname(g_sysctl_wire_name, &wire_limit, &wire_limit_size, NULL, 0);
193 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "user_wire sysctl failed");
194 buffer = wire_to_limit(wire_limit, &buffer_size);
195 ret = clock_gettime(CLOCK_MONOTONIC, &start);
196 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to get current time.");
197 while (true) {
198 ret = clock_gettime(CLOCK_MONOTONIC, &now);
199 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to get current time.");
200 if (now.tv_sec - start.tv_sec >= kNumSecondsToSpin) {
201 break;
202 }
203 }
204 ret = munlock(buffer, buffer_size);
205 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to unlock memory.");
206 free(buffer);
207 }
208 #endif /* TARGET_OS_OSX */
209