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