From 25d39513e7c757eb8ae5ac9f6f9c43db7138235f Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Tue, 27 Oct 2015 21:32:33 +0000 Subject: [PATCH] MIB: initial tree population Change-Id: I28ef0a81a59faaf341bfc15178df89474779a136 --- minix/drivers/tty/tty/tty.h | 2 +- minix/include/minix/config.h | 3 +- minix/include/minix/dmap.h | 3 + minix/lib/libminc/Makefile | 2 +- minix/lib/libsys/getuptime.c | 9 +- minix/servers/mib/Makefile | 4 +- minix/servers/mib/hw.c | 140 ++++ minix/servers/mib/kern.c | 476 ++++++++++++ minix/servers/mib/main.c | 4 + minix/servers/mib/mib.h | 18 + minix/servers/mib/proc.c | 1169 +++++++++++++++++++++++++++++ minix/servers/mib/vm.c | 154 ++++ minix/usr.bin/trace/service/mib.c | 262 +++++++ 13 files changed, 2239 insertions(+), 7 deletions(-) create mode 100644 minix/servers/mib/hw.c create mode 100644 minix/servers/mib/proc.c create mode 100644 minix/servers/mib/vm.c diff --git a/minix/drivers/tty/tty/tty.h b/minix/drivers/tty/tty/tty.h index 12c42526a..c079aeab8 100644 --- a/minix/drivers/tty/tty/tty.h +++ b/minix/drivers/tty/tty/tty.h @@ -4,7 +4,7 @@ #include /* First minor numbers for the various classes of TTY devices. */ -#define CONS_MINOR 0 +/* CONS_MINOR is defined in minix/dmap.h */ #define LOG_MINOR 15 #define RS232_MINOR 16 #define VIDEO_MINOR 125 diff --git a/minix/include/minix/config.h b/minix/include/minix/config.h index 1e543272d..27631f549 100644 --- a/minix/include/minix/config.h +++ b/minix/include/minix/config.h @@ -3,7 +3,8 @@ /* Minix release and version numbers. */ #define OS_NAME "Minix" -#define OS_RELEASE "3.3.0" +#define OS_RELEASE "3.3.0" /* 3.m.p */ +#define OS_REV 303000000 /* see NetBSD sys/param.h: 3mm00pp00 */ #define OS_CONFIG "GENERIC" #define OS_VERSION OS_NAME " " OS_RELEASE " (" OS_CONFIG ")" diff --git a/minix/include/minix/dmap.h b/minix/include/minix/dmap.h index 00ae632ca..5cf821478 100644 --- a/minix/include/minix/dmap.h +++ b/minix/include/minix/dmap.h @@ -91,6 +91,9 @@ # define IMGRD_DEV 6 /* minor device for /dev/imgrd */ # define RAM_DEV_FIRST 7 /* first minor device for /dev/ram* */ +/* Minor device numbers for the TTY driver. */ +# define CONS_MINOR 0 /* console device */ + #define CTRLR(n) ((n)==0 ? 3 : (8 + 2*((n)-1))) /* magic formula */ /* Minor device numbers for log driver. */ diff --git a/minix/lib/libminc/Makefile b/minix/lib/libminc/Makefile index 4375cf88b..804c8b358 100644 --- a/minix/lib/libminc/Makefile +++ b/minix/lib/libminc/Makefile @@ -288,7 +288,7 @@ CLEANFILES+= ${f:C/\.o/.bc/} init.o kernel_utils.o link.o loadname.o lseek.o _mcontext.o mknod.o \ mmap.o nanosleep.o open.o pread.o pwrite.o read.o sbrk.o \ select.o setuid.o sigprocmask.o stack_utils.o stat.o stime.o \ - syscall.o _ucontext.o umask.o unlink.o wait4.o write.o \ + svrctl.o syscall.o _ucontext.o umask.o unlink.o wait4.o write.o \ kill.o ${f} ${f:C/\.o/.bc/}: ${LIBMINIXCDIR}/sys/${f:C/\.o/.c/} OBJS+= ${f} diff --git a/minix/lib/libsys/getuptime.c b/minix/lib/libsys/getuptime.c index 6b7f68770..298c787fc 100644 --- a/minix/lib/libsys/getuptime.c +++ b/minix/lib/libsys/getuptime.c @@ -13,9 +13,12 @@ getuptime(clock_t * uptime, clock_t * realtime, time_t * boottime) minix_kerninfo = get_minix_kerninfo(); /* We assume atomic 32-bit field retrieval. TODO: 64-bit support. */ - *uptime = minix_kerninfo->kclockinfo->uptime; - *realtime = minix_kerninfo->kclockinfo->realtime; - *boottime = minix_kerninfo->kclockinfo->boottime; + if (uptime != NULL) + *uptime = minix_kerninfo->kclockinfo->uptime; + if (realtime != NULL) + *realtime = minix_kerninfo->kclockinfo->realtime; + if (boottime != NULL) + *boottime = minix_kerninfo->kclockinfo->boottime; return OK; } diff --git a/minix/servers/mib/Makefile b/minix/servers/mib/Makefile index ca4de7151..cc5b04f5c 100644 --- a/minix/servers/mib/Makefile +++ b/minix/servers/mib/Makefile @@ -1,7 +1,9 @@ # Makefile for the Management Information Base (MIB) server PROG= mib -SRCS= main.c tree.c kern.c minix.c +SRCS= main.c tree.c kern.c vm.c hw.c proc.c minix.c + +CPPFLAGS+= -I${NETBSDSRCDIR}/minix DPADD+= ${LIBSYS} LDADD+= -lsys diff --git a/minix/servers/mib/hw.c b/minix/servers/mib/hw.c new file mode 100644 index 000000000..63de32c98 --- /dev/null +++ b/minix/servers/mib/hw.c @@ -0,0 +1,140 @@ +/* MIB service - hw.c - implementation of the CTL_HW subtree */ + +#include "mib.h" + +#if defined(__i386__) +static const char mach[] = "i386"; /* machine (cpu) type */ +static const char arch[] = "i386"; /* architecture */ +#elif defined(__arm__) +static const char mach[] = "evbarm"; /* machine (cpu) type */ +static const char arch[] = "evbarm"; /* architecture */ +#else +#error "unknown machine architecture" +#endif + +/* + * Implementation of CTL_HW HW_PHYSMEM/HW_PHYSMEM64. + */ +static ssize_t +mib_hw_physmem(struct mib_call * call __unused, struct mib_node * node, + struct mib_oldp * oldp, struct mib_newp * newp __unused) +{ + struct vm_stats_info vsi; + u_quad_t physmem64; + unsigned int physmem; + + if (vm_info_stats(&vsi) != OK) + return EINVAL; + + physmem64 = (u_quad_t)vsi.vsi_total * vsi.vsi_pagesize; + + if (node->node_size == sizeof(int)) { + if (physmem64 > UINT_MAX) + physmem = UINT_MAX; + else + physmem = (unsigned int)physmem64; + + return mib_copyout(oldp, 0, &physmem, sizeof(physmem)); + } else + return mib_copyout(oldp, 0, &physmem64, sizeof(physmem64)); +} + +/* + * Implementation of CTL_HW HW_USERMEM/HW_USERMEM64. + */ +static ssize_t +mib_hw_usermem(struct mib_call * call __unused, struct mib_node * node, + struct mib_oldp * oldp, struct mib_newp * newp __unused) +{ + struct vm_stats_info vsi; + struct vm_usage_info vui; + u_quad_t usermem64; + unsigned int usermem; + + if (vm_info_stats(&vsi) != OK) + return EINVAL; + + usermem64 = (u_quad_t)vsi.vsi_total * vsi.vsi_pagesize; + + if (vm_info_usage(KERNEL, &vui) != OK) + return EINVAL; + + if (usermem64 >= vui.vui_total) + usermem64 -= vui.vui_total; + else + usermem64 = 0; + + if (node->node_size == sizeof(int)) { + if (usermem64 > UINT_MAX) + usermem = UINT_MAX; + else + usermem = (unsigned int)usermem64; + + return mib_copyout(oldp, 0, &usermem, sizeof(usermem)); + } else + return mib_copyout(oldp, 0, &usermem64, sizeof(usermem64)); +} + +/* + * Implementation of CTL_HW HW_NCPUONLINE. + */ +static ssize_t +mib_hw_ncpuonline(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + struct machine machine; + int ncpuonline; + + if (sys_getmachine(&machine) != OK) + return EINVAL; + + ncpuonline = machine.processors_count; + + return mib_copyout(oldp, 0, &ncpuonline, sizeof(ncpuonline)); +} + +/* The CTL_HW nodes. */ +static struct mib_node mib_hw_table[] = { +/* 1*/ [HW_MACHINE] = MIB_STRING(_P | _RO, mach, "machine", + "Machine class"), +/* 2*/ /* HW_MODEL: not yet supported */ +/* 3*/ [HW_NCPU] = MIB_INT(_P | _RO, CONFIG_MAX_CPUS, + "ncpu", "Number of CPUs configured"), +/* 4*/ [HW_BYTEORDER] = MIB_INT(_P | _RO, BYTE_ORDER, "byteorder", + "System byte order"), +/* 5*/ [HW_PHYSMEM] = MIB_FUNC(_P | _RO | CTLFLAG_UNSIGNED | + CTLTYPE_INT, sizeof(int), mib_hw_physmem, + "physmem", "Bytes of physical memory"), +/* 6*/ [HW_USERMEM] = MIB_FUNC(_P | _RO | CTLFLAG_UNSIGNED | + CTLTYPE_INT, sizeof(int), mib_hw_usermem, + "usermem", "Bytes of non-kernel memory"), +/* 7*/ [HW_PAGESIZE] = MIB_INT(_P | _RO, PAGE_SIZE, "pagesize", + "Software page size"), +/* 8*/ /* HW_DISKNAMES: not yet supported */ +/* 9*/ /* HW_IOSTATS: not yet supported */ +/*10*/ [HW_MACHINE_ARCH] = MIB_STRING(_P | _RO, arch, "machine_arch", + "Machine CPU class"), +/*11*/ /* HW_ALIGNBYTES: not yet supported */ +/*12*/ /* HW_CNMAGIC: not yet supported */ +/*13*/ [HW_PHYSMEM64] = MIB_FUNC(_P | _RO | CTLTYPE_QUAD, + sizeof(u_quad_t), mib_hw_physmem, + "physmem64", "Bytes of physical memory"), +/*14*/ [HW_USERMEM64] = MIB_FUNC(_P | _RO | CTLTYPE_QUAD, + sizeof(u_quad_t), mib_hw_usermem, + "usermem64", "Bytes of non-kernel memory"), +/*15*/ /* HW_IOSTATNAMES: not yet supported */ +/*16*/ [HW_NCPUONLINE] = MIB_FUNC(_P | _RO | CTLTYPE_INT, sizeof(int), + mib_hw_ncpuonline, "ncpuonline", + "Number of CPUs online"), +}; + +/* + * Initialize the CTL_HW subtree. + */ +void +mib_hw_init(struct mib_node * node) +{ + + MIB_INIT_ENODE(node, mib_hw_table); +} diff --git a/minix/servers/mib/kern.c b/minix/servers/mib/kern.c index 688c3ca62..e1565a230 100644 --- a/minix/servers/mib/kern.c +++ b/minix/servers/mib/kern.c @@ -2,10 +2,486 @@ #include "mib.h" +#include +#include +#include + +#include "servers/vfs/const.h" +#include "servers/vfs/dmap.h" + +static char hostname[MAXHOSTNAMELEN], domainname[MAXHOSTNAMELEN]; + +/* + * Verification for CTL_KERN KERN_SECURELVL. + */ +static int +mib_kern_securelvl(struct mib_call * call __unused, struct mib_node * node, + void * ptr, size_t size __unused) +{ + int v; + + memcpy(&v, ptr, sizeof(v)); + + /* + * Only ever allow the security level to be increased. This is a mock + * implementation. TODO: implement actual support for security levels. + */ + return (v >= node->node_int); +} + +/* + * Implementation of CTL_KERN KERN_CLOCKRATE. + */ +static ssize_t +mib_kern_clockrate(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + struct clockinfo clockinfo; + + memset(&clockinfo, 0, sizeof(clockinfo)); + + clockinfo.hz = sys_hz(); + clockinfo.tick = 1000000 / clockinfo.hz; + clockinfo.profhz = clockinfo.hz; + clockinfo.stathz = clockinfo.hz; + + /* + * Number of microseconds that can be corrected per clock tick through + * adjtime(2). The kernel allows correction of one clock tick per + * clock tick, which means it should be the same as .tick.. I think. + * TODO: get this from the kernel itself. + */ + clockinfo.tickadj = clockinfo.tick; + + return mib_copyout(oldp, 0, &clockinfo, sizeof(clockinfo)); +} + +/* + * Implementation of CTL_KERN KERN_PROFILING. + */ +static ssize_t +mib_kern_profiling(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp __unused, + struct mib_newp * newp __unused) +{ + + /* As per sysctl(7). We have a different profiling API. */ + return EOPNOTSUPP; +} + +/* + * Implementation of CTL_KERN KERN_HARDCLOCK_TICKS. + */ +static ssize_t +mib_kern_hardclock_ticks(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + int uptime; + + /* + * The number of hardclock (hardware clock driver) ticks is what we + * call the number of monotonic clock ticks AKA the uptime clock ticks. + */ + uptime = (int)getticks(); + + return mib_copyout(oldp, 0, &uptime, sizeof(uptime)); +} + +/* + * Implementation of CTL_KERN KERN_ROOT_DEVICE. + */ +static ssize_t +mib_kern_root_device(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + char name[PATH_MAX]; + struct sysgetenv sysgetenv; + + sysgetenv.key = __UNCONST("rootdevname"); + sysgetenv.keylen = strlen(sysgetenv.key) + 1; + sysgetenv.val = name; + sysgetenv.vallen = sizeof(name); + + if (svrctl(PMGETPARAM, &sysgetenv) != 0) + return EINVAL; + + name[MIN(sysgetenv.vallen, sizeof(name) - 1)] = '\0'; + + return mib_copyout(oldp, 0, name, strlen(name) + 1); +} + +/* + * Implementation of CTL_KERN KERN_CCPU. + */ +static ssize_t +mib_kern_ccpu(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + int ccpu; + + ccpu = (int)cpuavg_getccpu(); + + return mib_copyout(oldp, 0, &ccpu, sizeof(ccpu)); +} + +/* + * Implementation of CTL_KERN KERN_CP_TIME. + */ +static ssize_t +mib_kern_cp_time(struct mib_call * call, struct mib_node * node __unused, + struct mib_oldp * oldp, struct mib_newp * newp __unused) +{ + uint64_t ticks[MINIX_CPUSTATES], sum[MINIX_CPUSTATES]; + unsigned int cpu; + int i, r, do_sum; + + /* + * If a subnode is provided, it identifies the CPU number for which to + * return information. If no subnode is provided, but a size is given + * that allows returning information for all CPUs, return information + * for all of them in an array. If no such size is given either, + * return a summation of all CPU statistics. Both we and the kernel + * are considering the number of configured CPUs (hw.ncpu). + */ + if (call->call_namelen > 1) + return EINVAL; + + if (call->call_namelen == 1) { + /* Do not bother saving on this call if oldp is NULL. */ + if ((r = sys_getcputicks(ticks, call->call_name[0])) != OK) + return r; + + return mib_copyout(oldp, 0, ticks, sizeof(ticks)); + } + + if (oldp == NULL) + return sizeof(ticks); /* implying a summation request */ + + do_sum = (mib_getoldlen(oldp) == sizeof(ticks)); + + if (do_sum) + memset(&sum, 0, sizeof(sum)); + + for (cpu = 0; cpu < CONFIG_MAX_CPUS; cpu++) { + if ((r = sys_getcputicks(ticks, cpu)) != OK) + return r; + + if (do_sum) { + for (i = 0; i < MINIX_CPUSTATES; i++) + sum[i] += ticks[i]; + } else { + if ((r = mib_copyout(oldp, cpu * sizeof(ticks), ticks, + sizeof(ticks))) < 0) + return r; + } + } + + if (do_sum) + return mib_copyout(oldp, 0, sum, sizeof(sum)); + else + return cpu * sizeof(ticks); +} + +/* + * Implementation of CTL_KERN KERN_CONSDEV. + */ +static ssize_t +mib_kern_consdev(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + dev_t dev; + + dev = makedev(TTY_MAJOR, CONS_MINOR); + + /* No support for legacy 32-bit requests. */ + return mib_copyout(oldp, 0, &dev, sizeof(dev)); +} + +/* + * Verification for CTL_KERN KERN_FORKFSLEEP. + */ +static int +mib_kern_forkfsleep(struct mib_call * call __unused, + struct mib_node * node __unused, void * ptr, size_t size __unused) +{ + int v; + + memcpy(&v, ptr, sizeof(v)); + + return (v >= 0 && v <= MAXSLP * 1000); /* rules from NetBSD */ +} + +/* + * Implementation of CTL_KERN KERN_DRIVERS. + */ +static ssize_t +mib_kern_drivers(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + struct dmap dmap_tab[NR_DEVICES]; + struct kinfo_drivers drivers[NR_DEVICES + 1]; + unsigned int count; + devmajor_t maj; + + /* + * On MINIX3, we list only drivers that are actually running. + */ + + if (getsysinfo(VFS_PROC_NR, SI_DMAP_TAB, dmap_tab, + sizeof(dmap_tab)) != OK) + return EINVAL; + + count = 0; + + /* + * Compatibility hack. NetBSD userland expects that the name of the + * PTY driver is "pts". Add an extra entry for this purpose if needed. + */ + if (dmap_tab[PTY_MAJOR].dmap_driver != NONE && + strcmp(dmap_tab[PTY_MAJOR].dmap_label, "pts")) { + if (mib_inrange(oldp, 0)) { + memset(&drivers[0], 0, sizeof(drivers[0])); + strlcpy(drivers[count].d_name, "pts", + sizeof(drivers[0].d_name)); + drivers[count].d_bmajor = -1; + drivers[count].d_cmajor = PTY_MAJOR; + } + count++; + } + + for (maj = 0; maj < NR_DEVICES; maj++) { + if (dmap_tab[maj].dmap_driver == NONE) + continue; + + if (mib_inrange(oldp, sizeof(drivers[0]) * count)) { + memset(&drivers[count], 0, sizeof(drivers[0])); + + strlcpy(drivers[count].d_name, + dmap_tab[maj].dmap_label, + sizeof(drivers[0].d_name)); + + /* + * We do not know whether the device is a block device, + * character device, or both. In any case, a driver + * has only one major number. + */ + drivers[count].d_bmajor = maj; + drivers[count].d_cmajor = maj; + } + count++; + } + + return mib_copyout(oldp, 0, drivers, count * sizeof(drivers[0])); +} + +/* + * Implementation of CTL_KERN KERN_BOOTTIME. + */ +static ssize_t +mib_kern_boottime(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + struct timeval tv; + + memset(&tv, 0, sizeof(tv)); + + if (getuptime(NULL, NULL, &tv.tv_sec) != OK) + return EINVAL; + + return mib_copyout(oldp, 0, &tv, sizeof(tv)); +} + +/* The CTL_KERN KERN_SYSVIPC nodes. */ +static struct mib_node mib_kern_ipc_table[] = { +/* 1*/ /* KERN_SYSVIPC_INFO: not yet supported */ +/* 2*/ [KERN_SYSVIPC_MSG] = MIB_INT(_P | _RO, 0, "sysvmsg", "System V " + "style message support available"), +/* 3*/ [KERN_SYSVIPC_SEM] = MIB_INT(_P | _RO, 1, "sysvsem", "System V " + "style semaphore support available"), +/* 4*/ [KERN_SYSVIPC_SHM] = MIB_INT(_P | _RO, 1, "sysvshm", "System V " + "style shared memory support available"), +/* 5*/ /* KERN_SYSVIPC_SHMMAX: not yet supported */ +/* 6*/ /* KERN_SYSVIPC_SHMMNI: not yet supported */ +/* 7*/ /* KERN_SYSVIPC_SHMSEG: not yet supported */ +/* 8*/ /* KERN_SYSVIPC_SHMMAXPGS: not yet supported */ +/* 9*/ /* KERN_SYSVIPC_SHMUSEPHYS: not yet supported */ + /* In addition, NetBSD has a number of dynamic nodes here. */ +}; + +/* The CTL_KERN nodes. */ static struct mib_node mib_kern_table[] = { +/* 1*/ [KERN_OSTYPE] = MIB_STRING(_P | _RO, OS_NAME, "ostype", + "Operating system type"), +/* 2*/ [KERN_OSRELEASE] = MIB_STRING(_P | _RO, OS_RELEASE, "osrelease", + "Operating system release"), +/* 3*/ [KERN_OSREV] = MIB_INT(_P | _RO , OS_REV, "osrevision", + "Operating system revision"), +/* 4*/ [KERN_VERSION] = MIB_STRING(_P | _RO, OS_VERSION, "version", + "Kernel version"), +/* 5*/ [KERN_MAXVNODES] = MIB_INT(_P | _RO, NR_VNODES, "maxvnodes", + "Maximum number of vnodes"), +/* 6*/ [KERN_MAXPROC] = MIB_INT(_P | _RO, NR_PROCS, "maxproc", + "Maximum number of simultaneous " + "processes"), +/* 7*/ [KERN_MAXFILES] = MIB_INT(_P | _RO, NR_VNODES, "maxfiles", + "Maximum number of open files"), /* 8*/ [KERN_ARGMAX] = MIB_INT(_P | _RO, ARG_MAX, "argmax", "Maximum number of bytes of arguments to " "execve(2)"), +/* 9*/ [KERN_SECURELVL] = MIB_INTV(_P | _RW, -1, mib_kern_securelvl, + "securelevel", "System security level"), +/*10*/ [KERN_HOSTNAME] = MIB_STRING(_P | _RW, hostname, "hostname", + "System hostname"), +/*11*/ [KERN_HOSTID] = MIB_INT(_P | _RW | CTLFLAG_HEX, 0, "hostid", + "System host ID number"), +/*12*/ [KERN_CLOCKRATE] = MIB_FUNC(_P | _RO | CTLTYPE_STRUCT, + sizeof(struct clockinfo), + mib_kern_clockrate, "clockrate", + "Kernel clock rates"), +/*13*/ /* KERN_VNODE: not yet implemented */ +/*14*/ /* KERN_PROC: not yet implemented */ +/*15*/ /* KERN_FILE: not yet implemented */ +/*16*/ [KERN_PROF] = MIB_FUNC(_P | _RO | CTLTYPE_NODE, 0, + mib_kern_profiling, "profiling", + "Profiling information (not available)"), +/*17*/ [KERN_POSIX1] = MIB_INT(_P | _RO, _POSIX_VERSION, + "posix1version", "Version of ISO/IEC 9945 " + "(POSIX 1003.1) with which the operating " + "system attempts to comply"), +/*18*/ [KERN_NGROUPS] = MIB_INT(_P | _RO, NGROUPS_MAX, "ngroups", + "Maximum number of supplemental groups"), +/*19*/ [KERN_JOB_CONTROL] = MIB_INT(_P | _RO, 0, "job_control", + "Whether job control is available"), +/*20*/ [KERN_SAVED_IDS] = MIB_INT(_P | _RO, 0, "saved_ids", + "Whether POSIX saved set-group/user ID is " + "available"), +/*21*/ /* KERN_OBOOTTIME: obsolete */ +/*22*/ [KERN_DOMAINNAME] = MIB_STRING(_P | _RW, domainname, + "domainname", "YP domain name"), +/*23*/ [KERN_MAXPARTITIONS] = MIB_INT(_P | _RO, NR_PARTITIONS, + "maxpartitions", "Maximum number of " + "partitions allowed per disk"), +/*24*/ /* KERN_RAWPARTITION: incompatible with our device node scheme */ +/*25*/ /* KERN_NTPTIME: not yet supported */ +/*26*/ /* KERN_TIMEX: not yet supported */ +/*27*/ /* KERN_AUTONICETIME: not yet supported */ +/*28*/ /* KERN_AUTONICEVAL: not yet supported */ +/*29*/ [KERN_RTC_OFFSET] = MIB_INT(_P | _RW, 0, "rtc_offset", "Offset " + "of real time clock from UTC in minutes"), +/*30*/ [KERN_ROOT_DEVICE] = MIB_FUNC(_P | _RO | CTLTYPE_STRING, 0, + mib_kern_root_device, "root_device", + "Name of the root device"), +/*31*/ [KERN_MSGBUFSIZE] = MIB_INT(_P | _RO, DIAG_BUFSIZE, "msgbufsize", + "Size of the kernel message buffer"), +/*32*/ [KERN_FSYNC] = MIB_INT(_P | _RO, 1, "fsync", "Whether the " + "POSIX 1003.1b File Synchronization Option" + " is available on this system"), +/*33*/ /* KERN_OLDSYSVMSG: obsolete */ +/*34*/ /* KERN_OLDSYSVSEM: obsolete */ +/*35*/ /* KERN_OLDSYSVSHM: obsolete */ +/*36*/ /* KERN_OLDSHORTCORENAME: obsolete */ +/*37*/ [KERN_SYNCHRONIZED_IO] = MIB_INT(_P | _RO, 0, "synchronized_io", + "Whether the POSIX 1003.1b Synchronized " + "I/O Option is available on this system"), +/*38*/ [KERN_IOV_MAX] = MIB_INT(_P | _RO, IOV_MAX, "iov_max", + "Maximum number of iovec structures per " + "process"), +/*39*/ /* KERN_MBUF: not yet supported */ +/*40*/ [KERN_MAPPED_FILES] = MIB_INT(_P | _RO, 1, "mapped_files", + "Whether the POSIX 1003.1b Memory Mapped " + "Files Option is available on this " + "system"), +/*41*/ [KERN_MEMLOCK] = MIB_INT(_P | _RO, 0, "memlock", "Whether " + "the POSIX 1003.1b Process Memory Locking " + "Option is available on this system"), +/*42*/ [KERN_MEMLOCK_RANGE] = MIB_INT(_P | _RO, 0, "memlock_range", + "Whether the POSIX 1003.1b Range Memory " + "Locking Option is available on this " + "system"), +/*43*/ [KERN_MEMORY_PROTECTION]= MIB_INT(_P | _RO, 0, "memory_protection", + "Whether the POSIX 1003.1b Memory " + "Protection Option is available on this " + "system"), +/*44*/ /* KERN_LOGIN_NAME_MAX: not yet supported */ +/*45*/ /* KERN_DEFCORENAME: obsolete */ +/*46*/ /* KERN_LOGSIGEXIT: not yet supported */ +/*47*/ [KERN_PROC2] = MIB_FUNC(_P | _RO | CTLTYPE_NODE, 0, + mib_kern_proc2, "proc2", + "Machine-independent process information"), +/*48*/ [KERN_PROC_ARGS] = MIB_FUNC(_P | _RO | CTLTYPE_NODE, 0, + mib_kern_proc_args, "proc_args", + "Process argument information"), +/*49*/ [KERN_FSCALE] = MIB_INT(_P | _RO, FSCALE, "fscale", + "Kernel fixed-point scale factor"), +/*50*/ [KERN_CCPU] = MIB_FUNC(_P | _RO | CTLTYPE_INT, sizeof(int), + mib_kern_ccpu, "ccpu", + "Scheduler exponential decay value"), +/*51*/ [KERN_CP_TIME] = MIB_FUNC(_P | _RO | CTLTYPE_NODE, 0, + mib_kern_cp_time, "cp_time", "Clock ticks " + "spent in different CPU states"), +/*52*/ /* KERN_OLDSYSVIPC_INFO: obsolete */ +/*53*/ /* KERN_MSGBUF: not yet supported */ +/*54*/ [KERN_CONSDEV] = MIB_FUNC(_P | _RO | CTLTYPE_STRUCT, + sizeof(dev_t), mib_kern_consdev, "consdev", + "Console device"), +/*55*/ [KERN_MAXPTYS] = MIB_INT(_P | _RO, NR_PTYS, "maxptys", + "Maximum number of pseudo-ttys"), +/*56*/ /* KERN_PIPE: not yet supported */ +/*57*/ [KERN_MAXPHYS] = MIB_INT(_P | _RO, 4*1024*1024, "maxphys", + "Maximum raw I/O transfer size"), + /* 4MB is the upper limit for AHCI */ +/*58*/ /* KERN_SBMAX: not yet supported */ +/*59*/ /* KERN_TKSTAT: not yet supported */ +/*60*/ [KERN_MONOTONIC_CLOCK] = MIB_INT(_P | _RO, _POSIX_MONOTONIC_CLOCK, + "monotonic_clock", + "Implementation version of the POSIX " + "1003.1b Monotonic Clock Option"), +/*61*/ /* KERN_URND: not yet supported */ +/*62*/ /* KERN_LABELSECTOR: not yet supported */ +/*63*/ /* KERN_LABELOFFSET: not yet supported */ +/*64*/ [KERN_LWP] = MIB_FUNC(_P | _RO | CTLTYPE_NODE, 0, + mib_kern_lwp, "lwp", + "System-wide LWP information"), +/*65*/ [KERN_FORKFSLEEP] = MIB_INTV(_P | _RW, 0, mib_kern_forkfsleep, + "forkfsleep", "Milliseconds to sleep on " + "fork failure due to process limits"), +/*66*/ /* KERN_POSIX_THREADS: not yet supported */ +/*67*/ /* KERN_POSIX_SEMAPHORES: not yet supported */ +/*68*/ /* KERN_POSIX_BARRIERS: not yet supported */ +/*69*/ /* KERN_POSIX_TIMERS: not yet supported */ +/*70*/ /* KERN_POSIX_SPIN_LOCKS: not yet supported */ +/*71*/ /* KERN_POSIX_READER_WRITER_LOCKS: not yet supported */ +/*72*/ [KERN_DUMP_ON_PANIC] = MIB_INT(_P | _RO, 0, "dump_on_panic", + "Perform a crash dump on system panic"), +/*73*/ /* KERN_SOMAXKVA: not yet supported */ +/*74*/ /* KERN_ROOT_PARTITION: incompatible with our device node scheme */ +/*75*/ [KERN_DRIVERS] = MIB_FUNC(_P | _RO | CTLTYPE_STRUCT, 0, + mib_kern_drivers, "drivers", + "List of all drivers with block and " + "character device numbers"), +/*76*/ /* KERN_BUF: not yet supported */ +/*77*/ /* KERN_FILE2: not yet supported */ +/*78*/ /* KERN_VERIEXEC: not yet supported */ +/*79*/ /* KERN_CP_ID: not yet supported */ +/*80*/ [KERN_HARDCLOCK_TICKS] = MIB_FUNC(_P | _RO | CTLFLAG_UNSIGNED | + CTLTYPE_INT, sizeof(int), + mib_kern_hardclock_ticks, + "hardclock_ticks", + "Number of hardclock ticks"), +/*81*/ /* KERN_ARND: not yet supported */ +/*82*/ [KERN_SYSVIPC] = MIB_NODE(_P | _RO, mib_kern_ipc_table, "ipc", + "SysV IPC options"), +/*83*/ [KERN_BOOTTIME] = MIB_FUNC(_P | _RO | CTLTYPE_STRUCT, + sizeof(struct timeval), mib_kern_boottime, + "boottime", "System boot time"), +/*84*/ /* KERN_EVCNT: not yet supported */ }; /* diff --git a/minix/servers/mib/main.c b/minix/servers/mib/main.c index 327231a04..8101c0e03 100644 --- a/minix/servers/mib/main.c +++ b/minix/servers/mib/main.c @@ -32,6 +32,8 @@ */ static struct mib_node mib_table[] = { /* 1*/ [CTL_KERN] = MIB_ENODE(_P | _RO, "kern", "High kernel"), +/* 2*/ [CTL_VM] = MIB_ENODE(_P | _RO, "vm", "Virtual memory"), +/* 6*/ [CTL_HW] = MIB_ENODE(_P | _RO, "hw", "Generic CPU, I/O"), /* 8*/ [CTL_USER] = MIB_ENODE(_P | _RO, "user", "User-level"), /*11*/ [CTL_VENDOR] = MIB_ENODE(_P | _RW, "vendor", "Vendor specific"), /*32*/ [CTL_MINIX] = MIB_ENODE(_P | _RO, "minix", "MINIX3 specific"), @@ -322,6 +324,8 @@ mib_init(int type __unused, sef_init_info_t * info __unused) * large enough to store the entry. */ mib_kern_init(&mib_table[CTL_KERN]); + mib_vm_init(&mib_table[CTL_VM]); + mib_hw_init(&mib_table[CTL_HW]); mib_minix_init(&mib_table[CTL_MINIX]); /* diff --git a/minix/servers/mib/mib.h b/minix/servers/mib/mib.h index b21fd8c68..089628435 100644 --- a/minix/servers/mib/mib.h +++ b/minix/servers/mib/mib.h @@ -6,6 +6,14 @@ #include #include +#if defined(__i386__) +#include "kernel/arch/i386/include/archconst.h" +#endif + +#ifndef CONFIG_MAX_CPUS +#define CONFIG_MAX_CPUS 1 +#endif + /* * The following setting toggles the existence of the minix.test subtree. For * production environments, it should probably be disabled, although it should @@ -261,8 +269,18 @@ void mib_tree_init(struct mib_node *); extern unsigned int nodes; extern unsigned int objects; +/* proc.c */ +ssize_t mib_kern_lwp(struct mib_call *, struct mib_node *, struct mib_oldp *, + struct mib_newp *); +ssize_t mib_kern_proc2(struct mib_call *, struct mib_node *, struct mib_oldp *, + struct mib_newp *); +ssize_t mib_kern_proc_args(struct mib_call *, struct mib_node *, + struct mib_oldp *, struct mib_newp *); + /* subtree modules */ void mib_kern_init(struct mib_node *); +void mib_vm_init(struct mib_node *); +void mib_hw_init(struct mib_node *); void mib_minix_init(struct mib_node *); #endif /* !_MINIX_MIB_MIB_H */ diff --git a/minix/servers/mib/proc.c b/minix/servers/mib/proc.c new file mode 100644 index 000000000..1c031872a --- /dev/null +++ b/minix/servers/mib/proc.c @@ -0,0 +1,1169 @@ +/* MIB service - proc.c - functionality based on service process tables */ +/* Eventually, the CTL_PROC subtree might end up here as well. */ + +#include "mib.h" + +#include +#include + +#include +#include "kernel/proc.h" +#include "servers/pm/mproc.h" +#include "servers/vfs/const.h" +#include "servers/vfs/fproc.h" + +typedef struct proc ixfer_proc_t; +typedef struct mproc ixfer_mproc_t; +typedef struct fproc ixfer_fproc_t; + +static ixfer_proc_t proc_tab[NR_TASKS + NR_PROCS]; +static ixfer_mproc_t mproc_tab[NR_PROCS]; +static ixfer_fproc_t fproc_tab[NR_PROCS]; + +/* + * The number of processes added to the current number of processes when doing + * a size estimation, so that the actual data retrieval does not end up with + * too little space if new processes have forked between the two calls. We do + * a process table update only once per clock tick, which means that typically + * no update will take place between the user process's size estimation request + * and its subsequent data retrieval request. On the other hand, if we do + * update process tables in between, quite a bit might have changed. + */ +#define EXTRA_PROCS 8 + +#define HASH_SLOTS (NR_PROCS / 4) /* expected nr. of processes in use */ +#define NO_SLOT (-1) +static int hash_tab[HASH_SLOTS]; /* hash table mapping from PID.. */ +static int hnext_tab[NR_PROCS]; /* ..to PM process slot */ + +static clock_t tabs_updated = 0; /* when the tables were last updated */ +static int tabs_valid = TRUE; /* FALSE if obtaining tables failed */ + +/* + * Update the process tables by pulling in new copies from the kernel, PM, and + * VFS, but only every so often and only if it has not failed before. Return + * TRUE iff the tables are now valid. + */ +static int +update_tables(void) +{ + clock_t now; + pid_t pid; + int r, kslot, mslot, hslot; + + /* + * If retrieving the tables failed at some point, do not keep trying + * all the time. Such a failure is very unlikely to be transient. + */ + if (tabs_valid == FALSE) + return FALSE; + + /* + * Update the tables once per clock tick at most. The update operation + * is rather heavy, transferring several hundreds of kilobytes between + * servers. Userland should be able to live with information that is + * outdated by at most one clock tick. + */ + now = getticks(); + + if (tabs_updated != 0 && tabs_updated == now) + return TRUE; + + /* Perform an actual update now. */ + tabs_valid = FALSE; + + /* Retrieve and check the kernel process table. */ + if ((r = sys_getproctab(proc_tab)) != OK) { + printf("MIB: unable to obtain kernel process table (%d)\n", r); + + return FALSE; + } + + for (kslot = 0; kslot < NR_TASKS + NR_PROCS; kslot++) { + if (proc_tab[kslot].p_magic != PMAGIC) { + printf("MIB: kernel process table mismatch\n"); + + return FALSE; + } + } + + /* Retrieve and check the PM process table. */ + r = getsysinfo(PM_PROC_NR, SI_PROC_TAB, mproc_tab, sizeof(mproc_tab)); + if (r != OK) { + printf("MIB: unable to obtain PM process table (%d)\n", r); + + return FALSE; + } + + for (mslot = 0; mslot < NR_PROCS; mslot++) { + if (mproc_tab[mslot].mp_magic != MP_MAGIC) { + printf("MIB: PM process table mismatch\n"); + + return FALSE; + } + } + + /* Retrieve the VFS process table, which has no magic number. */ + r = getsysinfo(VFS_PROC_NR, SI_PROC_TAB, fproc_tab, sizeof(fproc_tab)); + if (r != OK) { + printf("MIB: unable to obtain VFS process table (%d)\n", r); + + return FALSE; + } + + tabs_valid = TRUE; + tabs_updated = now; + + /* + * Build a hash table mapping from process IDs to slot numbers, for + * fast access. TODO: decide if this is better done on demand only. + */ + for (hslot = 0; hslot < HASH_SLOTS; hslot++) + hash_tab[hslot] = NO_SLOT; + + for (mslot = 0; mslot < NR_PROCS; mslot++) { + if (mproc_tab[mslot].mp_flags & IN_USE) { + if ((pid = mproc_tab[mslot].mp_pid) <= 0) + continue; + + hslot = mproc_tab[mslot].mp_pid % HASH_SLOTS; + + hnext_tab[mslot] = hash_tab[hslot]; + hash_tab[hslot] = mslot; + } + } + + return TRUE; +} + +/* + * Return the PM slot number for the given PID, or NO_SLOT if the PID is not in + * use by a process. + */ +static int +get_mslot(pid_t pid) +{ + int mslot; + + /* PID 0 identifies the kernel; checking this is up to the caller. */ + if (pid <= 0) + return NO_SLOT; + + for (mslot = hash_tab[pid % HASH_SLOTS]; mslot != NO_SLOT; + mslot = hnext_tab[mslot]) + if (mproc_tab[mslot].mp_pid == pid) + break; + + return mslot; +} + +/* + * Store the given number of clock ticks as a timeval structure. + */ +static void +ticks_to_timeval(struct timeval * tv, clock_t ticks) +{ + clock_t hz; + + hz = sys_hz(); + + tv->tv_sec = ticks / hz; + tv->tv_usec = (long)((ticks % hz) * 1000000ULL / hz); +} + +/* + * Generate a wchan message text for the cases that the process is blocked on + * IPC with another process, of which the endpoint is given as 'endpt' here. + * The name of the other process is to be stored in 'wmesg', which is a buffer + * of size 'wmsz'. The result should be null terminated. If 'ipc' is set, the + * process is blocked on a direct IPC call, in which case the name of the other + * process is enclosed in parentheses. If 'ipc' is not set, the call is made + * indirectly through VFS, and the name of the other process should not be + * enclosed in parentheses. If no name can be obtained, we use the endpoint of + * the other process instead. + */ +static void +fill_wmesg(char * wmesg, size_t wmsz, endpoint_t endpt, int ipc) +{ + const char *name; + int mslot; + + switch (endpt) { + case ANY: + name = "any"; + break; + case SELF: + name = "self"; + break; + case NONE: + name = "none"; + break; + default: + mslot = _ENDPOINT_P(endpt); + if (mslot >= -NR_TASKS && mslot < NR_PROCS && + (mslot < 0 || (mproc_tab[mslot].mp_flags & IN_USE))) + name = proc_tab[NR_TASKS + mslot].p_name; + else + name = NULL; + } + + if (name != NULL) + snprintf(wmesg, wmsz, "%s%s%s", + ipc ? "(" : "", name, ipc ? ")" : ""); + else + snprintf(wmesg, wmsz, "%s%d%s", + ipc ? "(" : "", endpt, ipc ? ")" : ""); +} + +/* + * Return the LWP status of a process, along with additional information in + * case the process is sleeping (LSSLEEP): a wchan value and text to indicate + * what the process is sleeping on, and possibly a flag field modification to + * indicate that the sleep is interruptible. + */ +static int +get_lwp_stat(int mslot, uint64_t * wcptr, char * wmptr, size_t wmsz, + int32_t * flag) +{ + struct mproc *mp; + struct fproc *fp; + struct proc *kp; + const char *wmesg; + uint64_t wchan; + endpoint_t endpt; + + mp = &mproc_tab[mslot]; + fp = &fproc_tab[mslot]; + kp = &proc_tab[NR_TASKS + mslot]; + + /* + * First cover all the cases that the process is not sleeping. In + * those cases, we need not return additional sleep information either. + */ + if (mp->mp_flags & (TRACE_ZOMBIE | ZOMBIE)) + return LSZOMB; + + if (mp->mp_flags & EXITING) + return LSDEAD; + + if ((mp->mp_flags & TRACE_STOPPED) || RTS_ISSET(kp, RTS_P_STOP)) + return LSSTOP; + + if (proc_is_runnable(kp)) + return LSRUN; + + /* + * The process is sleeping. In that case, we must also figure out why, + * and return an appropriate wchan value and human-readable wmesg text. + * + * The process can be blocked on either a known sleep state in PM or + * VFS, or otherwise on IPC communication with another process, or + * otherwise on a kernel RTS flag. In each case, decide what to use as + * wchan value and wmesg text, and whether the sleep is interruptible. + * + * The wchan value should be unique for the sleep reason. We use its + * lower eight bits to indicate a class: + * 0x00 = kernel task + * 0x01 = kerel RTS block + * 0x02 = PM call + * 0x03 = VFS call + * 0x04 = MIB call + * 0xff = blocked on process + * The upper bits are used for class-specific information. The actual + * value does not really matter, as long as it is nonzero and there is + * no overlap between the different values. + */ + wchan = 0; + wmesg = NULL; + + /* + * First see if the process is marked as blocked in the tables of PM or + * VFS. Such a block reason is always an interruptible sleep. Note + * that we do not use the kernel table at all in this case: each of the + * three tables is consistent within itself, but not necessarily + * consistent with any of the other tables, so we avoid internal + * mismatches if we can. + */ + if (mp->mp_flags & WAITING) { + wchan = 0x102; + wmesg = "wait"; + } else if (mp->mp_flags & SIGSUSPENDED) { + wchan = 0x202; + wmesg = "pause"; + } else if (fp->fp_blocked_on != FP_BLOCKED_ON_NONE) { + wchan = (fp->fp_blocked_on << 8) | 0x03; + switch (fp->fp_blocked_on) { + case FP_BLOCKED_ON_PIPE: + wmesg = "pipe"; + break; + case FP_BLOCKED_ON_LOCK: + wmesg = "lock"; + break; + case FP_BLOCKED_ON_POPEN: + wmesg = "popen"; + break; + case FP_BLOCKED_ON_SELECT: + wmesg = "select"; + break; + case FP_BLOCKED_ON_OTHER: + /* + * Add the task (= character driver) endpoint to the + * wchan value, and use the driver's process name, + * without parentheses, as wmesg text. + */ + wchan |= (uint64_t)fp->fp_task << 16; + fill_wmesg(wmptr, wmsz, fp->fp_task, FALSE /*ipc*/); + break; + default: + /* A newly added flag we don't yet know about? */ + wmesg = "???"; + break; + } + } + if (wchan != 0) { + *wcptr = wchan; + if (wmesg != NULL) /* NULL means "already set" here */ + strlcpy(wmptr, wmesg, wmsz); + *flag |= L_SINTR; + } + + /* + * See if the process is blocked on sending or receiving. If not, then + * use one of the kernel RTS flags as reason. + */ + endpt = P_BLOCKEDON(kp); + + switch (endpt) { + case MIB_PROC_NR: + /* This is really just aesthetics. */ + wchan = 0x04; + wmesg = "sysctl"; + break; + case NONE: + /* + * The process is not running, but also not blocked on IPC with + * another process. This means it must be stopped on a kernel + * RTS flag. + */ + wchan = ((uint64_t)kp->p_rts_flags << 8) | 0x01; + if (RTS_ISSET(kp, RTS_PROC_STOP)) + wmesg = "kstop"; + else if (RTS_ISSET(kp, RTS_SIGNALED) || + RTS_ISSET(kp, RTS_SIGNALED)) + wmesg = "ksignal"; + else if (RTS_ISSET(kp, RTS_NO_PRIV)) + wmesg = "knopriv"; + else if (RTS_ISSET(kp, RTS_PAGEFAULT) || + RTS_ISSET(kp, RTS_VMREQTARGET)) + wmesg = "fault"; + else if (RTS_ISSET(kp, RTS_NO_QUANTUM)) + wmesg = "sched"; + else + wmesg = "kflag"; + break; + case ANY: + /* + * If the process is blocked receiving from ANY, mark it as + * being in an interruptible sleep. This looks nicer, even + * though "interruptible" is not applicable to services at all. + */ + *flag |= L_SINTR; + break; + } + + /* + * If at this point wchan is still zero, the process is blocked sending + * or receiving. Use a wchan value based on the target endpoint, and + * use "(procname)" as wmesg text. + */ + if (wchan == 0) { + *wcptr = ((uint64_t)endpt << 8) | 0xff; + fill_wmesg(wmptr, wmsz, endpt, TRUE /*ipc*/); + } else { + *wcptr = wchan; + if (wmesg != NULL) /* NULL means "already set" here */ + strlcpy(wmptr, wmesg, wmsz); + } + + return LSSLEEP; +} + + +/* + * Fill the part of a LWP structure that is common between kernel tasks and + * user processes. Also return a CPU estimate in 'estcpu', because we generate + * the value as a side effect here, and the LWP structure has no estcpu field. + */ +static void +fill_lwp_common(struct kinfo_lwp * l, int kslot, uint32_t * estcpu) +{ + struct proc *kp; + struct timeval tv; + clock_t uptime; + uint32_t hz; + + kp = &proc_tab[kslot]; + + uptime = getticks(); + hz = sys_hz(); + + /* + * We use the process endpoint as the LWP ID. Not only does this allow + * users to obtain process endpoints with "ps -s" (thus replacing the + * MINIX3 ps(1)'s "ps -E"), but if we ever do implement kernel threads, + * this is probably still going to be accurate. + */ + l->l_lid = kp->p_endpoint; + + /* + * The time during which the process has not been swapped in or out is + * not applicable for us, and thus, we set it to the time the process + * has been running (in seconds). This value is relevant mostly for + * ps(1)'s CPU usage correction for processes that have just started. + */ + if (kslot >= NR_TASKS) + l->l_swtime = uptime - mproc_tab[kslot - NR_TASKS].mp_started; + else + l->l_swtime = uptime; + l->l_swtime /= hz; + + /* + * Sleep (dequeue) times are not maintained for kernel tasks, so + * pretend they are never asleep (which is pretty accurate). + */ + if (kslot < NR_TASKS) + l->l_slptime = 0; + else + l->l_slptime = (uptime - kp->p_dequeued) / hz; + + l->l_priority = kp->p_priority; + l->l_usrpri = kp->p_priority; + l->l_cpuid = kp->p_cpu; + ticks_to_timeval(&tv, kp->p_user_time + kp->p_sys_time); + l->l_rtime_sec = tv.tv_sec; + l->l_rtime_usec = tv.tv_usec; + + /* + * Obtain CPU usage percentages and estimates through library code + * shared between the kernel and this service; see its source for + * details. We note that the produced estcpu value is rather different + * from the one produced by NetBSD, but this should not be a problem. + */ + l->l_pctcpu = cpuavg_getstats(&kp->p_cpuavg, &l->l_cpticks, estcpu, + uptime, hz); +} + +/* + * Fill a LWP structure for a kernel task. Each kernel task has its own LWP, + * and all of them have negative PIDs. + */ +static void +fill_lwp_kern(struct kinfo_lwp * l, int kslot) +{ + uint32_t estcpu; + + memset(l, 0, sizeof(*l)); + + l->l_flag = L_INMEM | L_SINTR | L_SYSTEM; + l->l_stat = LSSLEEP; + l->l_pid = kslot - NR_TASKS; + + /* + * When showing LWP entries, ps(1) uses the process name rather than + * the LWP name. All kernel tasks are therefore shown as "[kernel]" + * anyway. We use the wmesg field to show the actual kernel task name. + */ + l->l_wchan = ((uint64_t)(l->l_pid) << 8) | 0x00; + strlcpy(l->l_wmesg, proc_tab[kslot].p_name, sizeof(l->l_wmesg)); + strlcpy(l->l_name, "kernel", sizeof(l->l_name)); + + fill_lwp_common(l, kslot, &estcpu); +} + +/* + * Fill a LWP structure for a user process. + */ +static void +fill_lwp_user(struct kinfo_lwp * l, int mslot) +{ + struct mproc *mp; + uint32_t estcpu; + + memset(l, 0, sizeof(*l)); + + mp = &mproc_tab[mslot]; + + l->l_flag = L_INMEM; + l->l_stat = get_lwp_stat(mslot, &l->l_wchan, l->l_wmesg, + sizeof(l->l_wmesg), &l->l_flag); + l->l_pid = mp->mp_pid; + strlcpy(l->l_name, mp->mp_name, sizeof(l->l_name)); + + fill_lwp_common(l, NR_TASKS + mslot, &estcpu); +} + +/* + * Implementation of CTL_KERN KERN_LWP. + */ +ssize_t +mib_kern_lwp(struct mib_call * call, struct mib_node * node __unused, + struct mib_oldp * oldp, struct mib_newp * newp __unused) +{ + struct kinfo_lwp lwp; + struct mproc *mp; + size_t copysz; + ssize_t off; + pid_t pid; + int r, elsz, elmax, kslot, mslot, last_mslot; + + if (call->call_namelen != 3) + return EINVAL; + + pid = (pid_t)call->call_name[0]; + elsz = call->call_name[1]; + elmax = call->call_name[2]; /* redundant with the given oldlen.. */ + + if (pid < -1 || elsz <= 0 || elmax < 0) + return EINVAL; + + if (!update_tables()) + return EINVAL; + + off = 0; + copysz = MIN((size_t)elsz, sizeof(lwp)); + + /* + * We model kernel tasks as LWP threads of the kernel (with PID 0). + * Modeling the kernel tasks as processes with negative PIDs, like + * ProcFS does, conflicts with the KERN_LWP API here: a PID of -1 + * indicates that the caller wants a full listing of LWPs. + */ + if (pid <= 0) { + for (kslot = 0; kslot < NR_TASKS; kslot++) { + if (mib_inrange(oldp, off) && elmax > 0) { + fill_lwp_kern(&lwp, kslot); + if ((r = mib_copyout(oldp, off, &lwp, + copysz)) < 0) + return r; + elmax--; + } + off += elsz; + } + + /* No need to add extra space here: NR_TASKS is static. */ + if (pid == 0) + return off; + } + + /* + * With PID 0 out of the way: the user requested the LWP for either a + * specific user process (pid > 0), or for all processes (pid < 0). + */ + if (pid > 0) { + if ((mslot = get_mslot(pid)) == NO_SLOT || + (mproc_tab[mslot].mp_flags & (TRACE_ZOMBIE | ZOMBIE))) + return ESRCH; + last_mslot = mslot; + } else { + mslot = 0; + last_mslot = NR_PROCS - 1; + } + + for (; mslot <= last_mslot; mslot++) { + mp = &mproc_tab[mslot]; + + if ((mp->mp_flags & (IN_USE | TRACE_ZOMBIE | ZOMBIE)) != + IN_USE) + continue; + + if (mib_inrange(oldp, off) && elmax > 0) { + fill_lwp_user(&lwp, mslot); + if ((r = mib_copyout(oldp, off, &lwp, copysz)) < 0) + return r; + elmax--; + } + off += elsz; + } + + if (oldp == NULL && pid < 0) + off += EXTRA_PROCS * elsz; + + return off; +} + + +/* + * Fill the part of a process structure that is common between kernel tasks and + * user processes. + */ +static void +fill_proc2_common(struct kinfo_proc2 * p, int kslot) +{ + struct vm_usage_info vui; + struct timeval tv; + struct proc *kp; + struct kinfo_lwp l; + + kp = &proc_tab[kslot]; + + /* + * Much of the information in the LWP structure also ends up in the + * process structure. In order to avoid duplication of some important + * code, first generate LWP values and then copy it them into the + * process structure. + */ + memset(&l, 0, sizeof(l)); + fill_lwp_common(&l, kslot, &p->p_estcpu); + + /* Obtain memory usage information from VM. Ignore failures. */ + memset(&vui, 0, sizeof(vui)); + (void)vm_info_usage(kp->p_endpoint, &vui); + + ticks_to_timeval(&tv, kp->p_user_time + kp->p_sys_time); + p->p_rtime_sec = l.l_rtime_sec; + p->p_rtime_usec = l.l_rtime_usec; + p->p_cpticks = l.l_cpticks; + p->p_pctcpu = l.l_pctcpu; + p->p_swtime = l.l_swtime; + p->p_slptime = l.l_slptime; + p->p_uticks = kp->p_user_time; + p->p_sticks = kp->p_sys_time; + /* TODO: p->p_iticks */ + ticks_to_timeval(&tv, kp->p_user_time); + p->p_uutime_sec = tv.tv_sec; + p->p_uutime_usec = tv.tv_usec; + ticks_to_timeval(&tv, kp->p_sys_time); + p->p_ustime_sec = tv.tv_sec; + p->p_ustime_usec = tv.tv_usec; + + p->p_priority = l.l_priority; + p->p_usrpri = l.l_usrpri; + + p->p_vm_rssize = howmany(vui.vui_total, PAGE_SIZE); + p->p_vm_vsize = howmany(vui.vui_virtual, PAGE_SIZE); + p->p_vm_msize = howmany(vui.vui_mvirtual, PAGE_SIZE); + + p->p_uru_maxrss = vui.vui_maxrss; + p->p_uru_minflt = vui.vui_minflt; + p->p_uru_majflt = vui.vui_majflt; + + p->p_cpuid = l.l_cpuid; +} + +/* + * Fill a process structure for the kernel pseudo-process (with PID 0). + */ +static void +fill_proc2_kern(struct kinfo_proc2 * p) +{ + + memset(p, 0, sizeof(*p)); + + p->p_flag = L_INMEM | L_SYSTEM | L_SINTR; + p->p_pid = 0; + p->p_stat = LSSLEEP; + p->p_nice = NZERO; + + /* Use the KERNEL task wchan, for consistency between ps and top. */ + p->p_wchan = ((uint64_t)KERNEL << 8) | 0x00; + strlcpy(p->p_wmesg, "kernel", sizeof(p->p_wmesg)); + + strlcpy(p->p_comm, "kernel", sizeof(p->p_comm)); + p->p_realflag = P_INMEM | P_SYSTEM | P_SINTR; + p->p_realstat = SACTIVE; + p->p_nlwps = NR_TASKS; + + /* + * By using the KERNEL slot here, the kernel process will get a proper + * CPU usage average. + */ + fill_proc2_common(p, KERNEL + NR_TASKS); +} + +/* + * Fill a process structure for a user process. + */ +static void +fill_proc2_user(struct kinfo_proc2 * p, int mslot) +{ + struct mproc *mp; + struct fproc *fp; + time_t boottime; + dev_t tty; + struct timeval tv; + int i, r, kslot, zombie; + + memset(p, 0, sizeof(*p)); + + if ((r = getuptime(NULL, NULL, &boottime)) != OK) + panic("getuptime failed: %d", r); + + kslot = NR_TASKS + mslot; + mp = &mproc_tab[mslot]; + fp = &fproc_tab[mslot]; + + zombie = (mp->mp_flags & (TRACE_ZOMBIE | ZOMBIE)); + tty = (!zombie) ? fp->fp_tty : NO_DEV; + + p->p_eflag = 0; + if (tty != NO_DEV) + p->p_eflag |= EPROC_CTTY; + if (mp->mp_pid == mp->mp_procgrp) /* TODO: job control support */ + p->p_eflag |= EPROC_SLEADER; + + p->p_exitsig = SIGCHLD; /* TODO */ + + p->p_flag = P_INMEM; + if (mp->mp_flags & TAINTED) + p->p_flag |= P_SUGID; + if (mp->mp_tracer != NO_TRACER) + p->p_flag |= P_TRACED; + if (tty != NO_DEV) + p->p_flag |= P_CONTROLT; + p->p_pid = mp->mp_pid; + if (mp->mp_parent >= 0 && mp->mp_parent < NR_PROCS) + p->p_ppid = mproc_tab[mp->mp_parent].mp_pid; + p->p_sid = mp->mp_procgrp; /* TODO: job control supported */ + p->p__pgid = mp->mp_procgrp; + p->p_tpgid = (tty != NO_DEV) ? mp->mp_procgrp : 0; + p->p_uid = mp->mp_effuid; + p->p_ruid = mp->mp_realuid; + p->p_gid = mp->mp_effgid; + p->p_rgid = mp->mp_realgid; + p->p_ngroups = MIN(mp->mp_ngroups, KI_NGROUPS); + for (i = 0; i < p->p_ngroups; i++) + p->p_groups[i] = mp->mp_sgroups[i]; + p->p_tdev = tty; + memcpy(&p->p_siglist, &mp->mp_sigpending, sizeof(p->p_siglist)); + memcpy(&p->p_sigmask, &mp->mp_sigmask, sizeof(p->p_sigmask)); + memcpy(&p->p_sigcatch, &mp->mp_catch, sizeof(p->p_sigcatch)); + memcpy(&p->p_sigignore, &mp->mp_ignore, sizeof(p->p_sigignore)); + p->p_nice = mp->mp_nice + NZERO; + strlcpy(p->p_comm, mp->mp_name, sizeof(p->p_comm)); + p->p_uvalid = 1; + ticks_to_timeval(&tv, mp->mp_started); + p->p_ustart_sec = boottime + tv.tv_sec; + p->p_ustart_usec = tv.tv_usec; + /* TODO: other rusage fields */ + ticks_to_timeval(&tv, mp->mp_child_utime + mp->mp_child_stime); + p->p_uctime_sec = tv.tv_sec; + p->p_uctime_usec = tv.tv_usec; + p->p_realflag = p->p_flag; + p->p_nlwps = (zombie) ? 0 : 1; + + p->p_stat = get_lwp_stat(mslot, &p->p_wchan, p->p_wmesg, + sizeof(p->p_wmesg), &p->p_flag); + + switch (p->p_stat) { + case LSRUN: + p->p_realstat = SACTIVE; + p->p_nrlwps = 1; + break; + case LSSLEEP: + p->p_realstat = SACTIVE; + if (p->p_flag & L_SINTR) + p->p_realflag |= P_SINTR; + break; + case LSSTOP: + p->p_realstat = SSTOP; + break; + case LSZOMB: + p->p_realstat = SZOMB; + break; + case LSDEAD: + p->p_stat = LSZOMB; /* ps(1) STAT does not know LSDEAD */ + p->p_realstat = SDEAD; + break; + default: + assert(0); + } + + if (!zombie) + fill_proc2_common(p, kslot); +} + +/* + * Implementation of CTL_KERN KERN_PROC2. + */ +ssize_t +mib_kern_proc2(struct mib_call * call, struct mib_node * node __unused, + struct mib_oldp * oldp, struct mib_newp * newp __unused) +{ + struct kinfo_proc2 proc2; + struct mproc *mp; + size_t copysz; + ssize_t off; + dev_t tty; + int r, req, arg, elsz, elmax, kmatch, zombie, mslot; + + if (call->call_namelen != 4) + return EINVAL; + + req = call->call_name[0]; + arg = call->call_name[1]; + elsz = call->call_name[2]; + elmax = call->call_name[3]; /* redundant with the given oldlen.. */ + + /* + * The kernel is special, in that it does not have a slot in the PM or + * VFS tables. As such, it is dealt with separately. While checking + * arguments, we might as well check whether the kernel is matched. + */ + switch (req) { + case KERN_PROC_ALL: + kmatch = TRUE; + break; + case KERN_PROC_PID: + case KERN_PROC_SESSION: + case KERN_PROC_PGRP: + case KERN_PROC_UID: + case KERN_PROC_RUID: + case KERN_PROC_GID: + case KERN_PROC_RGID: + kmatch = (arg == 0); + break; + case KERN_PROC_TTY: + kmatch = ((dev_t)arg == KERN_PROC_TTY_NODEV); + break; + default: + return EINVAL; + } + + if (elsz <= 0 || elmax < 0) + return EINVAL; + + if (!update_tables()) + return EINVAL; + + off = 0; + copysz = MIN((size_t)elsz, sizeof(proc2)); + + if (kmatch) { + if (mib_inrange(oldp, off) && elmax > 0) { + fill_proc2_kern(&proc2); + if ((r = mib_copyout(oldp, off, &proc2, copysz)) < 0) + return r; + elmax--; + } + off += elsz; + } + + for (mslot = 0; mslot < NR_PROCS; mslot++) { + mp = &mproc_tab[mslot]; + + if (!(mp->mp_flags & IN_USE)) + continue; + + switch (req) { + case KERN_PROC_PID: + if ((pid_t)arg != mp->mp_pid) + continue; + break; + case KERN_PROC_SESSION: /* TODO: job control support */ + case KERN_PROC_PGRP: + if ((pid_t)arg != mp->mp_procgrp) + continue; + break; + case KERN_PROC_TTY: + if ((dev_t)arg == KERN_PROC_TTY_REVOKE) + continue; /* TODO: revoke(2) support */ + /* Do not access the fproc_tab slot of zombies. */ + zombie = (mp->mp_flags & (TRACE_ZOMBIE | ZOMBIE)); + tty = (zombie) ? fproc_tab[mslot].fp_tty : NO_DEV; + if ((dev_t)arg == KERN_PROC_TTY_NODEV) { + if (tty != NO_DEV) + continue; + } else if ((dev_t)arg == NO_DEV || (dev_t)arg != tty) + continue; + break; + case KERN_PROC_UID: + if ((uid_t)arg != mp->mp_effuid) + continue; + break; + case KERN_PROC_RUID: + if ((uid_t)arg != mp->mp_realuid) + continue; + break; + case KERN_PROC_GID: + if ((gid_t)arg != mp->mp_effgid) + continue; + break; + case KERN_PROC_RGID: + if ((gid_t)arg != mp->mp_realgid) + continue; + break; + } + + if (mib_inrange(oldp, off) && elmax > 0) { + fill_proc2_user(&proc2, mslot); + if ((r = mib_copyout(oldp, off, &proc2, copysz)) < 0) + return r; + elmax--; + } + off += elsz; + } + + if (oldp == NULL && req != KERN_PROC_PID) + off += EXTRA_PROCS * elsz; + + return off; +} + +/* + * Implementation of CTL_KERN KERN_PROC_ARGS. + */ +ssize_t +mib_kern_proc_args(struct mib_call * call, struct mib_node * node __unused, + struct mib_oldp * oldp, struct mib_newp * newp __unused) +{ + char vbuf[PAGE_SIZE], sbuf[PAGE_SIZE], obuf[PAGE_SIZE]; + struct ps_strings pss; + struct mproc *mp; + char *buf, *p, *q, *pptr; + vir_bytes vaddr, vpage, spage, paddr, ppage; + size_t max, off, olen, oleft, oldlen, bytes, pleft; + unsigned int copybudget; + pid_t pid; + int req, mslot, count, aborted, ended; + ssize_t r; + + if (call->call_namelen != 2) + return EINVAL; + + pid = call->call_name[0]; + req = call->call_name[1]; + + switch (req) { + case KERN_PROC_ARGV: + case KERN_PROC_ENV: + case KERN_PROC_NARGV: + case KERN_PROC_NENV: + break; + default: + return EOPNOTSUPP; + } + + if (!update_tables()) + return EINVAL; + + if ((mslot = get_mslot(pid)) == NO_SLOT) + return ESRCH; + mp = &mproc_tab[mslot]; + if (mp->mp_flags & (TRACE_ZOMBIE | ZOMBIE)) + return ESRCH; + + /* We can return the count field size without copying in any data. */ + if (oldp == NULL && (req == KERN_PROC_NARGV || req == KERN_PROC_NENV)) + return sizeof(count); + + if (sys_datacopy(mp->mp_endpoint, + mp->mp_frame_addr + mp->mp_frame_len - sizeof(pss), + SELF, (vir_bytes)&pss, sizeof(pss)) != OK) + return EINVAL; + + /* + * Determine the upper size limit of the requested data. Not only may + * the size never exceed ARG_MAX, it may also not exceed the frame + * length as given in its original exec call. In fact, the frame + * length should be substantially larger: all strings for both the + * arguments and the environment are in there, along with other stuff, + * and there must be no overlap between strings. It is possible that + * the application called setproctitle(3), in which case the ps_strings + * pointers refer to data outside the frame altogether. However, this + * data should not exceed 2048 bytes, and we cover this by rounding up + * the frame length to a multiple of the page size. Anyhow, NetBSD + * blindly returns ARG_MAX when asked for a size estimate, so with this + * maximum we are already quite a bit more accurate. + */ + max = roundup(MIN(mp->mp_frame_len, ARG_MAX), PAGE_SIZE); + + switch (req) { + case KERN_PROC_NARGV: + count = pss.ps_nargvstr; + return mib_copyout(oldp, 0, &count, sizeof(count)); + case KERN_PROC_NENV: + count = pss.ps_nenvstr; + return mib_copyout(oldp, 0, &count, sizeof(count)); + case KERN_PROC_ARGV: + if (oldp == NULL) + return max; + vaddr = (vir_bytes)pss.ps_argvstr; + count = pss.ps_nargvstr; + break; + case KERN_PROC_ENV: + if (oldp == NULL) + return max; + vaddr = (vir_bytes)pss.ps_envstr; + count = pss.ps_nenvstr; + break; + } + + /* + * Go through the strings. Copy in entire, machine-aligned pages at + * once, in the hope that all data is stored consecutively, which it + * should be: we expect that the vector is followed by the strings, and + * that the strings are stored in order of vector reference. We keep + * up to two pages with copied-in data: one for the vector, and + * optionally one for string data. In addition, we keep one page with + * data to be copied out, so that we do not cause a lot of copy + * overhead for short strings. + * + * We stop whenever any of the following conditions are met: + * - copying in data from the target process fails for any reason; + * - we have processed the last index ('count') into the vector; + * - the current vector element is a NULL pointer; + * - the requested number of output bytes ('oldlen') has been reached; + * - the maximum number of output bytes ('max') has been reached; + * - the number of page copy-ins exceeds an estimated threshold; + * - copying out data fails for any reason (we then return the error). + * + * We limit the number of page copy-ins because otherwise a rogue + * process could create an argument vector consisting of only two-byte + * strings that all span two pages, causing us to copy up to 1GB of + * data with the current ARG_MAX value of 256K. No reasonable vector + * should cause more than (ARG_MAX / PAGE_SIZE) page copies for + * strings; we are nice enough to allow twice that. Vector copies do + * not count, as they are linear anyway. + * + * Unlike every other sysctl(2) call, we are supposed to truncate the + * resulting size (the returned 'oldlen') to the requested size (the + * given 'oldlen') *and* return the resulting size, rather than ENOMEM + * and the real size. Unfortunately, libkvm actually relies on this. + * + * Generally speaking, upon failure we just return a truncated result. + * In case of truncation, the data we copy out need not be null + * terminated. It is up to userland to process the data correctly. + */ + if (trunc_page(vaddr) == 0 || vaddr % sizeof(char *) != 0) + return 0; + + off = 0; + olen = 0; + aborted = FALSE; + + oldlen = mib_getoldlen(oldp); + if (oldlen > max) + oldlen = max; + + copybudget = (ARG_MAX / PAGE_SIZE) * 2; + + vpage = 0; + spage = 0; + + while (count > 0 && off + olen < oldlen && !aborted) { + /* + * Start by fetching the page containing the current vector + * element, if needed. We could limit the fetch to the vector + * size, but our hope is that for the simple cases, the strings + * are on the remainder of the same page, so we save a copy + * call. TODO: since the strings should follow the vector, we + * could start the copy at the base of the vector. + */ + if (trunc_page(vaddr) != vpage) { + vpage = trunc_page(vaddr); + if (sys_datacopy(mp->mp_endpoint, vpage, SELF, + (vir_bytes)vbuf, PAGE_SIZE) != OK) + break; + } + + /* Get the current vector element, pointing to a string. */ + memcpy(&pptr, &vbuf[vaddr - vpage], sizeof(pptr)); + paddr = (vir_bytes)pptr; + ppage = trunc_page(paddr); + if (ppage == 0) + break; + + /* Fetch the string itself, one page at a time at most. */ + do { + /* + * See if the string pointer falls inside either the + * vector page or the previously fetched string page + * (if any). If not, fetch a string page. + */ + if (ppage == vpage) { + buf = vbuf; + } else if (ppage == spage) { + buf = sbuf; + } else { + if (--copybudget == 0) { + aborted = TRUE; + break; + } + spage = ppage; + if (sys_datacopy(mp->mp_endpoint, spage, SELF, + (vir_bytes)sbuf, PAGE_SIZE) != OK) { + aborted = TRUE; + break; + } + buf = sbuf; + } + + /* + * We now have a string fragment in a buffer. See if + * the string is null terminated. If not, all the data + * up to the buffer end is part of the string, and the + * string continues on the next page. + */ + p = &buf[paddr - ppage]; + pleft = PAGE_SIZE - (paddr - ppage); + assert(pleft > 0); + + if ((q = memchr(p, '\0', pleft)) != NULL) { + bytes = (size_t)(q - p + 1); + assert(bytes <= pleft); + ended = TRUE; + } else { + bytes = pleft; + ended = FALSE; + } + + /* Limit the result to the requested length. */ + if (off + olen + bytes > oldlen) + bytes = oldlen - off - olen; + + /* + * Add 'bytes' bytes from string pointer 'p' to the + * output buffer, copying out its contents to userland + * if it has filled up. + */ + if (olen + bytes > sizeof(obuf)) { + oleft = sizeof(obuf) - olen; + memcpy(&obuf[olen], p, oleft); + + if ((r = mib_copyout(oldp, off, obuf, + sizeof(obuf))) < 0) + return r; + off += sizeof(obuf); + olen = 0; + + p += oleft; + bytes -= oleft; + } + if (bytes > 0) { + memcpy(&obuf[olen], p, bytes); + olen += bytes; + } + + /* + * Continue as long as we have not yet found the string + * end, and we have not yet filled the output buffer. + */ + paddr += pleft; + assert(trunc_page(paddr) == paddr); + ppage = paddr; + } while (!ended && off + olen < oldlen); + + vaddr += sizeof(char *); + count--; + } + + /* Copy out any remainder of the output buffer. */ + if (olen > 0) { + if ((r = mib_copyout(oldp, off, obuf, olen)) < 0) + return r; + off += olen; + } + + assert(off <= oldlen); + return off; +} diff --git a/minix/servers/mib/vm.c b/minix/servers/mib/vm.c new file mode 100644 index 000000000..5a9844fcd --- /dev/null +++ b/minix/servers/mib/vm.c @@ -0,0 +1,154 @@ +/* MIB service - vm.c - implementation of the CTL_VM subtree */ + +#include "mib.h" + +#include +#include + +/* + * Implementation of CTL_VM VM_LOADAVG. + */ +static ssize_t +mib_vm_loadavg(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + struct loadavg loadavg; + struct loadinfo loadinfo; + unsigned long proc_load; + u32_t ticks_per_slot, ticks; + unsigned int p; + int unfilled_ticks; + int h, slots, latest, slot; + int minutes[3] = { 1, 5, 15 }; + + assert(__arraycount(loadavg.ldavg) == __arraycount(minutes)); + + if (sys_getloadinfo(&loadinfo) != OK) + return EINVAL; + + memset(&loadavg, 0, sizeof(loadavg)); + + /* + * The following code is inherited from the old MINIX libc. + */ + + /* How many ticks are missing from the newest-filled slot? */ + ticks_per_slot = _LOAD_UNIT_SECS * sys_hz(); + unfilled_ticks = + ticks_per_slot - (loadinfo.last_clock % ticks_per_slot); + + for (p = 0; p < __arraycount(loadavg.ldavg); p++) { + latest = loadinfo.proc_last_slot; + slots = minutes[p] * 60 / _LOAD_UNIT_SECS; + proc_load = 0; + + /* + * Add up the total number of process ticks for this number + * of minutes (minutes[p]). Start with the newest slot, which + * is latest, and count back for the number of slots that + * correspond to the right number of minutes. Take wraparound + * into account by calculating the index modulo _LOAD_HISTORY, + * which is the number of slots of history kept. + */ + for (h = 0; h < slots; h++) { + slot = (latest - h + _LOAD_HISTORY) % _LOAD_HISTORY; + proc_load += loadinfo.proc_load_history[slot]; + } + + /* + * The load average over this number of minutes is the number + * of process-ticks divided by the number of ticks, not + * counting the number of ticks the last slot hasn't been + * around yet. + */ + ticks = slots * ticks_per_slot - unfilled_ticks; + + loadavg.ldavg[p] = 100UL * proc_load / ticks; + } + + loadavg.fscale = 100L; + + return mib_copyout(oldp, 0, &loadavg, sizeof(loadavg)); +} + +/* + * Implementation of CTL_VM VM_UVMEXP2. + */ +static ssize_t +mib_vm_uvmexp2(struct mib_call * call __unused, + struct mib_node * node __unused, struct mib_oldp * oldp, + struct mib_newp * newp __unused) +{ + struct vm_stats_info vsi; + struct uvmexp_sysctl ues; + unsigned int shift; + + if (vm_info_stats(&vsi) != OK) + return EINVAL; + + memset(&ues, 0, sizeof(ues)); + + /* + * TODO: by far most of the structure is not filled correctly yet, + * since the MINIX3 system does not provide much of the information + * exposed by NetBSD. This will gradually have to be filled in. + * For now, we provide just some basic information used by top(1). + */ + ues.pagesize = vsi.vsi_pagesize; + ues.pagemask = vsi.vsi_pagesize - 1; + for (shift = 0; shift < CHAR_BIT * sizeof(void *); shift++) + if ((1U << shift) == vsi.vsi_pagesize) + break; + if (shift < CHAR_BIT * sizeof(void *)) + ues.pageshift = shift; + ues.npages = vsi.vsi_total; + ues.free = vsi.vsi_free; + ues.filepages = vsi.vsi_cached; + /* + * We use one of the structure's unused fields to expose information + * not exposed by NetBSD, namely the largest area of physically + * contiguous memory. If NetBSD repurposes this field, we have to find + * another home for it (or expose it through a separate node or so). + */ + ues.unused1 = vsi.vsi_largest; + + return mib_copyout(oldp, 0, &ues, sizeof(ues)); +} + +/* The CTL_VM nodes. */ +static struct mib_node mib_vm_table[] = { +/* 1*/ /* VM_METER: not yet supported */ +/* 2*/ [VM_LOADAVG] = MIB_FUNC(_P | _RO | CTLTYPE_STRUCT, + sizeof(struct loadavg), mib_vm_loadavg, + "loadavg", "System load average history"), +/* 3*/ /* VM_UVMEXP: not yet supported */ +/* 4*/ /* VM_NKMEMPAGES: not yet supported */ +/* 5*/ [VM_UVMEXP2] = MIB_FUNC(_P | _RO | CTLTYPE_STRUCT, + sizeof(struct uvmexp_sysctl), + mib_vm_uvmexp2, "uvmexp2", + "Detailed system-wide virtual memory " + "statistics (MI)"), +/* 6*/ /* VM_ANONMIN: not yet supported */ +/* 7*/ /* VM_EXECMIN: not yet supported */ +/* 8*/ /* VM_FILEMIN: not yet supported */ +/* 9*/ [VM_MAXSLP] = MIB_INT(_P | _RO, MAXSLP, "maxslp", + "Maximum process sleep time before being " + "swapped"), +/*10*/ [VM_USPACE] = MIB_INT(_P | _RO, 0, "uspace", "Number of " + "bytes allocated for a kernel stack"), + /* MINIX3 processes don't have k-stacks */ +/*11*/ /* VM_ANONMAX: not yet supported */ +/*12*/ /* VM_EXECMAX: not yet supported */ +/*13*/ /* VM_FILEMAX: not yet supported */ +}; + +/* + * Initialize the CTL_VM subtree. + */ +void +mib_vm_init(struct mib_node * node) +{ + + MIB_INIT_ENODE(node, mib_vm_table); +} diff --git a/minix/usr.bin/trace/service/mib.c b/minix/usr.bin/trace/service/mib.c index 5f3fd6e5c..32cf8c4db 100644 --- a/minix/usr.bin/trace/service/mib.c +++ b/minix/usr.bin/trace/service/mib.c @@ -1,7 +1,10 @@ #include "inc.h" +#include #include +#include +#include struct sysctl_tab { int id; @@ -13,13 +16,272 @@ struct sysctl_tab { #define NODE(i,t) { .id = i, .size = __arraycount(t), .tab = t } #define PROC(i,s,p) { .id = i, .size = s, .proc = p } +/* + * Print CTL_KERN KERN_CLOCKRATE. + */ +static int +put_kern_clockrate(struct trace_proc * proc, const char * name, + int type __unused, const void * ptr, vir_bytes addr __unused, + size_t size __unused) +{ + struct clockinfo *ci; + + ci = (struct clockinfo *)ptr; + + put_value(proc, "hz", "%d", ci->hz); + put_value(proc, "tick", "%d", ci->tick); + if (verbose > 0) { + put_value(proc, "tickadj", "%d", ci->tickadj); + put_value(proc, "stathz", "%d", ci->stathz); + put_value(proc, "profhz", "%d", ci->profhz); + return TRUE; + } else + return FALSE; +} + +/* + * Print CTL_KERN KERN_PROC2. + */ +static int +put_kern_proc2(struct trace_proc * proc, const char * name, int type, + const void * ptr, vir_bytes addr, size_t size) +{ + const int *mib; + const char *text; + int i; + + if (type == ST_NAME) { + mib = (const int *)ptr; + + for (i = 0; i < size; i++) { + text = NULL; + + if (i == 0) { + switch (mib[i]) { + case KERN_PROC_ALL: text = ""; break; + case KERN_PROC_PID: text = ""; break; + case KERN_PROC_PGRP: text = ""; break; + case KERN_PROC_SESSION: + text = ""; break; + case KERN_PROC_TTY: text = ""; break; + case KERN_PROC_UID: text = ""; break; + case KERN_PROC_RUID: text = ""; break; + case KERN_PROC_GID: text = ""; break; + case KERN_PROC_RGID: text = ""; break; + } + } else if (i == 1 && mib[0] == KERN_PROC_TTY) { + switch ((dev_t)mib[i]) { + case KERN_PROC_TTY_NODEV: + text = ""; break; + case KERN_PROC_TTY_REVOKE: + text = ""; break; + } + } + + if (!valuesonly && text != NULL) + put_field(proc, NULL, text); + else + put_value(proc, NULL, "%d", mib[i]); + } + + /* + * Save the requested structure length, so that we can later + * determine how many elements were returned (see below). + */ + proc->sctl_arg = (size == 4) ? mib[2] : 0; + + return 0; + } + + if (proc->sctl_arg > 0) { + /* TODO: optionally dump struct kinfo_drivers array */ + put_open(proc, name, 0, "[", ", "); + if (size > 0) + put_tail(proc, size / proc->sctl_arg, 0); + put_close(proc, "]"); + } else + put_ptr(proc, name, addr); + + return TRUE; +} + +/* + * Print CTL_KERN KERN_PROC_ARGS. + */ +static int +put_kern_proc_args(struct trace_proc * proc, const char * name, int type, + const void * ptr, vir_bytes addr, size_t size) +{ + const int *mib; + const char *text; + int i, v; + + if (type == ST_NAME) { + mib = (const int *)ptr; + + for (i = 0; i < size; i++) { + text = NULL; + + if (i == 1) { + switch (mib[i]) { + case KERN_PROC_ARGV: text = ""; break; + case KERN_PROC_ENV: text = ""; break; + case KERN_PROC_NARGV: text = ""; break; + case KERN_PROC_NENV: text = ""; break; + } + } + + if (!valuesonly && text != NULL) + put_field(proc, NULL, text); + else + put_value(proc, NULL, "%d", mib[i]); + } + + /* Save the subrequest, so that we can later print data. */ + proc->sctl_arg = (size == 2) ? mib[1] : -999; + + return 0; + } + + if ((proc->sctl_arg == KERN_PROC_NARGV || + proc->sctl_arg == KERN_PROC_NENV) && size == sizeof(v) && + mem_get_data(proc->pid, addr, &v, sizeof(v)) >= 0) { + put_open(proc, name, PF_NONAME, "{", ", "); + + put_value(proc, NULL, "%d", v); + + put_close(proc, "}"); + } else + put_ptr(proc, name, addr); + + return TRUE; +} + +/* + * Print CTL_KERN KERN_CP_TIME. + */ +static int +put_kern_cp_time(struct trace_proc * proc, const char * name __unused, + int type, const void * ptr, vir_bytes addr __unused, size_t size) +{ + uint64_t *p; + unsigned int i; + const int *mib; + + if (type == ST_NAME) { + mib = (const int *)ptr; + for (i = 0; i < size; i++) + put_value(proc, NULL, "%d", mib[i]); + + return 0; + } + + p = (uint64_t *)ptr; + + /* TODO: support for multi-CPU results */ + for (i = 0; i < CPUSTATES; i++) + put_value(proc, NULL, "%"PRIu64, p[i]); + + return TRUE; +} + +/* + * Print CTL_KERN KERN_CONSDEV. + */ +static int +put_kern_consdev(struct trace_proc * proc, const char * name, + int type __unused, const void * ptr, vir_bytes addr __unused, + size_t size __unused) +{ + + put_dev(proc, NULL, *(dev_t *)ptr); + + return TRUE; +} + +/* + * Print CTL_KERN KERN_DRIVERS. + */ +static int +put_kern_drivers(struct trace_proc * proc, const char * name, + int type __unused, const void * ptr __unused, vir_bytes addr __unused, + size_t size) +{ + + /* TODO: optionally dump struct kinfo_drivers array */ + put_open(proc, name, 0, "[", ", "); + if (size > 0) + put_tail(proc, size / sizeof(struct kinfo_drivers), 0); + put_close(proc, "]"); + + return TRUE; +} + +/* + * Print CTL_KERN KERN_BOOTTIME. + */ +static int +put_kern_boottime(struct trace_proc * proc, const char * name, + int type __unused, const void * ptr __unused, vir_bytes addr, + size_t size) +{ + + if (size == sizeof(struct timeval)) + put_struct_timeval(proc, name, 0, addr); + else + put_ptr(proc, name, addr); + + return TRUE; +} + /* The CTL_KERN table. */ static const struct sysctl_tab kern_tab[] = { + PROC(KERN_CLOCKRATE, sizeof(struct clockinfo), put_kern_clockrate), + PROC(KERN_PROC2, 0, put_kern_proc2), + PROC(KERN_PROC_ARGS, 0, put_kern_proc_args), + PROC(KERN_CP_TIME, sizeof(uint64_t) * CPUSTATES, put_kern_cp_time), + PROC(KERN_CONSDEV, sizeof(dev_t), put_kern_consdev), + PROC(KERN_DRIVERS, 0, put_kern_drivers), + PROC(KERN_BOOTTIME, 0, put_kern_boottime), +}; + +/* + * Print CTL_VM VM_LOADAVG. + */ +static int +put_vm_loadavg(struct trace_proc * proc, const char * name __unused, + int type __unused, const void * ptr, vir_bytes addr __unused, + size_t size __unused) +{ + struct loadavg *loadavg; + unsigned int i; + + loadavg = (struct loadavg *)ptr; + + put_open(proc, "ldavg", 0, "{", ", "); + + for (i = 0; i < __arraycount(loadavg->ldavg); i++) + put_value(proc, NULL, "%"PRIu32, loadavg->ldavg[i]); + + put_close(proc, "}"); + + if (verbose > 0) { + put_value(proc, "fscale", "%ld", loadavg->fscale); + + return TRUE; + } else + return FALSE; +} + +/* The CTL_VM table. */ +static const struct sysctl_tab vm_tab[] = { + PROC(VM_LOADAVG, sizeof(struct loadavg), put_vm_loadavg), }; /* The top-level table, which is indexed by identifier. */ static const struct sysctl_tab root_tab[] = { [CTL_KERN] = NODE(0, kern_tab), + [CTL_VM] = NODE(0, vm_tab), }; /* -- 2.44.0