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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-23 13:50:08 +02:00
commit c1af31f75a
5 changed files with 69 additions and 36 deletions

View file

@ -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;
}