Skip to content

Commit 8b1cb3a

Browse files
feat: add per-CPU topology and the global huge-page pool
/sys/devices/system/cpu/cpuN/online /sys/devices/system/cpu/cpuN/topology/{core_id,physical_package_id, thread_siblings_list,core_siblings_list} /sys/kernel/mm/hugepages/hugepages-2048kB/{nr_,free_,surplus_,resv_, nr_overcommit_}hugepages The cpuN directories are created statically at mount rather than expanded dynamically: macOS never hot-plugs a core, so the logical CPU count is fixed for the life of the boot and there is nothing to expand. Each node carries its CPU number in the new sfssnode ssn_instance field, so one handler serves every cpuN instead of needing a function per CPU. Topology is derived rather than assumed. Every Mac is single-socket, so physical_package_id is 0 and core_siblings_list is every CPU. core_id and thread_siblings_list come from hw.logicalcpu_max against hw.physicalcpu_max: with no SMT (Apple Silicon) each CPU is its own core and its own sibling list, and with Hyper-Threading the consecutive threads sharing a core are reported as a range. /sys/kernel/mm/hugepages carries the two counters that are global-only on Linux (reservations and the overcommit limit) alongside the three the per-node directories already had. All read 0: macOS has no hugetlb pool to size, reserve or overcommit. Builds clean for arm64e, x86_64 and universal.
1 parent 7b67b83 commit 8b1cb3a

3 files changed

Lines changed: 208 additions & 1 deletion

File tree

include/fs/sysfs/sysfs.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,14 @@ struct sfssnode {
218218
sfsbaseid_t ssn_base_node_id; /* Base node id - unique. */
219219
uint16_t ssn_flags; /* Flags - SSN_FLAG_* */
220220

221+
/*
222+
* Which instance of a repeated node this is - the CPU number under
223+
* /sys/devices/system/cpu/cpuN, for example. Lets one read handler serve a
224+
* whole family of otherwise identical static nodes without a separate
225+
* function per instance. Zero for nodes that are not part of such a family.
226+
*/
227+
uint32_t ssn_instance;
228+
221229
/*
222230
* Structure linkage. Immutable once set.
223231
*/
@@ -454,6 +462,23 @@ extern int sysfs_do_node_numastat(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
454462
extern int sysfs_do_node_list(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
455463
extern int sysfs_do_cpu_kernel_max(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
456464

465+
/*
466+
* Per-CPU nodes (/sys/devices/system/cpu/cpuN/...). These serve every cpuN from
467+
* one handler, taking the CPU number from the structure node's ssn_instance.
468+
*/
469+
extern int sysfs_do_cpu_online(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
470+
extern int sysfs_do_cpu_package_id(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
471+
extern int sysfs_do_cpu_core_id(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
472+
extern int sysfs_do_cpu_thread_siblings(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
473+
extern int sysfs_do_cpu_core_siblings(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
474+
475+
/*
476+
* Number of logical CPUs, for building the per-CPU tree at mount time. macOS
477+
* never hot-plugs a core, so the count is fixed for the life of the boot and the
478+
* cpuN directories can be created statically.
479+
*/
480+
extern uint32_t sysfs_cpu_count(void);
481+
457482
#endif /* __FSBUNDLE__ */
458483

459484
#endif /* sysfs_h */

kext/sysfs_structure.c

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <string.h>
2424
#include <kern/assert.h>
2525
#include <kern/debug.h>
26+
#include <libkern/libkern.h>
2627
#include <libkern/OSMalloc.h>
2728
#include <mach/boolean.h>
2829
#include <sys/vnode.h>
@@ -50,6 +51,13 @@ STATIC void release_node(sfssnode_t *root);
5051
*/
5152
#define SYSFS_RELEASE_STACK_MAX 256
5253

54+
/*
55+
* Upper bound on generated /sys/devices/system/cpu/cpuN directories. The static
56+
* tree is torn down through release_node()'s fixed-size stack, so the number of
57+
* nodes the CPU loop can add has to stay well inside that bound.
58+
*/
59+
#define SYSFS_MAX_CPU_NODES 64
60+
5361
/*
5462
* Next node id. No need to lock this value because access is guaranteed to be
5563
* single-threaded. Start at 2 because the root node is always 1.
@@ -206,12 +214,77 @@ sysfs_structure_init(void)
206214
add_file(cpu_dir, "present", next_node_id++, 0, 0, NULL, sysfs_do_cpulist);
207215
add_file(cpu_dir, "offline", next_node_id++, 0, 0, NULL, sysfs_do_zero_count);
208216
add_file(cpu_dir, "kernel_max", next_node_id++, 0, 0, NULL, sysfs_do_cpu_kernel_max);
217+
218+
/*
219+
* cpu/cpuN/ with its topology. These are created statically, one per
220+
* logical CPU, because macOS never hot-plugs a core: the count is fixed
221+
* for the life of the boot, so there is nothing to expand dynamically.
222+
* Each node carries its CPU number in ssn_instance, which is how one
223+
* handler serves every cpuN.
224+
*/
225+
uint32_t ncpu = sysfs_cpu_count();
226+
for (uint32_t c = 0; c < ncpu && c < SYSFS_MAX_CPU_NODES; c++) {
227+
char cpuname[MAX_STRUCT_NODE_NAME_LEN];
228+
snprintf(cpuname, sizeof(cpuname), "cpu%u", c);
229+
230+
sfssnode_t *cpuN = add_directory(cpu_dir, cpuname,
231+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
232+
cpuN->ssn_instance = c;
233+
234+
sfssnode_t *f = add_file(cpuN, "online", next_node_id++, 0, 0,
235+
NULL, sysfs_do_cpu_online);
236+
f->ssn_instance = c;
237+
238+
sfssnode_t *topo = add_directory(cpuN, "topology",
239+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
240+
topo->ssn_instance = c;
241+
242+
f = add_file(topo, "core_id", next_node_id++, 0, 0,
243+
NULL, sysfs_do_cpu_core_id);
244+
f->ssn_instance = c;
245+
f = add_file(topo, "physical_package_id", next_node_id++, 0, 0,
246+
NULL, sysfs_do_cpu_package_id);
247+
f->ssn_instance = c;
248+
f = add_file(topo, "thread_siblings_list", next_node_id++, 0, 0,
249+
NULL, sysfs_do_cpu_thread_siblings);
250+
f->ssn_instance = c;
251+
f = add_file(topo, "core_siblings_list", next_node_id++, 0, 0,
252+
NULL, sysfs_do_cpu_core_siblings);
253+
f->ssn_instance = c;
254+
}
255+
209256
(void)add_directory(root_node, "firmware", SFSdir, next_node_id++, 0, 0, NULL, NULL);
210257
(void)add_directory(root_node, "fs", SFSdir, next_node_id++, 0, 0, NULL, NULL);
211258
(void)add_directory(root_node, "hypervisor", SFSdir, next_node_id++, 0, 0, NULL, NULL);
212-
(void)add_directory(root_node, "kernel", SFSdir, next_node_id++, 0, 0, NULL, NULL);
259+
sfssnode_t *kernel_dir =
260+
add_directory(root_node, "kernel", SFSdir, next_node_id++, 0, 0, NULL, NULL);
213261
(void)add_directory(root_node, "module", SFSdir, next_node_id++, 0, 0, NULL, NULL);
214262
(void)add_directory(root_node, "power", SFSdir, next_node_id++, 0, 0, NULL, NULL);
263+
264+
/*
265+
* /sys/kernel/mm/hugepages/hugepages-2048kB/ - the system-wide huge-page
266+
* pool, the counterpart to the per-node counters above. Linux exposes two
267+
* extra knobs here that have no per-node equivalent (reservations and the
268+
* overcommit limit); like the rest they are 0, because macOS has no
269+
* hugetlb pool to reserve from or overcommit.
270+
*/
271+
sfssnode_t *mm_dir = add_directory(kernel_dir, "mm",
272+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
273+
sfssnode_t *mm_hp_dir = add_directory(mm_dir, "hugepages",
274+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
275+
sfssnode_t *mm_hp_2m = add_directory(mm_hp_dir, "hugepages-2048kB",
276+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
277+
278+
add_file(mm_hp_2m, "nr_hugepages", next_node_id++, 0,
279+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
280+
add_file(mm_hp_2m, "free_hugepages", next_node_id++, 0,
281+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
282+
add_file(mm_hp_2m, "surplus_hugepages", next_node_id++, 0,
283+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
284+
add_file(mm_hp_2m, "resv_hugepages", next_node_id++, 0,
285+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
286+
add_file(mm_hp_2m, "nr_overcommit_hugepages", next_node_id++, 0,
287+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
215288
}
216289
}
217290

kext/sysfs_system.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ sysfs_ncpu(void)
5353
return (uint32_t)ncpu;
5454
}
5555

56+
/*
57+
* Public form of the above, for building the per-CPU tree at mount time.
58+
*/
59+
uint32_t
60+
sysfs_cpu_count(void)
61+
{
62+
return sysfs_ncpu();
63+
}
64+
5665
/*
5766
* Total physical memory in bytes.
5867
*/
@@ -208,6 +217,106 @@ sysfs_do_node_list(__unused sfsnode_t *snp, uio_t uio, __unused vfs_context_t ct
208217
return sysfs_copy_data(node0, (int)(sizeof(node0) - 1), uio);
209218
}
210219

220+
/*
221+
* How many logical CPUs share one physical core. Apple Silicon has no SMT, so
222+
* this is 1; an Intel Mac with Hyper-Threading reports 2.
223+
*/
224+
static uint32_t
225+
sysfs_threads_per_core(void)
226+
{
227+
int phys = 0;
228+
size_t len = sizeof(phys);
229+
uint32_t ncpu = sysfs_ncpu();
230+
231+
if (sysctlbyname("hw.physicalcpu_max", &phys, &len, NULL, 0) != 0 || phys <= 0) {
232+
return 1;
233+
}
234+
if ((uint32_t)phys >= ncpu) {
235+
return 1;
236+
}
237+
return ncpu / (uint32_t)phys;
238+
}
239+
240+
/*
241+
* The CPU number a per-CPU node stands for, carried on the structure node so one
242+
* handler can serve every cpuN.
243+
*/
244+
static uint32_t
245+
sysfs_node_cpu(sfsnode_t *snp)
246+
{
247+
return snp->node_structure_node->ssn_instance;
248+
}
249+
250+
/*
251+
* cpuN/online. macOS never offlines a core, so every CPU that exists is online.
252+
*/
253+
int
254+
sysfs_do_cpu_online(__unused sfsnode_t *snp, uio_t uio, __unused vfs_context_t ctx)
255+
{
256+
static const char one[] = "1\n";
257+
258+
return sysfs_copy_data(one, (int)(sizeof(one) - 1), uio);
259+
}
260+
261+
/*
262+
* cpuN/topology/physical_package_id. Every Mac is single-socket, so every CPU is
263+
* in package 0.
264+
*/
265+
int
266+
sysfs_do_cpu_package_id(__unused sfsnode_t *snp, uio_t uio, __unused vfs_context_t ctx)
267+
{
268+
static const char pkg[] = "0\n";
269+
270+
return sysfs_copy_data(pkg, (int)(sizeof(pkg) - 1), uio);
271+
}
272+
273+
/*
274+
* cpuN/topology/core_id - which physical core this logical CPU sits on. Without
275+
* SMT that is the CPU number itself; with Hyper-Threading the sibling threads of
276+
* a core are consecutive, so dividing by the thread count gives the core.
277+
*/
278+
int
279+
sysfs_do_cpu_core_id(sfsnode_t *snp, uio_t uio, __unused vfs_context_t ctx)
280+
{
281+
char buf[SYSFS_SYSTEM_BUFMAX];
282+
uint32_t cpu = sysfs_node_cpu(snp);
283+
uint32_t tpc = sysfs_threads_per_core();
284+
int len = snprintf(buf, sizeof(buf), "%u\n", cpu / tpc);
285+
286+
return sysfs_copy_data(buf, len, uio);
287+
}
288+
289+
/*
290+
* cpuN/topology/thread_siblings_list - the logical CPUs sharing this core. Just
291+
* this CPU without SMT; the consecutive run covering the core when SMT is on.
292+
*/
293+
int
294+
sysfs_do_cpu_thread_siblings(sfsnode_t *snp, uio_t uio, __unused vfs_context_t ctx)
295+
{
296+
char buf[SYSFS_SYSTEM_BUFMAX];
297+
uint32_t cpu = sysfs_node_cpu(snp);
298+
uint32_t tpc = sysfs_threads_per_core();
299+
int len;
300+
301+
if (tpc <= 1) {
302+
len = snprintf(buf, sizeof(buf), "%u\n", cpu);
303+
} else {
304+
uint32_t first = (cpu / tpc) * tpc;
305+
len = snprintf(buf, sizeof(buf), "%u-%u\n", first, first + tpc - 1);
306+
}
307+
return sysfs_copy_data(buf, len, uio);
308+
}
309+
310+
/*
311+
* cpuN/topology/core_siblings_list - the logical CPUs sharing this package.
312+
* Single-socket, so that is every CPU.
313+
*/
314+
int
315+
sysfs_do_cpu_core_siblings(__unused sfsnode_t *snp, uio_t uio, vfs_context_t ctx)
316+
{
317+
return sysfs_do_cpulist(snp, uio, ctx);
318+
}
319+
211320
/*
212321
* cpu/kernel_max - the largest CPU index the kernel could ever use. Linux
213322
* reports NR_CPUS-1; the analogous bound here is the maximum logical CPU count.

0 commit comments

Comments
 (0)