Jos pipe实现解析

    xiaoxiao2025-11-16  11

    在linux中,管道用到的非常频繁,比如说计算某个文件中每个单词出现的次数,sort A.file | uniq -c (A.file 中每一行都是一个单词)。即将 sort的输出导入到uniq进程的输入中。那么问题来了,sort的输出流是如何重定向到uniq的输入的呢?

    在JOS课程中,管道是如何实现的呢?在pipe()中做了什么?

    int pipe(int pfd[2]) { int r; struct Fd *fd0, *fd1; void *va; // allocate the file descriptor table entries if ((r = fd_alloc(&fd0)) < 0 || (r = sys_page_alloc(0, fd0, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0) goto err; if ((r = fd_alloc(&fd1)) < 0 || (r = sys_page_alloc(0, fd1, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0) goto err1; // allocate the pipe structure as first data page in both va = fd2data(fd0); if ((r = sys_page_alloc(0, va, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0) goto err2; if ((r = sys_page_map(0, va, 0, fd2data(fd1), PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0) { cprintf("pipe map file data failed.\n"); goto err3; } // set up fd structures fd0->fd_dev_id = devpipe.dev_id; fd0->fd_omode = O_RDONLY; fd1->fd_dev_id = devpipe.dev_id; fd1->fd_omode = O_WRONLY; if (debug) cprintf("[x] pipecreate x\n", thisenv->env_id, uvpt[PGNUM(va)]); pfd[0] = fd2num(fd0); pfd[1] = fd2num(fd1); return 0; err3: sys_page_unmap(0, va); err2: sys_page_unmap(0, fd1); err1: sys_page_unmap(0, fd0); err: return r; }

    首先,先申请文件描述符fd0, fd1,同时将两者的file data部分map到相同的page中,这样,对该page的读写在两个进程和子进程中是一致的,类似于共享内存。

    为什么在子进程也是一致的呢? 因为在申请page时使用了 PTE_SHARE 属性,在fork/spawn时,会直接将该page映射到子进程的addr中,因此,在子进程/父进程中,对addr的操作,两个进程都可以感知。

    另外一个重要的函数是dup(oldfd, newfd),它会将oldfd所对应的FD部分和FDData部分映射到newfd的相应部分。

    // Make file descriptor 'newfdnum' a duplicate of file descriptor 'oldfdnum'. // For instance, writing onto either file descriptor will affect the // file and the file offset of the other. // Closes any previously open file descriptor at 'newfdnum'. // This is implemented using virtual memory tricks (of course!). int dup(int oldfdnum, int newfdnum) { int r; char *ova, *nva; pte_t pte; struct Fd *oldfd, *newfd; if ((r = fd_lookup(oldfdnum, &oldfd)) < 0) return r; close(newfdnum); newfd = INDEX2FD(newfdnum); ova = fd2data(oldfd); nva = fd2data(newfd); if ((uvpd[PDX(ova)] & PTE_P) && (uvpt[PGNUM(ova)] & PTE_P)){ if ((r = sys_page_map(0, ova, 0, nva, uvpt[PGNUM(ova)] & PTE_SYSCALL)) < 0) goto err; } else { cprintf("dup file data not exist. oldfd %u to newfd %u\n", oldfdnum, newfdnum); } if ((r = sys_page_map(0, oldfd, 0, newfd, uvpt[PGNUM(oldfd)] & PTE_SYSCALL)) < 0) goto err; return newfdnum; err: sys_page_unmap(0, newfd); sys_page_unmap(0, nva); return r;

    最后,来看下最终的代码

    父进程申请了pipe,fd[0], fd[1] 代表标准输入和输出, fd[2]和fd[3]是pipe出来的文件描述符将相关页面进行dup和close操作后,各进程的虚拟地址与page的关系如下这样,对fd_data页面的读写就可以从父进程到子进程共享了 if ((r = pipe(p)) < 0) { cprintf("pipe: %e", r); exit(); } if (debug) cprintf("PIPE: %d %d\n", p[0], p[1]); if ((r = fork()) < 0) { cprintf("fork: %e", r); exit(); } cprintf("[x] sh pipe fork res=[x]\n", sys_getenvid(), r); if (r == 0) { if (p[0] != 0) { dup(p[0], 0); close(p[0]); } close(p[1]); goto again; } else { pipe_child = r; if (p[1] != 1) { dup(p[1], 1); close(p[1]); } close(p[0]); goto runit; } panic("| not implemented"); break;

    综上,pipe其实是将两个文件描述符对应的data空间map到 标准输入和标准输出中,同时共享到子进程中。

    相关资源:python入门教程(PDF版)
    最新回复(0)