Jos pipe实现解析

简介: unix v6中pipe的实现,首先需要了解页表的相关知识,以及fork的相关思想,这样能够更好的了解pipe是如何实现的。

在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("[%08x] pipecreate %08x\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;

最后,来看下最终的代码

  1. 父进程申请了pipe,fd[0], fd[1] 代表标准输入和输出, fd[2]和fd[3]是pipe出来的文件描述符
    fork后相关进程状态
  2. 将相关页面进行dup和close操作后,各进程的虚拟地址与page的关系如下
    dup操作后相关进程状态
  3. 这样,对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("[%08x] sh pipe fork res=[%08x]\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到 标准输入和标准输出中,同时共享到子进程中。

目录
相关文章
|
4月前
|
Perl
【不明觉厉】Angular的 pure pipe (纯管道) 和 impure pipe (非纯管道) 是啥意思?
【不明觉厉】Angular的 pure pipe (纯管道) 和 impure pipe (非纯管道) 是啥意思?
【不明觉厉】Angular的 pure pipe (纯管道) 和 impure pipe (非纯管道) 是啥意思?
|
6月前
56 # 实现 pipe 方法进行拷贝
56 # 实现 pipe 方法进行拷贝
21 0
|
网络协议 PHP
php stream_set_blocking设置非阻塞模式,php stream_set_blocking影响函数fgets()和fread()
php stream_set_blocking设置非阻塞模式,php stream_set_blocking影响函数fgets()和fread()
244 0
|
JavaScript
Node.js之Stream可读流readable
Node.js之Stream可读流readable
1431 0
|
JavaScript 网络协议 前端开发
Node.js之Stream可写流Writable
Node.js之Stream可写流Writable
1608 0
|
网络安全 网络虚拟化
内核中的UDP socket流程(10)——ip_append_data
作者:gfree.wind@gmail.com 博客:linuxfocus.blog.chinaunix.net 现在换一种风格,把一些对代码的解读直接写到代码段。那样看起来可能更好 继续ip_append_data,     /* hh_len是hardware header...
1182 0
|
缓存
内核中的UDP socket流程(11)——ip_append_data
作者:gfree.wind@gmail.com博客:linuxfocus.blog.chinaunix.net 继续ip_append_data,         if (copy > length)             copy = length;         if (!(rt->dst.
1306 0
|
网络协议
内核中的UDP socket流程(9)——ip_append_data
作者:gfree.wind@gmail.com 原文:http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=96739 下面开始分析ip_append_data这个函数。
1674 0