Files
frostbyte/user/polltest.c
T

153 lines
5.4 KiB
C

#include "ulib.h"
/*
* Boot-time driver for real poll(2) (poll.h's PollFd, syscall.c's sysPoll,
* sched.c's schedPollChannel + schedule()'s own pollDeadline expiry scan):
* waits for readiness across MORE THAN ONE fd at once, something no
* earlier demo in this kernel does -- every previous blocking read/write
* always committed ahead of time to exactly one fd (vfsBlockingChannel,
* vfs.h). Three phases:
*
* phase 1: a non-blocking poll (timeoutMs 0) on a shared pipe's read end
* nobody has written to yet -- must report zero ready fds WITHOUT ever
* parking the calling thread, proven simply by returning at all (a real
* accidental-park bug here would hang the whole boot to ci-test.sh's 25s
* QEMU timeout instead of merely failing a check, the same
* fault-injection-visible shape every other demo in this kernel already
* has).
*
* phase 2: a REAL, bounded-timeout poll on that same still-empty fd --
* proves schedule()'s pollDeadline expiry scan actually forces a wake
* when no fd ever becomes ready, not just phase 1's immediate-return
* fast path.
*
* phase 3: an INFINITE-timeout (-1) poll across TWO fds at once (the
* pipe pollwriter.c will write to, and a SECOND, entirely unrelated pipe
* nobody ever writes -- deliberately not fd 0/console: every open
* reader, including this process's own fd 0, gets a copy of every
* keystroke keyboardHandleScancode ever broadcasts, keyboard.h, and this
* same boot image has other demos genuinely typing at the shell
* concurrently, so fd 0 could not be trusted to stay silent for the
* whole wait) while a freshly spawned child (pollwriter.c) sleeps
* briefly then writes to the first pipe -- proves the real cross-process
* wake path (pipeWrite -> schedWakeChannel(schedPollChannel()) -> this
* thread's retried sysPoll re-checking every fd from scratch) AND that
* only the written pipe's own PollFd entry gets revents set, never the
* untouched second one's, i.e. the multiplexer is genuinely selective,
* not just "anything happened, assume the first entry".
*/
#define pollTimeoutMs 200
static int streq(const char* a, const char* b)
{
while (*a && *a == *b) {
a++;
b++;
}
return *a == *b;
}
int main(void)
{
print("polltest: start, pid=");
printDec(userGetpid());
print("\n");
long handle = userPipeCreate();
if (handle < 0) {
print("polltest: FAIL pipe create\n");
return 1;
}
long readFd = userPipeAttach(handle, 0);
if (readFd < 0) {
print("polltest: FAIL attach read end\n");
return 1;
}
/* Never written to by anyone -- the second phase-3 fd that must stay
not-ready for the whole wait. A private, unshared userPipe() end
(rather than a second userPipeCreate/userPipeAttach handle) is
enough since nothing else ever touches it. */
int idleFds[2];
if (userPipe(idleFds) != 0) {
print("polltest: FAIL idle pipe create\n");
return 1;
}
/* Spawned immediately, like fifomaster.c spawns its own two children,
rather than after phase 1/2 do their own work below -- every other
boot-time demo's runtime spawn happens right at its own start, and
pollwriter's own delay (pollWriterDelayMs, pollwriter.c) is already
comfortably longer than phase 2's bounded-timeout wait below, so
phase 3 still genuinely blocks waiting for it either way. */
char handleStr[24];
decToString(handle, handleStr);
char* writerArgv[] = { "/bin/pollwriter", handleStr, 0 };
long writerPid = userSpawn("/bin/pollwriter", writerArgv);
if (writerPid < 0) {
print("polltest: FAIL spawn pollwriter\n");
return 1;
}
PollFd single;
single.fd = (int)readFd;
single.events = pollIn;
single.revents = 12345;
long n = userPoll(&single, 1, 0);
if (n != 0 || single.revents != 0) {
print("polltest: FAIL non-blocking poll on empty pipe\n");
return 1;
}
print("polltest: non-blocking-poll-empty-ok\n");
single.revents = 12345;
n = userPoll(&single, 1, pollTimeoutMs);
if (n != 0 || single.revents != 0) {
print("polltest: FAIL bounded-timeout poll returned early or ready\n");
return 1;
}
print("polltest: poll-timeout-ok\n");
PollFd pfds[2];
pfds[0].fd = (int)readFd;
pfds[0].events = pollIn;
pfds[0].revents = 0;
pfds[1].fd = idleFds[0];
pfds[1].events = pollIn;
pfds[1].revents = 0;
print("polltest: blocking on multi-fd poll, waiting for pollwriter\n");
n = userPoll(pfds, 2, -1);
if (n != 1 || !(pfds[0].revents & pollIn) || pfds[1].revents != 0) {
print("polltest: FAIL multi-fd poll woke wrong or with wrong revents\n");
return 1;
}
print("polltest: poll-woken-by-write-ok\n");
char buffer[32];
long got = userRead((int)readFd, buffer, sizeof(buffer) - 1);
if (got <= 0) {
print("polltest: FAIL read after poll-ready\n");
return 1;
}
buffer[got] = 0;
int ok = streq(buffer, "poll payload");
print(ok ? "polltest: payload-ok\n" : "polltest: payload-FAIL\n");
if (!ok)
return 1;
long writerStatus = userWait(writerPid);
if (writerStatus != 0) {
print("polltest: FAIL pollwriter reported failure\n");
return 1;
}
userClose((int)readFd);
userClose(idleFds[0]);
userClose(idleFds[1]);
print("polltest: poll-multiplex-ok\n");
return 0;
}