From c1af31f75ac6a549d11153dd20b1f464bd69ad73 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Fri, 23 Oct 2026 13:50:08 +0200 Subject: [PATCH] smp: 64 processors, and say how many there are NCPU was 16, under what a desktop has today. 64 costs 48 more TSS I/O bitmaps and scheduler stacks, about 800 KB of BSS. Past 254 the APIC would need x2APIC mode, which is a separate change. With more than 16 there were two other ceilings. sched_getaffinity wrote "CPU 0" and returned 0, and musl clears every byte past the count returned -- so the mask came back empty and nproc, make -j and sysconf(_SC_NPROCESSORS_ONLN) all said 1, on any machine. It returns the mask of processors that started, and its size, now. And /proc rendered every file into 2 KB, which /proc/cpuinfo ran out of at the eighteenth processor without a word; /proc/pci, /proc/interrupts and /proc/mounts were each one bigger machine from the same cut. There is one 64 KB buffer for it now behind a sleeplock, since there is no allocator for contiguous pages, and the generators that write raw are given the real capacity instead of a constant. Checked under QEMU with -smp 24: nproc 24, /proc/cpuinfo whole. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4 --- include/file.h | 5 ++++ include/param.h | 2 +- kernel/dev/procfs.c | 54 +++++++++++++++++++++++++++++++------------ kernel/fs/procfs.c | 23 +++++++----------- kernel/sys/sys_proc.c | 21 +++++++++++++---- 5 files changed, 69 insertions(+), 36 deletions(-) diff --git a/include/file.h b/include/file.h index 97ba65c..2230caf 100644 --- a/include/file.h +++ b/include/file.h @@ -178,6 +178,11 @@ struct inode { // Graft it over `target`; 0 or a negative errno. In sysfs.c. int sysfs_mount(const char *target); +// The buffer /proc renders a file into, and how big it is; one at a time, so +// every get is followed by a put. In kernel/dev/procfs.c. +char *proc_genbuf_get(int *cap); +void proc_genbuf_put(void); + // Mounted FAT volumes, FAT_DEV onward. See include/fat.h. // Linux's limits, for every filesystem behind the VFS. A path component can be diff --git a/include/param.h b/include/param.h index 4e6426e..ad5f545 100644 --- a/include/param.h +++ b/include/param.h @@ -25,7 +25,7 @@ #define NPROC 128 // maximum number of processes #define KSTACKSIZE 8192 // per-process kernel stack; see kstacks[] in proc.c #define KSTACK_CANARY 0xDEADC0DEDEADC0DEULL // sentinel at kstack[0], see kstack_check_canary() -#define NCPU 16 // maximum number of CPUs +#define NCPU 64 // maximum number of CPUs #define NOFILE 128 // open files per process #define NFILE 512 // open files per system #define NINODE 200 // maximum number of active i-nodes diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c index 7f3b678..ce7ca82 100644 --- a/kernel/dev/procfs.c +++ b/kernel/dev/procfs.c @@ -43,7 +43,34 @@ #include "blkdev.h" #include "fat.h" -#define PROC_BUFSZ 2048 +// Every generated file is rendered whole into one buffer and then sliced, so +// this is the largest any of them can be. It was 2048, which a 24-processor +// /proc/cpuinfo passed at the eighteenth CPU, and /proc/pci, /proc/mounts and +// /proc/interrupts were each one larger machine away from the same quiet cut. +// +// One buffer, not one per read: 64 KB is sixteen pages and there is no +// allocator for contiguous pages. The lock serialises /proc reads, which are +// short and not a hot path -- top reads a few files a second. +#define PROC_BUFSZ (64 * 1024) + +static char genbuf[PROC_BUFSZ] __attribute__((aligned(4096))); +static struct sleeplock genlock = { + .lk = { .name = "procgen" }, .name = "procgen", +}; + +char * +proc_genbuf_get(int *cap) +{ + acquiresleep(&genlock); + *cap = PROC_BUFSZ; + return genbuf; +} + +void +proc_genbuf_put(void) +{ + releasesleep(&genlock); +} // The formatting helpers these files are built out of now live in // include/sbuf.h, because the debugger's introspection registry needs exactly @@ -383,18 +410,18 @@ proc_gen_global(int id, char *buf, int cap) case PROC_FILESYSTEMS: gen_filesystems(&sb); break; // SchFS renders straight into the buffer rather than through sbuf, because // it lives in kernel/fs/schfs and must not depend on this file's helpers. - case PROC_SCHFS: sb.len = schfs_kinfo(buf, PROC_BUFSZ); break; + case PROC_SCHFS: sb.len = schfs_kinfo(buf, cap); break; // Same arrangement as SchFS above: pci.c renders its own lines so that it // does not depend on this file's helpers. - case PROC_PCI: sb.len = pci_info(buf, PROC_BUFSZ); break; + case PROC_PCI: sb.len = pci_info(buf, cap); break; // Likewise irq.c: it owns the table, so it renders it. - case PROC_INTERRUPTS: sb.len = irq_info(buf, PROC_BUFSZ); break; + case PROC_INTERRUPTS: sb.len = irq_info(buf, cap); break; case 16: gen_schinit(&sb); break; // And likewise net.c, which is the only file here allowed to include lwIP's // headers -- reaching into the stack from this one would put them on every // kernel file's include path. - case PROC_NETIF: sb.len = net_info(buf, PROC_BUFSZ); break; - case PROC_RESOLV: sb.len = net_resolv(buf, PROC_BUFSZ); break; + case PROC_NETIF: sb.len = net_info(buf, cap); break; + case PROC_RESOLV: sb.len = net_resolv(buf, cap); break; // How this machine was booted, for the programs that start during one. // // schinit and the jobs it runs have the same problem the kernel does -- most @@ -412,7 +439,7 @@ proc_gen_global(int id, char *buf, int cap) break; case PROC_DEVICES: gen_devices(&sb); break; // The block layer renders its own table, like PCI and SchFS above. - case PROC_PARTITIONS: sb.len = blkdev_info(buf, PROC_BUFSZ); break; + case PROC_PARTITIONS: sb.len = blkdev_info(buf, cap); break; default: return 0; } return sb.len; @@ -433,22 +460,19 @@ procread(struct inode *ip, uint64 off, char *dst, int n) // above has warned about exactly that since it was written. It stayed a // local because a page from kalloc() for 2 KB felt worse; kmalloc is the // answer that was missing. - char *buf = kmalloc(PROC_BUFSZ); - int len; - - if (!buf) - return -ENOMEM; - len = proc_gen_global(ip->minor, buf, PROC_BUFSZ); + int cap; + char *buf = proc_genbuf_get(&cap); + int len = proc_gen_global(ip->minor, buf, cap); if (len <= 0 || (int)off >= len) { - kmfree(buf); + proc_genbuf_put(); return 0; // EOF } int avail = len - (int)off; if (n > avail) n = avail; memmove(dst, buf + off, n); - kmfree(buf); + proc_genbuf_put(); return n; } diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index ef3549b..12d0c53 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -53,12 +53,6 @@ // readable in a debugger, which matters more than density here. // --------------------------------------------------------------------------- -// Size of the scratch buffer every generator renders into. Must stay equal to -// PROC_BUFSZ in kernel/dev/procfs.c: the SchFS and PCI generators there write -// up to PROC_BUFSZ regardless of the cap they are handed, so a smaller value -// here would be a buffer overrun rather than a truncation. -#define PROCFS_GENBUF 2048 - #define PROCFS_ROOT 1ULL #define PROCFS_GLOBAL 0x10000ULL @@ -378,15 +372,14 @@ procfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) return r; } - // kmalloc, not kalloc: this buffer is PROCFS_GENBUF and kalloc's unit is a - // whole 4 KB page. The comment above about a page not being wasteful was - // written when a page was the only thing there was to ask for. - if ((buf = kmalloc(PROCFS_GENBUF)) == 0) - return -ENOMEM; - - len = gen_file(ip->inum, buf, PROCFS_GENBUF); + // The shared buffer in kernel/dev/procfs.c, 64 KB, held until the copy out + // is done. It was 2 KB from kmalloc, which cut /proc/cpuinfo off at the + // eighteenth processor. + int cap; + buf = proc_genbuf_get(&cap); + len = gen_file(ip->inum, buf, cap); if (off >= (uint64)len) { - kmfree(buf); + proc_genbuf_put(); return 0; } if (off + n > (uint64)len) @@ -400,7 +393,7 @@ procfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) memmove((char *)dst, buf + off, n); } - kmfree(buf); + proc_genbuf_put(); return r; } diff --git a/kernel/sys/sys_proc.c b/kernel/sys/sys_proc.c index 89868e3..0e633b5 100644 --- a/kernel/sys/sys_proc.c +++ b/kernel/sys/sys_proc.c @@ -360,16 +360,27 @@ addr_t sys_setgroups(void) { return 0; } addr_t sys_sched_yield(void) { return 0; } +// Every processor that started, since nothing here pins a process anywhere. +// +// This wrote "CPU 0" and returned 0 -- and the return value is the number of +// bytes of mask the kernel wrote, which musl uses to clear the rest. Zero +// meant all of it, so the mask came back empty and nproc, make -j and every +// thread pool sized from sysconf(_SC_NPROCESSORS_ONLN) saw one processor. addr_t sys_sched_getaffinity(void) { int pid, cpusetsize; addr_t maskp; + uchar m[(NCPU + 63) / 64 * 8]; argint(0, &pid); argint(1, &cpusetsize); argaddr(2, &maskp); - if (maskp && cpusetsize > 0) { - char *m = (char*)user_ptr(maskp, (uint64)cpusetsize); - if (m) { memset(m, 0, cpusetsize); m[0] = 1; } // CPU 0 only - } - return 0; + if (cpusetsize < (int)sizeof m || (cpusetsize & 7)) + return -EINVAL; + memset(m, 0, sizeof m); + for (int i = 0; i < ncpu && i < NCPU; i++) + if (cpus[i].started) + m[i / 8] |= 1 << (i % 8); + if (copy_to_user(maskp, m, sizeof m) < 0) + return -EFAULT; + return sizeof m; } addr_t sys_sched_setaffinity(void) { return 0; }