Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
295 lines
9.3 KiB
C
295 lines
9.3 KiB
C
// poll(2) / select(2) — real readiness-based I/O multiplexing over pipes,
|
|
// the console tty, and regular files (via file_poll()). This is the
|
|
// foundation every event-loop program (dash, toybox netcat, eventually a
|
|
// display server) sits on; the previous stubs returned -ENOSYS, which made
|
|
// anything poll-driven bail out instantly.
|
|
//
|
|
// Blocking strategy: scan all fds; if nothing is ready and the timeout
|
|
// allows, sleep until the next timer tick (10ms granularity at HZ=100) and
|
|
// rescan. A pending unblocked signal or kill interrupts the wait with
|
|
// -EINTR so handlers get a chance to run, matching POSIX.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
#include "core/proc.h"
|
|
#include "core/syscall.h"
|
|
#include "uapi/posix.h"
|
|
#include "core/uaccess.h"
|
|
#include "uapi/errno.h"
|
|
|
|
#define POLL_MAX_FDS 64
|
|
|
|
// Sleep one timer tick, or until killed/signaled. Returns 0 to keep
|
|
// waiting, -EINTR if the wait must be abandoned.
|
|
static int
|
|
poll_block_one_tick(void)
|
|
{
|
|
if (myproc()->killed)
|
|
return -EINTR;
|
|
if (myproc()->pending_signals & ~myproc()->signal_mask)
|
|
return -EINTR;
|
|
acquire(&tickslock);
|
|
uint t0 = ticks;
|
|
while (ticks == t0 && !myproc()->killed &&
|
|
!(myproc()->pending_signals & ~myproc()->signal_mask))
|
|
sleep(&ticks, &tickslock);
|
|
release(&tickslock);
|
|
return 0;
|
|
}
|
|
|
|
// Shared core: fds[] already in kernel memory. timeout_ticks < 0 means
|
|
// wait forever. Returns number of fds with nonzero revents, 0 on timeout,
|
|
// -EINTR if interrupted.
|
|
static int
|
|
do_poll(struct pollfd *fds, int nfds, int timeout_ticks)
|
|
{
|
|
for (;;) {
|
|
int nready = 0;
|
|
for (int i = 0; i < nfds; i++) {
|
|
fds[i].revents = 0;
|
|
if (fds[i].fd < 0)
|
|
continue; // negative fd: ignore, per poll(2)
|
|
if (fds[i].fd >= NOFILE || !myproc()->ofile[fds[i].fd]) {
|
|
fds[i].revents = POLLNVAL;
|
|
nready++;
|
|
continue;
|
|
}
|
|
int rev = file_poll(myproc()->ofile[fds[i].fd], fds[i].events);
|
|
if (rev) {
|
|
fds[i].revents = (short)rev;
|
|
nready++;
|
|
}
|
|
}
|
|
if (nready > 0 || timeout_ticks == 0)
|
|
return nready;
|
|
if (poll_block_one_tick() < 0)
|
|
return -EINTR;
|
|
if (timeout_ticks > 0)
|
|
timeout_ticks--;
|
|
}
|
|
}
|
|
|
|
// poll(fds, nfds, timeout_ms)
|
|
addr_t
|
|
sys_poll(void)
|
|
{
|
|
addr_t ufds;
|
|
int nfds, timeout_ms;
|
|
struct pollfd fds[POLL_MAX_FDS];
|
|
|
|
argaddr(0, &ufds);
|
|
argint(1, &nfds);
|
|
argint(2, &timeout_ms);
|
|
|
|
if (nfds < 0 || nfds > POLL_MAX_FDS)
|
|
return -EINVAL;
|
|
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
|
|
return -EFAULT;
|
|
|
|
// Rounded up: a caller asking for any wait at all must not get zero ticks,
|
|
// which poll() below treats as "return immediately".
|
|
int timeout_ticks = timeout_ms < 0 ? -1
|
|
: (timeout_ms + MSEC_PER_TICK - 1) / MSEC_PER_TICK;
|
|
int r = do_poll(fds, nfds, timeout_ticks);
|
|
|
|
if (r >= 0 && nfds > 0 &&
|
|
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
|
|
return -EFAULT;
|
|
return r;
|
|
}
|
|
|
|
// ppoll(fds, nfds, timespec*, sigmask) — sigmask is ignored (poll already
|
|
// breaks out for any deliverable signal); NULL timespec = wait forever.
|
|
addr_t
|
|
sys_ppoll(void)
|
|
{
|
|
addr_t ufds, utsp;
|
|
int nfds;
|
|
struct pollfd fds[POLL_MAX_FDS];
|
|
|
|
argaddr(0, &ufds);
|
|
argint(1, &nfds);
|
|
argaddr(2, &utsp);
|
|
|
|
if (nfds < 0 || nfds > POLL_MAX_FDS)
|
|
return -EINVAL;
|
|
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
|
|
return -EFAULT;
|
|
|
|
int timeout_ticks = -1;
|
|
if (utsp) {
|
|
int64 ts[2]; // {tv_sec, tv_nsec}
|
|
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
|
|
return -EFAULT;
|
|
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
|
|
}
|
|
|
|
int r = do_poll(fds, nfds, timeout_ticks);
|
|
|
|
if (r >= 0 && nfds > 0 &&
|
|
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
|
|
return -EFAULT;
|
|
return r;
|
|
}
|
|
|
|
// select(nfds, readfds, writefds, exceptfds, timeval*). fd_set is a bit
|
|
// array; with NOFILE=128 the interesting part fits in two 64-bit words.
|
|
#define FDSET_WORDS (NOFILE / 64)
|
|
|
|
static int
|
|
fdset_fetch(addr_t uaddr, uint64 *set)
|
|
{
|
|
if (!uaddr) {
|
|
set[0] = set[1] = 0;
|
|
return 0;
|
|
}
|
|
return copy_from_user(set, uaddr, FDSET_WORDS * sizeof(uint64));
|
|
}
|
|
|
|
static int
|
|
fdset_isset(uint64 *set, int fd) { return (set[fd / 64] >> (fd % 64)) & 1; }
|
|
static void
|
|
fdset_set(uint64 *set, int fd) { set[fd / 64] |= 1ULL << (fd % 64); }
|
|
|
|
addr_t
|
|
sys_select(void)
|
|
{
|
|
int nfds;
|
|
addr_t urd, uwr, uex, utv;
|
|
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
|
|
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
|
|
|
|
argint(0, &nfds);
|
|
argaddr(1, &urd);
|
|
argaddr(2, &uwr);
|
|
argaddr(3, &uex);
|
|
argaddr(4, &utv);
|
|
|
|
if (nfds < 0 || nfds > NOFILE)
|
|
return -EINVAL;
|
|
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
|
|
fdset_fetch(uex, ex) < 0)
|
|
return -EFAULT;
|
|
|
|
int timeout_ticks = -1;
|
|
if (utv) {
|
|
int64 tv[2]; // {tv_sec, tv_usec}
|
|
if (copy_from_user(tv, utv, sizeof(tv)) < 0)
|
|
return -EFAULT;
|
|
timeout_ticks = (int)(tv[0] * HZ + tv[1] / USEC_PER_TICK);
|
|
}
|
|
|
|
for (;;) {
|
|
int nready = 0;
|
|
for (int fd = 0; fd < nfds; fd++) {
|
|
int want_r = fdset_isset(rd, fd);
|
|
int want_w = fdset_isset(wr, fd);
|
|
if (!want_r && !want_w)
|
|
continue;
|
|
struct file *f = myproc()->ofile[fd];
|
|
if (!f)
|
|
return -EBADF;
|
|
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
|
|
// Hangup/error count as readable: a read() will not block
|
|
// (it returns EOF/error immediately) — same rule Linux uses.
|
|
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
|
|
fdset_set(ord, fd);
|
|
nready++;
|
|
}
|
|
if (want_w && (rev & (POLLOUT | POLLERR))) {
|
|
fdset_set(owr, fd);
|
|
nready++;
|
|
}
|
|
}
|
|
if (nready > 0 || timeout_ticks == 0) {
|
|
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
|
|
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
|
|
if (uex) { // no exceptional conditions exist here; report none
|
|
uint64 zero[FDSET_WORDS] = {0};
|
|
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
|
|
}
|
|
return nready;
|
|
}
|
|
if (poll_block_one_tick() < 0)
|
|
return -EINTR;
|
|
if (timeout_ticks > 0)
|
|
timeout_ticks--;
|
|
}
|
|
}
|
|
|
|
// pselect6: select with a timespec and (ignored) sigmask.
|
|
addr_t
|
|
sys_pselect6(void)
|
|
{
|
|
// Delegate: args 0-3 line up with select's; arg 4 is a timespec instead
|
|
// of a timeval and arg 5 the sigmask. Reuse sys_select's field reads but
|
|
// treat the timeout as nanoseconds.
|
|
int nfds;
|
|
addr_t urd, uwr, uex, utsp;
|
|
argint(0, &nfds);
|
|
argaddr(1, &urd);
|
|
argaddr(2, &uwr);
|
|
argaddr(3, &uex);
|
|
argaddr(4, &utsp);
|
|
|
|
// Convert the timespec (if any) into a timeval in place on the user
|
|
// stack? No — simplest correct route: temporarily run the same logic.
|
|
// sys_select re-reads the same registers, so just fix up the timeout
|
|
// interpretation here by patching r8 semantics is not possible; instead
|
|
// duplicate the small tail:
|
|
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
|
|
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
|
|
if (nfds < 0 || nfds > NOFILE)
|
|
return -EINVAL;
|
|
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
|
|
fdset_fetch(uex, ex) < 0)
|
|
return -EFAULT;
|
|
|
|
int timeout_ticks = -1;
|
|
if (utsp) {
|
|
int64 ts[2];
|
|
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
|
|
return -EFAULT;
|
|
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
|
|
}
|
|
|
|
for (;;) {
|
|
int nready = 0;
|
|
for (int fd = 0; fd < nfds; fd++) {
|
|
int want_r = fdset_isset(rd, fd);
|
|
int want_w = fdset_isset(wr, fd);
|
|
if (!want_r && !want_w)
|
|
continue;
|
|
struct file *f = myproc()->ofile[fd];
|
|
if (!f)
|
|
return -EBADF;
|
|
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
|
|
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
|
|
fdset_set(ord, fd);
|
|
nready++;
|
|
}
|
|
if (want_w && (rev & (POLLOUT | POLLERR))) {
|
|
fdset_set(owr, fd);
|
|
nready++;
|
|
}
|
|
}
|
|
if (nready > 0 || timeout_ticks == 0) {
|
|
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
|
|
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
|
|
if (uex) {
|
|
uint64 zero[FDSET_WORDS] = {0};
|
|
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
|
|
}
|
|
return nready;
|
|
}
|
|
if (poll_block_one_tick() < 0)
|
|
return -EINTR;
|
|
if (timeout_ticks > 0)
|
|
timeout_ticks--;
|
|
}
|
|
}
|