From: kaguya3311 <[email protected]>
To: "[email protected]" <[email protected]>
Subject: [ANNOUNCE] mises v0.40.0: dup, dup2 and pipe
Date: Thu, 23 Jul 2026 17:35:23 +0000 [thread overview]
Message-ID: <tixvy0kLB_Ah6NvthRVZjs4gf6CNqCH1Q17JtUg3NCd2eTchjdiZCHgxZcvV-6zE1bMfiABQPwf36fAHZdoAlmbmidh_vRs7XCp69F6o3cA=@protonmail.com> (raw)
[-- Attachment #1.1.1: Type: text/plain, Size: 4192 bytes --]
Mises v0.40.0 is tagged. It adds the three descriptor syscalls thatredirection is made of, which finishes the kernel side of `>` and `|`.
Since v0.39 an fd has been a counted reference to an open file
description, and a child inherits its parent's whole table across
spawn. What was still missing was any way for the parent to change
what a number *means* before it spawns, and any description that is a
channel between two processes rather than a file.
0x50 dup(fd) -> newfd second name, lowest free fd
0x51 dup2(oldfd, newfd) -> newfd second name at a chosen fd
0x52 pipe(fds_ptr, flags) -> 0 [read_fd, write_fd], two LE u32
These open a new 0x50..=0x5F "descriptor" band rather than spending the
file band's last three numbers. The narrow reason is that 0x10..=0x1F
had exactly three left and link(2) still wants one. The real reason is
that none of these takes a path: they operate on the fd table and on
open file descriptions, which is a different kind of object -- the same
argument the mount band and the system-control band each made. The file
band was going to overflow regardless, since lseek, fstat, fcntl,
ftruncate, ioctl and poll are all fd-shaped and path-free; those are
reserved in the new band. The ABI is append-only, so this was settled
before any code was written.
dup is literally the existing Tables::link. dup2 is the same with a
caller-chosen fd, plus a release of what it displaced -- a real close,
flush included. Two orderings in it are load-bearing: the new reference
is taken before the displaced one is dropped, and old == new returns
early having closed nothing but still validating. Get either wrong and
dup2 destroys the fd it was asked to preserve.
A pipe lives in a new kern::pipe, which is a second leaf lock that the
fd layer never nests under: retiring a pipe end wakes the scheduler, so
every close path extracts the dying description under the fd lock, drop
the guard, and only then retires it. A pipe's readers/writers count
DESCRIPTIONS, not fds, which is the whole of end-of-file -- a dup2 or a
spawn adds an fd to an existing description and moves neither count.
Buffered bytes drain before EOF is reported. A write with no reader is
EPIPE; there is no SIGPIPE to send.
Userland can now deadlock the machine, which is new. A parent that waits
for a child blocked on a pipe nobody drains is the classic pipeline
mistake and Mises now has it, for the same reason POSIX does.
Tested by user/pipetest and tests/pipe.rs. The probe re-launches itself
with a role operand so writer and reader are one binary; the children
write to fd 1 and mention no pipe, and the parent re-emits what it read
back out *framed*, so "arrived through the pipe" and "went straight to
the terminal" are two different strings -- which lets the test require
the raw form to be absent. It moves 12 KiB through a 4 KiB pipe with
both ends blocking.
Six bug-injections were run against it. Three were worth writing down:
- A dup2(fd, fd) check on stderr proves nothing. fds 0/1/2 share one
description, so a close-then-link dup2 drops it from three
references to two and the fd keeps working by accident. Confirmed
by running the injection with both forms present.
- A leaked pipe is invisible to every behavioural assertion. One
whose end counts are right but which is never freed reads, writes,
blocks and reports EOF perfectly. Only a count() hook sees the page
it pins each time.
- The drive-loop bound had to be sized from the measured healthy cost
(4 spins). At the neighbouring tests' 50,000,000 the deadlock
injection became a 60-second QEMU timeout with no output; at
200,000 it fails a named assertion in under a second.
Rationale, alternatives and invariants: ADR-0051.
`>` and `|` are now entirely user/sh's work, with no further kernel
change. There is no O_CLOEXEC yet, so a pipeline must spawn its writer
first and drain before it waits; both are documented.
Suite: 357 [ok] across 58 test binaries (60 Running lines), 0 failed,
0 warnings.
kaguya
[-- Attachment #1.1.2.1: Type: text/html, Size: 6501 bytes --]
[-- Attachment #1.2: publickey - [email protected] - 0x09BF9D29.asc --]
[-- Type: application/pgp-keys, Size: 856 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 343 bytes --]
reply other threads:[~2026-07-23 17:35 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='tixvy0kLB_Ah6NvthRVZjs4gf6CNqCH1Q17JtUg3NCd2eTchjdiZCHgxZcvV-6zE1bMfiABQPwf36fAHZdoAlmbmidh_vRs7XCp69F6o3cA=@protonmail.com' \
[email protected] \
[email protected] \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox