kernel: HTTPS through wget, half-close, and /proc/<pid>/fd
busybox wget hands TLS to ssl_client over a socketpair and then does shutdown(SHUT_WR). That cut the AF_UNIX link both ways, so ssl_client's reply was a SIGPIPE and every https:// download hung. SHUT_WR is now a half-close: the peer reads EOF and can still answer. TCP sockets get shutdown() at all (tcp_shutdown for the FIN); it used to do nothing. - exec keeps ignored signals and the signal mask, as execve(2) says. - /proc/<pid>/fd: a link per descriptor -- the path it was opened by, socket:[n], pipe:[n], anon_inode:[eventpoll]. /proc/self too. The global inode bit moves above pid << 8; at 0x10000 it was /proc/256. - fstat on a socket or pipe answers S_IFSOCK / S_IFIFO instead of EINVAL. - poll, select and epoll wake as soon as a pipe, socket or input device changes, not on the next tick. - Pipes are 64 KB of lazily allocated pages, copied in blocks, with PIPE_BUF atomicity: 300 MB through `yes | head | wc` in 7 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
906318f2b6
commit
8351e0323b
15 changed files with 434 additions and 71 deletions
|
|
@ -11,24 +11,40 @@
|
|||
#include "uapi/posix.h"
|
||||
#include "uapi/errno.h"
|
||||
|
||||
#define PIPESIZE 512
|
||||
// 64 KB, as on Linux, in pages taken the first time each is written.
|
||||
//
|
||||
// It was 512 bytes in the struct. That is xv6's size, and it made every pipe a
|
||||
// handoff per half kilobyte: busybox's wget does TLS in a helper process that
|
||||
// talks to it over pipes, a TLS record is 16 KB, and an HTTPS download that
|
||||
// should have taken nine seconds was still going after a minute and a half. A
|
||||
// shell pipeline that only ever moves a line still costs one page.
|
||||
#define PIPESIZE 65536
|
||||
#define PIPE_PAGES (PIPESIZE / PGSIZE)
|
||||
#define PIPE_BUF 4096 // writes up to this size are not interleaved
|
||||
|
||||
struct pipe {
|
||||
struct spinlock lock;
|
||||
char data[PIPESIZE];
|
||||
char *page[PIPE_PAGES];
|
||||
uint nread; // number of bytes read
|
||||
uint nwrite; // number of bytes written
|
||||
int readopen; // read fd is still open
|
||||
int writeopen; // write fd is still open
|
||||
};
|
||||
|
||||
// A pipe is about 560 bytes and used to come from kalloc(), which is a whole
|
||||
// 4096-byte page -- so every `|` in a shell command burned three and a half
|
||||
// kilobytes it never touched. This is what kmem_cache exists for, and the
|
||||
// cache is created on first use rather than at boot so a kernel that never
|
||||
// opens a pipe holds no page for one.
|
||||
// The struct is small and comes from a cache; the buffer is pages. The cache
|
||||
// is created on first use so a kernel that never opens a pipe holds no page
|
||||
// for one.
|
||||
static struct kmem_cache *pipe_cache;
|
||||
|
||||
static void
|
||||
pipe_free(struct pipe *p)
|
||||
{
|
||||
for(int i = 0; i < PIPE_PAGES; i++)
|
||||
if(p->page[i])
|
||||
kfree(p->page[i]);
|
||||
kmem_cache_free(pipe_cache, p);
|
||||
}
|
||||
|
||||
int
|
||||
pipealloc(struct file **f0, struct file **f1)
|
||||
{
|
||||
|
|
@ -42,6 +58,7 @@ pipealloc(struct file **f0, struct file **f1)
|
|||
goto bad;
|
||||
if((p = kmem_cache_alloc(pipe_cache)) == 0)
|
||||
goto bad;
|
||||
memset(p->page, 0, sizeof(p->page));
|
||||
p->readopen = 1;
|
||||
p->writeopen = 1;
|
||||
p->nwrite = 0;
|
||||
|
|
@ -55,6 +72,7 @@ pipealloc(struct file **f0, struct file **f1)
|
|||
(*f1)->readable = 0;
|
||||
(*f1)->writable = 1;
|
||||
(*f1)->pipe = p;
|
||||
(*f1)->ino = (*f0)->ino; // one pipe, as /proc/<pid>/fd names it
|
||||
return 0;
|
||||
|
||||
//PAGEBREAK: 20
|
||||
|
|
@ -81,9 +99,10 @@ pipeclose(struct pipe *p, int writable)
|
|||
}
|
||||
if(p->readopen == 0 && p->writeopen == 0){
|
||||
release(&p->lock);
|
||||
kmem_cache_free(pipe_cache, p);
|
||||
pipe_free(p);
|
||||
} else
|
||||
release(&p->lock);
|
||||
poll_kick(); // a poller sees the hangup now, not at the tick
|
||||
}
|
||||
|
||||
//PAGEBREAK: 40
|
||||
|
|
@ -108,8 +127,12 @@ pipewrite(struct pipe *p, char *addr, int n)
|
|||
return -EPIPE;
|
||||
}
|
||||
|
||||
for(i = 0; i < n; i++){
|
||||
while(p->nwrite == p->nread + PIPESIZE){ //DOC: pipewrite-full
|
||||
// A write of PIPE_BUF or less goes in whole or waits for room, so two
|
||||
// writers' records never interleave; a larger one goes in as room appears.
|
||||
uint want_room = (n <= PIPE_BUF) ? (uint)n : 1;
|
||||
i = 0;
|
||||
while(i < n){
|
||||
while(PIPESIZE - (p->nwrite - p->nread) < want_room){ //DOC: pipewrite-full
|
||||
if(p->readopen == 0){
|
||||
release(&p->lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
|
|
@ -120,12 +143,32 @@ pipewrite(struct pipe *p, char *addr, int n)
|
|||
return -1;
|
||||
}
|
||||
wakeup(&p->nread);
|
||||
release(&p->lock);
|
||||
poll_kick();
|
||||
acquire(&p->lock);
|
||||
if(PIPESIZE - (p->nwrite - p->nread) >= want_room)
|
||||
break;
|
||||
sleep(&p->nwrite, &p->lock); //DOC: pipewrite-sleep
|
||||
}
|
||||
p->data[p->nwrite++ % PIPESIZE] = addr[i];
|
||||
// One run: up to the end of this page, of what is left, of the room.
|
||||
uint off = p->nwrite % PIPESIZE;
|
||||
uint run = PGSIZE - off % PGSIZE;
|
||||
uint room = PIPESIZE - (p->nwrite - p->nread);
|
||||
if(run > (uint)(n - i)) run = (uint)(n - i);
|
||||
if(run > room) run = room;
|
||||
char **pg = &p->page[off / PGSIZE];
|
||||
if(*pg == 0 && (*pg = kalloc()) == 0){
|
||||
release(&p->lock);
|
||||
return i > 0 ? i : -ENOMEM;
|
||||
}
|
||||
memmove(*pg + off % PGSIZE, addr + i, run);
|
||||
p->nwrite += run;
|
||||
i += run;
|
||||
want_room = 1;
|
||||
}
|
||||
wakeup(&p->nread); //DOC: pipewrite-wakeup1
|
||||
release(&p->lock);
|
||||
poll_kick();
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
@ -142,13 +185,19 @@ piperead(struct pipe *p, char *addr, int n)
|
|||
}
|
||||
sleep(&p->nread, &p->lock); //DOC: piperead-sleep
|
||||
}
|
||||
for(i = 0; i < n; i++){ //DOC: piperead-copy
|
||||
if(p->nread == p->nwrite)
|
||||
break;
|
||||
addr[i] = p->data[p->nread++ % PIPESIZE];
|
||||
i = 0;
|
||||
while(i < n && p->nread != p->nwrite){ //DOC: piperead-copy
|
||||
uint off = p->nread % PIPESIZE;
|
||||
uint run = PGSIZE - off % PGSIZE;
|
||||
if(run > (uint)(n - i)) run = (uint)(n - i);
|
||||
if(run > p->nwrite - p->nread) run = p->nwrite - p->nread;
|
||||
memmove(addr + i, p->page[off / PGSIZE] + off % PGSIZE, run);
|
||||
p->nread += run;
|
||||
i += run;
|
||||
}
|
||||
wakeup(&p->nwrite); //DOC: piperead-wakeup
|
||||
release(&p->lock);
|
||||
poll_kick(); // a writer polling for room
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -168,8 +217,8 @@ pipe_poll(struct pipe *p, int readable, int writable)
|
|||
if(writable){
|
||||
if(p->readopen == 0)
|
||||
rev |= POLLERR;
|
||||
else if(p->nwrite < p->nread + PIPESIZE)
|
||||
rev |= POLLOUT;
|
||||
else if(PIPESIZE - (p->nwrite - p->nread) >= PIPE_BUF)
|
||||
rev |= POLLOUT; // room for a whole PIPE_BUF write, as on Linux
|
||||
}
|
||||
release(&p->lock);
|
||||
return rev;
|
||||
|
|
|
|||
Loading…
Reference in a new issue