#include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/mmu.h" #include "core/proc.h" #include "fs/fs.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "fs/file.h" #include "core/kmem.h" #include "uapi/posix.h" #include "uapi/errno.h" // 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 *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 }; // 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) { struct pipe *p; p = 0; *f0 = *f1 = 0; if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0) goto bad; if(!pipe_cache && !(pipe_cache = kmem_cache_create("pipe", sizeof(*p)))) 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; p->nread = 0; initlock(&p->lock, "pipe"); (*f0)->type = FD_PIPE; (*f0)->readable = 1; (*f0)->writable = 0; (*f0)->pipe = p; (*f1)->type = FD_PIPE; (*f1)->readable = 0; (*f1)->writable = 1; (*f1)->pipe = p; (*f1)->ino = (*f0)->ino; // one pipe, as /proc//fd names it return 0; //PAGEBREAK: 20 bad: if(p) kmem_cache_free(pipe_cache, p); if(*f0) fileclose(*f0); if(*f1) fileclose(*f1); return -1; } void pipeclose(struct pipe *p, int writable) { acquire(&p->lock); if(writable){ p->writeopen = 0; wakeup(&p->nread); } else { p->readopen = 0; wakeup(&p->nwrite); } if(p->readopen == 0 && p->writeopen == 0){ release(&p->lock); pipe_free(p); } else release(&p->lock); poll_kick(); // a poller sees the hangup now, not at the tick } //PAGEBREAK: 40 int pipewrite(struct pipe *p, char *addr, int n) { int i; acquire(&p->lock); // Writing to a pipe nobody is reading is SIGPIPE and EPIPE, in that order. // // This returned a bare -1 with no signal and no errno, so `yes | head -2` // came back with whatever errno happened to be lying around -- "Invalid // argument" -- instead of quietly ending the way every shell pipeline is // built to. Checked up front as well as in the blocking loop below: the // common case is that head has already exited by the time the next write // arrives, and the pipe is nowhere near full. if(p->readopen == 0){ release(&p->lock); sigsend(myproc()->pid, SIGPIPE); return -EPIPE; } // 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); return i > 0 ? i : -EPIPE; // a short write is still a write } if(myproc()->killed){ release(&p->lock); 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 } // 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; } int piperead(struct pipe *p, char *addr, int n) { int i; acquire(&p->lock); while(p->nread == p->nwrite && p->writeopen){ //DOC: pipe-empty if(myproc()->killed){ release(&p->lock); return -1; } if(sig_attention(myproc())){ // stopped, or a handler wants to run release(&p->lock); int e = sig_blocking_check(); if(e) return e; acquire(&p->lock); continue; } sleep(&p->nread, &p->lock); //DOC: piperead-sleep } 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; } // Readiness for poll(2)/select(2). Reports state, doesn't block. int pipe_poll(struct pipe *p, int readable, int writable) { int rev = 0; acquire(&p->lock); if(readable){ if(p->nread != p->nwrite) rev |= POLLIN; if(p->writeopen == 0) rev |= POLLHUP; } if(writable){ if(p->readopen == 0) rev |= POLLERR; 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; } // Bytes waiting to be read, for FIONREAD. int pipe_avail(struct pipe *p) { acquire(&p->lock); int n = (int)(p->nwrite - p->nread); release(&p->lock); return n; }