Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
176 lines
4.2 KiB
C
176 lines
4.2 KiB
C
#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"
|
|
|
|
#define PIPESIZE 512
|
|
|
|
struct pipe {
|
|
struct spinlock lock;
|
|
char data[PIPESIZE];
|
|
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.
|
|
static struct kmem_cache *pipe_cache;
|
|
|
|
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;
|
|
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;
|
|
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);
|
|
kmem_cache_free(pipe_cache, p);
|
|
} else
|
|
release(&p->lock);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
for(i = 0; i < n; i++){
|
|
while(p->nwrite == p->nread + PIPESIZE){ //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);
|
|
sleep(&p->nwrite, &p->lock); //DOC: pipewrite-sleep
|
|
}
|
|
p->data[p->nwrite++ % PIPESIZE] = addr[i];
|
|
}
|
|
wakeup(&p->nread); //DOC: pipewrite-wakeup1
|
|
release(&p->lock);
|
|
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;
|
|
}
|
|
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];
|
|
}
|
|
wakeup(&p->nwrite); //DOC: piperead-wakeup
|
|
release(&p->lock);
|
|
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(p->nwrite < p->nread + PIPESIZE)
|
|
rev |= POLLOUT;
|
|
}
|
|
release(&p->lock);
|
|
return rev;
|
|
}
|