feat: add pidfd and pipe
This commit is contained in:
1 parent
9e9c28fe80
commit
25f50f156a
2 files changed
+201
No files matched your search
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef FROSTBYTE_PIDFD_H
|
||||||
|
#define FROSTBYTE_PIDFD_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#define maxPidfds 32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Real Linux precedent: pidfd_open(2) (Linux 5.3+) exists for exactly the
|
||||||
|
* ABA-style race sched.c's own allocSlot doc comment describes and
|
||||||
|
* deliberately leaves unfixed for plain pid arguments -- killtest.c's own
|
||||||
|
* comment already admits it ("killing it again must fail cleanly, not
|
||||||
|
* resurrect a slot that has since been reused") without ever guarding
|
||||||
|
* against it. A raw numeric pid can always be reassigned to a completely
|
||||||
|
* unrelated process once its original holder is reaped, so a pid cached
|
||||||
|
* from an earlier spawn/lookup is fundamentally unsafe to act on later
|
||||||
|
* with no way to tell "still the same process" apart from "coincidentally
|
||||||
|
* the same number now". Real Linux's own fix was not to make pid itself
|
||||||
|
* opaque -- every existing kill(2)/waitpid(2)/setpgid(2) caller still
|
||||||
|
* takes a raw pid, completely unchanged, forever -- but to add a SEPARATE,
|
||||||
|
* opt-in handle type that pins one specific incarnation of a slot instead.
|
||||||
|
* This file mirrors that shape exactly: pidfdOpen captures the target
|
||||||
|
* thread's CURRENT sched.c Thread.generation (the same "distinguish this
|
||||||
|
* specific occupant from any future occupant of the same numeric slot"
|
||||||
|
* idea pipe.c's own pipeIncarnation already uses for pipe handles), and
|
||||||
|
* every later pidfd operation re-checks it before ever touching the
|
||||||
|
* target -- if the original thread has since been reaped (its generation
|
||||||
|
* reset to 0 by threadReap/threadDiscard's memset) or the slot has been
|
||||||
|
* handed to a brand new, unrelated thread (a different, fresh nonzero
|
||||||
|
* generation), the pidfd cleanly reports "gone" (threadInvalidId) instead
|
||||||
|
* of silently operating on whichever live thread now happens to occupy
|
||||||
|
* that slot number. sched.c's raw pid-based threadKill/threadJoin/
|
||||||
|
* sigRaiseInfo/threadSetPgid/threadSetPriority are deliberately untouched
|
||||||
|
* by this file -- see allocSlot's own doc comment (sched.c) for why that
|
||||||
|
* remains a separate, much larger, deferred topic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Opens a new pidfd handle on thread `targetId`, pinning its CURRENT
|
||||||
|
generation (sched.h). Returns a small non-negative handle for
|
||||||
|
pidfdTarget/pidfdClose below, or -1 for id 0 (idle, never a valid
|
||||||
|
target), an out-of-range id, an already-unused slot, or a full pidfd
|
||||||
|
table (maxPidfds handles system-wide, mirroring pipe.c's own fixed-size
|
||||||
|
maxPipes table). Does not itself keep the target alive or pinned in any
|
||||||
|
way -- it can still exit, be killed, and be reaped by anyone (this
|
||||||
|
kernel's pids are never ownership-scoped, see sched.h's parentId doc
|
||||||
|
comment) while a pidfd on it is open; that is exactly the case
|
||||||
|
pidfdTarget below exists to detect. */
|
||||||
|
int pidfdOpen(uint32 targetId);
|
||||||
|
|
||||||
|
/* Returns the live target thread id this handle still safely refers to,
|
||||||
|
or threadInvalidId if the handle itself is invalid/closed OR the
|
||||||
|
original target has been reaped or recycled since pidfdOpen -- the one
|
||||||
|
check every pidfd-based operation needs, folded into a single call so
|
||||||
|
no caller can accidentally act on a stale target by forgetting a
|
||||||
|
separate validity check. A target that has exited but not yet been
|
||||||
|
reaped (threadDead) still returns its real id here -- exactly like a
|
||||||
|
raw pid, waiting on it should still be able to observe the real exit
|
||||||
|
status once, via whatever reaps it. */
|
||||||
|
uint32 pidfdTarget(int handle);
|
||||||
|
|
||||||
|
/* Frees a pidfd handle. No-op on an already-invalid handle. Never affects
|
||||||
|
the target thread itself in any way -- a pidfd, like a real Linux one,
|
||||||
|
is purely an observer/signaler, not a reference that keeps anything
|
||||||
|
alive. */
|
||||||
|
void pidfdClose(int handle);
|
||||||
|
|
||||||
|
#endif
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
#ifndef FROSTBYTE_PIPE_H
|
||||||
|
#define FROSTBYTE_PIPE_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#define maxPipes 32
|
||||||
|
#define pipeBufferSize 256
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Anonymous pipes: a fixed table of ring-buffer-backed byte queues, each
|
||||||
|
* shared between one read end and one write end. The raw pipeRead/pipeWrite
|
||||||
|
* primitives are always non-blocking, matching real O_NONBLOCK semantics:
|
||||||
|
*
|
||||||
|
* pipeRead:
|
||||||
|
* >0 bytes actually read
|
||||||
|
* 0 end-of-file: buffer drained AND the write end is closed
|
||||||
|
* -1 no data right now but the write end is still open (retry later)
|
||||||
|
*
|
||||||
|
* pipeWrite:
|
||||||
|
* >0 bytes actually written
|
||||||
|
* 0 length was 0
|
||||||
|
* -1 broken pipe (no read end left), or no free space right now
|
||||||
|
* (retry later)
|
||||||
|
*
|
||||||
|
* pipeReadBlocking/pipeWriteBlocking are built on top: they park the calling
|
||||||
|
* kernel thread on the pipe's wait channel (sched.h) instead of returning
|
||||||
|
* "try again", and are woken as soon as the other end changes the condition
|
||||||
|
* (data arrives, space frees up, or either end closes). They only make sense
|
||||||
|
* from kernel-thread context (see the producer/consumer demo in kmain.c) -
|
||||||
|
* a single process's own fd table cannot hand one pipe end to a second,
|
||||||
|
* independent execution context by itself.
|
||||||
|
*
|
||||||
|
* pipeCreateDetached + pipeAddReader/pipeAddWriter (below) close that gap
|
||||||
|
* without needing fork(): a handle from pipeCreateDetached starts with
|
||||||
|
* NEITHER end attached (unlike pipeCreate, which assumes its caller
|
||||||
|
* immediately owns both), so it can be handed to two entirely separate,
|
||||||
|
* independently spawned processes -- typically as a decimal string in argv
|
||||||
|
* (see user/fifomaster.c) -- each of which attaches its own end via
|
||||||
|
* vfsPipeAttach (vfs.h). syscall.c's sysRead/sysWrite give a real blocking
|
||||||
|
* read()/write() to whichever of those two ends is waiting, using the same
|
||||||
|
* restart-the-syscall trick sysWait already uses for threadJoin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int pipeCreate(void);
|
||||||
|
|
||||||
|
/* Like pipeCreate, but neither end starts "attached": readers and writers
|
||||||
|
both begin at 0, and pipeRead/pipeWrite treat "nobody has attached that
|
||||||
|
end YET" as "try again" rather than the true EOF/broken-pipe result they
|
||||||
|
report once an end that WAS attached goes away (see everHadReader/
|
||||||
|
everHadWriter, pipe.c) -- otherwise a writer that attaches before any
|
||||||
|
reader does would see an immediately, permanently "broken" pipe, and a
|
||||||
|
reader that attaches first would see immediate spurious EOF. */
|
||||||
|
int pipeCreateDetached(void);
|
||||||
|
|
||||||
|
/* Registers a new, independent reader/writer on an existing (typically
|
||||||
|
pipeCreateDetached'd) handle -- vfsPipeAttach (vfs.h) calls these when a
|
||||||
|
process attaches its end. No-op on an invalid handle. */
|
||||||
|
void pipeAddReader(int handle);
|
||||||
|
void pipeAddWriter(int handle);
|
||||||
|
|
||||||
|
/* True if a write to this pipe can never succeed again: either the handle
|
||||||
|
is not valid, or a reader was attached at some point and every attached
|
||||||
|
reader has since gone away. False for a pipeCreateDetached handle that
|
||||||
|
simply has not had its reader attach yet -- that is "try again", not
|
||||||
|
broken. Lets syscall.c's sysWrite tell "keep retrying" apart from "give
|
||||||
|
up and return -1" without reaching into pipe.c's own Pipe struct. */
|
||||||
|
bool pipeWriteBroken(int handle);
|
||||||
|
|
||||||
|
/* The address schedBlockCurrent/schedWakeChannel use for this pipe's wait
|
||||||
|
queue -- the same one pipeReadBlocking/pipeWriteBlocking already block
|
||||||
|
on. Exposed so a syscall handler (syscall.c) can park on it via the
|
||||||
|
restart-the-syscall pattern instead of calling halt() directly (which,
|
||||||
|
run from inside an interrupt-gate syscall handler with IF=0, would freeze
|
||||||
|
the whole machine rather than just the calling thread -- see sysWait's
|
||||||
|
own doc comment in syscall.c for the same hazard). Returns nullptr for
|
||||||
|
an invalid handle. */
|
||||||
|
const void* pipeChannel(int handle);
|
||||||
|
|
||||||
|
bool pipeValid(int handle);
|
||||||
|
|
||||||
|
/* Opaque token identifying THIS specific pipe living at `handle`'s slot,
|
||||||
|
distinct from any other pipe that has occupied (or will ever occupy) the
|
||||||
|
same numeric handle before or after -- this fixed-size table reuses a
|
||||||
|
slot number the moment both its ends are gone (pipeCloseRead/
|
||||||
|
pipeCloseWrite's pipeReleaseIfUnused), so pipeValid(handle) alone cannot
|
||||||
|
tell a caller that cached a handle across such a release "the same pipe I
|
||||||
|
bound to earlier" apart from "a different, unrelated pipe that happens to
|
||||||
|
have been assigned the same number since" (see vfs.c's SFS-backed named
|
||||||
|
fifo bindings, sfsFifoHandleFor, the motivating caller). Always non-zero
|
||||||
|
for any handle pipeCreate/pipeCreateDetached has ever handed out; 0 for
|
||||||
|
an invalid handle, which can never equal a real incarnation, so a naive
|
||||||
|
"compare and treat 0 as impossible" check is always safe. */
|
||||||
|
uint32 pipeIncarnation(int handle);
|
||||||
|
|
||||||
|
int64 pipeRead(int handle, void* buffer, uint64 length);
|
||||||
|
int64 pipeWrite(int handle, const void* buffer, uint64 length);
|
||||||
|
|
||||||
|
/* Non-consuming readiness peeks for poll.h's vfsPollReady (vfs.c): report
|
||||||
|
exactly the same two conditions pipeRead/pipeWrite's own success paths
|
||||||
|
check (data queued or true EOF; free space in the ring), without moving
|
||||||
|
a single byte or touching readers/writers bookkeeping. False for an
|
||||||
|
invalid handle either way. */
|
||||||
|
bool pipeHasData(int handle);
|
||||||
|
bool pipeHasSpace(int handle);
|
||||||
|
|
||||||
|
/* Blocks (parks the calling thread and halts) until data is available or
|
||||||
|
true end-of-file is reached. Returns exactly what pipeRead would once the
|
||||||
|
wait is over; -1 only for an invalid handle (never blocks in that case). */
|
||||||
|
int64 pipeReadBlocking(int handle, void* buffer, uint64 length);
|
||||||
|
|
||||||
|
/* Blocks until free space is available. Returns exactly what pipeWrite would
|
||||||
|
once the wait is over; returns -1 immediately without blocking for an
|
||||||
|
invalid handle or a broken pipe (no reader left), since neither condition
|
||||||
|
can ever resolve by waiting. */
|
||||||
|
int64 pipeWriteBlocking(int handle, const void* buffer, uint64 length);
|
||||||
|
|
||||||
|
void pipeCloseRead(int handle);
|
||||||
|
void pipeCloseWrite(int handle);
|
||||||
|
|
||||||
|
/* Frees a pipeCreateDetached handle that nobody has ever attached a reader
|
||||||
|
or writer to (readers==0 and writers==0) -- the one case pipeCloseRead/
|
||||||
|
pipeCloseWrite's own release-when-both-zero check (pipeReleaseIfUnused)
|
||||||
|
can never reach on its own, since neither is ever called for an end that
|
||||||
|
was never attached in the first place. Needed by a named pipe (vfs.h's
|
||||||
|
vfsMkfifo/vfsUnlink) created but never opened by anyone: without this, its
|
||||||
|
underlying handle would sit "used" in the fixed-size pipe table forever
|
||||||
|
once the vfs node naming it is removed. No-op (and reports false) if the
|
||||||
|
handle is invalid or still has a reader or writer attached -- unlinking a
|
||||||
|
named pipe that IS currently open must leave it alone, matching real
|
||||||
|
unlink-while-open semantics (existing fds keep working off the raw handle
|
||||||
|
number, not the vfs node). */
|
||||||
|
bool pipeDestroyIfUnattached(int handle);
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in new issue
Block a user