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
605 lines
20 KiB
C
605 lines
20 KiB
C
// /proc as a real filesystem.
|
|
//
|
|
// It used to be a handful of character devices: init ran `mknod("/proc/uptime",
|
|
// 6, 2)` and friends, so every file was a real inode on the root filesystem
|
|
// with a major/minor pair behind it. That works for a fixed set of files and
|
|
// cannot work at all for the thing /proc is mostly *for* -- one directory per
|
|
// process. You cannot mknod a directory for a process that does not exist yet
|
|
// and will not exist in a minute, which is why `ps` and `top` have never
|
|
// worked: they call dirtree_flagread("/proc") and get a list with no pids in
|
|
// it.
|
|
//
|
|
// So this is a synthetic filesystem behind the same `struct fs_ops` vtable
|
|
// SchFS uses. Nothing is stored: every directory listing is generated from the
|
|
// process table at the moment it is asked for, and every file's contents are
|
|
// generated when it is read.
|
|
//
|
|
// The global files (meminfo, uptime, pci, ...) keep their existing generators
|
|
// in kernel/dev/procfs.c; this layer only decides what exists and hands the
|
|
// reads over. That split is deliberate -- the generators are about content and
|
|
// this file is about namespace, and they change for different reasons.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "klog.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "file.h"
|
|
#include "kmem.h"
|
|
#include "stat.h"
|
|
#include "uaccess.h"
|
|
#include "errno.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Inode numbering
|
|
//
|
|
// An inode number has to survive being handed out, stored in a struct inode,
|
|
// and given back later with no other context -- so it encodes the whole
|
|
// identity of the object.
|
|
//
|
|
// 1 the root, /proc
|
|
// PROCFS_GLOBAL | id a global file, id being the PROC_* minor that
|
|
// kernel/dev/procfs.c already uses
|
|
// pid << 8 the directory /proc/<pid>
|
|
// (pid << 8) | fileid a file inside it
|
|
//
|
|
// pid is bounded by NPROC and file ids by PF_MAX, so the ranges cannot
|
|
// collide. Shifting by 8 rather than packing tightly keeps the numbers
|
|
// readable in a debugger, which matters more than density here.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define PROCFS_ROOT 1ULL
|
|
#define PROCFS_GLOBAL 0x10000ULL
|
|
|
|
#define PF_DIR 0 // the directory itself
|
|
#define PF_STAT 1
|
|
#define PF_STATUS 2
|
|
#define PF_CMDLINE 3
|
|
#define PF_STATM 4
|
|
#define PF_EXE 5
|
|
#define PF_MAX 6
|
|
|
|
#define INO_PID(ino) ((int)((ino) >> 8))
|
|
#define INO_FILE(ino) ((int)((ino) & 0xFF))
|
|
#define INO_IS_GLOBAL(ino) (((ino) & PROCFS_GLOBAL) != 0)
|
|
|
|
// The global files, by the name they appear under and the PROC_* id that
|
|
// kernel/dev/procfs.c generates them from.
|
|
struct global_file { const char *name; int id; };
|
|
|
|
static const struct global_file globals[] = {
|
|
{ "meminfo", 1 },
|
|
{ "uptime", 2 },
|
|
{ "cpuinfo", 3 },
|
|
{ "version", 4 },
|
|
{ "stat", 5 },
|
|
{ "loadavg", 6 },
|
|
{ "mounts", 7 },
|
|
{ "filesystems", 8 },
|
|
{ "schfs", 9 },
|
|
{ "sysrq", 10 },
|
|
{ "schinit", 16 },
|
|
{ "pci", 11 },
|
|
{ "interrupts", 12 },
|
|
{ "devices", 13 },
|
|
{ "fsstat", 14 },
|
|
{ "netif", 15 },
|
|
// The nameservers DHCP supplied, in resolv.conf's own syntax. See
|
|
// net_resolv() and cmd/resolvconf.
|
|
{ "resolv.conf", 18 },
|
|
// "quiet" or "verbose" -- how this machine was booted. See cmd/schinitd.
|
|
{ "bootmode", 19 },
|
|
{ "bootdebug", 30 },
|
|
// What the slab allocator is holding. See include/kmem.h.
|
|
{ "slab", 20 },
|
|
// Disks and partitions, in Linux's format. See blkdev_info().
|
|
{ "partitions", 21 },
|
|
{ "kmsg", 17 },
|
|
};
|
|
#define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0])))
|
|
|
|
// Per-process files. `statm` and `status` are here because ps reads them; a
|
|
// missing file is not an error to it, but a present one is cheaper than the
|
|
// fallback paths it takes without them.
|
|
static const struct global_file pidfiles[] = {
|
|
{ "stat", PF_STAT },
|
|
{ "status", PF_STATUS },
|
|
{ "cmdline", PF_CMDLINE },
|
|
{ "statm", PF_STATM },
|
|
{ "exe", PF_EXE },
|
|
};
|
|
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Content
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// A tiny append-only formatter. procfs files are small and generated whole,
|
|
// so there is no reason for anything cleverer.
|
|
struct sb { char *p; int n, cap; };
|
|
|
|
static void sbputc(struct sb *s, char c)
|
|
{ if (s->n < s->cap - 1) s->p[s->n++] = c; }
|
|
|
|
static void sbputs(struct sb *s, const char *t)
|
|
{ while (*t) sbputc(s, *t++); }
|
|
|
|
static void sbputi(struct sb *s, int64 v)
|
|
{
|
|
char tmp[24];
|
|
int i = 0, neg = 0;
|
|
uint64 u;
|
|
if (v < 0) { neg = 1; u = (uint64)(-v); } else u = (uint64)v;
|
|
do { tmp[i++] = '0' + (int)(u % 10); u /= 10; } while (u);
|
|
if (neg) sbputc(s, '-');
|
|
while (i--) sbputc(s, tmp[i]);
|
|
}
|
|
|
|
// Linux's /proc/<pid>/stat, as far as anything here reads it.
|
|
//
|
|
// The field order is not negotiable: ps counts fields by position, so a
|
|
// missing one silently shifts everything after it. Fields SchOS does not
|
|
// track are zero, which is what Linux itself reports for things a given
|
|
// process has never done.
|
|
static char
|
|
state_char(int st)
|
|
{
|
|
switch (st) {
|
|
case RUNNING: return 'R';
|
|
case RUNNABLE: return 'R';
|
|
case SLEEPING: return 'S';
|
|
case ZOMBIE: return 'Z';
|
|
case EMBRYO: return 'R';
|
|
default: return '?';
|
|
}
|
|
}
|
|
|
|
static void
|
|
gen_pid_stat(struct sb *s, const struct procinfo *pi)
|
|
{
|
|
int i;
|
|
|
|
sbputi(s, pi->pid);
|
|
sbputs(s, " (");
|
|
sbputs(s, pi->name);
|
|
sbputs(s, ") ");
|
|
sbputc(s, state_char(pi->state));
|
|
sbputc(s, ' ');
|
|
sbputi(s, pi->ppid); // 4 ppid
|
|
sbputc(s, ' ');
|
|
sbputi(s, pi->pgid); // 5 pgrp
|
|
sbputc(s, ' ');
|
|
sbputi(s, pi->sid); // 6 session
|
|
// 7 tty_nr. Linux's dev_t: major 12 bits above minor's 8. /dev/ttyN is
|
|
// major 1, minor 10+N in devfs, so a process whose ctty is 1 reports
|
|
// (1 << 8) | 11. This was hardcoded 0, and that is why plain `ps` printed a
|
|
// header and nothing else: ps with no arguments shows only the processes on
|
|
// *this* terminal, and nothing could ever match. `ps -e` always worked.
|
|
sbputc(s, ' ');
|
|
sbputi(s, pi->ctty ? ((1 << 8) | (10 + pi->ctty)) : 0);
|
|
sbputs(s, " -1 0"); // 8 tpgid, 9 flags
|
|
|
|
// 10..13: minflt cminflt majflt cmajflt. There is no fault accounting, and
|
|
// a fabricated number is worse than none -- top would draw a plausible graph
|
|
// of something nobody measured.
|
|
for (i = 0; i < 4; i++)
|
|
sbputs(s, " 0");
|
|
|
|
// 14..17: utime stime cutime cstime, in ticks, which is the unit the field
|
|
// is defined in. These were zero too, which is why `ps` and `top` showed
|
|
// 00:00:00 for everything no matter how long it had been running.
|
|
sbputc(s, ' '); sbputi(s, (int64)pi->utime);
|
|
sbputc(s, ' '); sbputi(s, (int64)pi->stime);
|
|
sbputc(s, ' '); sbputi(s, (int64)pi->cutime);
|
|
sbputc(s, ' '); sbputi(s, (int64)pi->cstime);
|
|
|
|
sbputs(s, " 20 0 1 0 0 "); // priority nice num_threads itrealvalue starttime
|
|
sbputi(s, (int64)pi->sz); // 23 vsize, in bytes
|
|
sbputc(s, ' ');
|
|
sbputi(s, (int64)(pi->sz / PGSIZE)); // 24 rss, in pages
|
|
sbputs(s, " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n");
|
|
}
|
|
|
|
static void
|
|
gen_pid_status(struct sb *s, const struct procinfo *pi)
|
|
{
|
|
sbputs(s, "Name:\t"); sbputs(s, pi->name); sbputc(s, '\n');
|
|
sbputs(s, "State:\t"); sbputc(s, state_char(pi->state));
|
|
sbputs(s, "\nTgid:\t"); sbputi(s, pi->pid);
|
|
sbputs(s, "\nPid:\t"); sbputi(s, pi->pid);
|
|
sbputs(s, "\nPPid:\t"); sbputi(s, pi->ppid);
|
|
sbputs(s, "\nUid:\t"); sbputi(s, pi->uid);
|
|
sbputc(s, '\t'); sbputi(s, pi->uid);
|
|
sbputc(s, '\t'); sbputi(s, pi->uid);
|
|
sbputc(s, '\t'); sbputi(s, pi->uid);
|
|
sbputs(s, "\nVmSize:\t"); sbputi(s, (int64)(pi->sz / 1024));
|
|
sbputs(s, " kB\nThreads:\t1\n");
|
|
}
|
|
|
|
static void
|
|
gen_pid_statm(struct sb *s, const struct procinfo *pi)
|
|
{
|
|
int64 pages = (int64)(pi->sz / PGSIZE);
|
|
sbputi(s, pages); sbputc(s, ' ');
|
|
sbputi(s, pages); sbputs(s, " 0 0 0 0 0\n");
|
|
}
|
|
|
|
// argv is not kept after exec, so the best honest answer is the process name.
|
|
// Linux separates arguments with NUL and ps turns those into spaces; one
|
|
// NUL-terminated string is a valid degenerate case of that.
|
|
static void
|
|
gen_pid_cmdline(struct sb *s, const struct procinfo *pi)
|
|
{
|
|
sbputs(s, pi->name);
|
|
sbputc(s, '\0');
|
|
}
|
|
|
|
// Generate the whole of one file into `buf`. Returns its length.
|
|
static int
|
|
gen_file(uint64 ino, char *buf, int cap)
|
|
{
|
|
struct sb s = { buf, 0, cap };
|
|
struct procinfo pi;
|
|
|
|
if (INO_IS_GLOBAL(ino))
|
|
return proc_gen_global((int)(ino & 0xFF), buf, cap);
|
|
|
|
if (proc_snapshot_pid(INO_PID(ino), &pi) < 0)
|
|
return 0; // it exited between lookup and read
|
|
|
|
switch (INO_FILE(ino)) {
|
|
case PF_STAT: gen_pid_stat(&s, &pi); break;
|
|
case PF_STATUS: gen_pid_status(&s, &pi); break;
|
|
case PF_CMDLINE: gen_pid_cmdline(&s, &pi); break;
|
|
case PF_STATM: gen_pid_statm(&s, &pi); break;
|
|
case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); break;
|
|
default: return 0;
|
|
}
|
|
return s.n;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// fs_ops
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void
|
|
procfs_iload(struct inode *ip)
|
|
{
|
|
uint64 ino = ip->inum;
|
|
|
|
ip->valid = 1;
|
|
ip->nlink = 1;
|
|
ip->major = ip->minor = 0;
|
|
// Read-only, with two exceptions: /proc/sysrq is the deliberate write target
|
|
// (`echo c > /proc/sysrq` to crash on purpose), and /proc/schinit is where
|
|
// PID 1 publishes its job table. Marking everything 2 made filewrite()
|
|
// refuse with "permission denied", which the USB suite caught immediately --
|
|
// three of its checks drive the disk through sysrq.
|
|
ip->rwmode = (INO_IS_GLOBAL(ino) &&
|
|
((ino & 0xFF) == 10 || (ino & 0xFF) == 16)) ? 3 : 2;
|
|
ip->showmode = 1;
|
|
ip->atime = ip->mtime = ip->ctime = 0;
|
|
|
|
if (ino == PROCFS_ROOT || (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_DIR)) {
|
|
ip->type = T_DIR;
|
|
ip->size = 0;
|
|
ip->nlink = 2;
|
|
return;
|
|
}
|
|
|
|
// /proc/<pid>/exe is a symlink, as it is on Linux.
|
|
//
|
|
// It was a regular file holding the path as text, which reads the same from
|
|
// `cat` and is wrong to everything that matters: readlink() refused it with
|
|
// EINVAL, and `ps -o comm` -- which readlinks it -- fell back to the process
|
|
// name in brackets and printed `[init]` for everything.
|
|
//
|
|
// This one file has to report a real size, and every other procfs file
|
|
// deliberately reports zero. readlink(2) bounds its copy by ip->size, so a
|
|
// symlink whose size is 0 resolves to the empty string -- which is worse
|
|
// than the regular file was, because it looks like it worked. Generating it
|
|
// here costs one small render per stat() of one file.
|
|
if (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_EXE) {
|
|
char path[64];
|
|
int len = gen_file(ino, path, sizeof(path));
|
|
ip->type = T_SYMLINK;
|
|
ip->size = len > 0 ? (uint64)len : 0;
|
|
ip->nlink = 1;
|
|
return;
|
|
}
|
|
|
|
ip->type = T_FILE;
|
|
// Size is reported as zero.
|
|
//
|
|
// A synthetic file has no length until it is generated, and generating it
|
|
// here -- on every stat(), for every process -- to fill in a number almost
|
|
// nobody reads would be absurd. Linux does exactly the same thing, which is
|
|
// why `ls -l /proc` shows a column of zeroes, and why every tool that reads
|
|
// /proc reads until EOF instead of trusting st_size.
|
|
ip->size = 0;
|
|
}
|
|
|
|
static void procfs_istore(struct inode *ip) { (void)ip; }
|
|
static void procfs_ifree(struct inode *ip) { (void)ip; }
|
|
static void procfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
|
|
|
|
static int
|
|
procfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
|
|
{
|
|
char *buf;
|
|
int len, r;
|
|
|
|
if (ip->type == T_DIR)
|
|
return -EISDIR;
|
|
|
|
// The 2048-byte scratch used to be a local. KSTACKSIZE is 4096, so that was
|
|
// half the kernel stack in one array, on a path already several frames deep
|
|
// -- a generator with any locals of its own, or one more frame below it,
|
|
// runs off the end. /proc/schfs is what finally did it: the canary at the
|
|
// bottom of the stack came back zeroed. Nothing about SchFS caused this; it
|
|
// only pushed an existing overrun over the edge.
|
|
//
|
|
// A whole page for a 2 KB buffer is not wasteful here -- kalloc's unit is a
|
|
// page either way, and this is not a hot path.
|
|
// /proc/kmsg is 128 KB and comes out of a ring, so it does not go through
|
|
// the render-the-whole-file-then-slice path below. See klog_read().
|
|
if (INO_IS_GLOBAL(ip->inum) && (int)(ip->inum & 0xFF) == 17) {
|
|
char *k;
|
|
int got;
|
|
|
|
if (n > PGSIZE)
|
|
n = PGSIZE;
|
|
if ((k = kalloc()) == 0)
|
|
return -ENOMEM;
|
|
got = klog_read(off, k, (int)n);
|
|
if (got <= 0) {
|
|
kfree(k);
|
|
return 0;
|
|
}
|
|
r = got;
|
|
if (user_dst) {
|
|
if (copy_to_user(dst, k, got) < 0)
|
|
r = -EFAULT;
|
|
} else {
|
|
memmove((char *)dst, k, got);
|
|
}
|
|
kfree(k);
|
|
return r;
|
|
}
|
|
|
|
// 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) {
|
|
proc_genbuf_put();
|
|
return 0;
|
|
}
|
|
if (off + n > (uint64)len)
|
|
n = (uint)((uint64)len - off);
|
|
|
|
r = (int)n;
|
|
if (user_dst) {
|
|
if (copy_to_user(dst, buf + off, n) < 0)
|
|
r = -EFAULT;
|
|
} else {
|
|
memmove((char *)dst, buf + off, n);
|
|
}
|
|
|
|
proc_genbuf_put();
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
procfs_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n)
|
|
{
|
|
// Only /proc/sysrq accepts writes, and its handler lives with the other
|
|
// generators. Everything else is read-only, and says so.
|
|
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 10)
|
|
return proc_write_sysrq(user_src, src, n);
|
|
// /proc/schinit is the job table, and PID 1 is the one that knows it. The
|
|
// kernel holds the text rather than generating it: walking another
|
|
// process's memory to answer a read is not something a procfs generator
|
|
// should be doing, and PID 1 already knows when the table changed.
|
|
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 16)
|
|
return proc_write_schinit(user_src, src, n);
|
|
(void)off;
|
|
return -EROFS;
|
|
}
|
|
|
|
// Decimal string -> pid, or -1 if it is not all digits. This is also what
|
|
// makes a name like "meminfo" fall through to the global table.
|
|
static int
|
|
name_to_pid(const char *name)
|
|
{
|
|
int v = 0;
|
|
|
|
if (!name || !*name)
|
|
return -1;
|
|
while (*name) {
|
|
if (*name < '0' || *name > '9')
|
|
return -1;
|
|
v = v * 10 + (*name++ - '0');
|
|
if (v > 1 << 20)
|
|
return -1;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static struct inode *
|
|
procfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
|
{
|
|
struct procinfo pi;
|
|
int i, pid;
|
|
|
|
if (poff)
|
|
*poff = 0;
|
|
|
|
if (namecmp(name, ".") == 0)
|
|
return iget(dp->dev, dp->inum);
|
|
if (namecmp(name, "..") == 0)
|
|
return iget(dp->dev, PROCFS_ROOT); // /proc/<pid>/.. is /proc
|
|
|
|
// Inside /proc/<pid>/
|
|
if (dp->inum != PROCFS_ROOT) {
|
|
for (i = 0; i < NPIDFILES; i++)
|
|
if (namecmp(name, (char *)pidfiles[i].name) == 0)
|
|
return iget(dp->dev, (dp->inum & ~0xFFULL) | pidfiles[i].id);
|
|
return 0;
|
|
}
|
|
|
|
// In /proc itself: a number is a process, a word is a global file.
|
|
pid = name_to_pid(name);
|
|
if (pid > 0) {
|
|
if (proc_snapshot_pid(pid, &pi) < 0)
|
|
return 0;
|
|
return iget(dp->dev, ((uint64)pid << 8) | PF_DIR);
|
|
}
|
|
|
|
for (i = 0; i < NGLOBALS; i++)
|
|
if (namecmp(name, (char *)globals[i].name) == 0)
|
|
return iget(dp->dev, PROCFS_GLOBAL | globals[i].id);
|
|
return 0;
|
|
}
|
|
|
|
// Nothing in /proc can be created, removed or linked. Saying so with an error
|
|
// is better than a silent no-op: a program that thinks it wrote a file and
|
|
// finds it missing later has a much harder problem to debug.
|
|
static int procfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
|
{ (void)dp; (void)name; (void)inum; return -EROFS; }
|
|
static int procfs_dirunlink(struct inode *dp, char *name, uint off)
|
|
{ (void)dp; (void)name; (void)off; return -EROFS; }
|
|
static int procfs_dirempty(struct inode *dp) { (void)dp; return 0; }
|
|
static struct inode *procfs_ialloc(uint dev, short type)
|
|
{ (void)dev; (void)type; return 0; }
|
|
|
|
// One getdents64 record.
|
|
struct linux_dirent64 {
|
|
uint64 d_ino;
|
|
int64 d_off;
|
|
ushort d_reclen;
|
|
uchar d_type;
|
|
char d_name[];
|
|
};
|
|
|
|
#define DT_DIR_ 4
|
|
#define DT_REG_ 8
|
|
|
|
static int
|
|
emit(char *out, int cap, int used, uint64 ino, int type, const char *name)
|
|
{
|
|
struct linux_dirent64 *de;
|
|
int nlen = 0, reclen;
|
|
|
|
while (name[nlen]) nlen++;
|
|
reclen = (int)sizeof(*de) + nlen + 1;
|
|
reclen = (reclen + 7) & ~7; // 8-byte aligned, as getdents64 requires
|
|
if (used + reclen > cap)
|
|
return -1;
|
|
|
|
de = (struct linux_dirent64 *)(out + used);
|
|
de->d_ino = ino;
|
|
de->d_off = used + reclen;
|
|
de->d_reclen = (ushort)reclen;
|
|
de->d_type = (uchar)type;
|
|
memmove(de->d_name, name, nlen);
|
|
de->d_name[nlen] = 0;
|
|
return reclen;
|
|
}
|
|
|
|
static void
|
|
utoa(uint v, char *buf)
|
|
{
|
|
char t[12];
|
|
int i = 0, j = 0;
|
|
do { t[i++] = '0' + (v % 10); v /= 10; } while (v);
|
|
while (i) buf[j++] = t[--i];
|
|
buf[j] = 0;
|
|
}
|
|
|
|
// The listing is generated fresh each time, and *off is an index into it.
|
|
//
|
|
// A process that exits between two getdents calls therefore shifts everything
|
|
// after it up by one, and one entry can be missed. That is a real race and it
|
|
// is the same one Linux has -- /proc has no stable ordering to offer, and
|
|
// anything that wants a consistent snapshot has to re-read.
|
|
static int
|
|
procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
|
|
{
|
|
struct procinfo pi;
|
|
char name[16];
|
|
int used = 0, idx = 0, i, r;
|
|
uint64 want = *off;
|
|
|
|
#define EMIT(ino, type, nm) do { \
|
|
if (idx++ >= (int)want) { \
|
|
r = emit(out, cap, used, (ino), (type), (nm)); \
|
|
if (r < 0) goto full; \
|
|
used += r; (*off)++; \
|
|
} \
|
|
} while (0)
|
|
|
|
if (dp->inum == PROCFS_ROOT) {
|
|
EMIT(PROCFS_ROOT, DT_DIR_, ".");
|
|
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
|
for (i = 0; i < NGLOBALS; i++)
|
|
EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name);
|
|
for (i = 0; proc_snapshot(i, &pi) == 0; i++) {
|
|
utoa((uint)pi.pid, name);
|
|
EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name);
|
|
}
|
|
} else {
|
|
EMIT(dp->inum, DT_DIR_, ".");
|
|
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
|
for (i = 0; i < NPIDFILES; i++)
|
|
EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, DT_REG_, pidfiles[i].name);
|
|
}
|
|
|
|
full:
|
|
#undef EMIT
|
|
return used;
|
|
}
|
|
|
|
const struct fs_ops procfs_ops = {
|
|
.name = "procfs",
|
|
.iload = procfs_iload,
|
|
.istore = procfs_istore,
|
|
.ifree = procfs_ifree,
|
|
.read = procfs_read,
|
|
.write = procfs_write,
|
|
.truncate = procfs_truncate,
|
|
.dirlookup = procfs_dirlookup,
|
|
.dirlink = procfs_dirlink,
|
|
.dirunlink = procfs_dirunlink,
|
|
.dirempty = procfs_dirempty,
|
|
.ialloc = procfs_ialloc,
|
|
.getdents = procfs_getdents,
|
|
};
|
|
|
|
// Graft this filesystem over /proc.
|
|
//
|
|
// Called by init once it has made the directory. The mknod'd files that used
|
|
// to live there are simply covered: path resolution stops at the mount point,
|
|
// so they are unreachable and harmless. Removing them from init is a separate,
|
|
// later cleanup -- doing both at once would make a boot failure ambiguous
|
|
// between "the mount is wrong" and "the nodes are missing".
|
|
void
|
|
procfs_mount(void)
|
|
{
|
|
struct inode *dp = namei("/proc");
|
|
|
|
if (dp == 0) {
|
|
cprintf("procfs: /proc does not exist; not mounted\n");
|
|
return;
|
|
}
|
|
if (mount_add(dp->dev, dp->inum, PROCFS_DEV, PROCFS_ROOT, "/proc", "proc", "proc") < 0)
|
|
cprintf("procfs: /proc already has something mounted on it\n");
|
|
iput(dp);
|
|
}
|