Skip to content

Commit 084c065

Browse files
feat: add /sys/devices/system/node/node0/hugepages
Expose the Linux per-NUMA-node huge-page attributes: /sys/devices/system/node/node0/hugepages/hugepages-2048kB/ nr_hugepages, free_hugepages, surplus_hugepages /sys/devices/system is synthetic on Linux too - it describes CPUs, memory and NUMA nodes rather than discovered hardware - so it is built as static structure nodes rather than as part of the IOKit mirror. The SFSdevice lookup and readdir paths now consult static children alongside registry ones, but only at the /sys/devices root (registry id 0), where they take precedence so a device named "system" cannot shadow the hierarchy. Below that, ordinary static handling applies. macOS is not NUMA, so there is exactly one node. The huge-page size is 2MB on both Apple Silicon and Intel (the architectural large-page size; base pages are 16KB and 4KB respectively). All three counters read 0: macOS has no hugetlb pool - large pages are managed transparently by the VM, with nothing to size, reserve or overcommit - which is exactly what a Linux host reports when no huge pages are configured. Per-node directories carry these three files only; resv_/nr_overcommit_ are global to /sys/kernel/mm/hugepages. This is the first node in the filesystem with real content, so it also adds the add_file() structure helper and the first read handler (kext/sysfs_system.c). Builds clean for arm64e, x86_64 and universal.
1 parent afed5a1 commit 084c065

4 files changed

Lines changed: 153 additions & 2 deletions

File tree

include/fs/sysfs/sysfs.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,16 @@ extern uint64_t sysfs_get_fileid(uint64_t regid, uint64_t objectid, sfsbaseid_
430430
extern int sysfs_atoi(const char *p, const char **end_ptr);
431431
extern size_t sysfs_get_node_size_attr(sfsnode_t *snp, kauth_cred_t creds);
432432

433+
/*
434+
* Content for /sys/devices/system (sysfs_system.c).
435+
*
436+
* sysfs_do_zero_count renders an always-zero counter as Linux renders a sysfs
437+
* integer attribute ("0\n"); SYSFS_ZERO_COUNT_LEN is the length it produces, so
438+
* a node using it can report the right size without a size function.
439+
*/
440+
#define SYSFS_ZERO_COUNT_LEN 2
441+
extern int sysfs_do_zero_count(sfsnode_t *snp, uio_t uio, vfs_context_t ctx);
442+
433443
#endif /* __FSBUNDLE__ */
434444

435445
#endif /* sysfs_h */

kext/sysfs_structure.c

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ STATIC sfssnode_t *add_node(sfssnode_t *parent, const char *name, sfstype type,
3838
STATIC sfssnode_t *add_directory(sfssnode_t *parent, const char *name, sfstype type, sfsbaseid_t node_id, uint16_t flags, boolean_t raw,
3939
sysfs_node_size_fn node_size_fn, sysfs_read_data_fn node_read_data_fn);
4040

41+
STATIC sfssnode_t *add_file(sfssnode_t *parent, const char *name, sfsbaseid_t node_id, uint16_t flags, size_t size,
42+
sysfs_node_size_fn node_size_fn, sysfs_read_data_fn node_read_data_fn);
43+
4144
STATIC void release_node(sfssnode_t *root);
4245

4346
/*
@@ -128,7 +131,49 @@ sysfs_structure_init(void)
128131
* has no static "."/".." children - the dynamic readdir emits those
129132
* itself (see sysfs_devices_readdir), exactly as procfs does for /proc/sys.
130133
*/
131-
(void)add_node(root_node, "devices", SFSdevice, next_node_id++, SSN_FLAG_DYNAMIC, 0, NULL, NULL);
134+
sfssnode_t *devices_node =
135+
add_node(root_node, "devices", SFSdevice, next_node_id++, SSN_FLAG_DYNAMIC, 0, NULL, NULL);
136+
137+
/*
138+
* /sys/devices/system - Linux's "system" pseudo-bus. Unlike the rest of
139+
* /sys/devices this is NOT part of the IOKit mirror: on Linux it is a
140+
* synthetic hierarchy describing CPUs, memory and NUMA nodes, so it is
141+
* built here as ordinary static structure nodes. The SFSdevice lookup and
142+
* readdir paths consult these static children alongside the registry
143+
* ones, but only at the /sys/devices root (registry id 0).
144+
*
145+
* macOS is not NUMA, so there is exactly one node, node0.
146+
*/
147+
sfssnode_t *system_dir = add_directory(devices_node, "system",
148+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
149+
sfssnode_t *node_dir = add_directory(system_dir, "node",
150+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
151+
sfssnode_t *node0_dir = add_directory(node_dir, "node0",
152+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
153+
154+
/*
155+
* node0/hugepages/hugepages-<size>kB/. The huge-page size is 2MB on both
156+
* Apple Silicon and Intel Macs (the architectural large-page size; the
157+
* base page is 16KB on arm64 and 4KB on x86_64), so the directory is
158+
* named for 2048kB on either.
159+
*
160+
* macOS has no hugetlb pool to size, reserve or overcommit - large pages
161+
* are managed transparently by the VM - so all three counters read 0,
162+
* exactly as on a Linux host where no huge pages have been allocated.
163+
* Per-node hugepages directories expose these three files only; the
164+
* resv_/nr_overcommit_ counters are global (/sys/kernel/mm/hugepages).
165+
*/
166+
sfssnode_t *hugepages_dir = add_directory(node0_dir, "hugepages",
167+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
168+
sfssnode_t *hp_2m_dir = add_directory(hugepages_dir, "hugepages-2048kB",
169+
SFSdir, next_node_id++, 0, 0, NULL, NULL);
170+
171+
add_file(hp_2m_dir, "nr_hugepages", next_node_id++, 0,
172+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
173+
add_file(hp_2m_dir, "free_hugepages", next_node_id++, 0,
174+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
175+
add_file(hp_2m_dir, "surplus_hugepages", next_node_id++, 0,
176+
SYSFS_ZERO_COUNT_LEN, NULL, sysfs_do_zero_count);
132177
(void)add_directory(root_node, "firmware", SFSdir, next_node_id++, 0, 0, NULL, NULL);
133178
(void)add_directory(root_node, "fs", SFSdir, next_node_id++, 0, 0, NULL, NULL);
134179
(void)add_directory(root_node, "hypervisor", SFSdir, next_node_id++, 0, 0, NULL, NULL);
@@ -231,6 +276,17 @@ add_directory(sfssnode_t *parent, const char *name, sfstype type, sfsbaseid_t no
231276
return snode;
232277
}
233278

279+
/*
280+
* Adds a file to the file system structure. Files are always leaf elements
281+
* (although that is not checked).
282+
*/
283+
STATIC sfssnode_t *
284+
add_file(sfssnode_t *parent, const char *name, sfsbaseid_t node_id, uint16_t flags, size_t size,
285+
sysfs_node_size_fn node_size_fn, sysfs_read_data_fn node_read_data_fn)
286+
{
287+
return add_node(parent, name, SFSfile, node_id, flags, size, node_size_fn, node_read_data_fn);
288+
}
289+
234290
#pragma mark -
235291
#pragma mark Clean up of Structure Nodes
236292

kext/sysfs_system.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2022-2026 Sunneva N. Mariu
3+
*
4+
* sysfs_system.c
5+
*
6+
* Content for /sys/devices/system - Linux's "system" pseudo-bus. Unlike the rest
7+
* of /sys/devices (which mirrors the IOKit registry), this hierarchy is
8+
* synthetic on Linux too: it describes CPUs, memory and NUMA nodes rather than
9+
* discovered hardware. The tree itself is built in sysfs_structure.c; this file
10+
* supplies the data its files read.
11+
*/
12+
#include <sys/uio.h>
13+
#include <sys/vnode.h>
14+
15+
#include <fs/sysfs/sysfs.h>
16+
17+
/*
18+
* A counter that is always zero, rendered the way Linux renders sysfs integer
19+
* attributes: the decimal value followed by a newline.
20+
*
21+
* This backs the per-node huge-page counters
22+
* (/sys/devices/system/node/node0/hugepages/hugepages-2048kB/{nr,free,surplus}_hugepages).
23+
* macOS has no hugetlb pool: large pages exist, but they are managed
24+
* transparently by the VM system and there is no persistent pool to size,
25+
* reserve or overcommit, so nothing can be allocated to a node. Reporting 0 is
26+
* both truthful and exactly what a Linux host with no huge pages configured
27+
* reports, which is what callers probing these files are prepared to handle.
28+
*/
29+
int
30+
sysfs_do_zero_count(sfsnode_t *snp, uio_t uio, __unused vfs_context_t ctx)
31+
{
32+
static const char zero[] = "0\n";
33+
34+
(void)snp;
35+
return sysfs_copy_data(zero, (int)(sizeof(zero) - 1), uio);
36+
}

kext/sysfs_vnops.c

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,29 @@ sysfs_vnop_lookup(struct vnop_lookup_args *ap)
347347
* distinguished by the matched regid/objectid.
348348
*/
349349
uint64_t regid = dir_snp->node_id.nodeid_regid;
350-
for (int a = 0; a < SYSFS_DEV_NATTRS; a++) {
350+
351+
/*
352+
* Static children of /sys/devices itself (currently "system", the
353+
* non-IOKit CPU/memory/NUMA hierarchy). These exist only at the
354+
* devices root - a real registry entry has no static children - and
355+
* take precedence, so a device that happened to be named "system"
356+
* cannot shadow them. Once matched, the child is an ordinary static
357+
* node and the normal static paths handle everything below it.
358+
*/
359+
if (regid == SYSFS_NO_REGID && sysfs_dev_is_dir(dir_snp->node_id.nodeid_objectid)) {
360+
sfssnode_t *static_child;
361+
TAILQ_FOREACH(static_child, &dir_snode->ssn_children, ssn_next) {
362+
if (strcmp(name, static_child->ssn_name) == 0) {
363+
match_node = static_child;
364+
match_node_id.nodeid_base_id = static_child->ssn_base_node_id;
365+
match_node_id.nodeid_regid = SYSFS_NO_REGID;
366+
match_node_id.nodeid_objectid = SYSFS_NO_OBJECTID;
367+
break;
368+
}
369+
}
370+
}
371+
372+
for (int a = 0; match_node == NULL && a < SYSFS_DEV_NATTRS; a++) {
351373
if (strcmp(name, sysfs_dev_attrs[a].name) == 0) {
352374
match_node = dir_snode;
353375
match_node_id.nodeid_base_id = dir_snode->ssn_base_node_id;
@@ -588,6 +610,33 @@ sysfs_devices_readdir(struct vnop_readdir_args *ap)
588610
nextpos += size;
589611
}
590612

613+
/*
614+
* Static children of /sys/devices itself (the non-IOKit "system" hierarchy).
615+
* Only the devices root has them; emitted before the registry entries so the
616+
* ordering matches lookup's precedence.
617+
*/
618+
if (regid == SYSFS_NO_REGID) {
619+
sfssnode_t *static_child;
620+
TAILQ_FOREACH(static_child, &dir_snp->node_structure_node->ssn_children, ssn_next) {
621+
if (uio_resid(uio) <= 0) {
622+
break;
623+
}
624+
int size = sysfs_calc_dirent_size(static_child->ssn_name);
625+
if (nextpos >= startpos) {
626+
error = sysfs_copyout_dirent(
627+
sysfs_is_directory_type(static_child->ssn_node_type) ? DT_DIR : DT_REG,
628+
sysfs_get_fileid(SYSFS_NO_REGID, SYSFS_NO_OBJECTID,
629+
static_child->ssn_base_node_id),
630+
static_child->ssn_name, uio, &size, nextpos + size);
631+
if (size == 0 || error != 0) {
632+
goto done;
633+
}
634+
numentries++;
635+
}
636+
nextpos += size;
637+
}
638+
}
639+
591640
/* Child registry entries, one subdirectory each. */
592641
for (unsigned int i = 0; error == 0 && uio_resid(uio) > 0; i++) {
593642
char childname[NAME_MAX + 1];

0 commit comments

Comments
 (0)