1 /*
2 * Test to validate that we can schedule threads on all hw.ncpus cores according to _os_cpu_number
3 *
4 * <rdar://problem/29545645>
5 * <rdar://problem/30445216>
6 *
7 * xcrun -sdk macosx.internal clang -o cpucount cpucount.c -ldarwintest -g -Weverything
8 * xcrun -sdk iphoneos.internal clang -arch arm64 -o cpucount-ios cpucount.c -ldarwintest -g -Weverything
9 * xcrun -sdk macosx.internal clang -o cpucount cpucount.c -ldarwintest -arch arm64e -Weverything
10 */
11
12 #include <darwintest.h>
13 #include "test_utils.h"
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <pthread.h>
19 #include <sys/sysctl.h>
20 #include <sys/proc_info.h>
21 #include <libproc.h>
22
23 #include <mach/mach.h>
24 #include <mach/mach_time.h>
25
26 #include <os/tsd.h> /* private header for _os_cpu_number */
27
28 T_GLOBAL_META(
29 T_META_RUN_CONCURRENTLY(false),
30 T_META_BOOTARGS_SET("enable_skstb=1"),
31 T_META_CHECK_LEAKS(false),
32 T_META_ASROOT(true),
33 T_META_ALL_VALID_ARCHS(true),
34 T_META_RADAR_COMPONENT_NAME("xnu"),
35 T_META_RADAR_COMPONENT_VERSION("scheduler")
36 );
37
38 #define KERNEL_BOOTARGS_MAX_SIZE 1024
39 static char kernel_bootargs[KERNEL_BOOTARGS_MAX_SIZE];
40
41 #define KERNEL_VERSION_MAX_SIZE 1024
42 static char kernel_version[KERNEL_VERSION_MAX_SIZE];
43
44 static mach_timebase_info_data_t timebase_info;
45
46 static uint64_t
abs_to_nanos(uint64_t abs)47 abs_to_nanos(uint64_t abs)
48 {
49 return abs * timebase_info.numer / timebase_info.denom;
50 }
51
52 static int32_t
get_csw_count()53 get_csw_count()
54 {
55 struct proc_taskinfo taskinfo;
56 int rv;
57
58 rv = proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &taskinfo, sizeof(taskinfo));
59 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "PROC_PIDTASKINFO");
60
61 return taskinfo.pti_csw;
62 }
63
64 // noinline hopefully keeps the optimizer from hoisting it out of the loop
65 // until rdar://68253516 is fixed.
66 __attribute__((noinline))
67 static uint32_t
fixed_os_cpu_number(void)68 fixed_os_cpu_number(void)
69 {
70 uint32_t cpu_number = _os_cpu_number();
71
72 return cpu_number;
73 }
74
75
76 T_DECL(count_cpus, "Tests we can schedule bound threads on all hw.ncpus cores and that _os_cpu_number matches", XNU_T_META_SOC_SPECIFIC)
77 {
78 int rv;
79
80 setvbuf(stdout, NULL, _IONBF, 0);
81 setvbuf(stderr, NULL, _IONBF, 0);
82
83 /* Validate what kind of kernel we're on */
84 size_t kernel_version_size = sizeof(kernel_version);
85 rv = sysctlbyname("kern.version", kernel_version, &kernel_version_size, NULL, 0);
86 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.version");
87
88 T_LOG("kern.version: %s\n", kernel_version);
89
90 /* Double check that darwintest set the boot arg we requested */
91 size_t kernel_bootargs_size = sizeof(kernel_bootargs);
92 rv = sysctlbyname("kern.bootargs", kernel_bootargs, &kernel_bootargs_size, NULL, 0);
93 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.bootargs");
94
95 T_LOG("kern.bootargs: %s\n", kernel_bootargs);
96
97 if (NULL == strstr(kernel_bootargs, "enable_skstb=1")) {
98 T_FAIL("enable_skstb=1 boot-arg is missing");
99 }
100
101 kern_return_t kr;
102 kr = mach_timebase_info(&timebase_info);
103 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_timebase_info");
104
105 int bound_cpu_out = 0;
106 size_t bound_cpu_out_size = sizeof(bound_cpu_out);
107 rv = sysctlbyname("kern.sched_thread_bind_cpu", &bound_cpu_out, &bound_cpu_out_size, NULL, 0);
108
109 if (rv == -1) {
110 if (errno == ENOENT) {
111 T_FAIL("kern.sched_thread_bind_cpu doesn't exist, must set enable_skstb=1 boot-arg on development kernel");
112 }
113 if (errno == EPERM) {
114 T_FAIL("must run as root");
115 }
116 }
117
118 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "read kern.sched_thread_bind_cpu");
119 T_QUIET; T_ASSERT_EQ(bound_cpu_out, -1, "kern.sched_thread_bind_cpu should exist, start unbound");
120
121 struct sched_param param = {.sched_priority = 63};
122
123 rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
124 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_setschedparam");
125
126 uint32_t sysctl_ncpu = 0;
127 size_t ncpu_size = sizeof(sysctl_ncpu);
128 rv = sysctlbyname("hw.ncpu", &sysctl_ncpu, &ncpu_size, NULL, 0);
129 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "sysctlbyname(hw.ncpu)");
130
131 T_LOG("hw.ncpu: %2d\n", sysctl_ncpu);
132
133 T_ASSERT_GT(sysctl_ncpu, 0, "at least one CPU exists");
134
135 for (uint32_t cpu_to_bind = 0; cpu_to_bind < sysctl_ncpu; cpu_to_bind++) {
136 int32_t before_csw_count = get_csw_count();
137 T_LOG("(csw %4d) attempting to bind to cpu %2d\n", before_csw_count, cpu_to_bind);
138
139 uint64_t start = mach_absolute_time();
140
141 rv = sysctlbyname("kern.sched_thread_bind_cpu", NULL, 0, &cpu_to_bind, sizeof(cpu_to_bind));
142
143 uint64_t end = mach_absolute_time();
144
145 if (rv == -1 && errno == ENOTSUP) {
146 T_SKIP("Binding is available, but this process doesn't support binding (e.g. Rosetta on Aruba)");
147 }
148
149 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.sched_thread_bind_cpu(%u)", cpu_to_bind);
150
151 uint32_t os_cpu_number_reported = fixed_os_cpu_number();
152
153 bound_cpu_out = 0;
154 rv = sysctlbyname("kern.sched_thread_bind_cpu", &bound_cpu_out, &bound_cpu_out_size, NULL, 0);
155 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "read kern.sched_thread_bind_cpu");
156
157 T_QUIET; T_EXPECT_EQ((int)cpu_to_bind, bound_cpu_out,
158 "should report bound cpu id matching requested bind target");
159
160 uint64_t delta_abs = end - start;
161 uint64_t delta_ns = abs_to_nanos(delta_abs);
162
163 int32_t after_csw_count = get_csw_count();
164
165 T_LOG("(csw %4d) bound to cpu %2d in %f milliseconds\n",
166 after_csw_count, cpu_to_bind,
167 ((double)delta_ns / 1000000.0));
168
169 if (cpu_to_bind > 0) {
170 T_QUIET; T_EXPECT_LT(before_csw_count, after_csw_count,
171 "should have had to context switch to execute the bind");
172 }
173
174 T_LOG("cpu %2d reported id %2d\n",
175 cpu_to_bind, os_cpu_number_reported);
176
177 T_QUIET;
178 T_EXPECT_EQ(cpu_to_bind, os_cpu_number_reported,
179 "should report same CPU number as was bound to");
180 }
181
182 int unbind = -1; /* pass -1 in order to unbind the thread */
183
184 rv = sysctlbyname("kern.sched_thread_bind_cpu", NULL, 0, &unbind, sizeof(unbind));
185
186 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.sched_thread_bind_cpu(%u)", unbind);
187
188 rv = sysctlbyname("kern.sched_thread_bind_cpu", &bound_cpu_out, &bound_cpu_out_size, NULL, 0);
189
190 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "read kern.sched_thread_bind_cpu");
191 T_QUIET; T_ASSERT_EQ(bound_cpu_out, -1, "thread should be unbound at the end");
192
193 T_PASS("test has run threads on all CPUS");
194 }
195